aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
authorSam Potts <sam@potts.es>2020-03-29 12:02:59 +1100
committerSam Potts <sam@potts.es>2020-03-29 12:02:59 +1100
commit09598f07bf062886bfa22cd8682948567e92a19a (patch)
tree2ed0de186d43b2cc27422115fe75600a12e5d303 /src/js
parentef7b30c1b8a0e2d9a8cb71dfb41af0706bb8886e (diff)
parent155add66bd5b941699cea99f5c94cf8a87b030d8 (diff)
downloadplyr-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.js6
-rw-r--r--src/js/controls.js2
-rw-r--r--src/js/fullscreen.js5
-rw-r--r--src/js/html5.js3
-rw-r--r--src/js/listeners.js11
-rw-r--r--src/js/plugins/ads.js34
-rw-r--r--src/js/plugins/preview-thumbnails.js27
-rw-r--r--src/js/plugins/vimeo.js3
-rw-r--r--src/js/plyr.d.ts33
-rw-r--r--src/js/plyr.js5
-rw-r--r--src/js/utils/elements.js2
-rw-r--r--src/js/utils/is.js2
-rw-r--r--src/js/utils/promise.js14
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 };