Merge branch 'develop' of github.com:sampotts/plyr into develop
# Conflicts: # package.json # yarn.lock
This commit is contained in:
@ -151,7 +151,11 @@ const captions = {
|
||||
toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is.empty(tracks));
|
||||
|
||||
// Update available languages in list
|
||||
if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
|
||||
if (
|
||||
is.array(this.config.controls) &&
|
||||
this.config.controls.includes('settings') &&
|
||||
this.config.settings.includes('captions')
|
||||
) {
|
||||
controls.setCaptionsMenu.call(this);
|
||||
}
|
||||
},
|
||||
|
2
src/js/controls.js
vendored
2
src/js/controls.js
vendored
@ -111,7 +111,7 @@ const controls = {
|
||||
setAttributes(
|
||||
icon,
|
||||
extend(attributes, {
|
||||
role: 'presentation',
|
||||
'aria-hidden': 'true',
|
||||
focusable: 'false',
|
||||
}),
|
||||
);
|
||||
|
@ -8,6 +8,7 @@ import browser from './utils/browser';
|
||||
import { getElements, hasClass, toggleClass } from './utils/elements';
|
||||
import { on, triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
import { silencePromise } from './utils/promise';
|
||||
|
||||
class Fullscreen {
|
||||
constructor(player) {
|
||||
@ -118,7 +119,7 @@ class Fullscreen {
|
||||
|
||||
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`];
|
||||
|
||||
return element === this.target;
|
||||
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
||||
}
|
||||
|
||||
// Get target element
|
||||
@ -268,7 +269,7 @@ class Fullscreen {
|
||||
// iOS native fullscreen
|
||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||
this.target.webkitExitFullscreen();
|
||||
this.player.play();
|
||||
silencePromise(this.player.play());
|
||||
} else if (!Fullscreen.native || this.forceFallback) {
|
||||
this.toggleFallback(false);
|
||||
} else if (!this.prefix) {
|
||||
|
@ -6,6 +6,7 @@ import support from './support';
|
||||
import { removeElement } from './utils/elements';
|
||||
import { triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
import { silencePromise } from './utils/promise';
|
||||
import { setAspectRatio } from './utils/style';
|
||||
|
||||
const html5 = {
|
||||
@ -101,7 +102,7 @@ const html5 = {
|
||||
|
||||
// Resume playing
|
||||
if (!paused) {
|
||||
player.play();
|
||||
silencePromise(player.play());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -9,6 +9,7 @@ import browser from './utils/browser';
|
||||
import { getElement, getElements, matches, toggleClass } from './utils/elements';
|
||||
import { off, on, once, toggleListener, triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
import { silencePromise } from './utils/promise';
|
||||
import { getAspectRatio, setAspectRatio } from './utils/style';
|
||||
|
||||
class Listeners {
|
||||
@ -99,7 +100,7 @@ class Listeners {
|
||||
case 75:
|
||||
// Space and K key
|
||||
if (!repeat) {
|
||||
player.togglePlay();
|
||||
silencePromise(player.togglePlay());
|
||||
}
|
||||
break;
|
||||
|
||||
@ -431,9 +432,9 @@ class Listeners {
|
||||
|
||||
if (player.ended) {
|
||||
this.proxy(event, player.restart, 'restart');
|
||||
this.proxy(event, player.play, 'play');
|
||||
this.proxy(event, () => { silencePromise(player.play()) }, 'play');
|
||||
} else {
|
||||
this.proxy(event, player.togglePlay, 'play');
|
||||
this.proxy(event, () => { silencePromise(player.togglePlay()) }, 'play');
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -539,7 +540,7 @@ class Listeners {
|
||||
// Play/pause toggle
|
||||
if (elements.buttons.play) {
|
||||
Array.from(elements.buttons.play).forEach(button => {
|
||||
this.bind(button, 'click', player.togglePlay, 'play');
|
||||
this.bind(button, 'click', () => { silencePromise(player.togglePlay()) }, 'play');
|
||||
});
|
||||
}
|
||||
|
||||
@ -681,7 +682,7 @@ class Listeners {
|
||||
// If we're done seeking and it was playing, resume playback
|
||||
if (play && done) {
|
||||
seek.removeAttribute(attribute);
|
||||
player.play();
|
||||
silencePromise(player.play());
|
||||
} else if (!done && player.playing) {
|
||||
seek.setAttribute(attribute, '');
|
||||
player.pause();
|
||||
|
@ -11,6 +11,7 @@ import { triggerEvent } from '../utils/events';
|
||||
import i18n from '../utils/i18n';
|
||||
import is from '../utils/is';
|
||||
import loadScript from '../utils/load-script';
|
||||
import { silencePromise } from '../utils/promise';
|
||||
import { formatTime } from '../utils/time';
|
||||
import { buildUrlParams } from '../utils/urls';
|
||||
|
||||
@ -172,6 +173,17 @@ class Ads {
|
||||
// We assume the adContainer is the video container of the plyr element that will house the ads
|
||||
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media);
|
||||
|
||||
// Create ads loader
|
||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
|
||||
|
||||
// Listen and respond to ads loaded and error events
|
||||
this.loader.addEventListener(
|
||||
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
|
||||
event => this.onAdsManagerLoaded(event),
|
||||
false,
|
||||
);
|
||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
|
||||
|
||||
// Request video ads to be pre-loaded
|
||||
this.requestAds();
|
||||
}
|
||||
@ -183,17 +195,6 @@ class Ads {
|
||||
const { container } = this.player.elements;
|
||||
|
||||
try {
|
||||
// Create ads loader
|
||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
|
||||
|
||||
// Listen and respond to ads loaded and error events
|
||||
this.loader.addEventListener(
|
||||
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
|
||||
event => this.onAdsManagerLoaded(event),
|
||||
false,
|
||||
);
|
||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
|
||||
|
||||
// Request video ads
|
||||
const request = new google.ima.AdsRequest();
|
||||
request.adTagUrl = this.tagUrl;
|
||||
@ -369,7 +370,12 @@ class Ads {
|
||||
// TODO: So there is still this thing where a video should only be allowed to start
|
||||
// playing when the IMA SDK is ready or has failed
|
||||
|
||||
this.loadAds();
|
||||
if (this.player.ended) {
|
||||
this.loadAds();
|
||||
} else {
|
||||
// The SDK won't allow new ads to be called without receiving a contentComplete()
|
||||
this.loader.contentComplete();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@ -510,7 +516,7 @@ class Ads {
|
||||
this.playing = false;
|
||||
|
||||
// Play video
|
||||
this.player.media.play();
|
||||
silencePromise(this.player.media.play());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -563,6 +569,8 @@ class Ads {
|
||||
this.on('loaded', resolve);
|
||||
this.player.debug.log(this.manager);
|
||||
});
|
||||
// Now that the manager has been destroyed set it to also be un-initialized
|
||||
this.initialized = false;
|
||||
|
||||
// Now request some new advertisements
|
||||
this.requestAds();
|
||||
|
@ -137,19 +137,32 @@ class PreviewThumbnails {
|
||||
throw new Error('Missing previewThumbnails.src config attribute');
|
||||
}
|
||||
|
||||
// If string, convert into single-element list
|
||||
const urls = is.string(src) ? [src] : src;
|
||||
// Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
||||
const promises = urls.map(u => this.getThumbnail(u));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
// Resolve promise
|
||||
const sortAndResolve = () => {
|
||||
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
||||
this.thumbnails.sort((x, y) => x.height - y.height);
|
||||
|
||||
this.player.debug.log('Preview thumbnails', this.thumbnails);
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
// Via callback()
|
||||
if (is.function(src)) {
|
||||
src(thumbnails => {
|
||||
this.thumbnails = thumbnails;
|
||||
sortAndResolve();
|
||||
});
|
||||
}
|
||||
// VTT urls
|
||||
else {
|
||||
// If string, convert into single-element list
|
||||
const urls = is.string(src) ? [src] : src;
|
||||
// Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
||||
const promises = urls.map(u => this.getThumbnail(u));
|
||||
// Resolve
|
||||
Promise.all(promises).then(sortAndResolve);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -204,6 +204,9 @@ const vimeo = {
|
||||
player.embed.setPlaybackRate(input).then(() => {
|
||||
speed = input;
|
||||
triggerEvent.call(player, player.media, 'ratechange');
|
||||
}).catch(() => {
|
||||
// Cannot set Playback Rate, Video is probably not on Pro account
|
||||
player.options.speed = [1];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
33
src/js/plyr.d.ts
vendored
33
src/js/plyr.d.ts
vendored
@ -94,9 +94,8 @@ declare class Plyr {
|
||||
|
||||
/**
|
||||
* Gets or sets the quality for the player. The setter accepts a value from the options specified in your config.
|
||||
* Remarks: YouTube only. HTML5 will follow.
|
||||
*/
|
||||
quality: string;
|
||||
quality: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the current loop state of the player.
|
||||
@ -134,6 +133,21 @@ declare class Plyr {
|
||||
*/
|
||||
pip: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the aspect ratio for embedded players.
|
||||
*/
|
||||
ratio?: string;
|
||||
|
||||
/**
|
||||
* Returns the current video Provider
|
||||
*/
|
||||
readonly provider: 'html5' | 'vimeo' | 'youtube';
|
||||
|
||||
/**
|
||||
* Returns the native API for Vimeo or Youtube players
|
||||
*/
|
||||
readonly embed?: any;
|
||||
|
||||
readonly fullscreen: Plyr.FullscreenControl;
|
||||
|
||||
/**
|
||||
@ -472,11 +486,21 @@ declare namespace Plyr {
|
||||
* enabled: Whether to enable vi.ai ads. publisherId: Your unique vi.ai publisher ID.
|
||||
*/
|
||||
ads?: AdOptions;
|
||||
|
||||
/**
|
||||
* Vimeo Player Options.
|
||||
*/
|
||||
vimeo?: object;
|
||||
|
||||
/**
|
||||
* Youtube Player Options.
|
||||
*/
|
||||
youtube?: object;
|
||||
}
|
||||
|
||||
interface QualityOptions {
|
||||
default: string;
|
||||
options: string[];
|
||||
default: number;
|
||||
options: number[];
|
||||
}
|
||||
|
||||
interface LoopOptions {
|
||||
@ -507,6 +531,7 @@ declare namespace Plyr {
|
||||
enabled?: boolean;
|
||||
fallback?: boolean;
|
||||
allowAudio?: boolean;
|
||||
iosNative?: boolean;
|
||||
}
|
||||
|
||||
interface CaptionOptions {
|
||||
|
@ -27,6 +27,7 @@ import is from './utils/is';
|
||||
import loadSprite from './utils/load-sprite';
|
||||
import { clamp } from './utils/numbers';
|
||||
import { cloneDeep, extend } from './utils/objects';
|
||||
import { silencePromise } from './utils/promise';
|
||||
import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateRatio } from './utils/style';
|
||||
import { parseUrl } from './utils/urls';
|
||||
|
||||
@ -303,7 +304,7 @@ class Plyr {
|
||||
|
||||
// Autoplay if required
|
||||
if (this.isHTML5 && this.config.autoplay) {
|
||||
setTimeout(() => this.play(), 10);
|
||||
setTimeout(() => silencePromise(this.play()), 10);
|
||||
}
|
||||
|
||||
// Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||
@ -356,7 +357,7 @@ class Plyr {
|
||||
|
||||
// Intecept play with ads
|
||||
if (this.ads && this.ads.enabled) {
|
||||
this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
|
||||
this.ads.managerPromise.then(() => this.ads.play()).catch(() => silencePromise(this.media.play()));
|
||||
}
|
||||
|
||||
// Return the promise (for HTML5)
|
||||
|
@ -221,7 +221,7 @@ export function hasClass(element, className) {
|
||||
|
||||
// Element matches selector
|
||||
export function matches(element, selector) {
|
||||
const prototype = { Element };
|
||||
const {prototype} = Element;
|
||||
|
||||
function match() {
|
||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||
|
@ -19,7 +19,7 @@ const isEvent = input => instanceOf(input, Event);
|
||||
const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);
|
||||
const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
|
||||
const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
|
||||
const isPromise = input => instanceOf(input, Promise);
|
||||
const isPromise = input => instanceOf(input, Promise) && isFunction(input.then);
|
||||
|
||||
const isEmpty = input =>
|
||||
isNullOrUndefined(input) ||
|
||||
|
14
src/js/utils/promise.js
Normal file
14
src/js/utils/promise.js
Normal file
@ -0,0 +1,14 @@
|
||||
import is from './is';
|
||||
/**
|
||||
* Silence a Promise-like object.
|
||||
* This is useful for avoiding non-harmful, but potentially confusing "uncaught
|
||||
* play promise" rejection error messages.
|
||||
* @param {Object} value An object that may or may not be `Promise`-like.
|
||||
*/
|
||||
export function silencePromise(value) {
|
||||
if (is.promise(value)) {
|
||||
value.then(null, () => {});
|
||||
}
|
||||
}
|
||||
|
||||
export default { silencePromise };
|
Reference in New Issue
Block a user