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
* 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';
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
// with some tiny modifications
function isError(value) {
switch ({}.toString.call(value)) {
switch (Object.prototype.toString.call(value)) {
case '[object Error]':
return true;
case '[object Exception]':
@ -110,7 +110,15 @@ function isError(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) {
@ -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() {
if (!('fetch' in _window)) return false;
@ -668,6 +694,8 @@ var utils = {
isObject: isObject,
isError: isError,
isErrorEvent: isErrorEvent,
isDOMError: isDOMError,
isDOMException: isDOMException,
isUndefined: isUndefined,
isFunction: isFunction,
isPlainObject: isPlainObject,
@ -675,6 +703,8 @@ var utils = {
isArray: isArray,
isEmptyObject: isEmptyObject,
supportsErrorEvent: supportsErrorEvent,
supportsDOMError: supportsDOMError,
supportsDOMException: supportsDOMException,
supportsFetch: supportsFetch,
supportsReferrerPolicy: supportsReferrerPolicy,
supportsPromiseRejectionEvent: supportsPromiseRejectionEvent,
@ -729,10 +759,24 @@ var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Ran
function getLocationHref() {
if (typeof document === 'undefined' || document.location == null) return '';
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
*
@ -1140,6 +1184,44 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
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);
}
@ -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 isObject$1 = utils.isObject;
var isPlainObject$1 = utils.isPlainObject;
var isErrorEvent$1 = utils.isErrorEvent;
var isUndefined$1 = utils.isUndefined;
var isFunction$1 = utils.isFunction;
var isString$1 = utils.isString;
@ -1782,7 +1866,7 @@ Raven.prototype = {
// webpack (using a build step causes webpack #1617). Grunt verifies that
// this value matches package.json during build.
// See: https://github.com/getsentry/raven-js/issues/465
VERSION: '3.24.2',
VERSION: '3.25.2',
debug: false,
@ -2114,6 +2198,23 @@ Raven.prototype = {
if (isErrorEvent$1(ex) && ex.error) {
// If it is an ErrorEvent with `error` property, extract it to get actual 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)) {
// we have a real Error object
ex = ex;
@ -2125,6 +2226,7 @@ Raven.prototype = {
ex = new Error(options.message);
} else {
// 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 valid ErrorEvent (one with an error property)
// it's not an Error
@ -3073,8 +3175,8 @@ Raven.prototype = {
var hasPushAndReplaceState =
!isChromePackagedApp &&
_window$2.history &&
history.pushState &&
history.replaceState;
_window$2.history.pushState &&
_window$2.history.replaceState;
if (autoBreadcrumbs.location && hasPushAndReplaceState) {
// TODO: remove onpopstate handler on uninstall()
var oldOnPopState = _window$2.onpopstate;
@ -3103,8 +3205,8 @@ Raven.prototype = {
};
};
fill$1(history, 'pushState', historyReplacementFunction, wrappedBuiltIns);
fill$1(history, 'replaceState', historyReplacementFunction, wrappedBuiltIns);
fill$1(_window$2.history, 'pushState', historyReplacementFunction, wrappedBuiltIns);
fill$1(_window$2.history, 'replaceState', historyReplacementFunction, wrappedBuiltIns);
}
if (autoBreadcrumbs.console && 'console' in _window$2 && console.log) {
@ -3320,7 +3422,7 @@ Raven.prototype = {
}
]
},
culprit: fileurl
transaction: fileurl
},
options
);
@ -3394,7 +3496,7 @@ Raven.prototype = {
if (this._hasNavigator && _navigator.userAgent) {
httpData.headers = {
'User-Agent': navigator.userAgent
'User-Agent': _navigator.userAgent
};
}
@ -3435,7 +3537,7 @@ Raven.prototype = {
if (
!last ||
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;

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

584
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 define === 'function' && define.amd ? define('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
loadScript: function loadScript(url) {
return new Promise(function (resolve, reject) {
@ -730,7 +748,7 @@ var utils = {
// Add text node
if (utils.is.string(text)) {
element.textContent = text;
element.innerText = text;
}
// Return built element
@ -884,14 +902,16 @@ var utils = {
},
// Toggle class on an element
toggleClass: function toggleClass(element, className, toggle) {
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
toggleClass: function toggleClass(element, className, force) {
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);
return toggle && !contains || !toggle && contains;
element.classList[method](className);
return element.classList.contains(className);
}
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
closest: function closest(array, value) {
if (!utils.is.array(array) || !array.length) {
@ -2093,7 +2119,7 @@ var controls = {
break;
}
progress.textContent = '% ' + suffix.toLowerCase();
progress.innerText = '% ' + suffix.toLowerCase();
}
this.elements.display[type] = progress;
@ -2167,7 +2193,7 @@ var controls = {
var forceHours = utils.getHours(this.duration) > 0;
// 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
case 'timeupdate':
case 'seeking':
case 'seeked':
value = utils.getPercentage(this.currentTime, this.duration);
// Set seek range value only if it's a 'natural' time event
@ -2293,7 +2320,7 @@ var controls = {
// Calculate percentage
var percent = 0;
var clientRect = this.elements.inputs.seek.getBoundingClientRect();
var clientRect = this.elements.progress.getBoundingClientRect();
var visible = this.config.classNames.tooltip + '--visible';
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() {
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;
}
@ -2920,7 +2948,6 @@ var controls = {
// Seek tooltip
if (this.config.tooltips.seek) {
var tooltip = utils.createElement('span', {
role: 'tooltip',
class: this.config.classNames.tooltip
}, '00:00');
@ -3471,7 +3498,7 @@ var captions = {
// Set the span content
if (utils.is.string(caption)) {
content.textContent = caption.trim();
content.innerText = caption.trim();
} else {
content.appendChild(caption);
}
@ -3601,7 +3628,7 @@ var defaults$1 = {
// Sprite (for icons)
loadSprite: true,
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)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -3709,8 +3736,7 @@ var defaults$1 = {
},
youtube: {
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',
poster: 'https://img.youtube.com/vi/{0}/maxresdefault.jpg,https://img.youtube.com/vi/{0}/hqdefault.jpg'
api: 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet'
},
googleIMA: {
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'
@ -3806,13 +3832,13 @@ var defaults$1 = {
embed: 'plyr__video-embed',
embedContainer: 'plyr__video-embed__container',
poster: 'plyr__poster',
posterEnabled: 'plyr__poster-enabled',
ads: 'plyr__ads',
control: 'plyr__control',
playing: 'plyr--playing',
paused: 'plyr--paused',
stopped: 'plyr--stopped',
loading: 'plyr--loading',
error: 'plyr--has-error',
hover: 'plyr--hover',
tooltip: 'plyr__tooltip',
cues: 'plyr__cues',
@ -4207,8 +4233,10 @@ var ui = {
// Set the title
ui.setTitle.call(this);
// Set the poster image
ui.setPoster.call(this);
// Assure the poster image is set, if the property was added before the element was created
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
setPoster: function setPoster() {
if (!utils.is.element(this.elements.poster) || utils.is.empty(this.poster)) {
return;
// Toggle poster
togglePoster: function togglePoster(enable) {
utils.toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
},
// 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
var posters = this.poster.split(',');
this.elements.poster.style.backgroundImage = posters.map(function (p) {
return 'url(\'' + p + '\')';
}).join(',');
// Load the image, and set poster if successful
var loadPromise = utils.loadImage(poster).then(function () {
_this2.elements.poster.style.backgroundImage = 'url(\'' + poster + '\')';
Object.assign(_this2.elements.poster.style, {
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
this.toggleControls(!this.playing);
ui.toggleControls.call(this);
},
// Check if media is loading
checkLoading: function checkLoading(event) {
var _this2 = this;
var _this3 = this;
this.loading = ['stalled', 'waiting'].includes(event.type);
@ -4295,38 +4349,24 @@ var ui = {
// Timer to prevent flicker when seeking
this.timers.loading = setTimeout(function () {
// Toggle container class hook
utils.toggleClass(_this2.elements.container, _this2.config.classNames.loading, _this2.loading);
// Update progress bar loading class state
utils.toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading);
// Show controls if loading, hide if done
_this2.toggleControls(_this2.loading);
// Update controls visibility
ui.toggleControls.call(_this3);
}, this.loading ? 250 : 0);
},
// Check if media failed to load
checkFailed: function checkFailed() {
var _this3 = this;
// Toggle controls based on state and `force` argument
toggleControls: function toggleControls(force) {
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) {
utils.toggleClass(this.elements.container, this.config.classNames.loading, false);
utils.toggleClass(this.elements.container, this.config.classNames.error, true);
if (controls$$1 && this.config.hideControls) {
// Show controls if force, loading, paused, or button interaction, otherwise hide
this.toggleControls(Boolean(force || this.loading || this.paused || controls$$1.pressed || controls$$1.hover));
}
// 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);
});
// Toggle controls visibility based on mouse movement
if (this.player.config.hideControls) {
// Toggle controls on mouse events and entering fullscreen
utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', function (event) {
_this2.player.toggleControls(event);
});
utils.on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) {
var controls$$1 = _this2.player.elements.controls;
// 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
@ -4581,7 +4643,7 @@ var Listeners = function () {
var _this3 = this;
// 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);
});
@ -4607,7 +4669,7 @@ var Listeners = function () {
});
// 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);
});
@ -4626,9 +4688,6 @@ var Listeners = function () {
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
// 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 () {
@ -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
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');
// Current time invert
@ -4887,23 +4984,48 @@ var Listeners = function () {
return controls.updateSeekTooltip.call(_this4.player, event);
});
// Toggle controls visibility based on mouse movement
if (this.player.config.hideControls) {
// Watch for cursor over controls so they don't hide when trying to interact
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
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) {
_this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
});
// Focus in/out on controls
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
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 = {
setup: function setup() {
var _this = this;
@ -5050,11 +5180,8 @@ var vimeo = {
// Get original image
url.pathname = url.pathname.split('_')[0] + '.jpg';
// Set attribute
player.media.setAttribute('poster', url.href);
// Update
ui.setPoster.call(player);
// Set and show poster
ui.setPoster.call(player, url.href);
});
// Setup instance
@ -5074,15 +5201,13 @@ var vimeo = {
// Create a faux HTML5 API using the Vimeo API
player.media.play = function () {
player.embed.play().then(function () {
player.media.paused = false;
});
assurePlaybackState.call(player, true);
return player.embed.play();
};
player.media.pause = function () {
player.embed.pause().then(function () {
player.media.paused = true;
});
assurePlaybackState.call(player, false);
return player.embed.pause();
};
player.media.stop = function () {
@ -5098,26 +5223,35 @@ var vimeo = {
return currentTime;
},
set: function set(time) {
// Get current paused state
// Vimeo will automatically play on seek
var paused = player.media.paused;
// Vimeo will automatically play on seek if the video hasn't been played before
// 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
utils.dispatchEvent.call(player, player.media, 'seeking');
media.seeking = true;
utils.dispatchEvent.call(player, media, 'seeking');
// Seek after events
player.embed.setCurrentTime(time).catch(function () {
// If paused, mute until seek is complete
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
});
// Restore pause state
if (paused) {
player.pause();
}
}
});
@ -5265,17 +5399,12 @@ var vimeo = {
});
player.embed.on('play', function () {
// Only fire play if paused before
if (player.media.paused) {
utils.dispatchEvent.call(player, player.media, 'play');
}
player.media.paused = false;
assurePlaybackState.call(player, true);
utils.dispatchEvent.call(player, player.media, 'playing');
});
player.embed.on('pause', function () {
player.media.paused = true;
utils.dispatchEvent.call(player, player.media, 'pause');
assurePlaybackState.call(player, false);
});
player.embed.on('timeupdate', function (data) {
@ -5306,7 +5435,6 @@ var vimeo = {
player.embed.on('seeked', function () {
player.media.seeking = false;
utils.dispatchEvent.call(player, player.media, 'seeked');
utils.dispatchEvent.call(player, player.media, 'play');
});
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 = {
setup: function setup() {
var _this = this;
@ -5491,7 +5627,26 @@ var youtube = {
player.media = utils.replaceElement(container, player.media);
// 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
// https://developers.google.com/youtube/iframe_api_reference
@ -5578,10 +5733,12 @@ var youtube = {
// Create a faux HTML5 API using the YouTube API
player.media.play = function () {
assurePlaybackState$1.call(player, true);
instance.playVideo();
};
player.media.pause = function () {
assurePlaybackState$1.call(player, false);
instance.pauseVideo();
};
@ -5599,23 +5756,17 @@ var youtube = {
return Number(instance.getCurrentTime());
},
set: function set(time) {
// Vimeo will automatically play on seek
var paused = player.media.paused;
// Set seeking flag
// If paused, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
if (player.paused) {
player.embed.mute();
}
// Set seeking state and trigger event
player.media.seeking = true;
// Trigger seeking
utils.dispatchEvent.call(player, player.media, 'seeking');
// Seek after events sent
instance.seekTo(time);
// Restore pause state
if (paused) {
player.pause();
}
}
});
@ -5738,6 +5889,14 @@ var youtube = {
// Reset timer
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
// -1 Unstarted
// 0 Ended
@ -5757,7 +5916,7 @@ var youtube = {
break;
case 0:
player.media.paused = true;
assurePlaybackState$1.call(player, false);
// YouTube doesn't support loop for a single video, so mimick it.
if (player.media.loop) {
@ -5771,17 +5930,11 @@ var youtube = {
break;
case 1:
// If we were seeking, fire seeked event
if (player.media.seeking) {
utils.dispatchEvent.call(player, player.media, 'seeked');
}
player.media.seeking = false;
// Only fire play if paused before
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
if (player.media.paused) {
utils.dispatchEvent.call(player, player.media, 'play');
}
player.media.paused = false;
player.media.pause();
} else {
assurePlaybackState$1.call(player, true);
utils.dispatchEvent.call(player, player.media, 'playing');
@ -5800,13 +5953,16 @@ var youtube = {
// Get quality
controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
}
break;
case 2:
player.media.paused = true;
utils.dispatchEvent.call(player, player.media, 'pause');
// Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
if (!player.muted) {
player.embed.unMute();
}
assurePlaybackState$1.call(player, false);
break;
@ -6740,7 +6896,7 @@ var Plyr = function () {
}
// Set config
this.config = utils.extend({}, defaults$1, options || {}, function () {
this.config = utils.extend({}, defaults$1, Plyr.defaults, options || {}, function () {
try {
return JSON.parse(_this.media.getAttribute('data-plyr-config'));
} catch (e) {
@ -7180,114 +7336,35 @@ var Plyr = function () {
/**
* Toggle the player controls
* @param {boolean} toggle - Whether to show the controls
* @param {boolean} [toggle] - Whether to show the controls
*/
}, {
key: 'toggleControls',
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...
if (!utils.is.element(this.elements.controls)) {
return;
// Negate the argument if not undefined since adding the class to hides the controls
var force = typeof toggle === 'undefined' ? undefined : !toggle;
// Apply and get updated state
var hiding = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, force);
// Close menu
if (hiding && this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
controls.toggleMenu.call(this, false);
}
// Don't hide if no UI support or it's audio
if (!this.supported.ui || this.isAudio) {
return;
// Trigger event on change
if (hiding !== isHidden) {
var eventName = hiding ? 'controlshidden' : 'controlsshown';
utils.dispatchEvent.call(this, this.media, eventName);
}
var delay = 0;
var show = toggle;
var isEnterFullscreen = 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);
}
}
// Clear timer on every call
clearTimeout(this.timers.controls);
// 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;
}
}
// 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);
return !hiding;
}
return false;
}
/**
@ -7325,7 +7402,7 @@ var Plyr = function () {
}, {
key: 'destroy',
value: function destroy(callback) {
var _this3 = this;
var _this2 = this;
var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
@ -7338,22 +7415,22 @@ var Plyr = function () {
document.body.style.overflow = '';
// GC for embed
_this3.embed = null;
_this2.embed = null;
// If it's a soft destroy, make minimal changes
if (soft) {
if (Object.keys(_this3.elements).length) {
if (Object.keys(_this2.elements).length) {
// Remove elements
utils.removeElement(_this3.elements.buttons.play);
utils.removeElement(_this3.elements.captions);
utils.removeElement(_this3.elements.controls);
utils.removeElement(_this3.elements.wrapper);
utils.removeElement(_this2.elements.buttons.play);
utils.removeElement(_this2.elements.captions);
utils.removeElement(_this2.elements.controls);
utils.removeElement(_this2.elements.wrapper);
// Clear for GC
_this3.elements.buttons.play = null;
_this3.elements.captions = null;
_this3.elements.controls = null;
_this3.elements.wrapper = null;
_this2.elements.buttons.play = null;
_this2.elements.captions = null;
_this2.elements.controls = null;
_this2.elements.wrapper = null;
}
// Callback
@ -7362,26 +7439,26 @@ var Plyr = function () {
}
} else {
// Unbind listeners
_this3.listeners.clear();
_this2.listeners.clear();
// 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
utils.dispatchEvent.call(_this3, _this3.elements.original, 'destroyed', true);
utils.dispatchEvent.call(_this2, _this2.elements.original, 'destroyed', true);
// Callback
if (utils.is.function(callback)) {
callback.call(_this3.elements.original);
callback.call(_this2.elements.original);
}
// Reset state
_this3.ready = false;
_this2.ready = false;
// Clear for garbage collection
setTimeout(function () {
_this3.elements = null;
_this3.media = null;
_this2.elements = null;
_this2.media = null;
}, 200);
}
};
@ -7900,10 +7977,7 @@ var Plyr = function () {
return;
}
if (utils.is.string(input)) {
this.media.setAttribute('poster', input);
ui.setPoster.call(this);
}
ui.setPoster.call(this, input);
}
/**
@ -8084,6 +8158,8 @@ var Plyr = function () {
return Plyr;
}();
Plyr.defaults = utils.cloneDeep(defaults$1);
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

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

View File

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

1
src/js/controls.js vendored
View File

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

View File

@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
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)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',

View File

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

View File

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