Skip to content

Commit

Permalink
feat(autoplay): manage autoplay promise and allowMutedAutoPlay flag (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Ziv authored and OrenMe committed Oct 25, 2017
1 parent 334902b commit 79a2610
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 62 deletions.
77 changes: 77 additions & 0 deletions docs/autoplay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## Managing Autoplay on the Player
If you would to start playing your content automatically, simply set the value of the player configuration `playback.autoplay` accordingly.

>### playback.autoplay
>##### Type: `boolean`
>##### Default: `false`
>##### Description: Start playback automatically.
>If set to `true`, playback will start automatically when the player receives the content. If set to `false`, a user action will be required to start playback.
#### Example - Basic usage:
```js
var config = {
playback: {
autoplay: true
},
sources: {...}
};
// No additional code required - playback will begin automatically
var player = Playkit.loadPlayer(config);
```

>**_Note_**:
> Some developers simulate the autoplay behavior by calling the play() method right after creating the player without manipulating the player configuration. However, we strongly recommend not doing this!
>```js
>var config = {
> sources: {...}
>};
>var player = Playkit.loadPlayer(config);
>// Bad practice!
>player.play();
>```
> #### Why it's bad?
> When you use the method described above, the player has no way of knowing whether the call to `play()` has been implemented via user action or via an API call. This can cause unexpected behavior in browsers that do not support autoplay.
### What if the browser does not allow autoplay?
On some platforms, web browsers do not allow to playback to begin automatically. The platforms that block attempts at playback are mostly targeted at web browsers on mobile devices; however, some desktop browsers have also started to align these restrictions to create a unified behavior between platforms (currently Safari11 and beginning with Chrome64).
#### The Good News
These restrictions apply only to automatic playback with sound, but not to automatic playback without sound.
The PlayKitJS core can manage this logic on its own, and identify whether the current environment does not support autoplay with sound. In this case, the player will apply the autoplay with sound.
To use this feature, you'll need to set the `playback.allowMutedAutoPlay` accordingly to the desired behavior.
>### playback.allowMutedAutoPlay
>##### Type: `boolean`
>##### Default: `true`
>##### Description: Start playback automatically without sound if autoplay with sound is not allowed.
>If set to `true` and the run-time browser blocks autoplay, autoplay will begin muted until any user action is executed on the player, which will then unmute playback. If set to `false` and the run-time browser blocks autoplay, playback will not start automatically.
The following matrix summarizes the results for configuring `playback.allowMutedAutoPlay` when `playback.autoplay=true`:
| Browser Policy | `allowMutedAutoPlay=true` |` allowMutedAutoPlay=false`
| ----------------- | ----------------- | ------ |
| Blocks autoplay | Muted autoplay | No autoplay (user action is required) |
| Allows autoplay | Autoplay with/without sound according to the player configuration | Autoplay with/without sound according to the player configuration
#### Example - Basic usage:
```js
var config = {
playback: {
autoplay: true,
allowMutedAutoPlay: true
},
sources: {...}
};
// If browser blocks autoplay, playback will start muted
var player = Playkit.loadPlayer(config);
```
#### Example - Basic usage 2:
```js
var config = {
playback: {
autoplay: true,
allowMutedAutoPlay: false
},
sources: {...}
};
// If browser blocks autoplay, playback will not start automatically
var player = Playkit.loadPlayer(config);
```
7 changes: 7 additions & 0 deletions flow-typed/interfaces/engine-capabilty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//@flow
declare type CapabilityResult = { [capabilityName: string]: any };

declare interface ICapability {
static runCapability(): void;
static getCapability(): Promise<CapabilityResult>;
}
2 changes: 2 additions & 0 deletions flow-typed/interfaces/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ declare interface IEngine {
static id: string;
static createEngine(source: Source, config: Object): IEngine;
static canPlaySource(source: Source): boolean;
static runCapabilities(): void;
static getCapabilities(): Promise<Object>;
restore(source: Source, config: Object): void;
destroy(): void;
attach(): void;
Expand Down
3 changes: 3 additions & 0 deletions src/assets/encoding-sources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"Base64Mp4Source": "data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAABPptZGF0AAACrQYF//+p3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0OCByMjcyMSA3MmQ1M2FiIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNiAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD00IHRocmVhZHM9MSBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MiBrZXlpbnQ9MjUwIGtleWludF9taW49MjUgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD00MCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIzLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IGlwX3JhdGlvPTEuNDAgYXE9MToxLjAwAIAAAAAQZYiEADf//vaH+BTZWBP/gd4CAExhdmM1Ny42NC4xMDAAQiAIwRg4AAAACEGaJGxDf/7gAAAACEGeQniFf8SBIRAEYIwcAAAACAGeYXRCf8eAIRAEYIwcAAAACAGeY2pCf8eBIRAEYIwcAAAADkGaaEmoQWiZTAhv//7hAAAACkGehkURLCv/xIEhEARgjBwAAAAIAZ6ldEJ/x4EhEARg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"
}
35 changes: 35 additions & 0 deletions src/engines/html5/capabilities/html5-autoplay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @flow
import * as Utils from '../../../utils/util'
import * as EncodingSources from '../../../assets/encoding-sources.json'

export default class Html5AutoPlayCapability implements ICapability {
static _vid: HTMLVideoElement = Utils.Dom.createElement('video');
static _promise: Promise<*>;

/***
* Runs the test for autoplay capability.
* @public
* @static
* @returns {void}
*/
static runCapability(): void {
try {
Html5AutoPlayCapability._vid.src = EncodingSources.Base64Mp4Source;
Html5AutoPlayCapability._promise = Html5AutoPlayCapability._vid.play() || Promise.resolve();
} catch (e) {
Html5AutoPlayCapability._promise = Promise.reject();
}
}

/**
* Gets the test result for autoplay capability.
* @returns {Promise<CapabilityResult>} - The result object for autoplay capability.
* @static
* @public
*/
static getCapability(): Promise<CapabilityResult> {
return Html5AutoPlayCapability._promise
.then(() => ({autoplay: true}))
.catch(() => ({autoplay: false}));
}
}
32 changes: 32 additions & 0 deletions src/engines/html5/capabilities/html5-is-supported.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// @flow
import * as Utils from '../../../utils/util'

export default class Html5IsSupportedCapability implements ICapability {
static _vid: HTMLVideoElement = Utils.Dom.createElement('video');
static _result: boolean;

/***
* Runs the test for isSupported capability.
* @public
* @static
* @returns {void}
*/
static runCapability(): void {
try {
Html5IsSupportedCapability._vid.volume = 0.5;
Html5IsSupportedCapability._result = !!Html5IsSupportedCapability._vid.canPlayType;
} catch (e) {
Html5IsSupportedCapability._result = false;
}
}

/**
* Gets the test result for isSupported capability.
* @returns {Promise<CapabilityResult>} - The result object for isSupported capability.
* @static
* @public
*/
static getCapability(): Promise<CapabilityResult> {
return Promise.resolve({isSupported: Html5IsSupportedCapability._result});
}
}
64 changes: 42 additions & 22 deletions src/engines/html5/html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import AudioTrack from '../../track/audio-track'
import {TextTrack as PKTextTrack} from '../../track/text-track'
import {Cue} from '../../track/vtt-cue'
import * as Utils from '../../utils/util'
import Html5AutoPlayCapability from './capabilities/html5-autoplay'
import Html5IsSupportedCapability from './capabilities/html5-is-supported'

/**
* Html5 engine for playback.
Expand Down Expand Up @@ -52,8 +54,17 @@ export default class Html5 extends FakeEventTarget implements IEngine {
*/
_canLoadMediaSourceAdapterPromise: Promise<*>;

/**
* The html5 capabilities handlers.
* @private
* @static
*/
static _capabilities: Array<typeof ICapability> = [Html5AutoPlayCapability, Html5IsSupportedCapability];

/**
* @type {string} - The engine id.
* @public
* @static
*/
static id: string = "html5";

Expand Down Expand Up @@ -81,6 +92,33 @@ export default class Html5 extends FakeEventTarget implements IEngine {
return MediaSourceProvider.canPlaySource(source, preferNative);
}

/**
* Runs the html5 capabilities tests.
* @returns {void}
* @public
* @static
*/
static runCapabilities(): void {
Html5._capabilities.forEach(capability => capability.runCapability());
}

/**
* Gets the html5 capabilities.
* @return {Promise<Object>} - The html5 capabilities object.
* @public
* @static
*/
static getCapabilities(): Promise<Object> {
let promises = [];
Html5._capabilities.forEach(capability => promises.push(capability.getCapability()));
return Promise.all(promises)
.then((arrayOfResults) => {
const mergedResults = {};
arrayOfResults.forEach(res => Object.assign(mergedResults, res));
return {[Html5.id]: mergedResults};
});
}

/**
* @constructor
* @param {Source} source - The selected source object.
Expand Down Expand Up @@ -281,7 +319,10 @@ export default class Html5 extends FakeEventTarget implements IEngine {
* @returns {void}
*/
play(): void {
this._el.play();
let playPromise = this._el.play();
if (playPromise) {
playPromise.catch(() => this.dispatchEvent(new FakeEvent(CustomEvents.AUTOPLAY_FAILED)));
}
}

/**
Expand Down Expand Up @@ -662,27 +703,6 @@ export default class Html5 extends FakeEventTarget implements IEngine {
return this._el.getAttribute('playsinline') === '';
}

/**
* Test video element to check if html5 engine is supported.
*/
static TEST_VID: HTMLVideoElement;

/**
* Checks if the html5 engine is supported.
* @returns {boolean} - The isSupported result.
* @static
* @public
*/
static isSupported() {
try {
Html5.TEST_VID = Utils.Dom.createElement('video');
Html5.TEST_VID.volume = 0.5;
} catch (e) {
return false;
}
return !!Html5.TEST_VID.canPlayType;
}

/**
* Initializes the engine.
* @param {Source} source - The selected source object.
Expand Down
8 changes: 8 additions & 0 deletions src/event/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ const HTML5_EVENTS: { [event: string]: string } = {
};

const CUSTOM_EVENTS: { [event: string]: string } = {
/**
* Fires when browser fails to autoplay with sound
*/
AUTOPLAY_FAILED: 'autoplayfailed',
/**
* Fires when browser fails to autoplay with sound but start muted autoplay instead
*/
FALLBACK_TO_MUTED_AUTOPLAY: 'fallbacktomutedautoplay',
/**
* Fires when change source flow started
*/
Expand Down
1 change: 1 addition & 0 deletions src/player-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"playsinline": true,
"preload": "none",
"autoplay": false,
"allowMutedAutoPlay": true,
"muted": false,
"options": {
"html5": {
Expand Down
Loading

0 comments on commit 79a2610

Please sign in to comment.