Skip to content

Commit

Permalink
Merge branch 'master' into feature/danger
Browse files Browse the repository at this point in the history
  • Loading branch information
johnBartos committed Feb 22, 2018
2 parents 0989ce3 + 6b1985f commit 63188d4
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 16 deletions.
20 changes: 15 additions & 5 deletions src/js/controller/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ViewModel from 'view/view-model';
import changeStateEvent from 'events/change-state-event';
import eventsMiddleware from 'controller/events-middleware';
import Events from 'utils/backbone.events';
import { canAutoplay } from 'utils/can-autoplay';
import { OS, Features } from 'environment/environment';
import { streamType } from 'providers/utils/stream-type';
import Promise, { resolved } from 'polyfills/promise';
Expand Down Expand Up @@ -254,6 +255,17 @@ Object.assign(Controller.prototype, {
// this player has been destroyed
return;
}

// Detect and store browser autoplay setting in the model.
const adConfig = _this._model.get('advertising');
canAutoplay(mediaPool, {
cancelable: checkAutoStartCancelable,
muted: _this.getMute(),
allowMuted: adConfig ? adConfig.autoplayadsmuted : false
})
.then(result => _model.set('canAutoplay', result))
.catch(function() {});

if (!OS.mobile && _model.get('autostart') === true) {
// Autostart immediately if we're not mobile and not waiting for the player to become viewable first
_autoStart();
Expand Down Expand Up @@ -492,11 +504,10 @@ Object.assign(Controller.prototype, {
if (_model.get('state') === STATE_ERROR) {
return;
}
_programController.position = pos;
if (!_model.get('scrubbing') && _model.get('state') !== STATE_PLAYING) {
_play(meta);
}

_programController.position = pos;
}

function _item(index, meta) {
Expand Down Expand Up @@ -742,7 +753,7 @@ Object.assign(Controller.prototype, {
this.getState = _getState;
this.next = _nextUp;
this.setConfig = (newConfig) => {
setConfig(_this, newConfig);
setConfig(_this, newConfig);
};
this.setItemIndex = _setItem;

Expand Down Expand Up @@ -771,7 +782,7 @@ Object.assign(Controller.prototype, {
updateProgramSoundSettings();
};
this.setPlaybackRate = (playbackRate) => {
_model.setPlaybackRate(playbackRate);
_model.setPlaybackRate(playbackRate);
};
this.getProvider = () => _model.get('provider');
this.getWidth = () => _model.get('containerWidth');
Expand Down Expand Up @@ -897,7 +908,6 @@ Object.assign(Controller.prototype, {
const apiQueue = new ApiQueueDecorator(this, [
'play',
'pause',
'seek',
'setCurrentAudioTrack',
'setCurrentCaptions',
'setCurrentQuality',
Expand Down
9 changes: 5 additions & 4 deletions src/js/polyfills/webvtt.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ function CueStyleBox(window, cue) {
position: 'relative',
paddingLeft: 0,
paddingRight: 0,
left: 0,
top: 0,
bottom: 0,
display: 'inline',
Expand Down Expand Up @@ -478,7 +479,7 @@ function CueStyleBox(window, cue) {
// the right from there.
if (!cue.vertical) {
this.applyStyles({
paddingLeft: this.formatStyle(textPos, '%'),
left: this.formatStyle(textPos, '%'),
width: this.formatStyle(cue.size, '%')
});
// Vertical box orientation; textPos is the distance from the top edge of the
Expand All @@ -495,7 +496,7 @@ function CueStyleBox(window, cue) {
this.applyStyles({
top: this.formatStyle(box.top, 'px'),
bottom: this.formatStyle(box.bottom, 'px'),
paddingLeft: this.formatStyle(box.left, 'px'),
left: this.formatStyle(box.left, 'px'),
paddingRight: this.formatStyle(box.right, 'px'),
height: 'auto',
width: this.formatStyle(box.width, 'px')
Expand Down Expand Up @@ -632,7 +633,7 @@ BoxPosition.prototype.toCSSCompatValues = function (reference) {
return {
top: this.top - reference.top,
bottom: reference.bottom - this.bottom,
paddingLeft: this.left - reference.left,
left: this.left - reference.left,
paddingRight: reference.right - this.right,
height: this.height,
width: this.width
Expand Down Expand Up @@ -787,7 +788,7 @@ function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions, num
break;
case 'rl':
styleBox.applyStyles({
paddingLeft: styleBox.formatStyle(linePos, '%')
left: styleBox.formatStyle(linePos, '%')
});
break;
case 'lr':
Expand Down
6 changes: 5 additions & 1 deletion src/js/program/media-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ export default class MediaController extends Eventable {
}

get setup() {
return this.mediaModel && this.mediaModel.get('setup');
return this.mediaModel.get('setup');
}

get started() {
return this.mediaModel.get('started');
}

set activeItem(item) {
Expand Down
6 changes: 6 additions & 0 deletions src/js/program/media-element-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export default function MediaElementPool() {
// Reserve an element exclusively for ads
const adElement = pool.shift();

// Reserve an element exclusively for feature testing.
const testElement = pool.shift();

return {
prime() {
elements.forEach(primeMediaElementForPlayback);
Expand All @@ -28,6 +31,9 @@ export default function MediaElementPool() {
getAdElement() {
return adElement;
},
getTestElement() {
return testElement;
},
clean(mediaElement) {
// Try to clean the media element so that we don't see frames of the previous video when reusing a tag
// We don't want to call load again if the media element is already clean
Expand Down
2 changes: 1 addition & 1 deletion src/js/program/program-constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// The number of tags allocated in the media pool
export const MEDIA_POOL_SIZE = 3;
export const MEDIA_POOL_SIZE = 4;
// The number of seconds before a BGL trigger at which we should start background loading. This ensures that we have
// kicked off background loading before being able to transition to that item
export const BACKGROUND_LOAD_OFFSET = 2;
Expand Down
11 changes: 7 additions & 4 deletions src/js/program/program-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import cancelable from 'utils/cancelable';
import { MediaControllerListener } from 'program/program-listeners';
import Eventable from 'utils/eventable';

import { ERROR, PLAYER_STATE, STATE_BUFFERING } from 'events/events';
import { ERROR, PLAYER_STATE, STATE_BUFFERING, STATE_IDLE } from 'events/events';
import { Features } from '../environment/environment';

/** @private Do not include in JSDocs */
Expand Down Expand Up @@ -536,17 +536,20 @@ class ProgramController extends Eventable {

/**
* Seeks the media to the provided position.
* If the media is not attached, set the item's starttime, so that when reattaching, it resumes at that time.
* If the media is not attached, set the item's starttime so that when reattaching, it resumes at that time.
* If we seek before an item loads, set the item's starttime so that when playback begins, we buffer at that time.
* @param {number} pos - The position to start at or seek to.
* @returns {void}
*/
set position(pos) {
const { mediaController } = this;
const { mediaController, model } = this;
if (!mediaController) {
return;
}

if (mediaController.attached) {
if (!mediaController.started && !mediaController.preloaded && model.get(PLAYER_STATE) === STATE_IDLE) {
mediaController.item.starttime = pos;
} else if (mediaController.attached) {
mediaController.position = pos;
} else {
mediaController.item.starttime = pos;
Expand Down
81 changes: 81 additions & 0 deletions src/js/utils/can-autoplay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// https://github.com/video-dev/can-autoplay/ (modified)
//
// MIT License

// Copyright (c) 2017 video-dev

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import createPlayPromise from '../providers/utils/play-promise';
import Promise from '../polyfills/promise';

/**
* Small video file with audio.
* Source: https://github.com/mathiasbynens/small
*
* @constant
* @default
* @type {String}
*/
const VIDEO = 'data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=';

function startPlayback (element, { muted, timeout }) {
// Configure element.
element.muted = muted;
element.src = VIDEO;

// Start playback.
const promise = (element.play() || createPlayPromise(element))
.then(() => true)
.catch(() => false);

// Return playback promise, or timeout.
const timer = new Promise((resolve, reject) => {
setTimeout(reject, timeout, new Error('Autoplay test timed out'));
});
return Promise.race([ promise, timer ]);
}

export const AUTOPLAY_ENABLED = 'autoplayEnabled';
export const AUTOPLAY_MUTED = 'autoplayMuted';
export const AUTOPLAY_DISABLED = 'autoplayDisabled';

export function canAutoplay (mediaPool, { cancelable, muted = false, allowMuted = false, timeout = 250 }) {
const element = mediaPool.getTestElement();

// Run the first test: autoplay with specified muted setting.
return startPlayback(element, { muted, timeout }).then(result => {
if (cancelable.cancelled()) {
throw new Error('Autoplay test was cancelled');
}

// Second optional test: autoplay muted.
if (result === false && muted === false && allowMuted) {
muted = true;
return startPlayback(element, { muted, timeout });
}
return result;
}).then(result => {
// Return autoplay flag.
if (result === true) {
return muted ? AUTOPLAY_MUTED : AUTOPLAY_ENABLED;
}
return AUTOPLAY_DISABLED;
});
}
2 changes: 1 addition & 1 deletion test/unit/media-pool-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MEDIA_POOL_SIZE } from 'program/program-constants';
import sinon from 'sinon';

describe('Media Element Pool', function () {
const numTags = MEDIA_POOL_SIZE - 1;
const numTags = MEDIA_POOL_SIZE - 2; // Subtract preallocated ad & test elements.
let mediaPool = null;
beforeEach(function () {
mediaPool = new MediaPool();
Expand Down

0 comments on commit 63188d4

Please sign in to comment.