This commit is contained in:
Sam Potts 2018-10-24 23:04:18 +11:00
parent 779e45c11b
commit e49da6c13f
17 changed files with 349 additions and 172 deletions

View File

@ -1,3 +1,10 @@
# v3.4.5
- Added download button option to download either current source or a custom URL you specify in options
- Prevent immediate hiding of controls on mobile (thanks @jamesoflol)
- Don't hide controls on focusout event (fixes #1122) (thanks @jamesoflol)
- Fix HTML5 quality settings being incorrectly set in local storage (thanks @TechGuard)
# v3.4.4
- Fixed issue with double binding for `click` and `touchstart` for `clickToPlay` option

View File

@ -28,6 +28,7 @@ controls: [
'settings', // Settings menu
'pip', // Picture-in-picture (currently Safari only)
'airplay', // Airplay (currently Safari only)
'download', // Show a download button with a link to either the current source or a custom URL you specify in your options
'fullscreen', // Toggle fullscreen
];
```

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

240
dist/plyr.js vendored
View File

@ -178,6 +178,11 @@ typeof navigator === "object" && (function (global, factory) {
// Accept a URL object
if (instanceOf(input, window.URL)) {
return true;
} // Must be string from here
if (!isString(input)) {
return false;
} // Add the protocol if required
@ -1006,6 +1011,13 @@ typeof navigator === "object" && (function (global, factory) {
return wrapper.innerHTML;
}
var resources = {
pip: 'PIP',
airplay: 'AirPlay',
html5: 'HTML5',
vimeo: 'Vimeo',
youtube: 'YouTube'
};
var i18n = {
get: function get() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
@ -1018,6 +1030,10 @@ typeof navigator === "object" && (function (global, factory) {
var string = getDeep(config.i18n, key);
if (is.empty(string)) {
if (Object.keys(resources).includes(key)) {
return resources[key];
}
return '';
}
@ -1330,23 +1346,18 @@ typeof navigator === "object" && (function (global, factory) {
if ('href' in use) {
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path);
} else {
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', path);
} // Add <use> to <svg>
} // Always set the older attribute even though it's "deprecated" (it'll be around for ages)
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', path); // Add <use> to <svg>
icon.appendChild(use);
return icon;
},
// Create hidden text label
createLabel: function createLabel(type) {
createLabel: function createLabel(key) {
var attr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
// Skip i18n for abbreviations and brand names
var universals = {
pip: 'PIP',
airplay: 'AirPlay'
};
var text = universals[type] || i18n.get(type, this.config);
var text = i18n.get(key, this.config);
var attributes = Object.assign({}, attr, {
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
});
@ -1368,20 +1379,29 @@ typeof navigator === "object" && (function (global, factory) {
},
// Create a <button>
createButton: function createButton(buttonType, attr) {
var button = createElement('button');
var attributes = Object.assign({}, attr);
var type = toCamelCase(buttonType);
var toggle = false;
var label;
var icon;
var labelPressed;
var iconPressed;
var props = {
element: 'button',
toggle: false,
label: null,
icon: null,
labelPressed: null,
iconPressed: null
};
['element', 'icon', 'label'].forEach(function (key) {
if (Object.keys(attributes).includes(key)) {
props[key] = attributes[key];
delete attributes[key];
}
}); // Default to 'button' type to prevent form submission
if (!('type' in attributes)) {
if (props.element === 'button' && !Object.keys(attributes).includes('type')) {
attributes.type = 'button';
}
} // Set class name
if ('class' in attributes) {
if (Object.keys(attributes).includes('class')) {
if (!attributes.class.includes(this.config.classNames.control)) {
attributes.class += " ".concat(this.config.classNames.control);
}
@ -1392,69 +1412,76 @@ typeof navigator === "object" && (function (global, factory) {
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 += " ".concat(this.config.classNames.control, "--overlaid");
type = 'play';
label = 'play';
icon = 'play';
props.label = 'play';
props.icon = 'play';
break;
default:
label = type;
icon = buttonType;
} // Setup toggle icon and labels
if (is.empty(props.label)) {
props.label = type;
}
if (is.empty(props.icon)) {
props.icon = buttonType;
}
if (toggle) {
}
var button = createElement(props.element); // Setup toggle icon and labels
if (props.toggle) {
// Icon
button.appendChild(controls.createIcon.call(this, iconPressed, {
button.appendChild(controls.createIcon.call(this, props.iconPressed, {
class: 'icon--pressed'
}));
button.appendChild(controls.createIcon.call(this, icon, {
button.appendChild(controls.createIcon.call(this, props.icon, {
class: 'icon--not-pressed'
})); // Label/Tooltip
button.appendChild(controls.createLabel.call(this, labelPressed, {
button.appendChild(controls.createLabel.call(this, props.labelPressed, {
class: 'label--pressed'
}));
button.appendChild(controls.createLabel.call(this, label, {
button.appendChild(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));
} // Merge attributes
button.appendChild(controls.createIcon.call(this, props.icon));
button.appendChild(controls.createLabel.call(this, props.label));
} // Merge and set attributes
extend(attributes, getAttributesFromSelector(this.config.selectors.buttons[type], attributes));
@ -2307,6 +2334,17 @@ typeof navigator === "object" && (function (global, factory) {
controls.focusFirstMenuItem.call(this, target, tabFocus);
},
// Set the download link
setDownloadLink: function setDownloadLink() {
var button = this.elements.buttons.download; // Bail if no button
if (!is.element(button)) {
return;
} // Set download link
button.setAttribute('href', this.download);
},
// Build the default HTML
// TODO: Set order based on order in the config.controls array?
create: function create(data) {
@ -2512,6 +2550,25 @@ typeof navigator === "object" && (function (global, factory) {
if (this.config.controls.includes('airplay') && support.airplay) {
container.appendChild(controls.createButton.call(this, 'airplay'));
} // Download button
if (this.config.controls.includes('download')) {
var _attributes = {
element: 'a',
href: this.download,
target: '_blank'
};
var download = this.config.urls.download;
if (!is.url(download) && this.isEmbed) {
extend(_attributes, {
icon: "logo-".concat(this.provider),
label: this.provider
});
}
container.appendChild(controls.createButton.call(this, 'download', _attributes));
} // Toggle fullscreen button
@ -3178,7 +3235,8 @@ typeof navigator === "object" && (function (global, factory) {
controls: ['play-large', // 'restart',
// 'rewind',
'play', // 'fast-forward',
'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen'],
'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', // 'download',
'fullscreen'],
settings: ['captions', 'quality', 'speed'],
// Localisation
i18n: {
@ -3198,6 +3256,7 @@ typeof navigator === "object" && (function (global, factory) {
unmute: 'Unmute',
enableCaptions: 'Enable captions',
disableCaptions: 'Disable captions',
download: 'Download',
enterFullscreen: 'Enter fullscreen',
exitFullscreen: 'Exit fullscreen',
frameTitle: 'Player for {title}',
@ -3226,6 +3285,7 @@ typeof navigator === "object" && (function (global, factory) {
},
// URLs
urls: {
download: null,
vimeo: {
sdk: 'https://player.vimeo.com/api/player.js',
iframe: 'https://player.vimeo.com/video/{0}?{1}',
@ -3250,6 +3310,7 @@ typeof navigator === "object" && (function (global, factory) {
mute: null,
volume: null,
captions: null,
download: null,
fullscreen: null,
pip: null,
airplay: null,
@ -3262,7 +3323,7 @@ typeof navigator === "object" && (function (global, factory) {
events: [// Events to watch on HTML5 media elements and bubble
// https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
'ended', 'progress', 'stalled', 'playing', 'waiting', 'canplay', 'canplaythrough', 'loadstart', 'loadeddata', 'loadedmetadata', 'timeupdate', 'volumechange', 'play', 'pause', 'error', 'seeking', 'seeked', 'emptied', 'ratechange', 'cuechange', // Custom events
'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled', 'languagechange', 'controlshidden', 'controlsshown', 'ready', // YouTube
'download', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled', 'languagechange', 'controlshidden', 'controlsshown', 'ready', // YouTube
'statechange', // Quality
'qualitychange', // Ads
'adsloaded', 'adscontentpause', 'adscontentresume', 'adstarted', 'adsmidpoint', 'adscomplete', 'adsallcomplete', 'adsimpression', 'adsclick'],
@ -3284,6 +3345,7 @@ typeof navigator === "object" && (function (global, factory) {
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"]',
@ -3396,7 +3458,7 @@ typeof navigator === "object" && (function (global, factory) {
};
/**
* Get provider by URL
* @param {string} url
* @param {String} url
*/
function getProviderByUrl(url) {
@ -3923,8 +3985,10 @@ typeof navigator === "object" && (function (global, factory) {
var controls$$1 = this.elements.controls;
if (controls$$1 && this.config.hideControls) {
// Show controls if force, loading, paused, or button interaction, otherwise hide
this.toggleControls(Boolean(force || this.loading || this.paused || controls$$1.pressed || controls$$1.hover));
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
var recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now(); // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
this.toggleControls(Boolean(force || this.loading || this.paused || controls$$1.pressed || controls$$1.hover || recentTouchSeek));
}
}
};
@ -4283,7 +4347,7 @@ typeof navigator === "object" && (function (global, factory) {
if (!is.element(wrapper)) {
return;
} // On click play, pause ore restart
} // On click play, pause or restart
on.call(player, elements.container, 'click', function (event) {
@ -4336,6 +4400,10 @@ typeof navigator === "object" && (function (global, factory) {
on.call(player, player.media, 'qualitychange', function (event) {
// Update UI
controls.updateSetting.call(player, 'quality', null, event.detail.quality);
}); // Update download link when ready and if quality changes
on.call(player, player.media, 'ready qualitychange', function () {
controls.setDownloadLink.call(player);
}); // Proxy events to container
// Bubble up key events for Edge
@ -4413,7 +4481,11 @@ typeof navigator === "object" && (function (global, factory) {
this.bind(elements.buttons.captions, 'click', function () {
return player.toggleCaptions();
}); // Fullscreen toggle
}); // Download
this.bind(elements.buttons.download, 'click', function () {
triggerEvent.call(player, player.media, 'download');
}, 'download'); // Fullscreen toggle
this.bind(elements.buttons.fullscreen, 'click', function () {
player.fullscreen.toggle();
@ -4476,9 +4548,11 @@ typeof navigator === "object" && (function (global, factory) {
if (is.keyboardEvent(event) && code !== 39 && code !== 37) {
return;
} // Was playing before?
} // Record seek time so we can prevent hiding controls for a few seconds after seek
player.lastSeekTime = Date.now(); // Was playing before?
var play = seek.hasAttribute(attribute); // Done seeking
var done = ['mouseup', 'touchend', 'keyup'].includes(event.type); // If we're done seeking and it was playing, resume playback
@ -4555,32 +4629,28 @@ typeof navigator === "object" && (function (global, factory) {
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
}); // Focus in/out on controls
}); // Show controls when they receive focus (e.g., when using keyboard tab key)
this.bind(elements.controls, 'focusin focusout', function (event) {
this.bind(elements.controls, 'focusin', function () {
var config = player.config,
elements = player.elements,
timers = player.timers;
var isFocusIn = event.type === 'focusin'; // Skip transition to prevent focus from scrolling the parent element
timers = player.timers; // Skip transition to prevent focus from scrolling the parent element
toggleClass(elements.controls, config.classNames.noTransition, isFocusIn); // Toggle
toggleClass(elements.controls, config.classNames.noTransition, true); // Toggle
ui.toggleControls.call(player, isFocusIn); // If focusin, hide again after delay
ui.toggleControls.call(player, true); // Restore transition
if (isFocusIn) {
// Restore transition
setTimeout(function () {
toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0); // Delay a little more for keyboard users
setTimeout(function () {
toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0); // Delay a little more for mouse users
var delay = _this2.touch ? 3000 : 4000; // Clear timer
var delay = _this2.touch ? 3000 : 4000; // Clear timer
clearTimeout(timers.controls); // Hide
clearTimeout(timers.controls); // Hide again after delay
timers.controls = setTimeout(function () {
return ui.toggleControls.call(player, false);
}, delay);
}
timers.controls = setTimeout(function () {
return ui.toggleControls.call(player, false);
}, delay);
}); // Mouse wheel for volume
this.bind(elements.inputs.volume, 'wheel', function (event) {
@ -5171,6 +5241,7 @@ typeof navigator === "object" && (function (global, factory) {
var currentSrc;
player.embed.getVideoUrl().then(function (value) {
currentSrc = value;
controls.setDownloadLink.call(player);
}).catch(function (error) {
_this2.debug.warn(error);
});
@ -6724,7 +6795,10 @@ typeof navigator === "object" && (function (global, factory) {
if (this.config.autoplay) {
this.play();
}
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
this.lastSeekTime = 0;
} // ---------------------------------------
// API
// ---------------------------------------
@ -7448,6 +7522,16 @@ typeof navigator === "object" && (function (global, factory) {
get: function get() {
return this.media.currentSrc;
}
/**
* Get a download URL (either source or custom)
*/
}, {
key: "download",
get: function get() {
var download = this.config.urls.download;
return is.url(download) ? download : this.source;
}
/**
* Set the poster image for a video
* @param {input} - the URL for the new poster image

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2804,6 +2804,11 @@ typeof navigator === "object" && (function (global, factory) {
// Accept a URL object
if (instanceOf(input, window.URL)) {
return true;
} // Must be string from here
if (!isString(input)) {
return false;
} // Add the protocol if required
@ -3669,6 +3674,13 @@ typeof navigator === "object" && (function (global, factory) {
return wrapper.innerHTML;
}
var resources = {
pip: 'PIP',
airplay: 'AirPlay',
html5: 'HTML5',
vimeo: 'Vimeo',
youtube: 'YouTube'
};
var i18n = {
get: function get() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
@ -3681,6 +3693,10 @@ typeof navigator === "object" && (function (global, factory) {
var string = getDeep(config.i18n, key);
if (is$1.empty(string)) {
if (Object.keys(resources).includes(key)) {
return resources[key];
}
return '';
}
@ -3993,23 +4009,18 @@ typeof navigator === "object" && (function (global, factory) {
if ('href' in use) {
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path);
} else {
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', path);
} // Add <use> to <svg>
} // Always set the older attribute even though it's "deprecated" (it'll be around for ages)
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', path); // Add <use> to <svg>
icon.appendChild(use);
return icon;
},
// Create hidden text label
createLabel: function createLabel(type) {
createLabel: function createLabel(key) {
var attr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
// Skip i18n for abbreviations and brand names
var universals = {
pip: 'PIP',
airplay: 'AirPlay'
};
var text = universals[type] || i18n.get(type, this.config);
var text = i18n.get(key, this.config);
var attributes = Object.assign({}, attr, {
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
});
@ -4031,20 +4042,29 @@ typeof navigator === "object" && (function (global, factory) {
},
// Create a <button>
createButton: function createButton(buttonType, attr) {
var button = createElement('button');
var attributes = Object.assign({}, attr);
var type = toCamelCase(buttonType);
var toggle = false;
var label;
var icon;
var labelPressed;
var iconPressed;
var props = {
element: 'button',
toggle: false,
label: null,
icon: null,
labelPressed: null,
iconPressed: null
};
['element', 'icon', 'label'].forEach(function (key) {
if (Object.keys(attributes).includes(key)) {
props[key] = attributes[key];
delete attributes[key];
}
}); // Default to 'button' type to prevent form submission
if (!('type' in attributes)) {
if (props.element === 'button' && !Object.keys(attributes).includes('type')) {
attributes.type = 'button';
}
} // Set class name
if ('class' in attributes) {
if (Object.keys(attributes).includes('class')) {
if (!attributes.class.includes(this.config.classNames.control)) {
attributes.class += " ".concat(this.config.classNames.control);
}
@ -4055,69 +4075,76 @@ typeof navigator === "object" && (function (global, factory) {
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 += " ".concat(this.config.classNames.control, "--overlaid");
type = 'play';
label = 'play';
icon = 'play';
props.label = 'play';
props.icon = 'play';
break;
default:
label = type;
icon = buttonType;
} // Setup toggle icon and labels
if (is$1.empty(props.label)) {
props.label = type;
}
if (is$1.empty(props.icon)) {
props.icon = buttonType;
}
if (toggle) {
}
var button = createElement(props.element); // Setup toggle icon and labels
if (props.toggle) {
// Icon
button.appendChild(controls.createIcon.call(this, iconPressed, {
button.appendChild(controls.createIcon.call(this, props.iconPressed, {
class: 'icon--pressed'
}));
button.appendChild(controls.createIcon.call(this, icon, {
button.appendChild(controls.createIcon.call(this, props.icon, {
class: 'icon--not-pressed'
})); // Label/Tooltip
button.appendChild(controls.createLabel.call(this, labelPressed, {
button.appendChild(controls.createLabel.call(this, props.labelPressed, {
class: 'label--pressed'
}));
button.appendChild(controls.createLabel.call(this, label, {
button.appendChild(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));
} // Merge attributes
button.appendChild(controls.createIcon.call(this, props.icon));
button.appendChild(controls.createLabel.call(this, props.label));
} // Merge and set attributes
extend(attributes, getAttributesFromSelector(this.config.selectors.buttons[type], attributes));
@ -4970,6 +4997,17 @@ typeof navigator === "object" && (function (global, factory) {
controls.focusFirstMenuItem.call(this, target, tabFocus);
},
// Set the download link
setDownloadLink: function setDownloadLink() {
var button = this.elements.buttons.download; // Bail if no button
if (!is$1.element(button)) {
return;
} // Set download link
button.setAttribute('href', this.download);
},
// Build the default HTML
// TODO: Set order based on order in the config.controls array?
create: function create(data) {
@ -5175,6 +5213,25 @@ typeof navigator === "object" && (function (global, factory) {
if (this.config.controls.includes('airplay') && support.airplay) {
container.appendChild(controls.createButton.call(this, 'airplay'));
} // Download button
if (this.config.controls.includes('download')) {
var _attributes = {
element: 'a',
href: this.download,
target: '_blank'
};
var download = this.config.urls.download;
if (!is$1.url(download) && this.isEmbed) {
extend(_attributes, {
icon: "logo-".concat(this.provider),
label: this.provider
});
}
container.appendChild(controls.createButton.call(this, 'download', _attributes));
} // Toggle fullscreen button
@ -5841,7 +5898,8 @@ typeof navigator === "object" && (function (global, factory) {
controls: ['play-large', // 'restart',
// 'rewind',
'play', // 'fast-forward',
'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen'],
'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', // 'download',
'fullscreen'],
settings: ['captions', 'quality', 'speed'],
// Localisation
i18n: {
@ -5861,6 +5919,7 @@ typeof navigator === "object" && (function (global, factory) {
unmute: 'Unmute',
enableCaptions: 'Enable captions',
disableCaptions: 'Disable captions',
download: 'Download',
enterFullscreen: 'Enter fullscreen',
exitFullscreen: 'Exit fullscreen',
frameTitle: 'Player for {title}',
@ -5889,6 +5948,7 @@ typeof navigator === "object" && (function (global, factory) {
},
// URLs
urls: {
download: null,
vimeo: {
sdk: 'https://player.vimeo.com/api/player.js',
iframe: 'https://player.vimeo.com/video/{0}?{1}',
@ -5913,6 +5973,7 @@ typeof navigator === "object" && (function (global, factory) {
mute: null,
volume: null,
captions: null,
download: null,
fullscreen: null,
pip: null,
airplay: null,
@ -5925,7 +5986,7 @@ typeof navigator === "object" && (function (global, factory) {
events: [// Events to watch on HTML5 media elements and bubble
// https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
'ended', 'progress', 'stalled', 'playing', 'waiting', 'canplay', 'canplaythrough', 'loadstart', 'loadeddata', 'loadedmetadata', 'timeupdate', 'volumechange', 'play', 'pause', 'error', 'seeking', 'seeked', 'emptied', 'ratechange', 'cuechange', // Custom events
'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled', 'languagechange', 'controlshidden', 'controlsshown', 'ready', // YouTube
'download', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled', 'languagechange', 'controlshidden', 'controlsshown', 'ready', // YouTube
'statechange', // Quality
'qualitychange', // Ads
'adsloaded', 'adscontentpause', 'adscontentresume', 'adstarted', 'adsmidpoint', 'adscomplete', 'adsallcomplete', 'adsimpression', 'adsclick'],
@ -5947,6 +6008,7 @@ typeof navigator === "object" && (function (global, factory) {
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"]',
@ -6059,7 +6121,7 @@ typeof navigator === "object" && (function (global, factory) {
};
/**
* Get provider by URL
* @param {string} url
* @param {String} url
*/
function getProviderByUrl(url) {
@ -6596,8 +6658,10 @@ typeof navigator === "object" && (function (global, factory) {
var controls$$1 = this.elements.controls;
if (controls$$1 && this.config.hideControls) {
// Show controls if force, loading, paused, or button interaction, otherwise hide
this.toggleControls(Boolean(force || this.loading || this.paused || controls$$1.pressed || controls$$1.hover));
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
var recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now(); // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
this.toggleControls(Boolean(force || this.loading || this.paused || controls$$1.pressed || controls$$1.hover || recentTouchSeek));
}
}
};
@ -6956,7 +7020,7 @@ typeof navigator === "object" && (function (global, factory) {
if (!is$1.element(wrapper)) {
return;
} // On click play, pause ore restart
} // On click play, pause or restart
on.call(player, elements.container, 'click', function (event) {
@ -7009,6 +7073,10 @@ typeof navigator === "object" && (function (global, factory) {
on.call(player, player.media, 'qualitychange', function (event) {
// Update UI
controls.updateSetting.call(player, 'quality', null, event.detail.quality);
}); // Update download link when ready and if quality changes
on.call(player, player.media, 'ready qualitychange', function () {
controls.setDownloadLink.call(player);
}); // Proxy events to container
// Bubble up key events for Edge
@ -7086,7 +7154,11 @@ typeof navigator === "object" && (function (global, factory) {
this.bind(elements.buttons.captions, 'click', function () {
return player.toggleCaptions();
}); // Fullscreen toggle
}); // Download
this.bind(elements.buttons.download, 'click', function () {
triggerEvent.call(player, player.media, 'download');
}, 'download'); // Fullscreen toggle
this.bind(elements.buttons.fullscreen, 'click', function () {
player.fullscreen.toggle();
@ -7149,9 +7221,11 @@ typeof navigator === "object" && (function (global, factory) {
if (is$1.keyboardEvent(event) && code !== 39 && code !== 37) {
return;
} // Was playing before?
} // Record seek time so we can prevent hiding controls for a few seconds after seek
player.lastSeekTime = Date.now(); // Was playing before?
var play = seek.hasAttribute(attribute); // Done seeking
var done = ['mouseup', 'touchend', 'keyup'].includes(event.type); // If we're done seeking and it was playing, resume playback
@ -7228,32 +7302,28 @@ typeof navigator === "object" && (function (global, factory) {
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
}); // Focus in/out on controls
}); // Show controls when they receive focus (e.g., when using keyboard tab key)
this.bind(elements.controls, 'focusin focusout', function (event) {
this.bind(elements.controls, 'focusin', function () {
var config = player.config,
elements = player.elements,
timers = player.timers;
var isFocusIn = event.type === 'focusin'; // Skip transition to prevent focus from scrolling the parent element
timers = player.timers; // Skip transition to prevent focus from scrolling the parent element
toggleClass(elements.controls, config.classNames.noTransition, isFocusIn); // Toggle
toggleClass(elements.controls, config.classNames.noTransition, true); // Toggle
ui.toggleControls.call(player, isFocusIn); // If focusin, hide again after delay
ui.toggleControls.call(player, true); // Restore transition
if (isFocusIn) {
// Restore transition
setTimeout(function () {
toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0); // Delay a little more for keyboard users
setTimeout(function () {
toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0); // Delay a little more for mouse users
var delay = _this2.touch ? 3000 : 4000; // Clear timer
var delay = _this2.touch ? 3000 : 4000; // Clear timer
clearTimeout(timers.controls); // Hide
clearTimeout(timers.controls); // Hide again after delay
timers.controls = setTimeout(function () {
return ui.toggleControls.call(player, false);
}, delay);
}
timers.controls = setTimeout(function () {
return ui.toggleControls.call(player, false);
}, delay);
}); // Mouse wheel for volume
this.bind(elements.inputs.volume, 'wheel', function (event) {
@ -7864,6 +7934,7 @@ typeof navigator === "object" && (function (global, factory) {
var currentSrc;
player.embed.getVideoUrl().then(function (value) {
currentSrc = value;
controls.setDownloadLink.call(player);
}).catch(function (error) {
_this2.debug.warn(error);
});
@ -9414,7 +9485,10 @@ typeof navigator === "object" && (function (global, factory) {
if (this.config.autoplay) {
this.play();
}
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
this.lastSeekTime = 0;
} // ---------------------------------------
// API
// ---------------------------------------
@ -10138,6 +10212,16 @@ typeof navigator === "object" && (function (global, factory) {
get: function get() {
return this.media.currentSrc;
}
/**
* Get a download URL (either source or custom)
*/
}, {
key: "download",
get: function get() {
var download = this.config.urls.download;
return is$1.url(download) ? download : this.source;
}
/**
* Set the poster image for a video
* @param {input} - the URL for the new poster image

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/plyr.svg vendored

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,6 +1,6 @@
{
"name": "plyr",
"version": "3.4.4",
"version": "3.4.5",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io",
"author": "Sam Potts <sam@potts.es>",

View File

@ -132,13 +132,13 @@ See [initialising](#initialising) for more information on advanced setups.
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
```html
<script src="https://cdn.plyr.io/3.4.4/plyr.js"></script>
<script src="https://cdn.plyr.io/3.4.5/plyr.js"></script>
```
...or...
```html
<script src="https://cdn.plyr.io/3.4.4/plyr.polyfilled.js"></script>
<script src="https://cdn.plyr.io/3.4.5/plyr.polyfilled.js"></script>
```
### CSS
@ -152,13 +152,13 @@ Include the `plyr.css` stylsheet into your `<head>`
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
```html
<link rel="stylesheet" href="https://cdn.plyr.io/3.4.4/plyr.css">
<link rel="stylesheet" href="https://cdn.plyr.io/3.4.5/plyr.css">
```
### SVG Sprite
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.4.4/plyr.svg`.
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.4.5/plyr.svg`.
## Ads
@ -315,6 +315,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable vi.ai ads. `publisherId`: Your unique vi.ai publisher ID. |
| `urls` | Object | See source. | If you wish to override any API URLs then you can do so here. You can also set a custom download URL for the download button. |
1. Vimeo only

View File

@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
// plyr.js v3.4.4
// plyr.js v3.4.5
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================

View File

@ -1,6 +1,6 @@
// ==========================================================================
// Plyr Polyfilled Build
// plyr.js v3.4.4
// plyr.js v3.4.5
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================