Skip to content

Commit

Permalink
New: add autoplay to media viewers (#433)
Browse files Browse the repository at this point in the history
* New: add autoplay to media viewers

* Fix: Fixing dash check and responding to comments

* Chore: Updating README
  • Loading branch information
Jeremy Press authored Oct 24, 2017
1 parent 6a667f1 commit 10982de
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 16 deletions.
6 changes: 6 additions & 0 deletions src/i18n/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ error_try_again_later=We're sorry the preview didn't load. Please try again late
error_bad_file=We're sorry the preivew didn't load. This file may be corrupted.

# Media Preview
# Label for autoplay in media player
media_autoplay=Autoplay
# Label for disabled autoplay in media player
media_autoplay_disabled=Disabled
# Label for enabled autoplay in media player
media_autoplay_enabled=Enabled
# Label for changing speed in media player
media_speed=Speed
# Label for normal speed in media player
Expand Down
1 change: 1 addition & 0 deletions src/lib/viewers/media/DashViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ class DashViewer extends VideoBaseViewer {
return;
}

this.checkAutoplay();
this.calculateVideoDimensions();
this.loadUI();
this.loadFilmStrip();
Expand Down
53 changes: 50 additions & 3 deletions src/lib/viewers/media/MediaBaseViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const CSS_CLASS_MEDIA = 'bp-media';
const CSS_CLASS_MEDIA_CONTAINER = 'bp-media-container';
const DEFAULT_VOLUME = 1;
const MEDIA_VOLUME_CACHE_KEY = 'media-volume';
const MEDIA_AUTOPLAY_CACHE_KEY = 'media-autoplay';
const MEDIA_VOLUME_INCREMENT = 0.05;
const EMIT_WAIT_TIME_IN_MILLIS = 100;

Expand All @@ -28,7 +29,7 @@ class MediaBaseViewer extends BaseViewer {
this.wrapperEl = this.containerEl.appendChild(document.createElement('div'));
this.wrapperEl.className = CSS_CLASS_MEDIA;

// Media Wrapper
// Media Container
this.mediaContainerEl = this.wrapperEl.appendChild(document.createElement('div'));
this.mediaContainerEl.setAttribute('tabindex', '-1');
this.mediaContainerEl.className = CSS_CLASS_MEDIA_CONTAINER;
Expand Down Expand Up @@ -98,7 +99,7 @@ class MediaBaseViewer extends BaseViewer {

if (Browser.isIOS()) {
// iOS doesn't fire loadeddata event until some data loads
// Adding autoplay helps with that and itself won't autoplay.
// Adding autoplay prevents this but won't actually autoplay the video.
// https://webkit.org/blog/6784/new-video-policies-for-ios/
this.mediaEl.autoplay = true;
}
Expand All @@ -107,6 +108,7 @@ class MediaBaseViewer extends BaseViewer {
.getPromise()
.then(() => {
this.mediaEl.src = this.mediaUrl;
this.checkAutoplay();
})
.catch(this.handleAssetError);
}
Expand Down Expand Up @@ -211,6 +213,50 @@ class MediaBaseViewer extends BaseViewer {
}
}

/**
* Handler for autoplay
*
* @private
* @emits autoplay
* @return {void}
*/
handleAutoplay() {
const isAutoplayEnabled = this.cache.get(MEDIA_AUTOPLAY_CACHE_KEY) === 'Enabled';
this.emit('autoplay', isAutoplayEnabled);
}

/**
* Determines if media should autoplay based on cached settings value.
*
* @private
* @emits volume
* @return {void}
*/
checkAutoplay() {
if (this.cache.get(MEDIA_AUTOPLAY_CACHE_KEY) !== 'Enabled') {
return;
}

// Play may return a promise depening on browser support. This promise
// will resolve when playback starts. If it fails, pause UI should be shown.
// https://webkit.org/blog/7734/auto-play-policy-changes-for-macos/
const autoPlayPromise = this.mediaEl.play();

if (autoPlayPromise && typeof autoPlayPromise.then === 'function') {
autoPlayPromise
.then(() => {
this.handleRate();
this.handleVolume();
})
.catch(() => {
this.pause();
});
} else {
// Fallback to traditional autoplay tag if play does not return a promise
this.mediaEl.autoplay = true;
}
}

/**
* Resize handler
*
Expand Down Expand Up @@ -262,6 +308,7 @@ class MediaBaseViewer extends BaseViewer {
this.mediaControls.addListener('toggleplayback', this.togglePlay);
this.mediaControls.addListener('togglemute', this.toggleMute);
this.mediaControls.addListener('ratechange', this.handleRate);
this.mediaControls.addListener('autoplaychange', this.handleAutoplay);
}

/**
Expand Down Expand Up @@ -321,6 +368,7 @@ class MediaBaseViewer extends BaseViewer {
this.hideLoadingIcon();
this.handleRate();
this.handleVolume();
this.emit('play');
}

/**
Expand Down Expand Up @@ -435,7 +483,6 @@ class MediaBaseViewer extends BaseViewer {
}
if (arguments.length === 0 || hasValidStart) {
this.mediaEl.play();
this.emit('play');
this.handleRate();
this.handleVolume();
}
Expand Down
12 changes: 12 additions & 0 deletions src/lib/viewers/media/MediaControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class MediaControls extends EventEmitter {
this.settings.removeListener('quality', this.handleQuality);
this.settings.removeListener('speed', this.handleRate);
this.settings.removeListener('subtitles', this.handleSubtitle);
this.settings.removeListener('autoplay', this.handleAutoplay);
this.settings.destroy();
this.settings = undefined;
}
Expand Down Expand Up @@ -189,6 +190,16 @@ class MediaControls extends EventEmitter {
this.emit('audiochange');
}

/**
* Autoplay handler
*
* @private
* @return {void}
*/
handleAutoplay() {
this.emit('autoplaychange');
}

/**
* Attaches settings menu
*
Expand All @@ -199,6 +210,7 @@ class MediaControls extends EventEmitter {
this.settings = new Settings(this.containerEl, this.cache);
this.settings.addListener('quality', this.handleQuality);
this.settings.addListener('speed', this.handleRate);
this.settings.addListener('autoplay', this.handleAutoplay);
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/lib/viewers/media/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ The DASH viewer fires the following events
| switchhistory | Gives quality switching history when the preview is destroyed | {array} quality switch objects |
| adaptation | Quality adapts to a change in bandwidth | {number} bandwidth |
| play | The video plays ||
| autoplay | the autoplay option has been enabled/disabled | {boolean} new autoplay value|
| pause | The video pauses ||
| seeked | The video skips to a time | {number} time |

Expand Down Expand Up @@ -103,6 +104,7 @@ The MP3 viewer fires the following events
| ratechange | Media speed changes | {string} playback speed |
| volumechange | The media volume changes | {number} volume between 0 and 1|
| play | The audio plays ||
| autoplay | the autoplay option has been enabled/disabled | {boolean} new autoplay value|
| pause | The audio pauses ||
| seeked | The audio skips to a time | {number} time |

Expand Down Expand Up @@ -163,6 +165,7 @@ The MP4 viewer fires the following events
| ratechange | Media speed changes | {string} playback speed |
| volumechange | The media volume changes | {number} volume between 0 and 1 |
| play | The video plays ||
| autoplay | the autoplay option has been enabled/disabled | {boolean} new autoplay value|
| pause | The video pauses ||
| seeked | The video skips to a time | {number} time |

Expand Down
31 changes: 30 additions & 1 deletion src/lib/viewers/media/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@ import EventEmitter from 'events';
import { addActivationListener, removeActivationListener, decodeKeydown, insertTemplate } from '../../util';
import { ICON_ARROW_LEFT, ICON_ARROW_RIGHT, ICON_CHECK_MARK } from '../../icons/icons';
import { CLASS_ELEM_KEYBOARD_FOCUS } from '../../constants';
import Browser from '../../Browser';

const TYPE_SPEED = 'speed';
const TYPE_QUALITY = 'quality';
const TYPE_AUTOPLAY = 'autoplay';
const CLASS_SETTINGS = 'bp-media-settings';
const CLASS_SETTINGS_SELECTED = 'bp-media-settings-selected';
const CLASS_SETTINGS_OPEN = 'bp-media-settings-is-open';
const CLASS_SETTINGS_SUBTITLES_UNAVAILABLE = 'bp-media-settings-subtitles-unavailable';
const CLASS_SETTINGS_AUDIOTRACKS_UNAVAILABLE = 'bp-media-settings-audiotracks-unavailable';
const CLASS_SETTINGS_AUTOPLAY_UNAVAILABLE = 'bp-media-settings-autoplay-unavailable';
const CLASS_SETTINGS_SUBTITLES_ON = 'bp-media-settings-subtitles-on';
const SELECTOR_SETTINGS_SUB_ITEM = '.bp-media-settings-sub-item';
const SELECTOR_SETTINGS_VALUE = '.bp-media-settings-value';
const MEDIA_SPEEDS = ['0.5', '1.0', '1.25', '1.5', '2.0'];

const SETTINGS_TEMPLATE = `<div class="bp-media-settings">
<div class="bp-media-settings-menu-main bp-media-settings-menu" role="menu">
<div class="bp-media-settings-item bp-media-settings-item-autoplay" data-type="autoplay" tabindex="0" role="menuitem" aria-haspopup="true">
<div class="bp-media-settings-label" aria-label="${__('media_autoplay')}">${__('media_autoplay')}</div>
<div class="bp-media-settings-value">${__('media_autoplay_disabled')}</div>
<div class="bp-media-settings-arrow">${ICON_ARROW_RIGHT}</div>
</div>
<div class="bp-media-settings-item bp-media-settings-item-speed" data-type="speed" tabindex="0" role="menuitem" aria-haspopup="true">
<div class="bp-media-settings-label" aria-label="${__('media_speed')}">${__('media_speed')}</div>
<div class="bp-media-settings-value">${__('media_speed_normal')}</div>
Expand All @@ -39,6 +47,20 @@ const SETTINGS_TEMPLATE = `<div class="bp-media-settings">
<div class="bp-media-settings-arrow">${ICON_ARROW_RIGHT}</div>
</div>
</div>
<div class="bp-media-settings-menu-autoplay bp-media-settings-menu" role="menu">
<div class="bp-media-settings-sub-item bp-media-settings-sub-item-autoplay" data-type="menu" tabindex="0" role="menuitem" aria-haspopup="true">
<div class="bp-media-settings-arrow">${ICON_ARROW_LEFT}</div>
<div class="bp-media-settings-label" aria-label="${__('media_autoplay')}">${__('media_autoplay')}</div>
</div>
<div class="bp-media-settings-sub-item" data-type="autoplay" data-value="Disabled" tabindex="0" role="menuitemradio">
<div class="bp-media-settings-icon">${ICON_CHECK_MARK}</div>
<div class="bp-media-settings-value">${__('media_autoplay_disabled')}</div>
</div>
<div class="bp-media-settings-sub-item" data-type="autoplay" data-value="Enabled" tabindex="0" role="menuitemradio">
<div class="bp-media-settings-icon">${ICON_CHECK_MARK}</div>
<div class="bp-media-settings-value">${__('media_autoplay_enabled')}</div>
</div>
</div>
<div class="bp-media-settings-menu-speed bp-media-settings-menu" role="menu">
<div class="bp-media-settings-sub-item bp-media-settings-sub-item-speed" data-type="menu" tabindex="0" role="menuitem" aria-haspopup="true">
<div class="bp-media-settings-arrow">${ICON_ARROW_LEFT}</div>
Expand Down Expand Up @@ -160,6 +182,11 @@ class Settings extends EventEmitter {
addActivationListener(this.settingsEl, this.menuEventHandler);
this.containerEl.classList.add(CLASS_SETTINGS_SUBTITLES_UNAVAILABLE);
this.containerEl.classList.add(CLASS_SETTINGS_AUDIOTRACKS_UNAVAILABLE);

if (Browser.isIOS()) {
this.containerEl.classList.add(CLASS_SETTINGS_AUTOPLAY_UNAVAILABLE);
}

this.init();
}

Expand All @@ -172,9 +199,11 @@ class Settings extends EventEmitter {
init() {
const quality = this.cache.get('media-quality') || 'auto';
const speed = this.cache.get('media-speed') || '1.0';
const autoplay = this.cache.get('media-autoplay') || 'Disabled';

this.chooseOption(TYPE_QUALITY, quality);
this.chooseOption(TYPE_SPEED, speed);
this.chooseOption(TYPE_AUTOPLAY, autoplay);
}

/**
Expand Down Expand Up @@ -276,7 +305,7 @@ class Settings extends EventEmitter {
* Show sub-menu
*
* @private
* @param {string} type - Either "speed" or "quality"
* @param {string} type - Either "speed", "quality", or "autoplay"
* @return {void}
*/
showSubMenu(type) {
Expand Down
12 changes: 11 additions & 1 deletion src/lib/viewers/media/Settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,28 @@ $item-hover-color: #f6fafd;
display: table;
}

.bp-media-settings-menu-autoplay,
.bp-media-settings-menu-quality,
.bp-media-settings-menu-speed,
.bp-media-settings-menu-subtitles,
.bp-media-settings-menu-audiotracks,
.bp-media-settings-show-autoplay .bp-media-settings-menu-main,
.bp-media-settings-show-speed .bp-media-settings-menu-main,
.bp-media-settings-show-quality .bp-media-settings-menu-main,
.bp-media-settings-show-subtitles .bp-media-settings-menu-main,
.bp-media-settings-show-audiotracks .bp-media-settings-menu-main {
display: none;
}

.bp-media-settings-show-autoplay .bp-media-settings-menu-autoplay,
.bp-media-settings-show-speed .bp-media-settings-menu-speed,
.bp-media-settings-show-quality .bp-media-settings-menu-quality,
.bp-media-settings-show-subtitles .bp-media-settings-menu-subtitles,
.bp-media-settings-show-audiotracks .bp-media-settings-menu-audiotracks {
display: table;
}

// For MP3 and MP4 we only have speed option
// For MP3 and MP4 we only have speed and autoplay options
.bp-media-mp4,
.bp-media-mp3 {
.bp-media-settings-item-quality,
Expand Down Expand Up @@ -107,6 +110,13 @@ $item-hover-color: #f6fafd;
}
}

.bp-media-settings-item-autoplay,
.bp-media-settings-menu-autoplay {
.bp-media-settings-autoplay-unavailable & {
display: none;
}
}

.bp-media-settings-item-audiotracks,
.bp-media-settings-menu-audiotracks {
.bp-media-settings-audiotracks-unavailable & {
Expand Down
6 changes: 5 additions & 1 deletion src/lib/viewers/media/__tests__/DashViewer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ describe('lib/viewers/media/DashViewer', () => {
sandbox.stub(dash, 'loadDashPlayer');
sandbox.stub(dash, 'resetLoadTimeout');
sandbox.stub(dash, 'loadAssets');
sandbox.stub(dash, 'checkAutoplay');
sandbox.stub(dash, 'getRepStatus').returns({ getPromise: () => Promise.resolve() });
sandbox.stub(Promise, 'all').returns(stubs.promise);

Expand All @@ -146,6 +147,7 @@ describe('lib/viewers/media/DashViewer', () => {
expect(dash.setup).to.be.called;
expect(dash.loadDashPlayer).to.be.called;
expect(dash.resetLoadTimeout).to.be.called;
expect(dash.checkAutoplay).to.be.called;
})
.catch(() => {});
});
Expand Down Expand Up @@ -488,9 +490,10 @@ describe('lib/viewers/media/DashViewer', () => {
expect(dash.showMedia).to.not.be.called;
});

it('should load the meta data for the media element, show the media/play button, load subs and set focus', () => {
it('should load the meta data for the media element, show the media/play button, load subs, check for autoplay, and set focus', () => {
sandbox.stub(dash, 'isDestroyed').returns(false);
sandbox.stub(dash, 'showMedia');
sandbox.stub(dash, 'checkAutoplay');
sandbox.stub(dash, 'calculateVideoDimensions');
sandbox.stub(dash, 'loadUI');
sandbox.stub(dash, 'loadFilmStrip');
Expand All @@ -503,6 +506,7 @@ describe('lib/viewers/media/DashViewer', () => {
sandbox.stub(dash, 'showPlayButton');

dash.loadeddataHandler();
expect(dash.checkAutoplay).to.be.called;
expect(dash.showMedia).to.be.called;
expect(dash.showPlayButton).to.be.called;
expect(dash.loadSubtitles).to.be.called;
Expand Down
Loading

0 comments on commit 10982de

Please sign in to comment.