This commit is contained in:
Sam Potts 2018-05-26 13:53:15 +10:00
parent edd67b0da3
commit cd51788b98
22 changed files with 1252 additions and 623 deletions

View File

@ -1,3 +1,15 @@
# v3.3.8
* Added missing URL polyfill
* Pause while seeking to mimic default HTML5 behaviour
* Add 'seeked' event listener to update progress (fixes #966)
* Trigger seeked event in youtube plugin if either playing or paused (fixes #921)
* Fix for YouTube and Vimeo autoplays on seek (fixes #876)
* Toggle controls improvements
* Cleanup unused code
* Poster image loading improvements
* Fix for seek tooltip vs click accuracy
# v3.3.7 # v3.3.7
* Poster fixes (thanks @friday) * Poster fixes (thanks @friday)

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

128
demo/dist/demo.js vendored
View File

@ -1,4 +1,4 @@
(function () { typeof navigator === "object" && (function () {
'use strict'; 'use strict';
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@ -97,7 +97,7 @@ function isObject(what) {
// Yanked from https://git.io/vS8DV re-used under CC0 // Yanked from https://git.io/vS8DV re-used under CC0
// with some tiny modifications // with some tiny modifications
function isError(value) { function isError(value) {
switch ({}.toString.call(value)) { switch (Object.prototype.toString.call(value)) {
case '[object Error]': case '[object Error]':
return true; return true;
case '[object Exception]': case '[object Exception]':
@ -110,7 +110,15 @@ function isError(value) {
} }
function isErrorEvent(value) { function isErrorEvent(value) {
return supportsErrorEvent() && {}.toString.call(value) === '[object ErrorEvent]'; return Object.prototype.toString.call(value) === '[object ErrorEvent]';
}
function isDOMError(value) {
return Object.prototype.toString.call(value) === '[object DOMError]';
}
function isDOMException(value) {
return Object.prototype.toString.call(value) === '[object DOMException]';
} }
function isUndefined(what) { function isUndefined(what) {
@ -153,6 +161,24 @@ function supportsErrorEvent() {
} }
} }
function supportsDOMError() {
try {
new DOMError(''); // eslint-disable-line no-new
return true;
} catch (e) {
return false;
}
}
function supportsDOMException() {
try {
new DOMException(''); // eslint-disable-line no-new
return true;
} catch (e) {
return false;
}
}
function supportsFetch() { function supportsFetch() {
if (!('fetch' in _window)) return false; if (!('fetch' in _window)) return false;
@ -668,6 +694,8 @@ var utils = {
isObject: isObject, isObject: isObject,
isError: isError, isError: isError,
isErrorEvent: isErrorEvent, isErrorEvent: isErrorEvent,
isDOMError: isDOMError,
isDOMException: isDOMException,
isUndefined: isUndefined, isUndefined: isUndefined,
isFunction: isFunction, isFunction: isFunction,
isPlainObject: isPlainObject, isPlainObject: isPlainObject,
@ -675,6 +703,8 @@ var utils = {
isArray: isArray, isArray: isArray,
isEmptyObject: isEmptyObject, isEmptyObject: isEmptyObject,
supportsErrorEvent: supportsErrorEvent, supportsErrorEvent: supportsErrorEvent,
supportsDOMError: supportsDOMError,
supportsDOMException: supportsDOMException,
supportsFetch: supportsFetch, supportsFetch: supportsFetch,
supportsReferrerPolicy: supportsReferrerPolicy, supportsReferrerPolicy: supportsReferrerPolicy,
supportsPromiseRejectionEvent: supportsPromiseRejectionEvent, supportsPromiseRejectionEvent: supportsPromiseRejectionEvent,
@ -729,10 +759,24 @@ var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Ran
function getLocationHref() { function getLocationHref() {
if (typeof document === 'undefined' || document.location == null) return ''; if (typeof document === 'undefined' || document.location == null) return '';
return document.location.href; return document.location.href;
} }
function getLocationOrigin() {
if (typeof document === 'undefined' || document.location == null) return '';
// Oh dear IE10...
if (!document.location.origin) {
document.location.origin =
document.location.protocol +
'//' +
document.location.hostname +
(document.location.port ? ':' + document.location.port : '');
}
return document.location.origin;
}
/** /**
* TraceKit.report: cross-browser processing of unhandled exceptions * TraceKit.report: cross-browser processing of unhandled exceptions
* *
@ -1140,6 +1184,44 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
element.func = UNKNOWN_FUNCTION; element.func = UNKNOWN_FUNCTION;
} }
if (element.url && element.url.substr(0, 5) === 'blob:') {
// Special case for handling JavaScript loaded into a blob.
// We use a synchronous AJAX request here as a blob is already in
// memory - it's not making a network request. This will generate a warning
// in the browser console, but there has already been an error so that's not
// that much of an issue.
var xhr = new XMLHttpRequest();
xhr.open('GET', element.url, false);
xhr.send(null);
// If we failed to download the source, skip this patch
if (xhr.status === 200) {
var source = xhr.responseText || '';
// We trim the source down to the last 300 characters as sourceMappingURL is always at the end of the file.
// Why 300? To be in line with: https://github.com/getsentry/sentry/blob/4af29e8f2350e20c28a6933354e4f42437b4ba42/src/sentry/lang/javascript/processor.py#L164-L175
source = source.slice(-300);
// Now we dig out the source map URL
var sourceMaps = source.match(/\/\/# sourceMappingURL=(.*)$/);
// If we don't find a source map comment or we find more than one, continue on to the next element.
if (sourceMaps) {
var sourceMapAddress = sourceMaps[1];
// Now we check to see if it's a relative URL.
// If it is, convert it to an absolute one.
if (sourceMapAddress.charAt(0) === '~') {
sourceMapAddress = getLocationOrigin() + sourceMapAddress.slice(1);
}
// Now we strip the '.map' off of the end of the URL and update the
// element so that Sentry can match the map to the blob.
element.url = sourceMapAddress.slice(0, -4);
}
}
}
stack.push(element); stack.push(element);
} }
@ -1651,10 +1733,12 @@ var console$1 = {
var isErrorEvent$1 = utils.isErrorEvent;
var isDOMError$1 = utils.isDOMError;
var isDOMException$1 = utils.isDOMException;
var isError$1 = utils.isError; var isError$1 = utils.isError;
var isObject$1 = utils.isObject; var isObject$1 = utils.isObject;
var isPlainObject$1 = utils.isPlainObject; var isPlainObject$1 = utils.isPlainObject;
var isErrorEvent$1 = utils.isErrorEvent;
var isUndefined$1 = utils.isUndefined; var isUndefined$1 = utils.isUndefined;
var isFunction$1 = utils.isFunction; var isFunction$1 = utils.isFunction;
var isString$1 = utils.isString; var isString$1 = utils.isString;
@ -1782,7 +1866,7 @@ Raven.prototype = {
// webpack (using a build step causes webpack #1617). Grunt verifies that // webpack (using a build step causes webpack #1617). Grunt verifies that
// this value matches package.json during build. // this value matches package.json during build.
// See: https://github.com/getsentry/raven-js/issues/465 // See: https://github.com/getsentry/raven-js/issues/465
VERSION: '3.24.2', VERSION: '3.25.2',
debug: false, debug: false,
@ -2114,6 +2198,23 @@ Raven.prototype = {
if (isErrorEvent$1(ex) && ex.error) { if (isErrorEvent$1(ex) && ex.error) {
// If it is an ErrorEvent with `error` property, extract it to get actual Error // If it is an ErrorEvent with `error` property, extract it to get actual Error
ex = ex.error; ex = ex.error;
} else if (isDOMError$1(ex) || isDOMException$1(ex)) {
// If it is a DOMError or DOMException (which are legacy APIs, but still supported in some browsers)
// then we just extract the name and message, as they don't provide anything else
// https://developer.mozilla.org/en-US/docs/Web/API/DOMError
// https://developer.mozilla.org/en-US/docs/Web/API/DOMException
var name = ex.name || (isDOMError$1(ex) ? 'DOMError' : 'DOMException');
var message = ex.message ? name + ': ' + ex.message : name;
return this.captureMessage(
message,
objectMerge$1(options, {
// neither DOMError or DOMException provide stack trace and we most likely wont get it this way as well
// but it's barely any overhead so we may at least try
stacktrace: true,
trimHeadFrames: options.trimHeadFrames + 1
})
);
} else if (isError$1(ex)) { } else if (isError$1(ex)) {
// we have a real Error object // we have a real Error object
ex = ex; ex = ex;
@ -2125,6 +2226,7 @@ Raven.prototype = {
ex = new Error(options.message); ex = new Error(options.message);
} else { } else {
// If none of previous checks were valid, then it means that // If none of previous checks were valid, then it means that
// it's not a DOMError/DOMException
// it's not a plain Object // it's not a plain Object
// it's not a valid ErrorEvent (one with an error property) // it's not a valid ErrorEvent (one with an error property)
// it's not an Error // it's not an Error
@ -3073,8 +3175,8 @@ Raven.prototype = {
var hasPushAndReplaceState = var hasPushAndReplaceState =
!isChromePackagedApp && !isChromePackagedApp &&
_window$2.history && _window$2.history &&
history.pushState && _window$2.history.pushState &&
history.replaceState; _window$2.history.replaceState;
if (autoBreadcrumbs.location && hasPushAndReplaceState) { if (autoBreadcrumbs.location && hasPushAndReplaceState) {
// TODO: remove onpopstate handler on uninstall() // TODO: remove onpopstate handler on uninstall()
var oldOnPopState = _window$2.onpopstate; var oldOnPopState = _window$2.onpopstate;
@ -3103,8 +3205,8 @@ Raven.prototype = {
}; };
}; };
fill$1(history, 'pushState', historyReplacementFunction, wrappedBuiltIns); fill$1(_window$2.history, 'pushState', historyReplacementFunction, wrappedBuiltIns);
fill$1(history, 'replaceState', historyReplacementFunction, wrappedBuiltIns); fill$1(_window$2.history, 'replaceState', historyReplacementFunction, wrappedBuiltIns);
} }
if (autoBreadcrumbs.console && 'console' in _window$2 && console.log) { if (autoBreadcrumbs.console && 'console' in _window$2 && console.log) {
@ -3320,7 +3422,7 @@ Raven.prototype = {
} }
] ]
}, },
culprit: fileurl transaction: fileurl
}, },
options options
); );
@ -3394,7 +3496,7 @@ Raven.prototype = {
if (this._hasNavigator && _navigator.userAgent) { if (this._hasNavigator && _navigator.userAgent) {
httpData.headers = { httpData.headers = {
'User-Agent': navigator.userAgent 'User-Agent': _navigator.userAgent
}; };
} }
@ -3435,7 +3537,7 @@ Raven.prototype = {
if ( if (
!last || !last ||
current.message !== last.message || // defined for captureMessage current.message !== last.message || // defined for captureMessage
current.culprit !== last.culprit // defined for captureException/onerror current.transaction !== last.transaction // defined for captureException/onerror
) )
return false; return false;

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.css vendored

File diff suppressed because one or more lines are too long

636
dist/plyr.js vendored
View File

@ -1,4 +1,4 @@
(function (global, factory) { typeof navigator === "object" && (function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define('Plyr', factory) : typeof define === 'function' && define.amd ? define('Plyr', factory) :
(global.Plyr = factory()); (global.Plyr = factory());
@ -602,6 +602,24 @@ var utils = {
}, },
// Load image avoiding xhr/fetch CORS issues
// Server status can't be obtained this way unfortunately, so this uses "naturalWidth" to determine if the image has loaded.
// By default it checks if it is at least 1px, but you can add a second argument to change this.
loadImage: function loadImage(src) {
var minWidth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return new Promise(function (resolve, reject) {
var image = new Image();
var handler = function handler() {
delete image.onload;
delete image.onerror;
(image.naturalWidth >= minWidth ? resolve : reject)(image);
};
Object.assign(image, { onload: handler, onerror: handler, src: src });
});
},
// Load an external script // Load an external script
loadScript: function loadScript(url) { loadScript: function loadScript(url) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@ -730,7 +748,7 @@ var utils = {
// Add text node // Add text node
if (utils.is.string(text)) { if (utils.is.string(text)) {
element.textContent = text; element.innerText = text;
} }
// Return built element // Return built element
@ -884,14 +902,16 @@ var utils = {
}, },
// Toggle class on an element // Mirror Element.classList.toggle, with IE compatibility for "force" argument
toggleClass: function toggleClass(element, className, toggle) { toggleClass: function toggleClass(element, className, force) {
if (utils.is.element(element)) { if (utils.is.element(element)) {
var contains = element.classList.contains(className); var method = 'toggle';
if (typeof force !== 'undefined') {
method = force ? 'add' : 'remove';
}
element.classList[toggle ? 'add' : 'remove'](className); element.classList[method](className);
return element.classList.contains(className);
return toggle && !contains || !toggle && contains;
} }
return null; return null;
@ -1274,6 +1294,12 @@ var utils = {
}, },
// Clone nested objects
cloneDeep: function cloneDeep(object) {
return JSON.parse(JSON.stringify(object));
},
// Get the closest value in an array // Get the closest value in an array
closest: function closest(array, value) { closest: function closest(array, value) {
if (!utils.is.array(array) || !array.length) { if (!utils.is.array(array) || !array.length) {
@ -2093,7 +2119,7 @@ var controls = {
break; break;
} }
progress.textContent = '% ' + suffix.toLowerCase(); progress.innerText = '% ' + suffix.toLowerCase();
} }
this.elements.display[type] = progress; this.elements.display[type] = progress;
@ -2167,7 +2193,7 @@ var controls = {
var forceHours = utils.getHours(this.duration) > 0; var forceHours = utils.getHours(this.duration) > 0;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
target.textContent = utils.formatTime(time, forceHours, inverted); target.innerText = utils.formatTime(time, forceHours, inverted);
}, },
@ -2236,6 +2262,7 @@ var controls = {
// Video playing // Video playing
case 'timeupdate': case 'timeupdate':
case 'seeking': case 'seeking':
case 'seeked':
value = utils.getPercentage(this.currentTime, this.duration); value = utils.getPercentage(this.currentTime, this.duration);
// Set seek range value only if it's a 'natural' time event // Set seek range value only if it's a 'natural' time event
@ -2293,7 +2320,7 @@ var controls = {
// Calculate percentage // Calculate percentage
var percent = 0; var percent = 0;
var clientRect = this.elements.inputs.seek.getBoundingClientRect(); var clientRect = this.elements.progress.getBoundingClientRect();
var visible = this.config.classNames.tooltip + '--visible'; var visible = this.config.classNames.tooltip + '--visible';
var toggle = function toggle(_toggle) { var toggle = function toggle(_toggle) {
@ -2354,9 +2381,10 @@ var controls = {
}, },
// Show the duration on metadataloaded // Show the duration on metadataloaded or durationchange events
durationUpdate: function durationUpdate() { durationUpdate: function durationUpdate() {
if (!this.supported.ui) { // Bail if no ui or durationchange event triggered after playing/seek when invertTime is false
if (!this.supported.ui || !this.config.invertTime && this.currentTime) {
return; return;
} }
@ -2920,7 +2948,6 @@ var controls = {
// Seek tooltip // Seek tooltip
if (this.config.tooltips.seek) { if (this.config.tooltips.seek) {
var tooltip = utils.createElement('span', { var tooltip = utils.createElement('span', {
role: 'tooltip',
class: this.config.classNames.tooltip class: this.config.classNames.tooltip
}, '00:00'); }, '00:00');
@ -3471,7 +3498,7 @@ var captions = {
// Set the span content // Set the span content
if (utils.is.string(caption)) { if (utils.is.string(caption)) {
content.textContent = caption.trim(); content.innerText = caption.trim();
} else { } else {
content.appendChild(caption); content.appendChild(caption);
} }
@ -3601,7 +3628,7 @@ var defaults$1 = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.3.7/plyr.svg', iconUrl: 'https://cdn.plyr.io/3.3.8/plyr.svg',
// Blank video (used to prevent errors on source change) // Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4', blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -3709,8 +3736,7 @@ var defaults$1 = {
}, },
youtube: { youtube: {
sdk: 'https://www.youtube.com/iframe_api', sdk: 'https://www.youtube.com/iframe_api',
api: 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet', api: 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet'
poster: 'https://img.youtube.com/vi/{0}/maxresdefault.jpg,https://img.youtube.com/vi/{0}/hqdefault.jpg'
}, },
googleIMA: { googleIMA: {
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js' sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'
@ -3806,13 +3832,13 @@ var defaults$1 = {
embed: 'plyr__video-embed', embed: 'plyr__video-embed',
embedContainer: 'plyr__video-embed__container', embedContainer: 'plyr__video-embed__container',
poster: 'plyr__poster', poster: 'plyr__poster',
posterEnabled: 'plyr__poster-enabled',
ads: 'plyr__ads', ads: 'plyr__ads',
control: 'plyr__control', control: 'plyr__control',
playing: 'plyr--playing', playing: 'plyr--playing',
paused: 'plyr--paused', paused: 'plyr--paused',
stopped: 'plyr--stopped', stopped: 'plyr--stopped',
loading: 'plyr--loading', loading: 'plyr--loading',
error: 'plyr--has-error',
hover: 'plyr--hover', hover: 'plyr--hover',
tooltip: 'plyr__tooltip', tooltip: 'plyr__tooltip',
cues: 'plyr__cues', cues: 'plyr__cues',
@ -4207,8 +4233,10 @@ var ui = {
// Set the title // Set the title
ui.setTitle.call(this); ui.setTitle.call(this);
// Set the poster image // Assure the poster image is set, if the property was added before the element was created
ui.setPoster.call(this); if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
ui.setPoster.call(this, this.poster);
}
}, },
@ -4250,17 +4278,43 @@ var ui = {
}, },
// Set the poster image // Toggle poster
setPoster: function setPoster() { togglePoster: function togglePoster(enable) {
if (!utils.is.element(this.elements.poster) || utils.is.empty(this.poster)) { utils.toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
return; },
// Set the poster image (async)
setPoster: function setPoster(poster) {
var _this2 = this;
// Set property regardless of validity
this.media.setAttribute('poster', poster);
// Bail if element is missing
if (!utils.is.element(this.elements.poster)) {
return Promise.reject();
} }
// Set the inline style // Load the image, and set poster if successful
var posters = this.poster.split(','); var loadPromise = utils.loadImage(poster).then(function () {
this.elements.poster.style.backgroundImage = posters.map(function (p) { _this2.elements.poster.style.backgroundImage = 'url(\'' + poster + '\')';
return 'url(\'' + p + '\')'; Object.assign(_this2.elements.poster.style, {
}).join(','); backgroundImage: 'url(\'' + poster + '\')',
// Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
backgroundSize: ''
});
ui.togglePoster.call(_this2, true);
return poster;
});
// Hide the element if the poster can't be loaded (otherwise it will just be a black element covering the video)
loadPromise.catch(function () {
return ui.togglePoster.call(_this2, false);
});
// Return the promise so the caller can use it as well
return loadPromise;
}, },
@ -4280,13 +4334,13 @@ var ui = {
} }
// Toggle controls // Toggle controls
this.toggleControls(!this.playing); ui.toggleControls.call(this);
}, },
// Check if media is loading // Check if media is loading
checkLoading: function checkLoading(event) { checkLoading: function checkLoading(event) {
var _this2 = this; var _this3 = this;
this.loading = ['stalled', 'waiting'].includes(event.type); this.loading = ['stalled', 'waiting'].includes(event.type);
@ -4295,38 +4349,24 @@ var ui = {
// Timer to prevent flicker when seeking // Timer to prevent flicker when seeking
this.timers.loading = setTimeout(function () { this.timers.loading = setTimeout(function () {
// Toggle container class hook // Update progress bar loading class state
utils.toggleClass(_this2.elements.container, _this2.config.classNames.loading, _this2.loading); utils.toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading);
// Show controls if loading, hide if done // Update controls visibility
_this2.toggleControls(_this2.loading); ui.toggleControls.call(_this3);
}, this.loading ? 250 : 0); }, this.loading ? 250 : 0);
}, },
// Check if media failed to load // Toggle controls based on state and `force` argument
checkFailed: function checkFailed() { toggleControls: function toggleControls(force) {
var _this3 = this; var controls$$1 = this.elements.controls;
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/networkState
this.failed = this.media.networkState === 3;
if (this.failed) { if (controls$$1 && this.config.hideControls) {
utils.toggleClass(this.elements.container, this.config.classNames.loading, false); // Show controls if force, loading, paused, or button interaction, otherwise hide
utils.toggleClass(this.elements.container, this.config.classNames.error, true); this.toggleControls(Boolean(force || this.loading || this.paused || controls$$1.pressed || controls$$1.hover));
} }
// Clear timer
clearTimeout(this.timers.failed);
// Timer to prevent flicker when seeking
this.timers.loading = setTimeout(function () {
// Toggle container class hook
utils.toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading);
// Show controls if loading, hide if done
_this3.toggleControls(_this3.loading);
}, this.loading ? 250 : 0);
} }
}; };
@ -4564,13 +4604,35 @@ var Listeners = function () {
}, 0); }, 0);
}); });
// Toggle controls visibility based on mouse movement // Toggle controls on mouse events and entering fullscreen
if (this.player.config.hideControls) { utils.on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) {
// Toggle controls on mouse events and entering fullscreen var controls$$1 = _this2.player.elements.controls;
utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', function (event) {
_this2.player.toggleControls(event); // Remove button states for fullscreen
});
} if (event.type === 'enterfullscreen') {
controls$$1.pressed = false;
controls$$1.hover = false;
}
// Show, then hide after a timeout unless another control event occurs
var show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type);
var delay = 0;
if (show) {
ui.toggleControls.call(_this2.player, true);
// Use longer timeout for touch devices
delay = _this2.player.touch ? 3000 : 2000;
}
// Clear timer
clearTimeout(_this2.player.timers.controls);
// Timer to prevent flicker when seeking
_this2.player.timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this2.player, false);
}, delay);
});
} }
// Listen for media events // Listen for media events
@ -4581,7 +4643,7 @@ var Listeners = function () {
var _this3 = this; var _this3 = this;
// Time change on media // Time change on media
utils.on(this.player.media, 'timeupdate seeking', function (event) { utils.on(this.player.media, 'timeupdate seeking seeked', function (event) {
return controls.timeUpdate.call(_this3.player, event); return controls.timeUpdate.call(_this3.player, event);
}); });
@ -4607,7 +4669,7 @@ var Listeners = function () {
}); });
// Check for buffer progress // Check for buffer progress
utils.on(this.player.media, 'progress playing', function (event) { utils.on(this.player.media, 'progress playing seeking seeked', function (event) {
return controls.updateProgress.call(_this3.player, event); return controls.updateProgress.call(_this3.player, event);
}); });
@ -4626,9 +4688,6 @@ var Listeners = function () {
return ui.checkLoading.call(_this3.player, event); return ui.checkLoading.call(_this3.player, event);
}); });
// Check if media failed to load
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
// If autoplay, then load advertisement if required // If autoplay, then load advertisement if required
// TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows // TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows
utils.on(this.player.media, 'playing', function () { utils.on(this.player.media, 'playing', function () {
@ -4850,9 +4909,47 @@ var Listeners = function () {
} }
}); });
// Set range input alternative "value", which matches the tooltip time (#954)
on(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this4.player.elements.progress.getBoundingClientRect();
var percent = 100 / clientRect.width * (event.pageX - clientRect.left);
event.currentTarget.setAttribute('seek-value', percent);
});
// Pause while seeking
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
var seek = event.currentTarget;
// Was playing before?
var play = seek.hasAttribute('play-on-seeked');
// Done seeking
var done = ['mouseup', 'touchend', 'keyup'].includes(event.type);
// If we're done seeking and it was playing, resume playback
if (play && done) {
seek.removeAttribute('play-on-seeked');
_this4.player.play();
} else if (!done && _this4.player.playing) {
seek.setAttribute('play-on-seeked', '');
_this4.player.pause();
}
});
// Seek // Seek
on(this.player.elements.inputs.seek, inputEvent, function (event) { on(this.player.elements.inputs.seek, inputEvent, function (event) {
_this4.player.currentTime = event.target.value / event.target.max * _this4.player.duration; var seek = event.currentTarget;
// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954)
var seekTo = seek.getAttribute('seek-value');
if (utils.is.empty(seekTo)) {
seekTo = seek.value;
}
seek.removeAttribute('seek-value');
_this4.player.currentTime = seekTo / seek.max * _this4.player.duration;
}, 'seek'); }, 'seek');
// Current time invert // Current time invert
@ -4887,23 +4984,48 @@ var Listeners = function () {
return controls.updateSeekTooltip.call(_this4.player, event); return controls.updateSeekTooltip.call(_this4.player, event);
}); });
// Toggle controls visibility based on mouse movement // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
if (this.player.config.hideControls) { on(this.player.elements.controls, 'mouseenter mouseleave', function (event) {
// Watch for cursor over controls so they don't hide when trying to interact _this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter';
on(this.player.elements.controls, 'mouseenter mouseleave', function (event) { });
_this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter';
});
// Watch for cursor over controls so they don't hide when trying to interact // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) { on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
_this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); _this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
}); });
// Focus in/out on controls // Focus in/out on controls
on(this.player.elements.controls, 'focusin focusout', function (event) { on(this.player.elements.controls, 'focusin focusout', function (event) {
_this4.player.toggleControls(event); var _player = _this4.player,
}); config = _player.config,
} elements = _player.elements,
timers = _player.timers;
// Skip transition to prevent focus from scrolling the parent element
utils.toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
// Toggle
ui.toggleControls.call(_this4.player, event.type === 'focusin');
// If focusin, hide again after delay
if (event.type === 'focusin') {
// Restore transition
setTimeout(function () {
utils.toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0);
// Delay a little more for keyboard users
var delay = _this4.touch ? 3000 : 4000;
// Clear timer
clearTimeout(timers.controls);
// Hide
timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this4.player, false);
}, delay);
}
});
// Mouse wheel for volume // Mouse wheel for volume
on(this.player.elements.inputs.volume, 'wheel', function (event) { on(this.player.elements.inputs.volume, 'wheel', function (event) {
@ -4955,6 +5077,14 @@ var Listeners = function () {
// ========================================================================== // ==========================================================================
// Set playback state and trigger change (only on actual change)
function assurePlaybackState(play) {
if (this.media.paused === play) {
this.media.paused = !play;
utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause');
}
}
var vimeo = { var vimeo = {
setup: function setup() { setup: function setup() {
var _this = this; var _this = this;
@ -5050,11 +5180,8 @@ var vimeo = {
// Get original image // Get original image
url.pathname = url.pathname.split('_')[0] + '.jpg'; url.pathname = url.pathname.split('_')[0] + '.jpg';
// Set attribute // Set and show poster
player.media.setAttribute('poster', url.href); ui.setPoster.call(player, url.href);
// Update
ui.setPoster.call(player);
}); });
// Setup instance // Setup instance
@ -5074,15 +5201,13 @@ var vimeo = {
// Create a faux HTML5 API using the Vimeo API // Create a faux HTML5 API using the Vimeo API
player.media.play = function () { player.media.play = function () {
player.embed.play().then(function () { assurePlaybackState.call(player, true);
player.media.paused = false; return player.embed.play();
});
}; };
player.media.pause = function () { player.media.pause = function () {
player.embed.pause().then(function () { assurePlaybackState.call(player, false);
player.media.paused = true; return player.embed.pause();
});
}; };
player.media.stop = function () { player.media.stop = function () {
@ -5098,26 +5223,35 @@ var vimeo = {
return currentTime; return currentTime;
}, },
set: function set(time) { set: function set(time) {
// Get current paused state // Vimeo will automatically play on seek if the video hasn't been played before
// Vimeo will automatically play on seek
var paused = player.media.paused;
// Set seeking flag // Get current paused state and volume etc
var embed = player.embed,
media = player.media,
paused = player.paused,
volume = player.volume;
player.media.seeking = true; // Set seeking state and trigger event
// Trigger seeking media.seeking = true;
utils.dispatchEvent.call(player, player.media, 'seeking'); utils.dispatchEvent.call(player, media, 'seeking');
// Seek after events // If paused, mute until seek is complete
player.embed.setCurrentTime(time).catch(function () { Promise.resolve(paused && embed.setVolume(0))
// Seek
.then(function () {
return embed.setCurrentTime(time);
})
// Restore paused
.then(function () {
return paused && embed.pause();
})
// Restore volume
.then(function () {
return paused && embed.setVolume(volume);
}).catch(function () {
// Do nothing // Do nothing
}); });
// Restore pause state
if (paused) {
player.pause();
}
} }
}); });
@ -5265,17 +5399,12 @@ var vimeo = {
}); });
player.embed.on('play', function () { player.embed.on('play', function () {
// Only fire play if paused before assurePlaybackState.call(player, true);
if (player.media.paused) {
utils.dispatchEvent.call(player, player.media, 'play');
}
player.media.paused = false;
utils.dispatchEvent.call(player, player.media, 'playing'); utils.dispatchEvent.call(player, player.media, 'playing');
}); });
player.embed.on('pause', function () { player.embed.on('pause', function () {
player.media.paused = true; assurePlaybackState.call(player, false);
utils.dispatchEvent.call(player, player.media, 'pause');
}); });
player.embed.on('timeupdate', function (data) { player.embed.on('timeupdate', function (data) {
@ -5306,7 +5435,6 @@ var vimeo = {
player.embed.on('seeked', function () { player.embed.on('seeked', function () {
player.media.seeking = false; player.media.seeking = false;
utils.dispatchEvent.call(player, player.media, 'seeked'); utils.dispatchEvent.call(player, player.media, 'seeked');
utils.dispatchEvent.call(player, player.media, 'play');
}); });
player.embed.on('ended', function () { player.embed.on('ended', function () {
@ -5388,6 +5516,14 @@ function mapQualityUnits(levels) {
})); }));
} }
// Set playback state and trigger change (only on actual change)
function assurePlaybackState$1(play) {
if (this.media.paused === play) {
this.media.paused = !play;
utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause');
}
}
var youtube = { var youtube = {
setup: function setup() { setup: function setup() {
var _this = this; var _this = this;
@ -5491,7 +5627,26 @@ var youtube = {
player.media = utils.replaceElement(container, player.media); player.media = utils.replaceElement(container, player.media);
// Set poster image // Set poster image
player.media.setAttribute('poster', utils.format(player.config.urls.youtube.poster, videoId)); var posterSrc = function posterSrc(format) {
return 'https://img.youtube.com/vi/' + videoId + '/' + format + 'default.jpg';
};
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
utils.loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
.catch(function () {
return utils.loadImage(posterSrc('sd'), 121);
}) // 480p padded 4:3
.catch(function () {
return utils.loadImage(posterSrc('hq'));
}) // 360p padded 4:3. Always exists
.then(function (image) {
return ui.setPoster.call(player, image.src);
}).then(function (posterSrc) {
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
if (!posterSrc.includes('maxres')) {
player.elements.poster.style.backgroundSize = 'cover';
}
});
// Setup instance // Setup instance
// https://developers.google.com/youtube/iframe_api_reference // https://developers.google.com/youtube/iframe_api_reference
@ -5578,10 +5733,12 @@ var youtube = {
// Create a faux HTML5 API using the YouTube API // Create a faux HTML5 API using the YouTube API
player.media.play = function () { player.media.play = function () {
assurePlaybackState$1.call(player, true);
instance.playVideo(); instance.playVideo();
}; };
player.media.pause = function () { player.media.pause = function () {
assurePlaybackState$1.call(player, false);
instance.pauseVideo(); instance.pauseVideo();
}; };
@ -5599,23 +5756,17 @@ var youtube = {
return Number(instance.getCurrentTime()); return Number(instance.getCurrentTime());
}, },
set: function set(time) { set: function set(time) {
// Vimeo will automatically play on seek // If paused, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
var paused = player.media.paused; if (player.paused) {
player.embed.mute();
// Set seeking flag }
// Set seeking state and trigger event
player.media.seeking = true; player.media.seeking = true;
// Trigger seeking
utils.dispatchEvent.call(player, player.media, 'seeking'); utils.dispatchEvent.call(player, player.media, 'seeking');
// Seek after events sent // Seek after events sent
instance.seekTo(time); instance.seekTo(time);
// Restore pause state
if (paused) {
player.pause();
}
} }
}); });
@ -5738,6 +5889,14 @@ var youtube = {
// Reset timer // Reset timer
clearInterval(player.timers.playing); clearInterval(player.timers.playing);
var seeked = player.media.seeking && [1, 2].includes(event.data);
if (seeked) {
// Unset seeking and fire seeked event
player.media.seeking = false;
utils.dispatchEvent.call(player, player.media, 'seeked');
}
// Handle events // Handle events
// -1 Unstarted // -1 Unstarted
// 0 Ended // 0 Ended
@ -5757,7 +5916,7 @@ var youtube = {
break; break;
case 0: case 0:
player.media.paused = true; assurePlaybackState$1.call(player, false);
// YouTube doesn't support loop for a single video, so mimick it. // YouTube doesn't support loop for a single video, so mimick it.
if (player.media.loop) { if (player.media.loop) {
@ -5771,42 +5930,39 @@ var youtube = {
break; break;
case 1: case 1:
// If we were seeking, fire seeked event // Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
if (player.media.seeking) {
utils.dispatchEvent.call(player, player.media, 'seeked');
}
player.media.seeking = false;
// Only fire play if paused before
if (player.media.paused) { if (player.media.paused) {
utils.dispatchEvent.call(player, player.media, 'play'); player.media.pause();
} else {
assurePlaybackState$1.call(player, true);
utils.dispatchEvent.call(player, player.media, 'playing');
// Poll to get playback progress
player.timers.playing = setInterval(function () {
utils.dispatchEvent.call(player, player.media, 'timeupdate');
}, 50);
// Check duration again due to YouTube bug
// https://github.com/sampotts/plyr/issues/374
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
if (player.media.duration !== instance.getDuration()) {
player.media.duration = instance.getDuration();
utils.dispatchEvent.call(player, player.media, 'durationchange');
}
// Get quality
controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
} }
player.media.paused = false;
utils.dispatchEvent.call(player, player.media, 'playing');
// Poll to get playback progress
player.timers.playing = setInterval(function () {
utils.dispatchEvent.call(player, player.media, 'timeupdate');
}, 50);
// Check duration again due to YouTube bug
// https://github.com/sampotts/plyr/issues/374
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
if (player.media.duration !== instance.getDuration()) {
player.media.duration = instance.getDuration();
utils.dispatchEvent.call(player, player.media, 'durationchange');
}
// Get quality
controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
break; break;
case 2: case 2:
player.media.paused = true; // Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
if (!player.muted) {
utils.dispatchEvent.call(player, player.media, 'pause'); player.embed.unMute();
}
assurePlaybackState$1.call(player, false);
break; break;
@ -6740,7 +6896,7 @@ var Plyr = function () {
} }
// Set config // Set config
this.config = utils.extend({}, defaults$1, options || {}, function () { this.config = utils.extend({}, defaults$1, Plyr.defaults, options || {}, function () {
try { try {
return JSON.parse(_this.media.getAttribute('data-plyr-config')); return JSON.parse(_this.media.getAttribute('data-plyr-config'));
} catch (e) { } catch (e) {
@ -7180,114 +7336,35 @@ var Plyr = function () {
/** /**
* Toggle the player controls * Toggle the player controls
* @param {boolean} toggle - Whether to show the controls * @param {boolean} [toggle] - Whether to show the controls
*/ */
}, { }, {
key: 'toggleControls', key: 'toggleControls',
value: function toggleControls(toggle) { value: function toggleControls(toggle) {
var _this2 = this; // Don't toggle if missing UI support or if it's audio
if (this.supported.ui && !this.isAudio) {
// Get state before change
var isHidden = utils.hasClass(this.elements.container, this.config.classNames.hideControls);
// We need controls of course... // Negate the argument if not undefined since adding the class to hides the controls
if (!utils.is.element(this.elements.controls)) { var force = typeof toggle === 'undefined' ? undefined : !toggle;
return;
}
// Don't hide if no UI support or it's audio // Apply and get updated state
if (!this.supported.ui || this.isAudio) { var hiding = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, force);
return;
}
var delay = 0; // Close menu
var show = toggle; if (hiding && this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
var isEnterFullscreen = false; controls.toggleMenu.call(this, false);
// Get toggle state if not set
if (!utils.is.boolean(toggle)) {
if (utils.is.event(toggle)) {
// Is the enter fullscreen event
isEnterFullscreen = toggle.type === 'enterfullscreen';
// Events that show the controls
var showEvents = ['touchstart', 'touchmove', 'mouseenter', 'mousemove', 'focusin'];
// Events that delay hiding
var delayEvents = ['touchmove', 'touchend', 'mousemove'];
// Whether to show controls
show = showEvents.includes(toggle.type);
// Delay hiding on move events
if (delayEvents.includes(toggle.type)) {
delay = 2000;
}
// Delay a little more for keyboard users
if (!this.touch && toggle.type === 'focusin') {
delay = 3000;
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
}
} else {
show = utils.hasClass(this.elements.container, this.config.classNames.hideControls);
} }
} // Trigger event on change
if (hiding !== isHidden) {
// Clear timer on every call var eventName = hiding ? 'controlshidden' : 'controlsshown';
clearTimeout(this.timers.controls); utils.dispatchEvent.call(this, this.media, eventName);
// If the mouse is not over the controls, set a timeout to hide them
if (show || this.paused || this.loading) {
// Check if controls toggled
var toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, false);
// Trigger event
if (toggled) {
utils.dispatchEvent.call(this, this.media, 'controlsshown');
}
// Always show controls when paused or if touch
if (this.paused || this.loading) {
return;
}
// Delay for hiding on touch
if (this.touch) {
delay = 3000;
} }
return !hiding;
} }
return false;
// If toggle is false or if we're playing (regardless of toggle),
// then set the timer to hide the controls
if (!show || this.playing) {
this.timers.controls = setTimeout(function () {
// We need controls of course...
if (!utils.is.element(_this2.elements.controls)) {
return;
}
// If the mouse is over the controls (and not entering fullscreen), bail
if ((_this2.elements.controls.pressed || _this2.elements.controls.hover) && !isEnterFullscreen) {
return;
}
// Restore transition behaviour
if (!utils.hasClass(_this2.elements.container, _this2.config.classNames.hideControls)) {
utils.toggleClass(_this2.elements.controls, _this2.config.classNames.noTransition, false);
}
// Set hideControls class
var toggled = utils.toggleClass(_this2.elements.container, _this2.config.classNames.hideControls, _this2.config.hideControls);
// Trigger event and close menu
if (toggled) {
utils.dispatchEvent.call(_this2, _this2.media, 'controlshidden');
if (_this2.config.controls.includes('settings') && !utils.is.empty(_this2.config.settings)) {
controls.toggleMenu.call(_this2, false);
}
}
}, delay);
}
} }
/** /**
@ -7325,7 +7402,7 @@ var Plyr = function () {
}, { }, {
key: 'destroy', key: 'destroy',
value: function destroy(callback) { value: function destroy(callback) {
var _this3 = this; var _this2 = this;
var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
@ -7338,22 +7415,22 @@ var Plyr = function () {
document.body.style.overflow = ''; document.body.style.overflow = '';
// GC for embed // GC for embed
_this3.embed = null; _this2.embed = null;
// If it's a soft destroy, make minimal changes // If it's a soft destroy, make minimal changes
if (soft) { if (soft) {
if (Object.keys(_this3.elements).length) { if (Object.keys(_this2.elements).length) {
// Remove elements // Remove elements
utils.removeElement(_this3.elements.buttons.play); utils.removeElement(_this2.elements.buttons.play);
utils.removeElement(_this3.elements.captions); utils.removeElement(_this2.elements.captions);
utils.removeElement(_this3.elements.controls); utils.removeElement(_this2.elements.controls);
utils.removeElement(_this3.elements.wrapper); utils.removeElement(_this2.elements.wrapper);
// Clear for GC // Clear for GC
_this3.elements.buttons.play = null; _this2.elements.buttons.play = null;
_this3.elements.captions = null; _this2.elements.captions = null;
_this3.elements.controls = null; _this2.elements.controls = null;
_this3.elements.wrapper = null; _this2.elements.wrapper = null;
} }
// Callback // Callback
@ -7362,26 +7439,26 @@ var Plyr = function () {
} }
} else { } else {
// Unbind listeners // Unbind listeners
_this3.listeners.clear(); _this2.listeners.clear();
// Replace the container with the original element provided // Replace the container with the original element provided
utils.replaceElement(_this3.elements.original, _this3.elements.container); utils.replaceElement(_this2.elements.original, _this2.elements.container);
// Event // Event
utils.dispatchEvent.call(_this3, _this3.elements.original, 'destroyed', true); utils.dispatchEvent.call(_this2, _this2.elements.original, 'destroyed', true);
// Callback // Callback
if (utils.is.function(callback)) { if (utils.is.function(callback)) {
callback.call(_this3.elements.original); callback.call(_this2.elements.original);
} }
// Reset state // Reset state
_this3.ready = false; _this2.ready = false;
// Clear for garbage collection // Clear for garbage collection
setTimeout(function () { setTimeout(function () {
_this3.elements = null; _this2.elements = null;
_this3.media = null; _this2.media = null;
}, 200); }, 200);
} }
}; };
@ -7900,10 +7977,7 @@ var Plyr = function () {
return; return;
} }
if (utils.is.string(input)) { ui.setPoster.call(this, input);
this.media.setAttribute('poster', input);
ui.setPoster.call(this);
}
} }
/** /**
@ -8084,6 +8158,8 @@ var Plyr = function () {
return Plyr; return Plyr;
}(); }();
Plyr.defaults = utils.cloneDeep(defaults$1);
return Plyr; return Plyr;
}))); })));

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

1044
dist/plyr.polyfilled.js vendored

File diff suppressed because it is too large Load Diff

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

View File

@ -241,11 +241,11 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
const branch = { const branch = {
current: gitbranch.sync(), current: gitbranch.sync(),
master: 'master', master: 'master',
beta: 'beta', develop: 'develop',
}; };
const allowed = [ const allowed = [
branch.master, branch.master,
branch.beta, branch.develop,
]; ];
const maxAge = 31536000; // 1 year const maxAge = 31536000; // 1 year
@ -257,7 +257,7 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
}, },
}, },
demo: { demo: {
uploadPath: branch.current === branch.beta ? 'beta/' : null, uploadPath: branch.current === branch.develop ? 'beta/' : null,
headers: { headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0', 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
Vary: 'Accept-Encoding', Vary: 'Accept-Encoding',

View File

@ -1,6 +1,6 @@
{ {
"name": "plyr", "name": "plyr",
"version": "3.3.7", "version": "3.3.8",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player", "description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io", "homepage": "https://plyr.io",
"main": "./dist/plyr.js", "main": "./dist/plyr.js",
@ -52,15 +52,7 @@
"stylelint-scss": "^3.1.0", "stylelint-scss": "^3.1.0",
"stylelint-selector-bem-pattern": "^2.0.0" "stylelint-selector-bem-pattern": "^2.0.0"
}, },
"keywords": [ "keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
"HTML5 Video",
"HTML5 Audio",
"Media Player",
"DASH",
"Shaka",
"WordPress",
"HLS"
],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/sampotts/plyr.git" "url": "git://github.com/sampotts/plyr.git"

View File

@ -128,13 +128,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. 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 ```html
<script src="https://cdn.plyr.io/3.3.7/plyr.js"></script> <script src="https://cdn.plyr.io/3.3.8/plyr.js"></script>
``` ```
...or... ...or...
```html ```html
<script src="https://cdn.plyr.io/3.3.7/plyr.polyfilled.js"></script> <script src="https://cdn.plyr.io/3.3.8/plyr.polyfilled.js"></script>
``` ```
### CSS ### CSS
@ -148,13 +148,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: If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
```html ```html
<link rel="stylesheet" href="https://cdn.plyr.io/3.3.7/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/3.3.8/plyr.css">
``` ```
### SVG Sprite ### 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 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.7/plyr.svg`. reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.3.8/plyr.svg`.
## Ads ## Ads

1
src/js/controls.js vendored
View File

@ -1165,7 +1165,6 @@ const controls = {
const tooltip = utils.createElement( const tooltip = utils.createElement(
'span', 'span',
{ {
role: 'tooltip',
class: this.config.classNames.tooltip, class: this.config.classNames.tooltip,
}, },
'00:00', '00:00',

View File

@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.3.7/plyr.svg', iconUrl: 'https://cdn.plyr.io/3.3.8/plyr.svg',
// Blank video (used to prevent errors on source change) // Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4', blankVideo: 'https://cdn.plyr.io/static/blank.mp4',

View File

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

View File

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