diff --git a/readme.md b/readme.md index a5acb614..dc3992b3 100644 --- a/readme.md +++ b/readme.md @@ -351,30 +351,30 @@ player.play(); // Start playback player.fullscreen.enter(); // Enter fullscreen ``` -| Method | Parameters | Description | -| ------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------- | -| `play()`¹ | - | Start playback. | -| `pause()` | - | Pause playback. | -| `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. | -| `stop()` | - | Stop playback and reset to start. | -| `restart()` | - | Restart playback. | -| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. | -| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. | -| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. | -| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. | -| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. | -| `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. | -| `fullscreen.exit()` | - | Exit fullscreen. | -| `fullscreen.toggle()` | - | Toggle fullscreen. | -| `airplay()` | - | Trigger the airplay dialog on supported devices. | -| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. | -| `on(event, function)` | String, Function | Add an event listener for the specified event. | -| `once(event, function)` | String, Function | Add an event listener for the specified event once. | -| `off(event, function)` | String, Function | Remove an event listener for the specified event. | -| `supports(type)` | String | Check support for a mime type. | -| `destroy()` | - | Destroy the instance and garbage collect any elements. | +| Method | Parameters | Description | +| -------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- | +| `play()`¹ | - | Start playback. | +| `pause()` | - | Pause playback. | +| `togglePlay(toggle)`¹ | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. | +| `stop()` | - | Stop playback and reset to start. | +| `restart()` | - | Restart playback. | +| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. | +| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. | +| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. | +| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. | +| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. | +| `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. | +| `fullscreen.exit()` | - | Exit fullscreen. | +| `fullscreen.toggle()` | - | Toggle fullscreen. | +| `airplay()` | - | Trigger the airplay dialog on supported devices. | +| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. | +| `on(event, function)` | String, Function | Add an event listener for the specified event. | +| `once(event, function)` | String, Function | Add an event listener for the specified event once. | +| `off(event, function)` | String, Function | Remove an event listener for the specified event. | +| `supports(type)` | String | Check support for a mime type. | +| `destroy()` | - | Destroy the instance and garbage collect any elements. | -1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in _some_ browsers - WebKit and Mozilla [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing. +1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for most browsers - e.g. Chrome, Firefox, Opera, Safari and Edge [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing. ## Getters and Setters diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 6dc069b2..d92552e2 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) { @@ -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..62def372 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'; @@ -510,7 +511,7 @@ class Ads { this.playing = false; // Play video - this.player.media.play(); + silencePromise(this.player.media.play()); } /** 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/promise.js b/src/js/utils/promise.js new file mode 100644 index 00000000..42fcc2c3 --- /dev/null +++ b/src/js/utils/promise.js @@ -0,0 +1,27 @@ +/** + * Returns whether an object is `Promise`-like (i.e. has a `then` method). + * + * @param {Object} value + * An object that may or may not be `Promise`-like. + * + * @return {boolean} + * Whether or not the object is `Promise`-like. + */ +export function isPromise(value) { + return value !== undefined && value !== null && typeof value.then === 'function'; +} + +/** + * 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 (isPromise(value)) { + value.then(null, () => {}); + } +}