Compare commits

...

26 Commits

Author SHA1 Message Date
Sam Potts 840e31a693 v3.3.12 2018-06-11 17:10:37 +10:00
Sam Potts 38f954ef17 Merge branch 'master' into develop
# Conflicts:
#	dist/plyr.js.map
#	dist/plyr.min.js
#	dist/plyr.min.js.map
#	dist/plyr.polyfilled.js.map
#	dist/plyr.polyfilled.min.js
#	dist/plyr.polyfilled.min.js.map
2018-06-11 16:48:54 +10:00
Sam Potts abd1182303 Merge 2018-06-11 16:45:40 +10:00
Sam Potts 1ad76800b0 Merge pull request #1024 from friday/event-bubble-detail
Event "detail" is lost in the synthetic event bubble/proxy
2018-06-11 16:13:02 +10:00
Albin Larsson cc97d7be6a Fix synthetic event bubble/proxy loses detail 2018-06-11 08:00:46 +02:00
Sam Potts f951cb372c Merge pull request #1023 from friday/make-utils-static
Make utils static
2018-06-11 14:41:06 +10:00
Albin Larsson 37a3ab202a Remove wrapper function around utils.is.element in Plyr.setup() (no lnger needed) 2018-06-11 05:44:57 +02:00
Albin Larsson b148adc0af Avoid using this to refer to utils or utils.is, since that means methods can't be used statically 2018-06-11 05:44:57 +02:00
Albin Larsson 16828e975a Move utils.is.getConstructor() to utils.getConstructor() 2018-06-11 05:44:57 +02:00
Sam Potts 7d26f41d64 Merge pull request #1015 from friday/captions-fixes-again
Captions rewrite (use index internally to support missing or duplicate languages)
2018-06-11 13:21:05 +10:00
Sam Potts f37f465ce4 Merge pull request #1020 from friday/1016
Vimeo: Update playback state and assure events are triggered on load
2018-06-11 11:48:03 +10:00
Sam Potts b199215525 Merge pull request #1021 from friday/vimeo-seek-while-playing
Fix for YouTube and Vimeo pausing after seek
2018-06-11 11:47:34 +10:00
Albin Larsson 94699f3255 Fix problem with YouTube and Vimeo seeking while playing 2018-06-11 02:21:00 +02:00
Albin Larsson d3e98eb27e Vimeo: Assure state is updated with autoplay (fixes #1016) 2018-06-11 00:00:16 +02:00
Albin Larsson 41012a9843 Typo 2018-06-10 22:00:15 +02:00
Albin Larsson c83487a293 Fix #1017, fix #980, fix #1014: Captions rewrite (use index internally) 2018-06-10 19:00:07 +02:00
Albin Larsson 1fab4919c0 controls.createMenuItem: Change input to object (too many params made it hard to read) 2018-06-10 18:57:19 +02:00
Albin Larsson 9dc0f28800 Avoid condition in getTracks 2018-06-10 18:57:19 +02:00
Albin Larsson b57784d1a5 Change debug warn 'Unsupported language option' to log 'Language option doesn't yet exist' since it doesn't have to be an error 2018-06-10 18:56:13 +02:00
Albin Larsson a80b31bf98 Fix #1003: Formatted captions issue 2018-06-10 18:56:13 +02:00
Sam Potts 76bb299c68 Restore default 2018-06-09 12:05:37 +10:00
Sam Potts 0c03accd41 Fix Sprite issue 2018-06-09 12:04:53 +10:00
Albin Larsson b12eeb0eb7 Merge captions setText and setCue into updateCues (fixes #998 and vimeo cuechange event) 2018-06-08 11:44:15 +02:00
Sam Potts 8e634862ff Merge pull request #1007 from cky917/master
fix:  After clicking on the progress bar, keyboard operations will not work.
2018-06-08 10:54:16 +10:00
cky 84424f7f67 fix: when the seek input is focused and the video is playing, the space key can't make the video pause, because after 'keyup', it always make the video play 2018-06-06 19:27:07 +08:00
cky c95d9923f7 fix: https://github.com/sampotts/plyr/issues/1006 2018-06-06 16:59:42 +08:00
23 changed files with 1006 additions and 814 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# v3.3.11
# v3.3.12
- Fix synthetic event bubble/proxy loses detail (thanks @friday!)
- Make utils static (thanks @friday!)
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+348 -271
View File
@@ -493,61 +493,63 @@ var utils = {
// Check variable types
is: {
object: function object(input) {
return this.getConstructor(input) === Object;
return utils.getConstructor(input) === Object;
},
number: function number(input) {
return this.getConstructor(input) === Number && !Number.isNaN(input);
return utils.getConstructor(input) === Number && !Number.isNaN(input);
},
string: function string(input) {
return this.getConstructor(input) === String;
return utils.getConstructor(input) === String;
},
boolean: function boolean(input) {
return this.getConstructor(input) === Boolean;
return utils.getConstructor(input) === Boolean;
},
function: function _function(input) {
return this.getConstructor(input) === Function;
return utils.getConstructor(input) === Function;
},
array: function array(input) {
return !this.nullOrUndefined(input) && Array.isArray(input);
return !utils.is.nullOrUndefined(input) && Array.isArray(input);
},
weakMap: function weakMap(input) {
return this.instanceof(input, WeakMap);
return utils.is.instanceof(input, WeakMap);
},
nodeList: function nodeList(input) {
return this.instanceof(input, NodeList);
return utils.is.instanceof(input, NodeList);
},
element: function element(input) {
return this.instanceof(input, Element);
return utils.is.instanceof(input, Element);
},
textNode: function textNode(input) {
return this.getConstructor(input) === Text;
return utils.getConstructor(input) === Text;
},
event: function event(input) {
return this.instanceof(input, Event);
return utils.is.instanceof(input, Event);
},
cue: function cue(input) {
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
return utils.is.instanceof(input, window.TextTrackCue) || utils.is.instanceof(input, window.VTTCue);
},
track: function track(input) {
return this.instanceof(input, TextTrack) || !this.nullOrUndefined(input) && this.string(input.kind);
return utils.is.instanceof(input, TextTrack) || !utils.is.nullOrUndefined(input) && utils.is.string(input.kind);
},
url: function url(input) {
return !this.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
return !utils.is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
},
nullOrUndefined: function nullOrUndefined(input) {
return input === null || typeof input === 'undefined';
},
empty: function empty(input) {
return this.nullOrUndefined(input) || (this.string(input) || this.array(input) || this.nodeList(input)) && !input.length || this.object(input) && !Object.keys(input).length;
return utils.is.nullOrUndefined(input) || (utils.is.string(input) || utils.is.array(input) || utils.is.nodeList(input)) && !input.length || utils.is.object(input) && !Object.keys(input).length;
},
instanceof: function _instanceof$$1(input, constructor) {
return Boolean(input && constructor && input instanceof constructor);
},
getConstructor: function getConstructor(input) {
return !this.nullOrUndefined(input) ? input.constructor : null;
}
},
getConstructor: function getConstructor(input) {
return !utils.is.nullOrUndefined(input) ? input.constructor : null;
},
// Unfortunately, due to mixed support, UA sniffing is required
getBrowser: function getBrowser() {
return {
@@ -637,26 +639,25 @@ var utils = {
return;
}
var prefix = 'cache-';
var prefix = 'cache';
var hasId = utils.is.string(id);
var isCached = false;
var exists = function exists() {
return document.querySelectorAll('#' + id).length;
return document.getElementById(id) !== null;
};
function injectSprite(data) {
var update = function update(container, data) {
container.innerHTML = data;
// Check again incase of race condition
if (hasId && exists()) {
return;
}
// Inject content
this.innerHTML = data;
// Inject the SVG to the body
document.body.insertBefore(this, document.body.childNodes[0]);
}
document.body.insertAdjacentElement('afterbegin', container);
};
// Only load once if ID set
if (!hasId || !exists()) {
@@ -672,13 +673,12 @@ var utils = {
// Check in cache
if (useStorage) {
var cached = window.localStorage.getItem(prefix + id);
var cached = window.localStorage.getItem(prefix + '-' + id);
isCached = cached !== null;
if (isCached) {
var data = JSON.parse(cached);
injectSprite.call(container, data.content);
return;
update(container, data.content);
}
}
@@ -689,12 +689,12 @@ var utils = {
}
if (useStorage) {
window.localStorage.setItem(prefix + id, JSON.stringify({
window.localStorage.setItem(prefix + '-' + id, JSON.stringify({
content: result
}));
}
injectSprite.call(container, result);
update(container, result);
}).catch(function () {});
}
},
@@ -1169,7 +1169,7 @@ var utils = {
// Bail if the value isn't a number
if (!utils.is.number(time)) {
return this.formatTime(null, displayHours, inverted);
return utils.formatTime(null, displayHours, inverted);
}
// Format time component to add leading zero
@@ -1178,9 +1178,9 @@ var utils = {
};
// Breakdown to hours, mins, secs
var hours = this.getHours(time);
var mins = this.getMinutes(time);
var secs = this.getSeconds(time);
var hours = utils.getHours(time);
var mins = utils.getMinutes(time);
var secs = utils.getSeconds(time);
// Do we need to display hours?
if (displayHours || hours > 0) {
@@ -1376,12 +1376,12 @@ var utils = {
// Parse URL if needed
if (input.startsWith('http://') || input.startsWith('https://')) {
var _parseUrl = this.parseUrl(input);
var _utils$parseUrl = utils.parseUrl(input);
search = _parseUrl.search;
search = _utils$parseUrl.search;
}
if (this.is.empty(search)) {
if (utils.is.empty(search)) {
return null;
}
@@ -1420,6 +1420,14 @@ var utils = {
},
// Like outerHTML, but also works for DocumentFragment
getHTML: function getHTML(element) {
var wrapper = document.createElement('div');
wrapper.appendChild(element);
return wrapper.innerHTML;
},
// Get aspect ratio for dimensions
getAspectRatio: function getAspectRatio(width, height) {
var getRatio = function getRatio(w, h) {
@@ -2161,9 +2169,15 @@ var controls = {
// Create a settings menu item
createMenuItem: function createMenuItem(value, list, type, title) {
var badge = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
var checked = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
createMenuItem: function createMenuItem(_ref) {
var value = _ref.value,
list = _ref.list,
type = _ref.type,
title = _ref.title,
_ref$badge = _ref.badge,
badge = _ref$badge === undefined ? null : _ref$badge,
_ref$checked = _ref.checked,
checked = _ref$checked === undefined ? false : _ref$checked;
var item = utils.createElement('li');
@@ -2479,8 +2493,13 @@ var controls = {
var sorting = _this3.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(function (quality) {
var label = controls.getLabel.call(_this3, 'quality', quality);
controls.createMenuItem.call(_this3, quality, list, type, label, getBadge(quality));
controls.createMenuItem.call(_this3, {
value: quality,
list: list,
type: type,
title: controls.getLabel.call(_this3, 'quality', quality),
badge: getBadge(quality)
});
});
controls.updateSetting.call(this, type, list);
@@ -2523,18 +2542,7 @@ var controls = {
switch (setting) {
case 'captions':
if (this.captions.active) {
if (this.options.captions.length > 2 || !this.options.captions.some(function (lang) {
return lang === 'enabled';
})) {
value = this.captions.language;
} else {
value = 'enabled';
}
} else {
value = '';
}
value = this.currentTrack;
break;
default:
@@ -2629,10 +2637,10 @@ var controls = {
// TODO: Captions or language? Currently it's mixed
var type = 'captions';
var list = this.elements.settings.panes.captions.querySelector('ul');
var tracks = captions.getTracks.call(this);
// Toggle the pane and tab
var toggle = captions.getTracks.call(this).length;
controls.toggleTab.call(this, type, toggle);
controls.toggleTab.call(this, type, tracks.length);
// Empty the menu
utils.emptyElement(list);
@@ -2641,28 +2649,33 @@ var controls = {
controls.checkMenu.call(this);
// If there's no captions, bail
if (!toggle) {
if (!tracks.length) {
return;
}
// Re-map the tracks into just the data we need
var tracks = captions.getTracks.call(this).map(function (track) {
// Generate options data
var options = tracks.map(function (track, value) {
return {
language: !utils.is.empty(track.language) ? track.language : 'enabled',
label: captions.getLabel.call(_this4, track)
value: value,
checked: _this4.captions.active && _this4.currentTrack === value,
title: captions.getLabel.call(_this4, track),
badge: track.language && controls.createBadge.call(_this4, track.language.toUpperCase()),
list: list,
type: 'language'
};
});
// Add the "Disabled" option to turn off captions
tracks.unshift({
language: '',
label: i18n.get('disabled', this.config)
options.unshift({
value: -1,
checked: !this.captions.active,
title: i18n.get('disabled', this.config),
list: list,
type: 'language'
});
// Generate options
tracks.forEach(function (track) {
controls.createMenuItem.call(_this4, track.language, list, 'language', track.label, track.language !== 'enabled' ? controls.createBadge.call(_this4, track.language.toUpperCase()) : null, track.language.toLowerCase() === _this4.language);
});
options.forEach(controls.createMenuItem.bind(this));
controls.updateSetting.call(this, type, list);
},
@@ -2716,8 +2729,12 @@ var controls = {
// Create items
this.options.speed.forEach(function (speed) {
var label = controls.getLabel.call(_this5, 'speed', speed);
controls.createMenuItem.call(_this5, speed, list, type, label);
controls.createMenuItem.call(_this5, {
value: speed,
list: list,
type: type,
title: controls.getLabel.call(_this5, 'speed', speed)
});
});
controls.updateSetting.call(this, type, list);
@@ -3191,10 +3208,10 @@ var controls = {
var replace = function replace(input) {
var result = input;
Object.entries(props).forEach(function (_ref) {
var _ref2 = slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
Object.entries(props).forEach(function (_ref2) {
var _ref3 = slicedToArray(_ref2, 2),
key = _ref3[0],
value = _ref3[1];
result = utils.replaceAll(result, '{' + key + '}', value);
});
@@ -3311,91 +3328,168 @@ var captions = {
active = this.config.captions.active;
}
// Set toggled state
this.toggleCaptions(active);
// Get language from storage, fallback to config
var language = this.storage.get('language') || this.config.captions.language;
if (language === 'auto') {
var _split = (navigator.language || navigator.userLanguage).split('-');
var _split2 = slicedToArray(_split, 1);
language = _split2[0];
}
// Set language and show if active
captions.setLanguage.call(this, language, active);
// Watch changes to textTracks and update captions menu
if (this.config.captions.update) {
utils.on(this.media.textTracks, 'addtrack removetrack', captions.update.bind(this));
if (this.isHTML5) {
var trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
utils.on(this.media.textTracks, trackEvents, captions.update.bind(this));
}
// Update available languages in list next tick (the event must not be triggered before the listeners)
setTimeout(captions.update.bind(this), 0);
},
update: function update() {
// Update tracks
var tracks = captions.getTracks.call(this);
this.options.captions = tracks.map(function (_ref) {
var language = _ref.language;
return language;
});
var _this = this;
// Set language if it hasn't been set already
if (!this.language) {
var language = this.config.captions.language;
var tracks = captions.getTracks.call(this, true);
// Get the wanted language
var _captions = this.captions,
language = _captions.language,
meta = _captions.meta;
if (language === 'auto') {
var _split = (navigator.language || navigator.userLanguage).split('-');
// Handle tracks (add event listener and "pseudo"-default)
var _split2 = slicedToArray(_split, 1);
if (this.isHTML5 && this.isVideo) {
tracks.filter(function (track) {
return !meta.get(track);
}).forEach(function (track) {
_this.debug.log('Track added', track);
// Attempt to store if the original dom element was "default"
meta.set(track, {
default: track.mode === 'showing'
});
language = _split2[0];
}
this.language = this.storage.get('language') || (language || '').toLowerCase();
// Turn off native caption rendering to avoid double captions
track.mode = 'hidden';
// Add event listener for cue changes
utils.on(track, 'cuechange', function () {
return captions.updateCues.call(_this);
});
});
}
// Toggle the class hooks
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
var trackRemoved = !tracks.find(function (track) {
return track === _this.captions.currentTrackNode;
});
var firstMatch = this.language !== language && tracks.find(function (track) {
return track.language === language;
});
// Update language if removed or first matching track added
if (trackRemoved || firstMatch) {
captions.setLanguage.call(this, language, this.config.captions.active);
}
// Enable or disable captions based on track length
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(tracks));
// Update available languages in list
if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
}
},
set: function set$$1(index) {
var setLanguage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var show = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var tracks = captions.getTracks.call(this);
// Set the captions language
setLanguage: function setLanguage() {
var _this = this;
// Setup HTML5 track rendering
if (this.isHTML5 && this.isVideo) {
captions.getTracks.call(this).forEach(function (track) {
// Show track
utils.on(track, 'cuechange', function (event) {
return captions.setCue.call(_this, event);
});
// Turn off native caption rendering to avoid double captions
// eslint-disable-next-line
track.mode = 'hidden';
});
// Get current track
var currentTrack = captions.getCurrentTrack.call(this);
// Check if suported kind
if (utils.is.track(currentTrack)) {
// If we change the active track while a cue is already displayed we need to update it
if (Array.from(currentTrack.activeCues || []).length) {
captions.setCue.call(this, currentTrack);
}
}
} else if (this.isVimeo && this.captions.active) {
this.embed.enableTextTrack(this.language);
// Disable captions if setting to -1
if (index === -1) {
this.toggleCaptions(false);
return;
}
if (!utils.is.number(index)) {
this.debug.warn('Invalid caption argument', index);
return;
}
if (!(index in tracks)) {
this.debug.warn('Track not found', index);
return;
}
if (this.captions.currentTrack !== index) {
this.captions.currentTrack = index;
var track = captions.getCurrentTrack.call(this);
var _ref = track || {},
language = _ref.language;
// Store reference to node for invalidation on remove
this.captions.currentTrackNode = track;
// Prevent setting language in some cases, since it can violate user's intentions
if (setLanguage) {
this.captions.language = language;
}
// Handle Vimeo captions
if (this.isVimeo) {
this.embed.enableTextTrack(language);
}
// Trigger event
utils.dispatchEvent.call(this, this.media, 'languagechange');
}
if (this.isHTML5 && this.isVideo) {
// If we change the active track while a cue is already displayed we need to update it
captions.updateCues.call(this);
}
// Show captions
if (show) {
this.toggleCaptions(true);
}
},
setLanguage: function setLanguage(language) {
var show = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
if (!utils.is.string(language)) {
this.debug.warn('Invalid language argument', language);
return;
}
// Normalize
this.captions.language = language.toLowerCase();
// Set currentTrack
var tracks = captions.getTracks.call(this);
var track = captions.getCurrentTrack.call(this, true);
captions.set.call(this, tracks.indexOf(track), false, show);
},
// Get the tracks
// Get current valid caption tracks
// If update is false it will also ignore tracks without metadata
// This is used to "freeze" the language options when captions.update is false
getTracks: function getTracks() {
// Return empty array at least
if (utils.is.nullOrUndefined(this.media)) {
return [];
}
var _this2 = this;
// Only get accepted kinds
return Array.from(this.media.textTracks || []).filter(function (track) {
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
// Handle media or textTracks missing or null
var tracks = Array.from((this.media || {}).textTracks || []);
// For HTML5, use cache instead of current tracks when it exists (if captions.update is false)
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
return tracks.filter(function (track) {
return !_this2.isHTML5 || update || _this2.captions.meta.has(track);
}).filter(function (track) {
return ['captions', 'subtitles'].includes(track.kind);
});
},
@@ -3403,32 +3497,20 @@ var captions = {
// Get the current track for the current language
getCurrentTrack: function getCurrentTrack() {
var _this2 = this;
var _this3 = this;
var fromLanguage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var tracks = captions.getTracks.call(this);
if (!tracks.length) {
return null;
}
// Get track based on current language
var track = tracks.find(function (track) {
return track.language.toLowerCase() === _this2.language;
var sortIsDefault = function sortIsDefault(track) {
return Number((_this3.captions.meta.get(track) || {}).default);
};
var sorted = Array.from(tracks).sort(function (a, b) {
return sortIsDefault(b) - sortIsDefault(a);
});
// Get the <track> with default attribute
if (!track) {
track = utils.getElement.call(this, 'track[default]');
}
// Get the first track
if (!track) {
var _tracks = slicedToArray(tracks, 1);
track = _tracks[0];
}
return track;
return !fromLanguage && tracks[this.currentTrack] || sorted.find(function (track) {
return track.language === _this3.captions.language;
}) || sorted[0];
},
@@ -3456,58 +3538,50 @@ var captions = {
},
// Display active caption if it contains text
setCue: function setCue(input) {
// Get the track from the event if needed
var track = utils.is.event(input) ? input.target : input;
var activeCues = track.activeCues;
var active = activeCues.length && activeCues[0];
var currentTrack = captions.getCurrentTrack.call(this);
// Only display current track
if (track !== currentTrack) {
return;
}
// Display a cue, if there is one
if (utils.is.cue(active)) {
captions.setText.call(this, active.getCueAsHTML());
} else {
captions.setText.call(this, null);
}
utils.dispatchEvent.call(this, this.media, 'cuechange');
},
// Set the current caption
setText: function setText(input) {
// Update captions using current track's active cues
// Also optional array argument in case there isn't any track (ex: vimeo)
updateCues: function updateCues(input) {
// Requires UI
if (!this.supported.ui) {
return;
}
if (utils.is.element(this.elements.captions)) {
var content = utils.createElement('span');
// Empty the container
utils.emptyElement(this.elements.captions);
// Default to empty
var caption = !utils.is.nullOrUndefined(input) ? input : '';
// Set the span content
if (utils.is.string(caption)) {
content.innerText = caption.trim();
} else {
content.appendChild(caption);
}
// Set new caption text
this.elements.captions.appendChild(content);
} else {
if (!utils.is.element(this.elements.captions)) {
this.debug.warn('No captions element to render to');
return;
}
// Only accept array or empty input
if (!utils.is.nullOrUndefined(input) && !Array.isArray(input)) {
this.debug.warn('updateCues: Invalid input', input);
return;
}
var cues = input;
// Get cues from track
if (!cues) {
var track = captions.getCurrentTrack.call(this);
cues = Array.from((track || {}).activeCues || []).map(function (cue) {
return cue.getCueAsHTML();
}).map(utils.getHTML);
}
// Set new caption text
var content = cues.map(function (cueText) {
return cueText.trim();
}).join('\n');
var changed = content !== this.elements.captions.innerHTML;
if (changed) {
// Empty the container and create a new child element
utils.emptyElement(this.elements.captions);
var caption = utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.caption));
caption.innerHTML = content;
this.elements.captions.appendChild(caption);
// Trigger event
utils.dispatchEvent.call(this, this.media, 'cuechange');
}
}
};
@@ -3610,7 +3684,7 @@ var defaults$1 = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.3.11/plyr.svg',
iconUrl: 'https://cdn.plyr.io/3.3.12/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -3811,6 +3885,7 @@ var defaults$1 = {
},
progress: '.plyr__progress',
captions: '.plyr__captions',
caption: '.plyr__caption',
menu: {
quality: '.js-plyr__menu__list--quality'
}
@@ -4426,7 +4501,7 @@ var Listeners = function () {
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
var focused = utils.getFocusElement();
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
if (utils.is.element(focused) && focused !== this.player.elements.inputs.seek && utils.matches(focused, this.player.config.selectors.editable)) {
return;
}
@@ -4789,9 +4864,11 @@ var Listeners = function () {
// Proxy events to container
// Bubble up key events for Edge
utils.on(this.player.media, this.player.config.events.concat(['keyup', 'keydown']).join(' '), function (event) {
var detail = {};
var _event$detail = event.detail,
detail = _event$detail === undefined ? {} : _event$detail;
// Get error details from media
if (event.type === 'error') {
detail = _this3.player.media.error;
}
@@ -4890,7 +4967,7 @@ var Listeners = function () {
// Settings menu items - use event delegation as items are added/removed
if (utils.matches(event.target, _this4.player.config.selectors.inputs.language)) {
proxy(event, function () {
_this4.player.language = event.target.value;
_this4.player.currentTrack = Number(event.target.value);
showHomeTab();
}, 'language');
} else if (utils.matches(event.target, _this4.player.config.selectors.inputs.quality)) {
@@ -4920,6 +4997,12 @@ var Listeners = function () {
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
var seek = event.currentTarget;
var code = event.keyCode ? event.keyCode : event.which;
var eventType = event.type;
if ((eventType === 'keydown' || eventType === 'keyup') && code !== 39 && code !== 37) {
return;
}
// Was playing before?
var play = seek.hasAttribute('play-on-seeked');
@@ -5079,6 +5162,9 @@ var Listeners = function () {
// Set playback state and trigger change (only on actual change)
function assurePlaybackState(play) {
if (play && !this.embed.hasPlayed) {
this.embed.hasPlayed = true;
}
if (this.media.paused === play) {
this.media.paused = !play;
utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause');
@@ -5231,24 +5317,25 @@ var vimeo = {
paused = player.paused,
volume = player.volume;
// Set seeking state and trigger event
var restorePause = paused && !embed.hasPlayed;
// Set seeking state and trigger event
media.seeking = true;
utils.dispatchEvent.call(player, media, 'seeking');
// If paused, mute until seek is complete
Promise.resolve(paused && embed.setVolume(0))
Promise.resolve(restorePause && embed.setVolume(0))
// Seek
.then(function () {
return embed.setCurrentTime(time);
})
// Restore paused
.then(function () {
return paused && embed.pause();
return restorePause && embed.pause();
})
// Restore volume
.then(function () {
return paused && embed.setVolume(volume);
return restorePause && embed.setVolume(volume);
}).catch(function () {
// Do nothing
});
@@ -5378,17 +5465,25 @@ var vimeo = {
captions.setup.call(player);
});
player.embed.on('cuechange', function (data) {
var cue = null;
player.embed.on('cuechange', function (_ref) {
var _ref$cues = _ref.cues,
cues = _ref$cues === undefined ? [] : _ref$cues;
if (data.cues.length) {
cue = utils.stripHTML(data.cues[0].text);
}
captions.setText.call(player, cue);
var strippedCues = cues.map(function (cue) {
return utils.stripHTML(cue.text);
});
captions.updateCues.call(player, strippedCues);
});
player.embed.on('loaded', function () {
// Assure state and events are updated on autoplay
player.embed.getPaused().then(function (paused) {
assurePlaybackState.call(player, !paused);
if (!paused) {
utils.dispatchEvent.call(player, player.media, 'playing');
}
});
if (utils.is.element(player.embed.element) && player.supported.ui) {
var frame = player.embed.element;
@@ -5518,6 +5613,9 @@ function mapQualityUnits(levels) {
// Set playback state and trigger change (only on actual change)
function assurePlaybackState$1(play) {
if (play && !this.embed.hasPlayed) {
this.embed.hasPlayed = true;
}
if (this.media.paused === play) {
this.media.paused = !play;
utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause');
@@ -5931,7 +6029,7 @@ var youtube = {
case 1:
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
if (player.media.paused) {
if (player.media.paused && !player.embed.hasPlayed) {
player.media.pause();
} else {
assurePlaybackState$1.call(player, true);
@@ -6922,7 +7020,8 @@ var Plyr = function () {
// Captions
this.captions = {
active: null,
currentTrack: null
currentTrack: -1,
meta: new WeakMap()
};
// Fullscreen
@@ -6933,8 +7032,7 @@ var Plyr = function () {
// Options
this.options = {
speed: [],
quality: [],
captions: []
quality: []
};
// Debugging
@@ -7310,8 +7408,8 @@ var Plyr = function () {
}
/**
* Set the captions language
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
* Set the caption track by index
* @param {number} - Caption index
*/
}, {
@@ -8002,60 +8100,41 @@ var Plyr = function () {
return Boolean(this.config.autoplay);
}
}, {
key: 'language',
key: 'currentTrack',
set: function set$$1(input) {
// Nothing specified
if (!utils.is.string(input)) {
return;
}
// If empty string is passed, assume disable captions
if (utils.is.empty(input)) {
this.toggleCaptions(false);
return;
}
// Normalize
var language = input.toLowerCase();
// Check for support
if (!this.options.captions.includes(language)) {
this.debug.warn('Unsupported language option: ' + language);
return;
}
// Ensure captions are enabled
this.toggleCaptions(true);
// Enabled only
if (language === 'enabled') {
return;
}
// If nothing to change, bail
if (this.language === language) {
return;
}
// Update config
this.captions.language = language;
// Clear caption
captions.setText.call(this, null);
// Update captions
captions.setLanguage.call(this);
// Trigger an event
utils.dispatchEvent.call(this, this.media, 'languagechange');
captions.set.call(this, input);
}
/**
* Get the current captions language
* Get the current caption track index (-1 if disabled)
*/
,
get: function get$$1() {
return this.captions.language;
var _captions = this.captions,
active = _captions.active,
currentTrack = _captions.currentTrack;
return active ? currentTrack : -1;
}
/**
* Set the wanted language for captions
* Since tracks can be added later it won't update the actual caption track until there is a matching track
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
*/
}, {
key: 'language',
set: function set$$1(input) {
captions.setLanguage.call(this, input);
}
/**
* Get the current track's language
*/
,
get: function get$$1() {
return (captions.getCurrentTrack.call(this) || {}).language;
}
/**
@@ -8131,9 +8210,7 @@ var Plyr = function () {
} else if (utils.is.nodeList(selector)) {
targets = Array.from(selector);
} else if (utils.is.array(selector)) {
targets = selector.filter(function (i) {
return utils.is.element(i);
});
targets = selector.filter(utils.is.element);
}
if (utils.is.empty(targets)) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+348 -271
View File
@@ -5876,61 +5876,63 @@ var utils = {
// Check variable types
is: {
object: function object(input) {
return this.getConstructor(input) === Object;
return utils.getConstructor(input) === Object;
},
number: function number(input) {
return this.getConstructor(input) === Number && !Number.isNaN(input);
return utils.getConstructor(input) === Number && !Number.isNaN(input);
},
string: function string(input) {
return this.getConstructor(input) === String;
return utils.getConstructor(input) === String;
},
boolean: function boolean(input) {
return this.getConstructor(input) === Boolean;
return utils.getConstructor(input) === Boolean;
},
function: function _function(input) {
return this.getConstructor(input) === Function;
return utils.getConstructor(input) === Function;
},
array: function array(input) {
return !this.nullOrUndefined(input) && Array.isArray(input);
return !utils.is.nullOrUndefined(input) && Array.isArray(input);
},
weakMap: function weakMap(input) {
return this.instanceof(input, WeakMap);
return utils.is.instanceof(input, WeakMap);
},
nodeList: function nodeList(input) {
return this.instanceof(input, NodeList);
return utils.is.instanceof(input, NodeList);
},
element: function element(input) {
return this.instanceof(input, Element);
return utils.is.instanceof(input, Element);
},
textNode: function textNode(input) {
return this.getConstructor(input) === Text;
return utils.getConstructor(input) === Text;
},
event: function event(input) {
return this.instanceof(input, Event);
return utils.is.instanceof(input, Event);
},
cue: function cue(input) {
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
return utils.is.instanceof(input, window.TextTrackCue) || utils.is.instanceof(input, window.VTTCue);
},
track: function track(input) {
return this.instanceof(input, TextTrack) || !this.nullOrUndefined(input) && this.string(input.kind);
return utils.is.instanceof(input, TextTrack) || !utils.is.nullOrUndefined(input) && utils.is.string(input.kind);
},
url: function url(input) {
return !this.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
return !utils.is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
},
nullOrUndefined: function nullOrUndefined(input) {
return input === null || typeof input === 'undefined';
},
empty: function empty(input) {
return this.nullOrUndefined(input) || (this.string(input) || this.array(input) || this.nodeList(input)) && !input.length || this.object(input) && !Object.keys(input).length;
return utils.is.nullOrUndefined(input) || (utils.is.string(input) || utils.is.array(input) || utils.is.nodeList(input)) && !input.length || utils.is.object(input) && !Object.keys(input).length;
},
instanceof: function _instanceof$$1(input, constructor) {
return Boolean(input && constructor && input instanceof constructor);
},
getConstructor: function getConstructor(input) {
return !this.nullOrUndefined(input) ? input.constructor : null;
}
},
getConstructor: function getConstructor(input) {
return !utils.is.nullOrUndefined(input) ? input.constructor : null;
},
// Unfortunately, due to mixed support, UA sniffing is required
getBrowser: function getBrowser() {
return {
@@ -6020,26 +6022,25 @@ var utils = {
return;
}
var prefix = 'cache-';
var prefix = 'cache';
var hasId = utils.is.string(id);
var isCached = false;
var exists = function exists() {
return document.querySelectorAll('#' + id).length;
return document.getElementById(id) !== null;
};
function injectSprite(data) {
var update = function update(container, data) {
container.innerHTML = data;
// Check again incase of race condition
if (hasId && exists()) {
return;
}
// Inject content
this.innerHTML = data;
// Inject the SVG to the body
document.body.insertBefore(this, document.body.childNodes[0]);
}
document.body.insertAdjacentElement('afterbegin', container);
};
// Only load once if ID set
if (!hasId || !exists()) {
@@ -6055,13 +6056,12 @@ var utils = {
// Check in cache
if (useStorage) {
var cached = window.localStorage.getItem(prefix + id);
var cached = window.localStorage.getItem(prefix + '-' + id);
isCached = cached !== null;
if (isCached) {
var data = JSON.parse(cached);
injectSprite.call(container, data.content);
return;
update(container, data.content);
}
}
@@ -6072,12 +6072,12 @@ var utils = {
}
if (useStorage) {
window.localStorage.setItem(prefix + id, JSON.stringify({
window.localStorage.setItem(prefix + '-' + id, JSON.stringify({
content: result
}));
}
injectSprite.call(container, result);
update(container, result);
}).catch(function () {});
}
},
@@ -6552,7 +6552,7 @@ var utils = {
// Bail if the value isn't a number
if (!utils.is.number(time)) {
return this.formatTime(null, displayHours, inverted);
return utils.formatTime(null, displayHours, inverted);
}
// Format time component to add leading zero
@@ -6561,9 +6561,9 @@ var utils = {
};
// Breakdown to hours, mins, secs
var hours = this.getHours(time);
var mins = this.getMinutes(time);
var secs = this.getSeconds(time);
var hours = utils.getHours(time);
var mins = utils.getMinutes(time);
var secs = utils.getSeconds(time);
// Do we need to display hours?
if (displayHours || hours > 0) {
@@ -6759,12 +6759,12 @@ var utils = {
// Parse URL if needed
if (input.startsWith('http://') || input.startsWith('https://')) {
var _parseUrl = this.parseUrl(input);
var _utils$parseUrl = utils.parseUrl(input);
search = _parseUrl.search;
search = _utils$parseUrl.search;
}
if (this.is.empty(search)) {
if (utils.is.empty(search)) {
return null;
}
@@ -6803,6 +6803,14 @@ var utils = {
},
// Like outerHTML, but also works for DocumentFragment
getHTML: function getHTML(element) {
var wrapper = document.createElement('div');
wrapper.appendChild(element);
return wrapper.innerHTML;
},
// Get aspect ratio for dimensions
getAspectRatio: function getAspectRatio(width, height) {
var getRatio = function getRatio(w, h) {
@@ -7544,9 +7552,15 @@ var controls = {
// Create a settings menu item
createMenuItem: function createMenuItem(value, list, type, title) {
var badge = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
var checked = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
createMenuItem: function createMenuItem(_ref) {
var value = _ref.value,
list = _ref.list,
type = _ref.type,
title = _ref.title,
_ref$badge = _ref.badge,
badge = _ref$badge === undefined ? null : _ref$badge,
_ref$checked = _ref.checked,
checked = _ref$checked === undefined ? false : _ref$checked;
var item = utils.createElement('li');
@@ -7862,8 +7876,13 @@ var controls = {
var sorting = _this3.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(function (quality) {
var label = controls.getLabel.call(_this3, 'quality', quality);
controls.createMenuItem.call(_this3, quality, list, type, label, getBadge(quality));
controls.createMenuItem.call(_this3, {
value: quality,
list: list,
type: type,
title: controls.getLabel.call(_this3, 'quality', quality),
badge: getBadge(quality)
});
});
controls.updateSetting.call(this, type, list);
@@ -7906,18 +7925,7 @@ var controls = {
switch (setting) {
case 'captions':
if (this.captions.active) {
if (this.options.captions.length > 2 || !this.options.captions.some(function (lang) {
return lang === 'enabled';
})) {
value = this.captions.language;
} else {
value = 'enabled';
}
} else {
value = '';
}
value = this.currentTrack;
break;
default:
@@ -8012,10 +8020,10 @@ var controls = {
// TODO: Captions or language? Currently it's mixed
var type = 'captions';
var list = this.elements.settings.panes.captions.querySelector('ul');
var tracks = captions.getTracks.call(this);
// Toggle the pane and tab
var toggle = captions.getTracks.call(this).length;
controls.toggleTab.call(this, type, toggle);
controls.toggleTab.call(this, type, tracks.length);
// Empty the menu
utils.emptyElement(list);
@@ -8024,28 +8032,33 @@ var controls = {
controls.checkMenu.call(this);
// If there's no captions, bail
if (!toggle) {
if (!tracks.length) {
return;
}
// Re-map the tracks into just the data we need
var tracks = captions.getTracks.call(this).map(function (track) {
// Generate options data
var options = tracks.map(function (track, value) {
return {
language: !utils.is.empty(track.language) ? track.language : 'enabled',
label: captions.getLabel.call(_this4, track)
value: value,
checked: _this4.captions.active && _this4.currentTrack === value,
title: captions.getLabel.call(_this4, track),
badge: track.language && controls.createBadge.call(_this4, track.language.toUpperCase()),
list: list,
type: 'language'
};
});
// Add the "Disabled" option to turn off captions
tracks.unshift({
language: '',
label: i18n.get('disabled', this.config)
options.unshift({
value: -1,
checked: !this.captions.active,
title: i18n.get('disabled', this.config),
list: list,
type: 'language'
});
// Generate options
tracks.forEach(function (track) {
controls.createMenuItem.call(_this4, track.language, list, 'language', track.label, track.language !== 'enabled' ? controls.createBadge.call(_this4, track.language.toUpperCase()) : null, track.language.toLowerCase() === _this4.language);
});
options.forEach(controls.createMenuItem.bind(this));
controls.updateSetting.call(this, type, list);
},
@@ -8099,8 +8112,12 @@ var controls = {
// Create items
this.options.speed.forEach(function (speed) {
var label = controls.getLabel.call(_this5, 'speed', speed);
controls.createMenuItem.call(_this5, speed, list, type, label);
controls.createMenuItem.call(_this5, {
value: speed,
list: list,
type: type,
title: controls.getLabel.call(_this5, 'speed', speed)
});
});
controls.updateSetting.call(this, type, list);
@@ -8574,10 +8591,10 @@ var controls = {
var replace = function replace(input) {
var result = input;
Object.entries(props).forEach(function (_ref) {
var _ref2 = slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
Object.entries(props).forEach(function (_ref2) {
var _ref3 = slicedToArray(_ref2, 2),
key = _ref3[0],
value = _ref3[1];
result = utils.replaceAll(result, '{' + key + '}', value);
});
@@ -8694,91 +8711,168 @@ var captions = {
active = this.config.captions.active;
}
// Set toggled state
this.toggleCaptions(active);
// Get language from storage, fallback to config
var language = this.storage.get('language') || this.config.captions.language;
if (language === 'auto') {
var _split = (navigator.language || navigator.userLanguage).split('-');
var _split2 = slicedToArray(_split, 1);
language = _split2[0];
}
// Set language and show if active
captions.setLanguage.call(this, language, active);
// Watch changes to textTracks and update captions menu
if (this.config.captions.update) {
utils.on(this.media.textTracks, 'addtrack removetrack', captions.update.bind(this));
if (this.isHTML5) {
var trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
utils.on(this.media.textTracks, trackEvents, captions.update.bind(this));
}
// Update available languages in list next tick (the event must not be triggered before the listeners)
setTimeout(captions.update.bind(this), 0);
},
update: function update() {
// Update tracks
var tracks = captions.getTracks.call(this);
this.options.captions = tracks.map(function (_ref) {
var language = _ref.language;
return language;
});
var _this = this;
// Set language if it hasn't been set already
if (!this.language) {
var language = this.config.captions.language;
var tracks = captions.getTracks.call(this, true);
// Get the wanted language
var _captions = this.captions,
language = _captions.language,
meta = _captions.meta;
if (language === 'auto') {
var _split = (navigator.language || navigator.userLanguage).split('-');
// Handle tracks (add event listener and "pseudo"-default)
var _split2 = slicedToArray(_split, 1);
if (this.isHTML5 && this.isVideo) {
tracks.filter(function (track) {
return !meta.get(track);
}).forEach(function (track) {
_this.debug.log('Track added', track);
// Attempt to store if the original dom element was "default"
meta.set(track, {
default: track.mode === 'showing'
});
language = _split2[0];
}
this.language = this.storage.get('language') || (language || '').toLowerCase();
// Turn off native caption rendering to avoid double captions
track.mode = 'hidden';
// Add event listener for cue changes
utils.on(track, 'cuechange', function () {
return captions.updateCues.call(_this);
});
});
}
// Toggle the class hooks
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
var trackRemoved = !tracks.find(function (track) {
return track === _this.captions.currentTrackNode;
});
var firstMatch = this.language !== language && tracks.find(function (track) {
return track.language === language;
});
// Update language if removed or first matching track added
if (trackRemoved || firstMatch) {
captions.setLanguage.call(this, language, this.config.captions.active);
}
// Enable or disable captions based on track length
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(tracks));
// Update available languages in list
if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
}
},
set: function set(index) {
var setLanguage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var show = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var tracks = captions.getTracks.call(this);
// Set the captions language
setLanguage: function setLanguage() {
var _this = this;
// Setup HTML5 track rendering
if (this.isHTML5 && this.isVideo) {
captions.getTracks.call(this).forEach(function (track) {
// Show track
utils.on(track, 'cuechange', function (event) {
return captions.setCue.call(_this, event);
});
// Turn off native caption rendering to avoid double captions
// eslint-disable-next-line
track.mode = 'hidden';
});
// Get current track
var currentTrack = captions.getCurrentTrack.call(this);
// Check if suported kind
if (utils.is.track(currentTrack)) {
// If we change the active track while a cue is already displayed we need to update it
if (Array.from(currentTrack.activeCues || []).length) {
captions.setCue.call(this, currentTrack);
}
}
} else if (this.isVimeo && this.captions.active) {
this.embed.enableTextTrack(this.language);
// Disable captions if setting to -1
if (index === -1) {
this.toggleCaptions(false);
return;
}
if (!utils.is.number(index)) {
this.debug.warn('Invalid caption argument', index);
return;
}
if (!(index in tracks)) {
this.debug.warn('Track not found', index);
return;
}
if (this.captions.currentTrack !== index) {
this.captions.currentTrack = index;
var track = captions.getCurrentTrack.call(this);
var _ref = track || {},
language = _ref.language;
// Store reference to node for invalidation on remove
this.captions.currentTrackNode = track;
// Prevent setting language in some cases, since it can violate user's intentions
if (setLanguage) {
this.captions.language = language;
}
// Handle Vimeo captions
if (this.isVimeo) {
this.embed.enableTextTrack(language);
}
// Trigger event
utils.dispatchEvent.call(this, this.media, 'languagechange');
}
if (this.isHTML5 && this.isVideo) {
// If we change the active track while a cue is already displayed we need to update it
captions.updateCues.call(this);
}
// Show captions
if (show) {
this.toggleCaptions(true);
}
},
setLanguage: function setLanguage(language) {
var show = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
if (!utils.is.string(language)) {
this.debug.warn('Invalid language argument', language);
return;
}
// Normalize
this.captions.language = language.toLowerCase();
// Set currentTrack
var tracks = captions.getTracks.call(this);
var track = captions.getCurrentTrack.call(this, true);
captions.set.call(this, tracks.indexOf(track), false, show);
},
// Get the tracks
// Get current valid caption tracks
// If update is false it will also ignore tracks without metadata
// This is used to "freeze" the language options when captions.update is false
getTracks: function getTracks() {
// Return empty array at least
if (utils.is.nullOrUndefined(this.media)) {
return [];
}
var _this2 = this;
// Only get accepted kinds
return Array.from(this.media.textTracks || []).filter(function (track) {
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
// Handle media or textTracks missing or null
var tracks = Array.from((this.media || {}).textTracks || []);
// For HTML5, use cache instead of current tracks when it exists (if captions.update is false)
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
return tracks.filter(function (track) {
return !_this2.isHTML5 || update || _this2.captions.meta.has(track);
}).filter(function (track) {
return ['captions', 'subtitles'].includes(track.kind);
});
},
@@ -8786,32 +8880,20 @@ var captions = {
// Get the current track for the current language
getCurrentTrack: function getCurrentTrack() {
var _this2 = this;
var _this3 = this;
var fromLanguage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var tracks = captions.getTracks.call(this);
if (!tracks.length) {
return null;
}
// Get track based on current language
var track = tracks.find(function (track) {
return track.language.toLowerCase() === _this2.language;
var sortIsDefault = function sortIsDefault(track) {
return Number((_this3.captions.meta.get(track) || {}).default);
};
var sorted = Array.from(tracks).sort(function (a, b) {
return sortIsDefault(b) - sortIsDefault(a);
});
// Get the <track> with default attribute
if (!track) {
track = utils.getElement.call(this, 'track[default]');
}
// Get the first track
if (!track) {
var _tracks = slicedToArray(tracks, 1);
track = _tracks[0];
}
return track;
return !fromLanguage && tracks[this.currentTrack] || sorted.find(function (track) {
return track.language === _this3.captions.language;
}) || sorted[0];
},
@@ -8839,58 +8921,50 @@ var captions = {
},
// Display active caption if it contains text
setCue: function setCue(input) {
// Get the track from the event if needed
var track = utils.is.event(input) ? input.target : input;
var activeCues = track.activeCues;
var active = activeCues.length && activeCues[0];
var currentTrack = captions.getCurrentTrack.call(this);
// Only display current track
if (track !== currentTrack) {
return;
}
// Display a cue, if there is one
if (utils.is.cue(active)) {
captions.setText.call(this, active.getCueAsHTML());
} else {
captions.setText.call(this, null);
}
utils.dispatchEvent.call(this, this.media, 'cuechange');
},
// Set the current caption
setText: function setText(input) {
// Update captions using current track's active cues
// Also optional array argument in case there isn't any track (ex: vimeo)
updateCues: function updateCues(input) {
// Requires UI
if (!this.supported.ui) {
return;
}
if (utils.is.element(this.elements.captions)) {
var content = utils.createElement('span');
// Empty the container
utils.emptyElement(this.elements.captions);
// Default to empty
var caption = !utils.is.nullOrUndefined(input) ? input : '';
// Set the span content
if (utils.is.string(caption)) {
content.innerText = caption.trim();
} else {
content.appendChild(caption);
}
// Set new caption text
this.elements.captions.appendChild(content);
} else {
if (!utils.is.element(this.elements.captions)) {
this.debug.warn('No captions element to render to');
return;
}
// Only accept array or empty input
if (!utils.is.nullOrUndefined(input) && !Array.isArray(input)) {
this.debug.warn('updateCues: Invalid input', input);
return;
}
var cues = input;
// Get cues from track
if (!cues) {
var track = captions.getCurrentTrack.call(this);
cues = Array.from((track || {}).activeCues || []).map(function (cue) {
return cue.getCueAsHTML();
}).map(utils.getHTML);
}
// Set new caption text
var content = cues.map(function (cueText) {
return cueText.trim();
}).join('\n');
var changed = content !== this.elements.captions.innerHTML;
if (changed) {
// Empty the container and create a new child element
utils.emptyElement(this.elements.captions);
var caption = utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.caption));
caption.innerHTML = content;
this.elements.captions.appendChild(caption);
// Trigger event
utils.dispatchEvent.call(this, this.media, 'cuechange');
}
}
};
@@ -8993,7 +9067,7 @@ var defaults$1 = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.3.11/plyr.svg',
iconUrl: 'https://cdn.plyr.io/3.3.12/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -9194,6 +9268,7 @@ var defaults$1 = {
},
progress: '.plyr__progress',
captions: '.plyr__captions',
caption: '.plyr__caption',
menu: {
quality: '.js-plyr__menu__list--quality'
}
@@ -9809,7 +9884,7 @@ var Listeners = function () {
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
var focused = utils.getFocusElement();
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
if (utils.is.element(focused) && focused !== this.player.elements.inputs.seek && utils.matches(focused, this.player.config.selectors.editable)) {
return;
}
@@ -10172,9 +10247,11 @@ var Listeners = function () {
// Proxy events to container
// Bubble up key events for Edge
utils.on(this.player.media, this.player.config.events.concat(['keyup', 'keydown']).join(' '), function (event) {
var detail = {};
var _event$detail = event.detail,
detail = _event$detail === undefined ? {} : _event$detail;
// Get error details from media
if (event.type === 'error') {
detail = _this3.player.media.error;
}
@@ -10273,7 +10350,7 @@ var Listeners = function () {
// Settings menu items - use event delegation as items are added/removed
if (utils.matches(event.target, _this4.player.config.selectors.inputs.language)) {
proxy(event, function () {
_this4.player.language = event.target.value;
_this4.player.currentTrack = Number(event.target.value);
showHomeTab();
}, 'language');
} else if (utils.matches(event.target, _this4.player.config.selectors.inputs.quality)) {
@@ -10303,6 +10380,12 @@ var Listeners = function () {
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
var seek = event.currentTarget;
var code = event.keyCode ? event.keyCode : event.which;
var eventType = event.type;
if ((eventType === 'keydown' || eventType === 'keyup') && code !== 39 && code !== 37) {
return;
}
// Was playing before?
var play = seek.hasAttribute('play-on-seeked');
@@ -10462,6 +10545,9 @@ var Listeners = function () {
// Set playback state and trigger change (only on actual change)
function assurePlaybackState(play) {
if (play && !this.embed.hasPlayed) {
this.embed.hasPlayed = true;
}
if (this.media.paused === play) {
this.media.paused = !play;
utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause');
@@ -10614,24 +10700,25 @@ var vimeo = {
paused = player.paused,
volume = player.volume;
// Set seeking state and trigger event
var restorePause = paused && !embed.hasPlayed;
// Set seeking state and trigger event
media.seeking = true;
utils.dispatchEvent.call(player, media, 'seeking');
// If paused, mute until seek is complete
Promise.resolve(paused && embed.setVolume(0))
Promise.resolve(restorePause && embed.setVolume(0))
// Seek
.then(function () {
return embed.setCurrentTime(time);
})
// Restore paused
.then(function () {
return paused && embed.pause();
return restorePause && embed.pause();
})
// Restore volume
.then(function () {
return paused && embed.setVolume(volume);
return restorePause && embed.setVolume(volume);
}).catch(function () {
// Do nothing
});
@@ -10761,17 +10848,25 @@ var vimeo = {
captions.setup.call(player);
});
player.embed.on('cuechange', function (data) {
var cue = null;
player.embed.on('cuechange', function (_ref) {
var _ref$cues = _ref.cues,
cues = _ref$cues === undefined ? [] : _ref$cues;
if (data.cues.length) {
cue = utils.stripHTML(data.cues[0].text);
}
captions.setText.call(player, cue);
var strippedCues = cues.map(function (cue) {
return utils.stripHTML(cue.text);
});
captions.updateCues.call(player, strippedCues);
});
player.embed.on('loaded', function () {
// Assure state and events are updated on autoplay
player.embed.getPaused().then(function (paused) {
assurePlaybackState.call(player, !paused);
if (!paused) {
utils.dispatchEvent.call(player, player.media, 'playing');
}
});
if (utils.is.element(player.embed.element) && player.supported.ui) {
var frame = player.embed.element;
@@ -10901,6 +10996,9 @@ function mapQualityUnits(levels) {
// Set playback state and trigger change (only on actual change)
function assurePlaybackState$1(play) {
if (play && !this.embed.hasPlayed) {
this.embed.hasPlayed = true;
}
if (this.media.paused === play) {
this.media.paused = !play;
utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause');
@@ -11314,7 +11412,7 @@ var youtube = {
case 1:
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
if (player.media.paused) {
if (player.media.paused && !player.embed.hasPlayed) {
player.media.pause();
} else {
assurePlaybackState$1.call(player, true);
@@ -12305,7 +12403,8 @@ var Plyr = function () {
// Captions
this.captions = {
active: null,
currentTrack: null
currentTrack: -1,
meta: new WeakMap()
};
// Fullscreen
@@ -12316,8 +12415,7 @@ var Plyr = function () {
// Options
this.options = {
speed: [],
quality: [],
captions: []
quality: []
};
// Debugging
@@ -12693,8 +12791,8 @@ var Plyr = function () {
}
/**
* Set the captions language
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
* Set the caption track by index
* @param {number} - Caption index
*/
}, {
@@ -13385,60 +13483,41 @@ var Plyr = function () {
return Boolean(this.config.autoplay);
}
}, {
key: 'language',
key: 'currentTrack',
set: function set(input) {
// Nothing specified
if (!utils.is.string(input)) {
return;
}
// If empty string is passed, assume disable captions
if (utils.is.empty(input)) {
this.toggleCaptions(false);
return;
}
// Normalize
var language = input.toLowerCase();
// Check for support
if (!this.options.captions.includes(language)) {
this.debug.warn('Unsupported language option: ' + language);
return;
}
// Ensure captions are enabled
this.toggleCaptions(true);
// Enabled only
if (language === 'enabled') {
return;
}
// If nothing to change, bail
if (this.language === language) {
return;
}
// Update config
this.captions.language = language;
// Clear caption
captions.setText.call(this, null);
// Update captions
captions.setLanguage.call(this);
// Trigger an event
utils.dispatchEvent.call(this, this.media, 'languagechange');
captions.set.call(this, input);
}
/**
* Get the current captions language
* Get the current caption track index (-1 if disabled)
*/
,
get: function get() {
return this.captions.language;
var _captions = this.captions,
active = _captions.active,
currentTrack = _captions.currentTrack;
return active ? currentTrack : -1;
}
/**
* Set the wanted language for captions
* Since tracks can be added later it won't update the actual caption track until there is a matching track
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
*/
}, {
key: 'language',
set: function set(input) {
captions.setLanguage.call(this, input);
}
/**
* Get the current track's language
*/
,
get: function get() {
return (captions.getCurrentTrack.call(this) || {}).language;
}
/**
@@ -13514,9 +13593,7 @@ var Plyr = function () {
} else if (utils.is.nodeList(selector)) {
targets = Array.from(selector);
} else if (utils.is.array(selector)) {
targets = selector.filter(function (i) {
return utils.is.element(i);
});
targets = selector.filter(utils.is.element);
}
if (utils.is.empty(targets)) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "plyr",
"version": "3.3.11",
"version": "3.3.12",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io",
"main": "./dist/plyr.js",
+6 -5
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.3.11/plyr.js"></script>
<script src="https://cdn.plyr.io/3.3.12/plyr.js"></script>
```
...or...
```html
<script src="https://cdn.plyr.io/3.3.11/plyr.polyfilled.js"></script>
<script src="https://cdn.plyr.io/3.3.12/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.3.11/plyr.css">
<link rel="stylesheet" href="https://cdn.plyr.io/3.3.12/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.3.11/plyr.svg`.
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.3.12/plyr.svg`.
## Ads
@@ -411,7 +411,8 @@ player.fullscreen.active; // false;
| `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples. |
| `poster` | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
| `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. |
| `currentTrack` | ✓ | ✓ | Gets or sets the caption track by index. `-1` means the track is missing or captions is not active |
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language data, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. |
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
| `pip` | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. |
+151 -111
View File
@@ -69,12 +69,18 @@ const captions = {
({ active } = this.config.captions);
}
// Set toggled state
this.toggleCaptions(active);
// Get language from storage, fallback to config
let language = this.storage.get('language') || this.config.captions.language;
if (language === 'auto') {
[ language ] = (navigator.language || navigator.userLanguage).split('-');
}
// Set language and show if active
captions.setLanguage.call(this, language, active);
// Watch changes to textTracks and update captions menu
if (this.config.captions.update) {
utils.on(this.media.textTracks, 'addtrack removetrack', captions.update.bind(this));
if (this.isHTML5) {
const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
utils.on(this.media.textTracks, trackEvents, captions.update.bind(this));
}
// Update available languages in list next tick (the event must not be triggered before the listeners)
@@ -82,21 +88,39 @@ const captions = {
},
update() {
// Update tracks
const tracks = captions.getTracks.call(this);
this.options.captions = tracks.map(({language}) => language);
const tracks = captions.getTracks.call(this, true);
// Get the wanted language
const { language, meta } = this.captions;
// Set language if it hasn't been set already
if (!this.language) {
let { language } = this.config.captions;
if (language === 'auto') {
[ language ] = (navigator.language || navigator.userLanguage).split('-');
}
this.language = this.storage.get('language') || (language || '').toLowerCase();
// Handle tracks (add event listener and "pseudo"-default)
if (this.isHTML5 && this.isVideo) {
tracks
.filter(track => !meta.get(track))
.forEach(track => {
this.debug.log('Track added', track);
// Attempt to store if the original dom element was "default"
meta.set(track, {
default: track.mode === 'showing',
});
// Turn off native caption rendering to avoid double captions
track.mode = 'hidden';
// Add event listener for cue changes
utils.on(track, 'cuechange', () => captions.updateCues.call(this));
});
}
// Toggle the class hooks
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
const trackRemoved = !tracks.find(track => track === this.captions.currentTrackNode);
const firstMatch = this.language !== language && tracks.find(track => track.language === language);
// Update language if removed or first matching track added
if (trackRemoved || firstMatch) {
captions.setLanguage.call(this, language, this.config.captions.active);
}
// Enable or disable captions based on track length
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(tracks));
// Update available languages in list
if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
@@ -104,70 +128,94 @@ const captions = {
}
},
// Set the captions language
setLanguage() {
// Setup HTML5 track rendering
if (this.isHTML5 && this.isVideo) {
captions.getTracks.call(this).forEach(track => {
// Show track
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
set(index, setLanguage = true, show = true) {
const tracks = captions.getTracks.call(this);
// Turn off native caption rendering to avoid double captions
// eslint-disable-next-line
track.mode = 'hidden';
});
// Disable captions if setting to -1
if (index === -1) {
this.toggleCaptions(false);
return;
}
// Get current track
const currentTrack = captions.getCurrentTrack.call(this);
if (!utils.is.number(index)) {
this.debug.warn('Invalid caption argument', index);
return;
}
// Check if suported kind
if (utils.is.track(currentTrack)) {
// If we change the active track while a cue is already displayed we need to update it
if (Array.from(currentTrack.activeCues || []).length) {
captions.setCue.call(this, currentTrack);
}
if (!(index in tracks)) {
this.debug.warn('Track not found', index);
return;
}
if (this.captions.currentTrack !== index) {
this.captions.currentTrack = index;
const track = captions.getCurrentTrack.call(this);
const { language } = track || {};
// Store reference to node for invalidation on remove
this.captions.currentTrackNode = track;
// Prevent setting language in some cases, since it can violate user's intentions
if (setLanguage) {
this.captions.language = language;
}
} else if (this.isVimeo && this.captions.active) {
this.embed.enableTextTrack(this.language);
// Handle Vimeo captions
if (this.isVimeo) {
this.embed.enableTextTrack(language);
}
// Trigger event
utils.dispatchEvent.call(this, this.media, 'languagechange');
}
if (this.isHTML5 && this.isVideo) {
// If we change the active track while a cue is already displayed we need to update it
captions.updateCues.call(this);
}
// Show captions
if (show) {
this.toggleCaptions(true);
}
},
// Get the tracks
getTracks() {
// Return empty array at least
if (utils.is.nullOrUndefined(this.media)) {
return [];
setLanguage(language, show = true) {
if (!utils.is.string(language)) {
this.debug.warn('Invalid language argument', language);
return;
}
// Normalize
this.captions.language = language.toLowerCase();
// Only get accepted kinds
return Array.from(this.media.textTracks || []).filter(track => [
'captions',
'subtitles',
].includes(track.kind));
// Set currentTrack
const tracks = captions.getTracks.call(this);
const track = captions.getCurrentTrack.call(this, true);
captions.set.call(this, tracks.indexOf(track), false, show);
},
// Get current valid caption tracks
// If update is false it will also ignore tracks without metadata
// This is used to "freeze" the language options when captions.update is false
getTracks(update = false) {
// Handle media or textTracks missing or null
const tracks = Array.from((this.media || {}).textTracks || []);
// For HTML5, use cache instead of current tracks when it exists (if captions.update is false)
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
return tracks
.filter(track => !this.isHTML5 || update || this.captions.meta.has(track))
.filter(track => [
'captions',
'subtitles',
].includes(track.kind));
},
// Get the current track for the current language
getCurrentTrack() {
getCurrentTrack(fromLanguage = false) {
const tracks = captions.getTracks.call(this);
if (!tracks.length) {
return null;
}
// Get track based on current language
let track = tracks.find(track => track.language.toLowerCase() === this.language);
// Get the <track> with default attribute
if (!track) {
track = utils.getElement.call(this, 'track[default]');
}
// Get the first track
if (!track) {
[track] = tracks;
}
return track;
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
return (!fromLanguage && tracks[this.currentTrack]) || sorted.find(track => track.language === this.captions.language) || sorted[0];
},
// Get UI label for track
@@ -193,56 +241,48 @@ const captions = {
return i18n.get('disabled', this.config);
},
// Display active caption if it contains text
setCue(input) {
// Get the track from the event if needed
const track = utils.is.event(input) ? input.target : input;
const { activeCues } = track;
const active = activeCues.length && activeCues[0];
const currentTrack = captions.getCurrentTrack.call(this);
// Only display current track
if (track !== currentTrack) {
return;
}
// Display a cue, if there is one
if (utils.is.cue(active)) {
captions.setText.call(this, active.getCueAsHTML());
} else {
captions.setText.call(this, null);
}
utils.dispatchEvent.call(this, this.media, 'cuechange');
},
// Set the current caption
setText(input) {
// Update captions using current track's active cues
// Also optional array argument in case there isn't any track (ex: vimeo)
updateCues(input) {
// Requires UI
if (!this.supported.ui) {
return;
}
if (utils.is.element(this.elements.captions)) {
const content = utils.createElement('span');
// Empty the container
utils.emptyElement(this.elements.captions);
// Default to empty
const caption = !utils.is.nullOrUndefined(input) ? input : '';
// Set the span content
if (utils.is.string(caption)) {
content.innerText = caption.trim();
} else {
content.appendChild(caption);
}
// Set new caption text
this.elements.captions.appendChild(content);
} else {
if (!utils.is.element(this.elements.captions)) {
this.debug.warn('No captions element to render to');
return;
}
// Only accept array or empty input
if (!utils.is.nullOrUndefined(input) && !Array.isArray(input)) {
this.debug.warn('updateCues: Invalid input', input);
return;
}
let cues = input;
// Get cues from track
if (!cues) {
const track = captions.getCurrentTrack.call(this);
cues = Array.from((track || {}).activeCues || [])
.map(cue => cue.getCueAsHTML())
.map(utils.getHTML);
}
// Set new caption text
const content = cues.map(cueText => cueText.trim()).join('\n');
const changed = content !== this.elements.captions.innerHTML;
if (changed) {
// Empty the container and create a new child element
utils.emptyElement(this.elements.captions);
const caption = utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.caption));
caption.innerHTML = content;
this.elements.captions.appendChild(caption);
// Trigger event
utils.dispatchEvent.call(this, this.media, 'cuechange');
}
},
};
+33 -36
View File
@@ -376,7 +376,7 @@ const controls = {
},
// Create a settings menu item
createMenuItem(value, list, type, title, badge = null, checked = false) {
createMenuItem({value, list, type, title, badge = null, checked = false}) {
const item = utils.createElement('li');
const label = utils.createElement('label', {
@@ -680,8 +680,13 @@ const controls = {
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
})
.forEach(quality => {
const label = controls.getLabel.call(this, 'quality', quality);
controls.createMenuItem.call(this, quality, list, type, label, getBadge(quality));
controls.createMenuItem.call(this, {
value: quality,
list,
type,
title: controls.getLabel.call(this, 'quality', quality),
badge: getBadge(quality),
});
});
controls.updateSetting.call(this, type, list);
@@ -722,16 +727,7 @@ const controls = {
switch (setting) {
case 'captions':
if (this.captions.active) {
if (this.options.captions.length > 2 || !this.options.captions.some(lang => lang === 'enabled')) {
value = this.captions.language;
} else {
value = 'enabled';
}
} else {
value = '';
}
value = this.currentTrack;
break;
default:
@@ -831,10 +827,10 @@ const controls = {
// TODO: Captions or language? Currently it's mixed
const type = 'captions';
const list = this.elements.settings.panes.captions.querySelector('ul');
const tracks = captions.getTracks.call(this);
// Toggle the pane and tab
const toggle = captions.getTracks.call(this).length;
controls.toggleTab.call(this, type, toggle);
controls.toggleTab.call(this, type, tracks.length);
// Empty the menu
utils.emptyElement(list);
@@ -843,34 +839,31 @@ const controls = {
controls.checkMenu.call(this);
// If there's no captions, bail
if (!toggle) {
if (!tracks.length) {
return;
}
// Re-map the tracks into just the data we need
const tracks = captions.getTracks.call(this).map(track => ({
language: !utils.is.empty(track.language) ? track.language : 'enabled',
label: captions.getLabel.call(this, track),
// Generate options data
const options = tracks.map((track, value) => ({
value,
checked: this.captions.active && this.currentTrack === value,
title: captions.getLabel.call(this, track),
badge: track.language && controls.createBadge.call(this, track.language.toUpperCase()),
list,
type: 'language',
}));
// Add the "Disabled" option to turn off captions
tracks.unshift({
language: '',
label: i18n.get('disabled', this.config),
options.unshift({
value: -1,
checked: !this.captions.active,
title: i18n.get('disabled', this.config),
list,
type: 'language',
});
// Generate options
tracks.forEach(track => {
controls.createMenuItem.call(
this,
track.language,
list,
'language',
track.label,
track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null,
track.language.toLowerCase() === this.language,
);
});
options.forEach(controls.createMenuItem.bind(this));
controls.updateSetting.call(this, type, list);
},
@@ -927,8 +920,12 @@ const controls = {
// Create items
this.options.speed.forEach(speed => {
const label = controls.getLabel.call(this, 'speed', speed);
controls.createMenuItem.call(this, speed, list, type, label);
controls.createMenuItem.call(this, {
value: speed,
list,
type,
title: controls.getLabel.call(this, 'speed', speed),
});
});
controls.updateSetting.call(this, type, list);
+2 -1
View File
@@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.3.11/plyr.svg',
iconUrl: 'https://cdn.plyr.io/3.3.12/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -328,6 +328,7 @@ const defaults = {
},
progress: '.plyr__progress',
captions: '.plyr__captions',
caption: '.plyr__caption',
menu: {
quality: '.js-plyr__menu__list--quality',
},
+12 -3
View File
@@ -74,7 +74,10 @@ class Listeners {
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = utils.getFocusElement();
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
if (utils.is.element(focused) && (
focused !== this.player.elements.inputs.seek &&
utils.matches(focused, this.player.config.selectors.editable))
) {
return;
}
@@ -411,7 +414,7 @@ class Listeners {
'keyup',
'keydown',
]).join(' '), event => {
let detail = {};
let {detail = {}} = event;
// Get error details from media
if (event.type === 'error') {
@@ -520,7 +523,7 @@ class Listeners {
proxy(
event,
() => {
this.player.language = event.target.value;
this.player.currentTrack = Number(event.target.value);
showHomeTab();
},
'language',
@@ -560,6 +563,12 @@ class Listeners {
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
const seek = event.currentTarget;
const code = event.keyCode ? event.keyCode : event.which;
const eventType = event.type;
if ((eventType === 'keydown' || eventType === 'keyup') && (code !== 39 && code !== 37)) {
return;
}
// Was playing before?
const play = seek.hasAttribute('play-on-seeked');
+18 -11
View File
@@ -9,6 +9,9 @@ import utils from './../utils';
// Set playback state and trigger change (only on actual change)
function assurePlaybackState(play) {
if (play && !this.embed.hasPlayed) {
this.embed.hasPlayed = true;
}
if (this.media.paused === play) {
this.media.paused = !play;
utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause');
@@ -153,19 +156,20 @@ const vimeo = {
// Get current paused state and volume etc
const { embed, media, paused, volume } = player;
const restorePause = paused && !embed.hasPlayed;
// Set seeking state and trigger event
media.seeking = true;
utils.dispatchEvent.call(player, media, 'seeking');
// If paused, mute until seek is complete
Promise.resolve(paused && embed.setVolume(0))
Promise.resolve(restorePause && embed.setVolume(0))
// Seek
.then(() => embed.setCurrentTime(time))
// Restore paused
.then(() => paused && embed.pause())
.then(() => restorePause && embed.pause())
// Restore volume
.then(() => paused && embed.setVolume(volume))
.then(() => restorePause && embed.setVolume(volume))
.catch(() => {
// Do nothing
});
@@ -301,17 +305,20 @@ const vimeo = {
captions.setup.call(player);
});
player.embed.on('cuechange', data => {
let cue = null;
if (data.cues.length) {
cue = utils.stripHTML(data.cues[0].text);
}
captions.setText.call(player, cue);
player.embed.on('cuechange', ({ cues = [] }) => {
const strippedCues = cues.map(cue => utils.stripHTML(cue.text));
captions.updateCues.call(player, strippedCues);
});
player.embed.on('loaded', () => {
// Assure state and events are updated on autoplay
player.embed.getPaused().then(paused => {
assurePlaybackState.call(player, !paused);
if (!paused) {
utils.dispatchEvent.call(player, player.media, 'playing');
}
});
if (utils.is.element(player.embed.element) && player.supported.ui) {
const frame = player.embed.element;
+4 -1
View File
@@ -66,6 +66,9 @@ function mapQualityUnits(levels) {
// Set playback state and trigger change (only on actual change)
function assurePlaybackState(play) {
if (play && !this.embed.hasPlayed) {
this.embed.hasPlayed = true;
}
if (this.media.paused === play) {
this.media.paused = !play;
utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause');
@@ -469,7 +472,7 @@ const youtube = {
case 1:
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
if (player.media.paused) {
if (player.media.paused && !player.embed.hasPlayed) {
player.media.pause();
} else {
assurePlaybackState.call(player, true);
+27 -53
View File
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
// plyr.js v3.3.11
// plyr.js v3.3.12
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
@@ -84,7 +84,8 @@ class Plyr {
// Captions
this.captions = {
active: null,
currentTrack: null,
currentTrack: -1,
meta: new WeakMap(),
};
// Fullscreen
@@ -96,7 +97,6 @@ class Plyr {
this.options = {
speed: [],
quality: [],
captions: [],
};
// Debugging
@@ -854,61 +854,35 @@ class Plyr {
}
/**
* Set the captions language
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
* Set the caption track by index
* @param {number} - Caption index
*/
set language(input) {
// Nothing specified
if (!utils.is.string(input)) {
return;
}
// If empty string is passed, assume disable captions
if (utils.is.empty(input)) {
this.toggleCaptions(false);
return;
}
// Normalize
const language = input.toLowerCase();
// Check for support
if (!this.options.captions.includes(language)) {
this.debug.warn(`Unsupported language option: ${language}`);
return;
}
// Ensure captions are enabled
this.toggleCaptions(true);
// Enabled only
if (language === 'enabled') {
return;
}
// If nothing to change, bail
if (this.language === language) {
return;
}
// Update config
this.captions.language = language;
// Clear caption
captions.setText.call(this, null);
// Update captions
captions.setLanguage.call(this);
// Trigger an event
utils.dispatchEvent.call(this, this.media, 'languagechange');
set currentTrack(input) {
captions.set.call(this, input);
}
/**
* Get the current captions language
* Get the current caption track index (-1 if disabled)
*/
get currentTrack() {
const { active, currentTrack } = this.captions;
return active ? currentTrack : -1;
}
/**
* Set the wanted language for captions
* Since tracks can be added later it won't update the actual caption track until there is a matching track
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
*/
set language(input) {
captions.setLanguage.call(this, input);
}
/**
* Get the current track's language
*/
get language() {
return this.captions.language;
return (captions.getCurrentTrack.call(this) || {}).language;
}
/**
@@ -1159,7 +1133,7 @@ class Plyr {
} else if (utils.is.nodeList(selector)) {
targets = Array.from(selector);
} else if (utils.is.array(selector)) {
targets = selector.filter(i => utils.is.element(i));
targets = selector.filter(utils.is.element);
}
if (utils.is.empty(targets)) {
+1 -1
View File
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr Polyfilled Build
// plyr.js v3.3.11
// plyr.js v3.3.12
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
+45 -39
View File
@@ -11,63 +11,64 @@ const utils = {
// Check variable types
is: {
object(input) {
return this.getConstructor(input) === Object;
return utils.getConstructor(input) === Object;
},
number(input) {
return this.getConstructor(input) === Number && !Number.isNaN(input);
return utils.getConstructor(input) === Number && !Number.isNaN(input);
},
string(input) {
return this.getConstructor(input) === String;
return utils.getConstructor(input) === String;
},
boolean(input) {
return this.getConstructor(input) === Boolean;
return utils.getConstructor(input) === Boolean;
},
function(input) {
return this.getConstructor(input) === Function;
return utils.getConstructor(input) === Function;
},
array(input) {
return !this.nullOrUndefined(input) && Array.isArray(input);
return !utils.is.nullOrUndefined(input) && Array.isArray(input);
},
weakMap(input) {
return this.instanceof(input, WeakMap);
return utils.is.instanceof(input, WeakMap);
},
nodeList(input) {
return this.instanceof(input, NodeList);
return utils.is.instanceof(input, NodeList);
},
element(input) {
return this.instanceof(input, Element);
return utils.is.instanceof(input, Element);
},
textNode(input) {
return this.getConstructor(input) === Text;
return utils.getConstructor(input) === Text;
},
event(input) {
return this.instanceof(input, Event);
return utils.is.instanceof(input, Event);
},
cue(input) {
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
return utils.is.instanceof(input, window.TextTrackCue) || utils.is.instanceof(input, window.VTTCue);
},
track(input) {
return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind));
return utils.is.instanceof(input, TextTrack) || (!utils.is.nullOrUndefined(input) && utils.is.string(input.kind));
},
url(input) {
return !this.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
return !utils.is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
},
nullOrUndefined(input) {
return input === null || typeof input === 'undefined';
},
empty(input) {
return (
this.nullOrUndefined(input) ||
((this.string(input) || this.array(input) || this.nodeList(input)) && !input.length) ||
(this.object(input) && !Object.keys(input).length)
utils.is.nullOrUndefined(input) ||
((utils.is.string(input) || utils.is.array(input) || utils.is.nodeList(input)) && !input.length) ||
(utils.is.object(input) && !Object.keys(input).length)
);
},
instanceof(input, constructor) {
return Boolean(input && constructor && input instanceof constructor);
},
getConstructor(input) {
return !this.nullOrUndefined(input) ? input.constructor : null;
},
},
getConstructor(input) {
return !utils.is.nullOrUndefined(input) ? input.constructor : null;
},
// Unfortunately, due to mixed support, UA sniffing is required
@@ -151,24 +152,23 @@ const utils = {
return;
}
const prefix = 'cache-';
const prefix = 'cache';
const hasId = utils.is.string(id);
let isCached = false;
const exists = () => document.querySelectorAll(`#${id}`).length;
const exists = () => document.getElementById(id) !== null;
const update = (container, data) => {
container.innerHTML = data;
function injectSprite(data) {
// Check again incase of race condition
if (hasId && exists()) {
return;
}
// Inject content
this.innerHTML = data;
// Inject the SVG to the body
document.body.insertBefore(this, document.body.childNodes[0]);
}
document.body.insertAdjacentElement('afterbegin', container);
};
// Only load once if ID set
if (!hasId || !exists()) {
@@ -184,13 +184,12 @@ const utils = {
// Check in cache
if (useStorage) {
const cached = window.localStorage.getItem(prefix + id);
const cached = window.localStorage.getItem(`${prefix}-${id}`);
isCached = cached !== null;
if (isCached) {
const data = JSON.parse(cached);
injectSprite.call(container, data.content);
return;
update(container, data.content);
}
}
@@ -204,14 +203,14 @@ const utils = {
if (useStorage) {
window.localStorage.setItem(
prefix + id,
`${prefix}-${id}`,
JSON.stringify({
content: result,
}),
);
}
injectSprite.call(container, result);
update(container, result);
})
.catch(() => {});
}
@@ -627,16 +626,16 @@ const utils = {
formatTime(time = 0, displayHours = false, inverted = false) {
// Bail if the value isn't a number
if (!utils.is.number(time)) {
return this.formatTime(null, displayHours, inverted);
return utils.formatTime(null, displayHours, inverted);
}
// Format time component to add leading zero
const format = value => `0${value}`.slice(-2);
// Breakdown to hours, mins, secs
let hours = this.getHours(time);
const mins = this.getMinutes(time);
const secs = this.getSeconds(time);
let hours = utils.getHours(time);
const mins = utils.getMinutes(time);
const secs = utils.getSeconds(time);
// Do we need to display hours?
if (displayHours || hours > 0) {
@@ -794,10 +793,10 @@ const utils = {
// Parse URL if needed
if (input.startsWith('http://') || input.startsWith('https://')) {
({ search } = this.parseUrl(input));
({ search } = utils.parseUrl(input));
}
if (this.is.empty(search)) {
if (utils.is.empty(search)) {
return null;
}
@@ -833,6 +832,13 @@ const utils = {
return fragment.firstChild.innerText;
},
// Like outerHTML, but also works for DocumentFragment
getHTML(element) {
const wrapper = document.createElement('div');
wrapper.appendChild(element);
return wrapper.innerHTML;
},
// Get aspect ratio for dimensions
getAspectRatio(width, height) {
const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h));
+1 -1
View File
@@ -21,7 +21,7 @@
transition: transform 0.4s ease-in-out;
width: 100%;
span {
.plyr__caption {
background: $plyr-captions-bg;
border-radius: 2px;
box-decoration-break: clone;