diff options
author | Sam Potts <sam@potts.es> | 2020-03-29 12:02:59 +1100 |
---|---|---|
committer | Sam Potts <sam@potts.es> | 2020-03-29 12:02:59 +1100 |
commit | 09598f07bf062886bfa22cd8682948567e92a19a (patch) | |
tree | 2ed0de186d43b2cc27422115fe75600a12e5d303 /src/js | |
parent | ef7b30c1b8a0e2d9a8cb71dfb41af0706bb8886e (diff) | |
parent | 155add66bd5b941699cea99f5c94cf8a87b030d8 (diff) | |
download | plyr-09598f07bf062886bfa22cd8682948567e92a19a.tar.lz plyr-09598f07bf062886bfa22cd8682948567e92a19a.tar.xz plyr-09598f07bf062886bfa22cd8682948567e92a19a.zip |
Merge branch 'develop' of github.com:sampotts/plyr into develop
# Conflicts:
# package.json
# yarn.lock
Diffstat (limited to 'src/js')
-rw-r--r-- | src/js/captions.js | 6 | ||||
-rw-r--r-- | src/js/controls.js | 2 | ||||
-rw-r--r-- | src/js/fullscreen.js | 5 | ||||
-rw-r--r-- | src/js/html5.js | 3 | ||||
-rw-r--r-- | src/js/listeners.js | 11 | ||||
-rw-r--r-- | src/js/plugins/ads.js | 34 | ||||
-rw-r--r-- | src/js/plugins/preview-thumbnails.js | 27 | ||||
-rw-r--r-- | src/js/plugins/vimeo.js | 3 | ||||
-rw-r--r-- | src/js/plyr.d.ts | 33 | ||||
-rw-r--r-- | src/js/plyr.js | 5 | ||||
-rw-r--r-- | src/js/utils/elements.js | 2 | ||||
-rw-r--r-- | src/js/utils/is.js | 2 | ||||
-rw-r--r-- | src/js/utils/promise.js | 14 |
13 files changed, 109 insertions, 38 deletions
diff --git a/src/js/captions.js b/src/js/captions.js index e33fd81a..04da4651 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -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); } }, diff --git a/src/js/controls.js b/src/js/controls.js index 66ec7139..37df497f 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -111,7 +111,7 @@ const controls = { setAttributes( icon, extend(attributes, { - role: 'presentation', + 'aria-hidden': 'true', focusable: 'false', }), ); diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index c74b3406..4d3c89ac 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -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) { diff --git a/src/js/html5.js b/src/js/html5.js index 0591a709..6e8c6483 100644 --- a/src/js/html5.js +++ b/src/js/html5.js @@ -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()); } }); diff --git a/src/js/listeners.js b/src/js/listeners.js index 6a0046ee..d134a350 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -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(); diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 6b4fca10..9f1088fa 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -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(); diff --git a/src/js/plugins/preview-thumbnails.js b/src/js/plugins/preview-thumbnails.js index 86eeebc8..290ce949 100644 --- a/src/js/plugins/preview-thumbnails.js +++ b/src/js/plugins/preview-thumbnails.js @@ -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); + } }); } diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index fa965d8e..010cf5f7 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -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]; }); }, }); diff --git a/src/js/plyr.d.ts b/src/js/plyr.d.ts index cd204a6f..d68935b2 100644 --- a/src/js/plyr.d.ts +++ b/src/js/plyr.d.ts @@ -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 { diff --git a/src/js/plyr.js b/src/js/plyr.js index 00d33463..00b95a5f 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -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) diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js index b88aad0c..bdf18bfd 100644 --- a/src/js/utils/elements.js +++ b/src/js/utils/elements.js @@ -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); diff --git a/src/js/utils/is.js b/src/js/utils/is.js index b005cd31..24f176cc 100644 --- a/src/js/utils/is.js +++ b/src/js/utils/is.js @@ -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) || diff --git a/src/js/utils/promise.js b/src/js/utils/promise.js new file mode 100644 index 00000000..f45b46ab --- /dev/null +++ b/src/js/utils/promise.js @@ -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 }; |