Added download button

This commit is contained in:
Sam Potts
2018-09-28 00:42:42 +10:00
parent 515ae32160
commit fac134dd95
9 changed files with 133 additions and 55 deletions

View File

@ -133,6 +133,7 @@ const defaults = {
'settings',
'pip',
'airplay',
'download',
'fullscreen',
],
settings: ['captions', 'quality', 'speed'],
@ -155,6 +156,7 @@ const defaults = {
unmute: 'Unmute',
enableCaptions: 'Enable captions',
disableCaptions: 'Disable captions',
download: 'Download',
enterFullscreen: 'Enter fullscreen',
exitFullscreen: 'Exit fullscreen',
frameTitle: 'Player for {title}',
@ -210,6 +212,7 @@ const defaults = {
mute: null,
volume: null,
captions: null,
download: null,
fullscreen: null,
pip: null,
airplay: null,
@ -245,6 +248,7 @@ const defaults = {
'cuechange',
// Custom events
'download',
'enterfullscreen',
'exitfullscreen',
'captionsenabled',
@ -290,6 +294,7 @@ const defaults = {
fastForward: '[data-plyr="fast-forward"]',
mute: '[data-plyr="mute"]',
captions: '[data-plyr="captions"]',
download: '[data-plyr="download"]',
fullscreen: '[data-plyr="fullscreen"]',
pip: '[data-plyr="pip"]',
airplay: '[data-plyr="airplay"]',

View File

@ -15,7 +15,7 @@ export const types = {
/**
* Get provider by URL
* @param {string} url
* @param {String} url
*/
export function getProviderByUrl(url) {
// YouTube

139
src/js/controls.js vendored
View File

@ -122,17 +122,13 @@ const controls = {
},
// Create hidden text label
createLabel(type, attr = {}) {
// Skip i18n for abbreviations and brand names
const universals = {
pip: 'PIP',
airplay: 'AirPlay',
};
const text = universals[type] || i18n.get(type, this.config);
createLabel(key, attr = {}) {
const text = i18n.get(key, this.config);
const attributes = Object.assign({}, attr, {
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' '),
});
return createElement('span', attributes, text);
},
@ -161,21 +157,32 @@ const controls = {
// Create a <button>
createButton(buttonType, attr) {
const button = createElement('button');
const attributes = Object.assign({}, attr);
let type = toCamelCase(buttonType);
let toggle = false;
let label;
let icon;
let labelPressed;
let iconPressed;
const props = {
element: 'button',
toggle: false,
label: null,
icon: null,
labelPressed: null,
iconPressed: null,
};
if (!('type' in attributes)) {
['element', 'icon', 'label'].forEach(key => {
if (Object.keys(attributes).includes(key)) {
props[key] = attributes[key];
delete attributes[key];
}
});
// Default to 'button' type to prevent form submission
if (props.element === 'button' && !Object.keys(attributes).includes('type')) {
attributes.type = 'button';
}
if ('class' in attributes) {
// Set class name
if (Object.keys(attributes).includes('class')) {
if (!attributes.class.includes(this.config.classNames.control)) {
attributes.class += ` ${this.config.classNames.control}`;
}
@ -186,82 +193,87 @@ const controls = {
// Large play button
switch (buttonType) {
case 'play':
toggle = true;
label = 'play';
labelPressed = 'pause';
icon = 'play';
iconPressed = 'pause';
props.toggle = true;
props.label = 'play';
props.labelPressed = 'pause';
props.icon = 'play';
props.iconPressed = 'pause';
break;
case 'mute':
toggle = true;
label = 'mute';
labelPressed = 'unmute';
icon = 'volume';
iconPressed = 'muted';
props.toggle = true;
props.label = 'mute';
props.labelPressed = 'unmute';
props.icon = 'volume';
props.iconPressed = 'muted';
break;
case 'captions':
toggle = true;
label = 'enableCaptions';
labelPressed = 'disableCaptions';
icon = 'captions-off';
iconPressed = 'captions-on';
props.toggle = true;
props.label = 'enableCaptions';
props.labelPressed = 'disableCaptions';
props.icon = 'captions-off';
props.iconPressed = 'captions-on';
break;
case 'fullscreen':
toggle = true;
label = 'enterFullscreen';
labelPressed = 'exitFullscreen';
icon = 'enter-fullscreen';
iconPressed = 'exit-fullscreen';
props.toggle = true;
props.label = 'enterFullscreen';
props.labelPressed = 'exitFullscreen';
props.icon = 'enter-fullscreen';
props.iconPressed = 'exit-fullscreen';
break;
case 'play-large':
attributes.class += ` ${this.config.classNames.control}--overlaid`;
type = 'play';
label = 'play';
icon = 'play';
props.label = 'play';
props.icon = 'play';
break;
default:
label = type;
icon = buttonType;
if (is.empty(props.label)) {
props.label = type;
}
if (is.empty(props.icon)) {
props.icon = buttonType;
}
}
const button = createElement(props.element);
// Setup toggle icon and labels
if (toggle) {
if (props.toggle) {
// Icon
button.appendChild(
controls.createIcon.call(this, iconPressed, {
controls.createIcon.call(this, props.iconPressed, {
class: 'icon--pressed',
}),
);
button.appendChild(
controls.createIcon.call(this, icon, {
controls.createIcon.call(this, props.icon, {
class: 'icon--not-pressed',
}),
);
// Label/Tooltip
button.appendChild(
controls.createLabel.call(this, labelPressed, {
controls.createLabel.call(this, props.labelPressed, {
class: 'label--pressed',
}),
);
button.appendChild(
controls.createLabel.call(this, label, {
controls.createLabel.call(this, props.label, {
class: 'label--not-pressed',
}),
);
} else {
button.appendChild(controls.createIcon.call(this, icon));
button.appendChild(controls.createLabel.call(this, label));
button.appendChild(controls.createIcon.call(this, props.icon));
button.appendChild(controls.createLabel.call(this, props.label));
}
// Merge attributes
// Merge and set attributes
extend(attributes, getAttributesFromSelector(this.config.selectors.buttons[type], attributes));
setAttributes(button, attributes);
// We have multiple play buttons
@ -1214,6 +1226,15 @@ const controls = {
controls.focusFirstMenuItem.call(this, target, tabFocus);
},
// Set the download link
setDownloadLink() {
// Set download link
const { download } = this.elements.buttons;
if (is.element(download)) {
download.setAttribute('href', this.source);
}
},
// Build the default HTML
// TODO: Set order based on order in the config.controls array?
create(data) {
@ -1490,6 +1511,28 @@ const controls = {
container.appendChild(controls.createButton.call(this, 'airplay'));
}
// Download button
if (this.config.controls.includes('download')) {
const attributes = {
element: 'a',
href: this.source,
target: '_blank',
};
if (this.isHTML5) {
extend(attributes, {
download: '',
});
} else if (this.isEmbed) {
extend(attributes, {
icon: `logo-${this.provider}`,
label: this.provider,
});
}
container.appendChild(controls.createButton.call(this, 'download', attributes));
}
// Toggle fullscreen button
if (this.config.controls.includes('fullscreen')) {
container.appendChild(controls.createButton.call(this, 'fullscreen'));

View File

@ -431,6 +431,11 @@ class Listeners {
controls.updateSetting.call(player, 'quality', null, event.detail.quality);
});
// Update download link
on.call(player, player.media, 'ready qualitychange', () => {
controls.setDownloadLink.call(player);
});
// Proxy events to container
// Bubble up key events for Edge
const proxyEvents = player.config.events.concat(['keyup', 'keydown']).join(' ');
@ -517,6 +522,16 @@ class Listeners {
// Captions toggle
this.bind(elements.buttons.captions, 'click', () => player.toggleCaptions());
// Download
this.bind(
elements.buttons.download,
'click',
() => {
triggerEvent.call(player, player.media, 'download');
},
'download',
);
// Fullscreen toggle
this.bind(
elements.buttons.fullscreen,
@ -698,7 +713,7 @@ class Listeners {
});
// Show controls when they receive focus (e.g., when using keyboard tab key)
this.bind(elements.controls, 'focusin', event => {
this.bind(elements.controls, 'focusin', () => {
const { config, elements, timers } = player;
// Skip transition to prevent focus from scrolling the parent element
@ -712,7 +727,7 @@ class Listeners {
toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0);
// Delay a little more for keyboard users
// Delay a little more for mouse users
const delay = this.touch ? 3000 : 4000;
// Clear timer

View File

@ -71,7 +71,7 @@ const vimeo = {
// For Vimeo we have an extra 300% height <div> to hide the standard controls and UI
setAspectRatio(input) {
const [x, y] = (is.string(input) ? input : this.config.ratio).split(':');
const padding = 100 / x * y;
const padding = (100 / x) * y;
this.elements.wrapper.style.paddingBottom = `${padding}%`;
if (this.supported.ui) {
@ -278,6 +278,7 @@ const vimeo = {
.getVideoUrl()
.then(value => {
currentSrc = value;
controls.setDownloadLink.call(player);
})
.catch(error => {
this.debug.warn(error);