1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-05 19:42:38 +02:00

Moving to node_modules folder to make easier to upgrade

trying to move from Bootstrap 3 to Bootstrap 5
This commit is contained in:
Daniel 2021-10-26 14:52:45 -03:00
parent 047e363a16
commit d4d042e041
8460 changed files with 1355889 additions and 547977 deletions

49543
node_modules/videojs-contrib-ads/test/dist/bundle.js generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
[{
"src": "/examples/basic-ad-plugin/superclip-low.webm",
"type": "video/webm"
}]

View file

@ -0,0 +1,95 @@
import videojs from 'video.js';
import sinon from 'sinon';
import document from 'global/document';
import window from 'global/window';
import _ from 'lodash';
const Html5 = videojs.getTech('Html5');
const backup = {
Html5: {
isSupported: Html5.isSupported,
setSource: Html5.prototype.setSource
}
};
const common = {
beforeEach() {
// Fake HTML 5 support.
Html5.isSupported = function() {
return true;
};
delete Html5.setSource;
this.sandbox = sinon.sandbox.create();
// Use fake timers to replace setTimeout and so forth.
this.clock = sinon.useFakeTimers();
// Create video element and player.
this.video = document.createElement('video');
// backfill broken phantom implementation(s)
if (/phantom/i.test(window.navigator.userAgent)) {
this.video.removeAttribute = function(attr) {
this[attr] = '';
};
this.video.load = function() {};
this.video.play = function() {};
this.video.pause = function() {};
}
document.getElementById('qunit-fixture').appendChild(this.video);
this.player = videojs(this.video);
// Tick the clock because videojs player creation is now async.
this.clock.tick(1000);
this.player.buffered = function() {
return videojs.createTimeRange(0, 0);
};
this.player.ads(this.adsOptions);
},
afterEach() {
// Restore original state of the Html5 component.
Html5.isSupported = backup.Html5.isSupported;
Html5.prototype.setSource = backup.Html5.setSource;
// Restore setTimeout et al.
this.clock.restore();
// Kill the player and its element (i.e. `this.video`).
this.player.dispose();
// Kill the "contentplayback" spy.
this.contentPlaybackSpy = this.contentPlaybackReason = null;
this.sandbox.restore();
}
};
/*
* Composes per-module `beforeEach` and `afterEach` hooks with common/shared
* hooks.
*
* @param {Object} [hooks]
* @param {Function} [hooks.beforeEach]
* @param {Function} [hooks.afterEach]
* @return {Object}
*/
const sharedModuleHooks = function(hooks) {
hooks = hooks || {};
return {
beforeEach: _.flow(common.beforeEach, hooks.beforeEach || _.noop),
afterEach: _.flow(common.afterEach, hooks.afterEach || _.noop)
};
};
export default sharedModuleHooks;

View file

@ -0,0 +1,4 @@
WEBVTT
00:00.700 --> 00:04.110
Captions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,191 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import videojs from 'video.js';
import window from 'global/window';
import sharedModuleHooks from './lib/shared-module-hooks.js';
import _ from 'lodash';
const sharedHooks = sharedModuleHooks();
const timerExists = function(env, id) {
return env.clock.timers.hasOwnProperty(id);
};
// Stub mobile browsers to force cancelContentPlay to be used
const fakeVideojs = function() {
this.videojs = sinon.stub(videojs, 'browser').get(() => {
return {
IS_ANDROID: true,
IS_IOS: true
};
});
};
// Restore original videojs behavior
const restoreVideojs = function() {
this.videojs.restore();
};
// Run custom hooks before sharedModuleHooks, as videojs must be
// modified before setting up the player and videojs-contrib-ads
QUnit.module('Cancel Content Play', {
beforeEach: _.flow(function() {
this.adsOptions = {};
}, fakeVideojs, sharedHooks.beforeEach),
afterEach: _.flow(function() {
this.adsOptions = null;
}, restoreVideojs, sharedHooks.afterEach)
});
QUnit.test('pauses to wait for prerolls when the plugin loads BEFORE play', function(assert) {
const spy = sinon.spy(this.player, 'pause');
this.player.paused = function() {
return false;
};
this.player.trigger('adsready');
this.player.trigger('play');
this.clock.tick(1);
this.player.trigger('play');
this.clock.tick(1);
assert.strictEqual(spy.callCount, 2, 'play attempts are paused');
});
QUnit.test('pauses to wait for prerolls when the plugin loads AFTER play', function(assert) {
const pauseSpy = sinon.spy(this.player, 'pause');
this.player.paused = function() {
return false;
};
this.player.trigger('play');
this.clock.tick(1);
this.player.trigger('play');
this.clock.tick(1);
assert.equal(pauseSpy.callCount, 2, 'play attempts are paused');
});
QUnit.test('stops canceling play events when an ad is playing', function(assert) {
const setTimeoutSpy = sinon.spy(window, 'setTimeout');
const pauseSpy = sinon.spy(this.player, 'pause');
// Throughout this test, we check both that the expected timeout is
// populated on the `clock` _and_ that `setTimeout` has been called the
// expected number of times.
assert.notOk(this.player.ads._cancelledPlay, 'we have not canceled a play event');
this.player.paused = () => {
return false;
};
this.player.trigger('play');
assert.strictEqual(setTimeoutSpy.callCount, 1, 'one timer was created (`_prerollTimeout`)');
assert.ok(timerExists(this, this.player.ads._state._timeout), 'preroll timeout exists after play');
assert.equal(this.player.ads._cancelledPlay, true);
this.clock.tick(1);
assert.equal(pauseSpy.callCount, 1);
this.player.trigger('adsready');
assert.ok(timerExists(this, this.player.ads._state._timeout), 'preroll timeout exists after adsready');
this.player.ads.startLinearAdMode();
assert.notOk(timerExists(this, this.player.ads._state._timeout), 'preroll timeout no longer exists');
this.player.trigger('play');
assert.equal(pauseSpy.callCount, 1, 'pause is not called while in an ad break');
window.setTimeout.restore();
});
QUnit.test("cancelContentPlay doesn\'t block play in content playback", function(assert) {
const pauseSpy = sinon.spy(this.player, 'pause');
this.player.trigger('loadstart');
this.player.trigger('adscanceled');
this.player.paused = () => {
return false;
};
this.player.trigger('play');
assert.strictEqual(pauseSpy.callCount, 1, 'pause should have been called');
assert.strictEqual(
this.player.ads._cancelledPlay, false,
'cancelContentPlay is not called while resuming'
);
// enters ContentPlayback
this.player.trigger('playing');
this.player.trigger('play');
assert.strictEqual(pauseSpy.callCount, 1, 'pause should not have been called again');
assert.notOk(this.player.ads._cancelledPlay, 'cancelContentPlay does nothing in content playback');
});
QUnit.test('content is resumed after ads if a user initiated play event is canceled', function(assert) {
const playSpy = sinon.spy(this.player, 'play');
const setTimeoutSpy = sinon.spy(window, 'setTimeout');
const pauseSpy = sinon.spy(this.player, 'pause');
this.player.paused = () => {
return false;
};
this.player.trigger('play');
this.player.trigger('adsready');
assert.strictEqual(setTimeoutSpy.callCount, 1, 'one timer was created (`_prerollTimeout`)');
assert.ok(timerExists(this, this.player.ads._state._timeout), 'preroll timeout exists');
assert.ok(this.player.ads._cancelledPlay, true, 'play has been canceled');
assert.ok(pauseSpy.callCount, 1, 'pause was called');
this.player.ads.startLinearAdMode();
this.player.ads.endLinearAdMode();
assert.strictEqual(playSpy.callCount, 1, 'play should be called by the snapshot restore');
this.player.trigger('play');
assert.ok(pauseSpy.callCount, 1, 'pause was not called again');
});
// Set up contrib-ads options and run custom hooks before sharedModuleHooks, as
// videojs must be modified before setting up the player and videojs-contrib-ads
QUnit.module('Cancel Content Play (w/ Stitched Ads)', {
beforeEach: _.flow(function() {
this.adsOptions = {
stitchedAds: true
};
}, fakeVideojs, sharedHooks.beforeEach),
afterEach: _.flow(function() {
this.adsOptions = null;
}, restoreVideojs, sharedHooks.afterEach)
});
QUnit.test('does not pause to wait for prerolls when the plugin loads BEFORE play', function(assert) {
const spy = sinon.spy(this.player, 'pause');
this.player.paused = function() {
return false;
};
this.player.trigger('adsready');
this.player.trigger('play');
this.clock.tick(1);
this.player.trigger('play');
this.clock.tick(1);
assert.strictEqual(spy.callCount, 0, 'play attempts are not paused');
});
QUnit.test('does not pause to wait for prerolls when the plugin loads AFTER play', function(assert) {
const pauseSpy = sinon.spy(this.player, 'pause');
this.player.paused = function() {
return false;
};
this.player.trigger('play');
this.clock.tick(1);
this.player.trigger('play');
this.clock.tick(1);
assert.equal(pauseSpy.callCount, 0, 'play attempts are not paused');
});

View file

@ -0,0 +1,181 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import sharedModuleHooks from './lib/shared-module-hooks.js';
QUnit.module('Cue Metadata Text Tracks', sharedModuleHooks({
beforeEach() {
this.tt = {
player: this.player,
kind: 'metadata',
mode: 'hidden',
id: '1',
startTime: 1,
endTime: 2,
addEventListener(event, cb) {
if (event === 'cuechange') {
cb.apply(this, [this]);
}
},
activeCues: []
};
},
afterEach() {
this.player.ads.cueTextTracks.getSupportedAdCue = function(player, cue) {
return cue;
};
this.player.ads.cueTextTracks.getCueId = function(cue) {
return cue.id;
};
this.player.ads.cueTextTracks.setMetadataTrackMode = function(track) {
track.mode = 'hidden';
};
}
}));
QUnit.test('runs processMetadataTrack callback as tracks are added', function(assert) {
const tt = this.tt;
const processMetadataTrackSpy = sinon.spy();
const cueTextTracks = this.player.ads.cueTextTracks;
// Start by adding a text track before processing
this.player.addRemoteTextTrack(tt);
cueTextTracks.processMetadataTracks(this.player, processMetadataTrackSpy);
assert.strictEqual(processMetadataTrackSpy.callCount, 1);
// add a new text track after initial processing
this.player.textTracks().trigger({
track: this.tt,
type: 'addtrack'
});
assert.strictEqual(processMetadataTrackSpy.callCount, 2);
});
QUnit.test('does not call processMetadataTrack callback until tracks available', function(assert) {
const processMetadataTrackSpy = sinon.spy();
const cueTextTracks = this.player.ads.cueTextTracks;
cueTextTracks.processMetadataTracks(this.player, processMetadataTrackSpy);
assert.strictEqual(processMetadataTrackSpy.callCount, 0);
const addTrackEvent = {
track: this.tt,
type: 'addtrack'
};
this.player.textTracks().trigger(addTrackEvent);
assert.strictEqual(processMetadataTrackSpy.callCount, 1);
});
QUnit.test('setMetadataTrackMode should work when overriden', function(assert) {
const tt = this.tt;
const cueTextTracks = this.player.ads.cueTextTracks;
cueTextTracks.setMetadataTrackMode(tt);
assert.strictEqual(tt.mode, 'hidden');
cueTextTracks.setMetadataTrackMode = function(track) {
track.mode = 'disabled';
};
cueTextTracks.setMetadataTrackMode(tt);
assert.strictEqual(tt.mode, 'disabled');
});
QUnit.test('getSupportedAdCue should work when overriden', function(assert) {
const cue = {
startTime: 0,
endTime: 1
};
const cueTextTracks = this.player.ads.cueTextTracks;
let supportedCue = cueTextTracks.getSupportedAdCue(this.player, cue);
assert.strictEqual(supportedCue, cue);
cueTextTracks.getSupportedAdCue = function(player, subcue) {
return -1;
};
supportedCue = cueTextTracks.getSupportedAdCue(this.player, cue);
assert.strictEqual(supportedCue, -1);
});
QUnit.test('getCueId should work when overriden', function(assert) {
const originalTextTracks = this.player.textTracks;
const cue = {
startTime: 0,
endTime: 1,
id: 1,
inner: {
id: 2
}
};
const tt = this.tt;
tt.activeCues = [cue];
this.player.textTracks = function() {
return {
length: 1,
0: tt
};
};
const cueTextTracks = this.player.ads.cueTextTracks;
let cueId = cueTextTracks.getCueId(cue);
assert.strictEqual(cueId, 1);
cueTextTracks.getCueId = function(subcue) {
return subcue.inner.id;
};
cueId = cueTextTracks.getCueId(cue);
assert.strictEqual(cueId, 2);
// Clean Up
this.player.textTracks = originalTextTracks;
});
QUnit.test('processAdTrack runs processCue callback', function(assert) {
const processCueSpy = sinon.spy();
const cueTextTracks = this.player.ads.cueTextTracks;
const cues = [{
startTime: 0,
endTime: 1,
id: 1,
callCount: 0
}];
cueTextTracks.processAdTrack(this.player, cues, processCueSpy);
assert.strictEqual(processCueSpy.callCount, 1);
const processCue = function(player, cueData, cueId, startTime) {
cueData.callCount += 1;
};
cueTextTracks.processAdTrack(this.player, cues, processCue);
assert.strictEqual(cues[0].callCount, 1);
});
QUnit.test('processAdTrack runs cancelAds callback', function(assert) {
const cancelAdsSpy = sinon.spy();
const cueTextTracks = this.player.ads.cueTextTracks;
const cues = [{
startTime: 0,
endTime: 1,
id: 1,
callCount: 0
}];
const processCue = function(player, cueData, cueId, startTime) {
return;
};
const cancelAds = function(player, cueData, cueId, startTime) {
cueData.callCount += 1;
};
cueTextTracks.processAdTrack(this.player, cues, processCue, cancelAdsSpy);
assert.strictEqual(cancelAdsSpy.callCount, 1);
cueTextTracks.processAdTrack(this.player, cues, processCue, cancelAds);
assert.strictEqual(cues[0].callCount, 1);
});

View file

@ -0,0 +1,140 @@
/*
TODO:
* timeupdate, adtimeupdate, contenttimeupdate
* loadstart, adloadstart, contentloadstart
* play, adplay, contentplay
* loadeddata, adloadeddata, contentloadeddata
* loadedmetadata, adloadedmetadata, contentloadedmetadata
*/
import videojs from 'video.js';
import '../../examples/basic-ad-plugin/example-plugin.js';
import document from 'global/document';
import QUnit from 'qunit';
QUnit.module('Events and Midrolls', {
beforeEach() {
this.video = document.createElement('video');
this.fixture = document.querySelector('#qunit-fixture');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.exampleAds({
adServerUrl: '/test/integration/lib/inventory.json',
playPreroll: false,
midrollPoint: 1
});
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
// Mute the player to allow playback without user interaction
this.player.muted(true);
},
afterEach() {
this.player.dispose();
}
});
QUnit.test('Midrolls', function(assert) {
const done = assert.async();
let beforeMidroll = true;
const seenInAdMode = [];
const seenInContentResuming = [];
const seenOutsideAdModeBefore = [];
const seenOutsideAdModeAfter = [];
this.player.on('adend', () => {
beforeMidroll = false;
});
let events = [
'suspend',
'abort',
'error',
'emptied',
'stalled',
'canplay',
'canplaythrough',
'waiting',
'seeking',
'durationchange',
'progress',
'pause',
'ratechange',
'volumechange',
'firstplay',
'suspend',
'playing',
'ended'
];
events = events.concat(events.map(function(e) {
return 'ad' + e;
}));
events = events.concat(events.map(function(e) {
return 'content' + e;
}));
this.player.on(events, (e) => {
const str = e.type;
if (this.player.ads.isInAdMode()) {
if (this.player.ads.isContentResuming()) {
seenInContentResuming.push(str);
} else {
seenInAdMode.push(str);
}
} else if (beforeMidroll) {
seenOutsideAdModeBefore.push(str);
} else {
seenOutsideAdModeAfter.push(str);
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
videojs.log(this.player.currentTime(), this.player.currentSrc());
if (this.player.currentTime() > 1.1) {
seenOutsideAdModeBefore.forEach((event) => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix before midroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix before midroll');
});
seenInAdMode.forEach((event) => {
assert.ok(/^ad/.test(event), event + ' has ad prefix during midroll');
});
seenInContentResuming.forEach((event) => {
assert.ok(/^content/.test(event), event + ' has content prefix during midroll');
});
seenOutsideAdModeAfter.forEach((event) => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix after midroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix after midroll');
});
done();
}
});
// Seek to right before the midroll
this.player.one('playing', () => {
this.player.currentTime(0.9);
});
this.player.ready(this.player.play);
});

View file

@ -0,0 +1,67 @@
import videojs from 'video.js';
import '../../examples/basic-ad-plugin/example-plugin.js';
import QUnit from 'qunit';
import document from 'global/document';
QUnit.module('Final Events With No Postroll', {
beforeEach() {
this.video = document.createElement('video');
this.fixture = document.querySelector('#qunit-fixture');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.exampleAds({
adServerUrl: '/test/integration/lib/inventory.json',
playPreroll: false,
playMidroll: false,
playPostroll: false
});
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
// Mute the player to allow playback without user interaction
this.player.muted(true);
},
afterEach() {
this.player.dispose();
}
});
QUnit.test('final ended event with no postroll: just 1', function(assert) {
const done = assert.async();
let endedEvents = 0;
// Prevent the test from timing out by making it run faster
this.player.ads.settings.postrollTimeout = 1;
this.player.on('ended', () => {
endedEvents++;
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.one('ended', () => {
// Run checks after a pause in case there are multiple ended events.
setTimeout(() => {
assert.equal(endedEvents, 1, 'exactly one ended with no postroll');
done();
}, 1000);
});
// Seek to end once we're ready so postroll can play quickly
this.player.one('playing', () => {
this.player.currentTime(46);
});
this.player.ready(this.player.play);
});

View file

@ -0,0 +1,133 @@
import videojs from 'video.js';
import '../../examples/basic-ad-plugin/example-plugin.js';
import QUnit from 'qunit';
import document from 'global/document';
QUnit.module('Initial Events With No Preroll', {
beforeEach() {
this.video = document.createElement('video');
this.fixture = document.querySelector('#qunit-fixture');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.exampleAds({
adServerUrl: '/test/integration/lib/inventory.json',
playPreroll: false,
playMidroll: false
});
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
// Mute the player to allow playback without user interaction
this.player.muted(true);
},
afterEach() {
this.player.dispose();
}
});
QUnit.test('initial play event with no preroll: one please', function(assert) {
const done = assert.async();
let playEvents = 0;
this.player.on('play', () => {
playEvents++;
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
if (this.player.currentTime() > 1) {
assert.equal(playEvents, 1, '1 play event');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('initial playing event with no preroll: 1+', function(assert) {
const done = assert.async();
let playingEvents = 0;
this.player.on('playing', () => {
playingEvents++;
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
if (this.player.currentTime() > 1) {
assert.ok(playingEvents >= 1, '1+ playing events');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('no ended event at start if video with no preroll', function(assert) {
const done = assert.async();
let endedEvents = 0;
this.player.on('ended', () => {
endedEvents++;
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
if (this.player.currentTime() > 1) {
assert.equal(endedEvents, 0, 'no ended events');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('initial loadstart event with no preroll: one please', function(assert) {
const done = assert.async();
let loadstartEvents = 0;
this.player.on('loadstart', () => {
loadstartEvents++;
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
if (this.player.currentTime() > 1) {
assert.equal(loadstartEvents, 1, '1 loadstart event');
done();
}
});
this.player.ready(this.player.play);
});

View file

@ -0,0 +1,184 @@
/*
TODO:
* timeupdate, adtimeupdate, contenttimeupdate
* loadstart, adloadstart, contentloadstart
* play, adplay, contentplay
* contentended
* loadeddata, adloadeddata, contentloadeddata
* loadedmetadata, adloadedmetadata, contentloadedmetadata
*/
import videojs from 'video.js';
import '../../examples/basic-ad-plugin/example-plugin.js';
import document from 'global/document';
import QUnit from 'qunit';
QUnit.module('Events and Postrolls', {
beforeEach() {
this.video = document.createElement('video');
this.fixture = document.querySelector('#qunit-fixture');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.exampleAds({
adServerUrl: '/test/integration/lib/inventory.json',
playPreroll: false,
playMidroll: false
});
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
// Mute the player to allow playback without user interaction
this.player.muted(true);
},
afterEach() {
this.player.dispose();
}
});
QUnit.test('ended event and postrolls: 0 before postroll, 1 after', function(assert) {
const done = assert.async();
let beforePostroll = true;
let endedBeforePostroll = 0;
let endedAfterPostroll = 0;
this.player.on('adend', () => {
beforePostroll = false;
});
this.player.on('ended', () => {
if (beforePostroll) {
endedBeforePostroll++;
} else {
endedAfterPostroll++;
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.one('ended', () => {
if (beforePostroll) {
assert.ok(false, 'ended before postroll!');
}
// Run checks after a pause in case there are multiple ended events.
setTimeout(() => {
assert.equal(endedBeforePostroll, 0, 'no ended before postroll');
assert.equal(endedAfterPostroll, 1, 'exactly one ended after postroll');
done();
}, 1000);
});
this.player.ready(() => {
this.player.play();
this.player.currentTime(46);
});
});
QUnit.test('Event prefixing and postrolls', function(assert) {
const done = assert.async();
let beforePostroll = true;
const seenInAdMode = [];
const seenInContentResuming = [];
const seenOutsideAdModeBefore = [];
const seenOutsideAdModeAfter = [];
this.player.on('adend', () => {
beforePostroll = false;
});
let events = [
'suspend',
'abort',
'error',
'emptied',
'stalled',
'canplay',
'canplaythrough',
'waiting',
'seeking',
'durationchange',
'progress',
'pause',
'ratechange',
'volumechange',
'firstplay',
'suspend',
'playing',
'ended'
];
events = events.concat(events.map(function(e) {
return 'ad' + e;
}));
events = events.concat(events.map(function(e) {
return 'content' + e;
}));
this.player.on(events, (e) => {
if (e.type === 'contentended') {
return;
}
const str = e.type;
if (this.player.ads.isInAdMode()) {
if (this.player.ads.isContentResuming()) {
seenInContentResuming.push(str);
} else {
seenInAdMode.push(str);
}
} else if (beforePostroll) {
seenOutsideAdModeBefore.push(str);
} else {
seenOutsideAdModeAfter.push(str);
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('ended', () => {
seenOutsideAdModeBefore.forEach((event) => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix before postroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix before postroll');
});
seenInAdMode.forEach((event) => {
assert.ok(/^ad/.test(event), event + ' has ad prefix during postroll');
});
seenInContentResuming.forEach((event) => {
assert.ok(/^content/.test(event), event + ' has content prefix during postroll');
});
seenOutsideAdModeAfter.forEach((event) => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix after postroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix after postroll');
});
done();
});
// Seek to end once we're ready so postroll can play quickly
this.player.one('playing', () => {
this.player.currentTime(46);
});
this.player.ready(this.player.play);
});

View file

@ -0,0 +1,338 @@
/*
TODO:
* adplay, contentplay
* adplaying, contentplaying
* adloadstart, contentloadstart
* adended
* adloadeddata, contentloadeddata
* adloadedmetadata, contentloadedmetadata
*/
import videojs from 'video.js';
import '../../examples/basic-ad-plugin/example-plugin.js';
import QUnit from 'qunit';
import document from 'global/document';
QUnit.module('Events and Prerolls', {
beforeEach() {
this.video = document.createElement('video');
this.fixture = document.querySelector('#qunit-fixture');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.exampleAds({
adServerUrl: '/test/integration/lib/inventory.json'
});
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
// Mute the player to allow playback without user interaction
this.player.muted(true);
},
afterEach() {
this.player.dispose();
}
});
QUnit.test('playing event and prerolls: 0 before preroll, 1+ after', function(assert) {
const done = assert.async();
let beforePreroll = true;
let playingBeforePreroll = 0;
let playingAfterPreroll = 0;
this.player.on('adend', () => {
beforePreroll = false;
});
this.player.on('playing', () => {
if (beforePreroll) {
playingBeforePreroll++;
} else {
playingAfterPreroll++;
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
if (this.player.currentTime() > 1) {
assert.equal(playingBeforePreroll, 0, 'no playing before preroll');
assert.ok(playingAfterPreroll > 0, 'playing after preroll');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('ended event and prerolls: not even once', function(assert) {
const done = assert.async();
let ended = 0;
this.player.on('ended', () => {
ended++;
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
if (this.player.currentTime() > 1) {
assert.equal(ended, 0, 'no ended events');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('loadstart event and prerolls: 1 before preroll, 0 after', function(assert) {
const done = assert.async();
let beforePreroll = true;
let loadstartBeforePreroll = 0;
let loadstartAfterPreroll = 0;
this.player.on('adend', () => {
beforePreroll = false;
});
this.player.on('loadstart', (e) => {
if (beforePreroll) {
loadstartBeforePreroll++;
} else {
loadstartAfterPreroll++;
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', (e) => {
if (this.player.currentTime() > 1) {
assert.equal(loadstartBeforePreroll, 1, 'loadstart before preroll');
assert.equal(loadstartAfterPreroll, 0, 'loadstart after preroll');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('loadedmetadata event and prerolls: 1 before preroll, 0 after', function(assert) {
const done = assert.async();
let beforePreroll = true;
let loadedmetadataBeforePreroll = 0;
let loadedmetadataAfterPreroll = 0;
this.player.on('adend', () => {
beforePreroll = false;
});
this.player.on('loadedmetadata', (e) => {
if (beforePreroll) {
loadedmetadataBeforePreroll++;
} else {
loadedmetadataAfterPreroll++;
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', (e) => {
if (this.player.currentTime() > 1) {
assert.equal(loadedmetadataBeforePreroll, 1, 'loadedmetadata before preroll');
assert.equal(loadedmetadataAfterPreroll, 0, 'loadedmetadata after preroll');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('loadeddata event and prerolls: 1 before preroll, 0 after', function(assert) {
const done = assert.async();
let beforePreroll = true;
let loadeddataBeforePreroll = 0;
let loadeddataAfterPreroll = 0;
this.player.on('adend', () => {
beforePreroll = false;
});
this.player.on('loadeddata', (e) => {
if (beforePreroll) {
loadeddataBeforePreroll++;
} else {
loadeddataAfterPreroll++;
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', (e) => {
if (this.player.currentTime() > 1) {
assert.equal(loadeddataBeforePreroll, 1, 'loadeddata before preroll');
assert.equal(loadeddataAfterPreroll, 0, 'loadeddata after preroll');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('play event and prerolls: 1 before preroll, 0 after', function(assert) {
const done = assert.async();
let beforePreroll = true;
let playBeforePreroll = 0;
let playAfterPreroll = 0;
this.player.on('adend', () => {
beforePreroll = false;
});
this.player.on('play', () => {
if (beforePreroll) {
playBeforePreroll++;
} else {
playAfterPreroll++;
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
if (this.player.currentTime() > 1) {
// 2
assert.equal(playBeforePreroll, 1, 'play before preroll');
assert.equal(playAfterPreroll, 0, 'play after preroll');
done();
}
});
this.player.ready(this.player.play);
});
QUnit.test('Event prefixing and prerolls', function(assert) {
const done = assert.async();
let beforePreroll = true;
const seenInAdMode = [];
const seenInContentResuming = [];
const seenOutsideAdModeBefore = [];
const seenOutsideAdModeAfter = [];
this.player.on('adend', () => {
beforePreroll = false;
});
let events = [
'suspend',
'abort',
'error',
'emptied',
'stalled',
'canplay',
'canplaythrough',
'waiting',
'seeking',
'durationchange',
'timeupdate',
'progress',
'pause',
'ratechange',
'volumechange',
'firstplay',
'suspend'
];
events = events.concat(events.map(function(e) {
return 'ad' + e;
}));
events = events.concat(events.map(function(e) {
return 'content' + e;
}));
this.player.on(events, (e) => {
const str = e.type;
if (this.player.ads.isInAdMode()) {
if (this.player.ads.isContentResuming()) {
seenInContentResuming.push(str);
} else {
seenInAdMode.push(str);
}
} else if (beforePreroll) {
seenOutsideAdModeBefore.push(str);
} else {
seenOutsideAdModeAfter.push(str);
}
});
this.player.on(['error', 'aderror'], () => {
assert.ok(false, 'no errors');
done();
});
this.player.on('timeupdate', () => {
if (this.player.currentTime() > 0) {
seenOutsideAdModeBefore.forEach((event) => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix before preroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix before preroll');
});
seenInAdMode.forEach((event) => {
assert.ok(/^ad/.test(event), event + ' has ad prefix during preroll');
});
seenInContentResuming.forEach((event) => {
assert.ok(/^content/.test(event), event + ' has content prefix during preroll');
});
seenOutsideAdModeAfter.forEach((event) => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix after preroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix after preroll');
});
done();
}
});
this.player.ready(this.player.play);
});

View file

@ -0,0 +1,302 @@
import QUnit from 'qunit';
import window from 'global/window';
import document from 'global/document';
import sharedModuleHooks from './lib/shared-module-hooks.js';
QUnit.module('Ad Macros', sharedModuleHooks({}));
QUnit.test('player.id', function(assert) {
this.player.options_['data-player'] = '12345';
const result = this.player.ads.adMacroReplacement('{player.id}');
assert.equal(result, '12345');
});
QUnit.test('player dimensions', function(assert) {
this.player.options_['data-player'] = '12345';
this.player.dimensions(200, 100);
const resultHeight = this.player.ads.adMacroReplacement('{player.height}');
const resultWidth = this.player.ads.adMacroReplacement('{player.width}');
assert.equal(resultHeight, 100, 'player.height was replaced');
assert.equal(resultWidth, 200, 'player.width was replaced');
});
QUnit.test('mediainfo', function(assert) {
/* eslint-disable camelcase */
this.player.mediainfo = {
id: 1,
name: 2,
description: 3,
tags: 4,
reference_id: 5,
duration: 6,
ad_keys: 7
};
/* eslint-enable camelcase */
const result = this.player.ads.adMacroReplacement('{mediainfo.id}' +
'{mediainfo.name}' +
'{mediainfo.description}' +
'{mediainfo.tags}' +
'{mediainfo.reference_id}' +
'{mediainfo.duration}' +
'{mediainfo.ad_keys}');
assert.equal(result, '1234567');
});
QUnit.test('playlistinfo', function(assert) {
this.player.playlistinfo = {
id: 1,
name: 2
};
const result = this.player.ads.adMacroReplacement('{playlistinfo.id}' +
'{playlistinfo.name}');
assert.equal(result, '12');
});
QUnit.test('player.duration', function(assert) {
this.player.duration = function() {
return 5;
};
const result = this.player.ads.adMacroReplacement('{player.duration}');
assert.equal(result, 5);
});
QUnit.test('player.pageUrl', function(assert) {
const result = this.player.ads.adMacroReplacement('{player.pageUrl}');
assert.equal(result, document.referrer, 'tests run in iframe, so referrer should be used');
});
QUnit.test('timestamp', function(assert) {
this.player.duration = function() {
return 5;
};
const result = this.player.ads.adMacroReplacement('{timestamp}');
assert.equal(result, new Date().getTime());
});
QUnit.test('document.referrer', function(assert) {
const result = this.player.ads.adMacroReplacement('{document.referrer}');
assert.equal(
result,
document.referrer,
'"' + result + '" was the document.referrer'
);
});
QUnit.test('window.location.href', function(assert) {
const result = this.player.ads.adMacroReplacement('{window.location.href}');
assert.equal(
result,
window.location.href,
'"' + result + '" was the window.location.href'
);
});
QUnit.test('random', function(assert) {
const result = this.player.ads.adMacroReplacement('{random}');
assert.ok(result.match(/^\d+$/), '"' + result + '" is a random number');
});
QUnit.test('mediainfo.custom_fields', function(assert) {
/* eslint-disable camelcase */
this.player.mediainfo = {
custom_fields: {
dog: 1,
cat: 2,
guinea_pig: 3
},
customFields: {
dog: 1,
cat: 2,
guinea_pig: 3
}
};
/* eslint-enable camelcase */
const result = this.player.ads.adMacroReplacement('{mediainfo.custom_fields.dog}' +
'{mediainfo.custom_fields.cat}' +
'{mediainfo.custom_fields.guinea_pig}' +
'{mediainfo.customFields.dog}' +
'{mediainfo.customFields.cat}' +
'{mediainfo.customFields.guinea_pig}');
assert.equal(result, '123123');
});
QUnit.test('pageVariables', function(assert) {
window.animal = {
dog: 'Old Buddy',
cat: {
maineCoon: 'Huge the Cat',
champion: {
name: 'Champ'
}
}
};
window.bird = null;
window.isAwesome = true;
window.foo = function() {};
window.bar = {};
const result = this.player.ads.adMacroReplacement('Number: {pageVariable.scrollX}, ' +
'Boolean: {pageVariable.isAwesome}, ' +
'Null: {pageVariable.bird}, ' +
'Undefined: {pageVariable.thisDoesNotExist}, ' +
'Function: {pageVariable.foo}, ' +
'Object: {pageVariable.bar}, ' +
'Nested 2x: {pageVariable.animal.dog}, ' +
'Nested 3x: {pageVariable.animal.cat.maineCoon}, ' +
'Nested 4x: {pageVariable.animal.cat.champion.name}');
assert.equal(
result,
'Number: 0, ' +
'Boolean: true, ' +
'Null: null, ' +
'Undefined: , ' +
'Function: , ' +
'Object: , ' +
'Nested 2x: Old Buddy, ' +
'Nested 3x: Huge the Cat, ' +
'Nested 4x: Champ'
);
});
QUnit.test('multiple, identical macros', function(assert) {
const result = this.player.ads.adMacroReplacement('...&documentrefferer1={document.referrer}&documentrefferer2={document.referrer}&windowlocation1={window.location.href}&windowlocation2={window.location.href}');
const expected = `...&documentrefferer1=${document.referrer}&documentrefferer2=${document.referrer}&windowlocation1=${window.location.href}&windowlocation2=${window.location.href}`;
assert.equal(
result,
expected,
`"${result}" includes 2 replaced document.referrer and 2 window.location.href strings`
);
});
QUnit.test('uriEncode', function(assert) {
/* eslint-disable camelcase */
this.player.mediainfo = {
custom_fields: {
urlParam: '? &'
}
};
/* eslint-enable camelcase */
window.foo = '& ?';
const result = this.player.ads.adMacroReplacement('{mediainfo.custom_fields.urlParam}{pageVariable.foo}', true);
assert.equal(result, '%3F%20%26%26%20%3F');
});
QUnit.test('customMacros', function(assert) {
const result = this.player.ads.adMacroReplacement('The sky is {skyColor}. {exclamation}!', false, {
'{skyColor}': 'blue',
'{exclamation}': 'Hooray'
});
assert.equal(result, 'The sky is blue. Hooray!');
});
QUnit.test('default values', function(assert) {
/* eslint-disable camelcase */
this.player.mediainfo = {
customFields: {
set: 1
},
reference_id: 'abc'
};
/* eslint-enable camelcase */
window.testvar1 = 'a';
assert.equal(
this.player.ads.adMacroReplacement('{mediainfo.customFields.set=other}'), '1',
'custom fields: set value is not replaced by default'
);
assert.equal(
this.player.ads.adMacroReplacement('{mediainfo.customFields.unsset=2}'), '2',
'custom fields: unset value is replaced by default'
);
assert.equal(
this.player.ads.adMacroReplacement('{mediainfo.ad_keys=key=value}'), 'key=value',
'equals in default value preserved'
);
assert.equal(
this.player.ads.adMacroReplacement('{mediainfo.reference_id=Other}'), 'abc',
'mediainfo: set value is not replaced by default'
);
assert.equal(
this.player.ads.adMacroReplacement('{mediainfo.description=xyz}'), 'xyz',
'mediainfo: unset value is replaced by default'
);
assert.equal(
this.player.ads.adMacroReplacement('{pageVariable.testvar1=b}'), 'a',
'pageVariable: set value is not replaced by default'
);
assert.equal(
this.player.ads.adMacroReplacement('{pageVariable.testvar2=c}'), 'c',
'pageVariable: unset value is replaced by default'
);
});
QUnit.test('tcfMacros', function(assert) {
let callback;
const dummyData = {
cmpId: 10,
cmpVersion: 27,
gdprApplies: true,
tcfPolicyVersion: 2,
eventStatus: 'cmpuishown',
cmpStatus: 'loaded',
listenerId: null,
tcString: 'abcdefg',
isServiceSpecific: true,
useNonStandardStacks: false,
purposeOneTreatment: false,
publisherCC: 'DE'
};
const oldtcf = window.__tcfapi;
// Stub of TCF API, enough to register an event listener. The callback is called immediately and on change to consent data.
// https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md
window.__tcfapi = function(cmd, version, cb) {
if (cmd === 'addEventListener') {
callback = cb;
cb(dummyData, true);
}
};
this.player.ads.listenToTcf();
assert.equal(
this.player.ads.adMacroReplacement('{tcf.gdprApplies}&{tcf.gdprAppliesInt}&{tcf.tcString}'), 'true&1&abcdefg',
'tcf macros resolved'
);
// Call callbak with changed data
dummyData.tcString = 'zyxwvu';
callback(dummyData, true);
assert.equal(
this.player.ads.adMacroReplacement('{tcf.tcString}'), 'zyxwvu',
'tcf macros resolved with updated data'
);
window.__tcfapi = oldtcf;
});

View file

@ -0,0 +1,260 @@
import videojs from 'video.js';
import '../../examples/basic-ad-plugin/example-plugin.js';
import QUnit from 'qunit';
import document from 'global/document';
import window from 'global/window';
import sinon from 'sinon';
QUnit.module('Integration: play middleware', {
beforeEach() {
this.video = document.createElement('video');
this.fixture = document.querySelector('#qunit-fixture');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.exampleAds({
adServerUrl: '/test/integration/lib/inventory.json'
});
// Mute the player to allow playback without user interaction
this.player.muted(true);
},
afterEach() {
this.player.dispose();
}
});
QUnit.test('the `_playRequested` flag is set on the first play request', function(assert) {
const done = assert.async();
let finish;
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
const onAdstart = () => {
assert.strictEqual(
this.player.ads._playRequested, true,
'_playRequested is true when the play method is used'
);
finish();
};
const onTimeupdate = () => {
if (this.player.currentTime() > 0) {
assert.strictEqual(
this.player.ads._playRequested, true,
'_playRequested is true when the play method is used'
);
finish();
}
};
finish = () => {
this.player.off('adstart', onAdstart);
this.player.off('timeupdate', onTimeupdate);
done();
};
// When the preroll starts
this.player.on('adstart', onAdstart);
// If there wasn't an ad
this.player.on('timeupdate', onTimeupdate);
this.player.ready(this.player.play);
});
QUnit.test('blocks calls to play to wait for prerolls if adsready BEFORE play', function(assert) {
const done = assert.async();
const techPlaySpy = sinon.spy(this.video, 'play');
const playEventSpy = sinon.spy();
let seenAdsReady = false;
let finish;
this.player.on('play', playEventSpy);
this.player.on('adsready', () => {
seenAdsReady = true;
});
const onTimeupdate = () => {
if (this.player.currentTime() > 0) {
assert.strictEqual(
techPlaySpy.callCount, 0,
"tech play shouldn't be called while waiting for prerolls"
);
assert.strictEqual(
playEventSpy.callCount, 1,
'play event should be triggered'
);
finish();
}
};
const onAdstart = () => {
// sometimes play will happen just after adstart
window.setTimeout(() => {
assert.strictEqual(
techPlaySpy.callCount, 0,
"tech play shouldn't be called while waiting for prerolls"
);
assert.strictEqual(
playEventSpy.callCount, 1,
'play event should be triggered'
);
finish();
}, 1);
};
const onError = function() {
assert.ok(false, 'no errors, or adtimeout');
finish();
};
finish = () => {
this.player.off('adstart', onAdstart);
this.player.off('timeupdate', onTimeupdate);
this.player.off(['error', 'aderror', 'adtimeout'], onError);
done();
};
// When the preroll starts
this.player.on('adstart', onAdstart);
// Once we are in content
this.player.on('timeupdate', onTimeupdate);
this.player.on(['error', 'aderror', 'adtimeout'], onError);
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
this.player.ready(() => {
if (seenAdsReady) {
this.player.play();
} else {
this.player.on('adsready', this.player.play);
}
});
});
QUnit.test('stops blocking play when an ad is playing', function(assert) {
const done = assert.async();
this.player.on('adstart', () => {
assert.strictEqual(this.player.ads._shouldBlockPlay, true);
});
// Wait for the ad to start playing
this.player.on('ads-ad-started', () => {
assert.strictEqual(
this.player.ads._shouldBlockPlay, false,
'should stop blocking once in an adbreak'
);
done();
});
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
this.player.ready(this.player.play);
});
QUnit.test("playMiddleware doesn\'t block play in content playback", function(assert) {
const done = assert.async();
this.player.on('adstart', () => {
assert.strictEqual(this.player.ads._shouldBlockPlay, true);
});
const onTimeupdate = () => {
if (this.player.currentTime() > 0) {
assert.strictEqual(
this.player.ads._shouldBlockPlay, false,
'should stop blocking in content'
);
this.player.off('timeupdate', onTimeupdate);
done();
}
};
// Wait for the ad to start playing
this.player.on('timeupdate', onTimeupdate);
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
this.player.ready(this.player.play);
});
QUnit.test("don't trigger play event if another middleware terminates", function(assert) {
const done = assert.async();
const fixture = document.querySelector('#qunit-fixture');
const vid = document.createElement('video');
const playSpy = sinon.spy();
let shouldTerminate = true;
const anotherMiddleware = function(player) {
return {
setSource(srcObj, next) {
next(null, srcObj);
},
callPlay() {
if (shouldTerminate === true) {
shouldTerminate = false;
return videojs.middleware.TERMINATOR;
}
},
play(terminated, value) {
}
};
};
// Register the other middleware
videojs.use('video/webm', anotherMiddleware);
// Don't use the shared player, setup new localPlayer
fixture.appendChild(vid);
const localPlayer = videojs(vid);
// Setup before example integration
localPlayer.on('nopreroll', () => {
localPlayer.ready(localPlayer.play);
});
// Don't play preroll ads
localPlayer.exampleAds({
adServerUrl: '/test/integration/lib/inventory.json',
playPreroll: false
});
localPlayer.on('play', playSpy);
// Wait for the middleware to run
localPlayer.setTimeout(() => {
assert.strictEqual(
localPlayer.ads._playBlocked, false,
'play should not have been blocked'
);
assert.strictEqual(
playSpy.callCount, 0,
'play event should not be triggered'
);
done();
}, 1);
localPlayer.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
});

View file

@ -0,0 +1,524 @@
import videojs from 'video.js';
import * as snapshot from '../../src/snapshot.js';
import QUnit from 'qunit';
import document from 'global/document';
import window from 'global/window';
import sinon from 'sinon';
import sharedModuleHooks from './lib/shared-module-hooks.js';
QUnit.module('Video Snapshot', sharedModuleHooks({
beforeEach() {
const captionTrack = document.createElement('track');
const otherTrack = document.createElement('track');
captionTrack.setAttribute('kind', 'captions');
captionTrack.setAttribute('src', '/test/integration/lib/testcaption.vtt');
otherTrack.setAttribute('src', '/test/integration/lib/testcaption.vtt');
this.video.appendChild(captionTrack);
this.video.appendChild(otherTrack);
this.player.ended = function() {
return false;
};
}
}));
QUnit.test('restores the original video src after ads', function(assert) {
const originalSrc = {
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
};
this.player.src(originalSrc);
this.player.trigger('adsready');
this.player.trigger('play');
this.player.ads.startLinearAdMode();
this.player.src({
src: '//example.com/ad.webm',
type: 'video/webm'
});
this.player.ads.endLinearAdMode();
assert.strictEqual(this.player.currentSrc(), originalSrc.src, 'the original src is restored');
});
QUnit.test('waits for the video to become seekable before restoring the time', function(assert) {
assert.expect(2);
this.player.trigger('adsready');
this.player.trigger('play');
// the video plays to time 100
const setTimeoutSpy = sinon.spy(window, 'setTimeout');
this.video.currentTime = 100;
this.player.ads.startLinearAdMode();
this.player.src({
src: '//example.com/ad.webm',
type: 'video/webm'
});
// the ad resets the current time
this.video.currentTime = 0;
this.player.ads.endLinearAdMode();
// we call setTimeout an extra time restorePlayerSnapshot
setTimeoutSpy.reset();
this.player.trigger('canplay');
assert.strictEqual(setTimeoutSpy.callCount, 1, 'restoring the time should be delayed');
assert.strictEqual(this.video.currentTime, 0, 'currentTime is not modified');
window.setTimeout.restore();
});
QUnit.test('the current time is restored at the end of an ad', function(assert) {
assert.expect(1);
this.player.trigger('adsready');
this.video.currentTime = 100;
this.player.trigger('play');
// the video plays to time 100
this.player.ads.startLinearAdMode();
this.player.src({
src: '//example.com/ad.webm',
type: 'video/webm'
});
// the ad resets the current time
this.video.currentTime = 0;
this.player.ads.endLinearAdMode();
this.player.trigger('canplay');
this.clock.tick(1000);
assert.strictEqual(this.video.currentTime, 100, 'currentTime was restored');
});
QUnit.test('only restores the player snapshot if the src changed', function(assert) {
this.player.trigger('adsready');
this.player.trigger('play');
const playSpy = sinon.spy(this.player, 'play');
const srcSpy = sinon.spy(this.player, 'src');
const currentTimeSpy = sinon.spy(this.player, 'currentTime');
// with a separate video display or server-side ad insertion, ads play but
// the src never changes. Modifying the src or currentTime would introduce
// unnecessary seeking and rebuffering
this.player.ads.startLinearAdMode();
this.player.ads.endLinearAdMode();
assert.ok(playSpy.called, 'content playback resumed');
assert.ok(srcSpy.alwaysCalledWithExactly(), 'the src was not reset');
this.player.trigger('playing');
// the src wasn't changed, so we shouldn't be waiting on loadedmetadata to
// update the currentTime
this.player.trigger('loadedmetadata');
assert.ok(currentTimeSpy.alwaysCalledWithExactly(), 'no seeking occurred');
});
QUnit.test('snapshot does not resume playback after post-rolls', function(assert) {
const playSpy = sinon.spy(this.player, 'play');
// start playback
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.trigger('loadedmetadata');
this.player.trigger('adsready');
this.player.tech_.trigger('play');
// trigger an ad
this.player.ads.startLinearAdMode();
this.player.src({
src: '//example.com/ad.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.trigger('loadedmetadata');
this.player.ads.endLinearAdMode();
// resume playback
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.trigger('canplay');
// "canplay" causes the `restorePlayerSnapshot` function in the plugin
// to be called. This causes content playback to be resumed after 20
// attempts of a 50ms timeout (20 * 50 == 1000).
this.clock.tick(1000);
assert.strictEqual(playSpy.callCount, 1, 'content playback resumed');
this.player.trigger('playing');
// if the video ends (regardless of burned in post-roll or otherwise) when
// stopLinearAdMode fires next we should not hit play() since we have reached
// the end of the stream
this.player.ended = function() {
return true;
};
this.player.trigger('ended');
playSpy.reset();
// trigger a post-roll
this.player.ads.startLinearAdMode();
this.player.src({
src: '//example.com/ad.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.trigger('loadedmetadata');
this.player.ads.endLinearAdMode();
this.player.trigger('playing');
this.player.trigger('ended');
assert.strictEqual(playSpy.callCount, 0, 'content playback should not have been resumed');
});
QUnit.test('snapshot does not resume playback after a burned-in post-roll', function(assert) {
this.player.trigger('adsready');
this.player.trigger('play');
const playSpy = sinon.spy(this.player, 'play');
const loadSpy = sinon.spy(this.player, 'load');
this.player.ads.startLinearAdMode();
this.player.ads.endLinearAdMode();
this.player.trigger('playing');
assert.ok(playSpy.called, 'content playback resumed');
// if the video ends (regardless of burned in post-roll or otherwise) when
// stopLinearAdMode fires next we should not hit play() since we have reached
// the end of the stream
this.player.ended = function() {
return true;
};
this.player.trigger('ended');
playSpy.reset();
// trigger a post-roll
this.player.currentTime(30);
this.player.ads.startLinearAdMode();
this.player.currentTime(50);
this.player.ads.endLinearAdMode();
this.player.trigger('ended');
assert.strictEqual(this.player.currentTime(), 50, 'currentTime should not be reset using burned in ads');
assert.notOk(loadSpy.called, 'player.load() should not be called if the player is ended.');
assert.notOk(playSpy.called, 'content playback should not have been resumed');
});
QUnit.test('snapshot does not resume playback after multiple post-rolls', function(assert) {
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.trigger('adsready');
this.player.trigger('play');
const playSpy = sinon.spy(this.player, 'play');
// with a separate video display or server-side ad insertion, ads play but
// the src never changes. Modifying the src or currentTime would introduce
// unnecessary seeking and rebuffering
this.player.ads.startLinearAdMode();
this.player.ads.endLinearAdMode();
this.player.trigger('playing');
assert.ok(playSpy.called, 'content playback resumed');
// if the video ends (regardless of burned in post-roll or otherwise) when
// stopLinearAdMode fires next we should not hit play() since we have reached
// the end of the stream
this.player.ended = function() {
return true;
};
this.player.trigger('ended');
playSpy.reset();
// trigger a lot of post-rolls
this.player.ads.startLinearAdMode();
this.player.src({
src: 'http://example.com/ad1.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.src({
src: 'http://example.com/ad2.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.ads.endLinearAdMode();
this.player.trigger('playing');
this.player.trigger('ended');
assert.notOk(playSpy.called, 'content playback should not resume');
});
QUnit.test('changing the source and then timing out does not restore a snapshot', function(assert) {
this.player.paused = function() {
return false;
};
// load and play the initial video
this.player.src({
src: 'http://example.com/movie.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.trigger('play');
this.player.trigger('adsready');
// preroll
this.player.ads.startLinearAdMode();
this.player.ads.endLinearAdMode();
this.player.trigger('playing');
// change the content and timeout the new ad response
this.player.src({
src: 'http://example.com/movie2.webm',
type: 'video/webm'
});
this.player.trigger('loadstart');
this.player.trigger('adtimeout');
assert.strictEqual('http://example.com/movie2.webm', this.player.currentSrc(), 'playing the second video');
});
// changing the src attribute to a URL that AdBlocker is intercepting
// doesn't update currentSrc, so when restoring the snapshot we
// should check for src attribute modifications as well
QUnit.test('checks for a src attribute change that isn\'t reflected in currentSrc', function(assert) {
let updatedSrc;
this.player.currentSource = function() {
return {src: 'content.webm', type: 'video/webm'};
};
this.player.trigger('adsready');
this.player.trigger('play');
this.player.ads.startLinearAdMode();
// `src` gets called internally to set the source back to its original
// value when the player snapshot is restored when `endLinearAdMode`
// is called.
this.player.tech_.src = function(source) {
if (source === undefined) {
return 'ad.webm';
}
updatedSrc = source;
};
this.player.src = function(source) {
if (source === undefined) {
return 'ad.webm';
}
updatedSrc = source;
};
this.player.ads.endLinearAdMode();
this.player.trigger('playing');
assert.deepEqual(updatedSrc, [this.player.currentSource()], 'restored src attribute');
});
QUnit.test('When captions are enabled, the content\'s tracks will be disabled during the ad', function(assert) {
const trackSrc = '/test/integration/lib/testcaption.vtt';
// Add a text track
const remoteTrack = this.player.addRemoteTextTrack({
kind: 'captions',
language: 'fr',
label: 'French',
src: trackSrc
});
const tracks = this.player.textTracks ? this.player.textTracks() : [];
let showing = 0;
let disabled = 0;
let i;
if (tracks.length <= 0) {
assert.expect(0);
videojs.log.warn('Did not detect text track support, skipping');
return;
}
assert.expect(3);
this.player.trigger('adsready');
this.player.trigger('play');
// set all modes to 'showing'
for (i = 0; i < tracks.length; i++) {
tracks[i].mode = 'showing';
}
for (i = 0; i < tracks.length; i++) {
if (tracks[i].mode === 'showing') {
showing++;
}
}
assert.strictEqual(showing, tracks.length, 'all tracks should be showing');
showing = 0;
this.player.ads.startLinearAdMode();
for (i = 0; i < tracks.length; i++) {
if (tracks[i].mode === 'disabled') {
disabled++;
}
}
assert.strictEqual(disabled, tracks.length, 'all tracks should be disabled');
this.player.ads.endLinearAdMode();
// Track mode should be restored after the ad ends
for (i = 0; i < tracks.length; i++) {
if (tracks[i].mode === 'showing') {
showing++;
}
}
assert.strictEqual(showing, tracks.length, 'all tracks should be showing');
// Cleanup
this.player.textTracks().removeTrack(remoteTrack);
});
QUnit.test('No snapshot if duration is Infinity', function(assert) {
const originalSrc = 'foobar';
const newSrc = 'barbaz';
this.player.duration(Infinity);
this.player.src({
src: originalSrc,
type: 'video/webm'
});
this.player.trigger('adsready');
this.player.play();
this.player.ads.startLinearAdMode();
this.player.src({
src: newSrc,
type: 'video/webm'
});
this.player.ads.endLinearAdMode();
assert.strictEqual(this.player.currentSrc(), newSrc, 'source is not reset');
});
QUnit.test('Snapshot and text tracks', function(assert) {
const trackSrc = '/test/integration/lib/testcaption.vtt';
const originalAddTrack = this.player.addTextTrack;
const originalTextTracks = this.player.textTracks;
// No text tracks at start
assert.equal(this.player.textTracks().length, 0);
this.player.addTextTrack('captions', 'Spanish', 'es');
// Add a text track
const remoteTrack = this.player.addRemoteTextTrack({
kind: 'captions',
language: 'fr',
label: 'French',
src: trackSrc
});
// Make sure both track modes are 'showing', since it's 'disabled' by default
this.player.textTracks()[0].mode = 'showing';
this.player.textTracks()[1].mode = 'showing';
// Text track looks good
assert.equal(this.player.textTracks().length, 2);
assert.equal(this.player.textTracks()[0].kind, 'captions');
assert.equal(this.player.textTracks()[0].language, 'es');
assert.equal(this.player.textTracks()[0].mode, 'showing');
assert.equal(this.player.remoteTextTrackEls().trackElements_[0].src, trackSrc);
assert.equal(this.player.textTracks()[1].kind, 'captions');
assert.equal(this.player.textTracks()[1].language, 'fr');
assert.equal(this.player.textTracks()[1].mode, 'showing');
// Do a snapshot, as if an ad is starting
this.player.ads.snapshot = snapshot.getPlayerSnapshot(this.player);
// Snapshot reflects the text track
assert.equal(this.player.ads.snapshot.suppressedTracks.length, 2);
assert.equal(this.player.ads.snapshot.suppressedTracks[0].track.kind, 'captions');
assert.equal(this.player.ads.snapshot.suppressedTracks[0].track.language, 'es');
assert.equal(this.player.ads.snapshot.suppressedTracks[0].mode, 'showing');
assert.equal(this.player.ads.snapshot.suppressedTracks[1].track.kind, 'captions');
assert.equal(this.player.ads.snapshot.suppressedTracks[1].track.language, 'fr');
assert.equal(this.player.ads.snapshot.suppressedTracks[1].mode, 'showing');
// Meanwhile, track is intact, just disabled
assert.equal(this.player.textTracks().length, 2);
assert.equal(this.player.textTracks()[0].kind, 'captions');
assert.equal(this.player.textTracks()[0].language, 'es');
assert.equal(this.player.textTracks()[0].mode, 'disabled');
assert.equal(this.player.remoteTextTrackEls().trackElements_[0].src, trackSrc);
assert.equal(this.player.textTracks()[1].kind, 'captions');
assert.equal(this.player.textTracks()[1].language, 'fr');
assert.equal(this.player.textTracks()[1].mode, 'disabled');
// Double check that the track remains disabled after 3s
this.clock.tick(3000);
assert.equal(this.player.textTracks()[0].mode, 'disabled');
assert.equal(this.player.textTracks()[1].mode, 'disabled');
// Restore the snapshot, as if an ad is ending
snapshot.restorePlayerSnapshot(this.player);
// Everything is back to normal
assert.equal(this.player.textTracks().length, 2);
assert.equal(this.player.textTracks()[0].kind, 'captions');
assert.equal(this.player.textTracks()[0].language, 'es');
assert.equal(this.player.textTracks()[0].mode, 'showing');
assert.equal(this.player.remoteTextTrackEls().trackElements_[0].src, trackSrc);
assert.equal(this.player.textTracks()[1].kind, 'captions');
assert.equal(this.player.textTracks()[1].language, 'fr');
assert.equal(this.player.textTracks()[1].mode, 'showing');
// Resetting mocked methods
this.player.textTracks().removeTrack(remoteTrack);
this.player.addTextTrack = originalAddTrack;
this.player.textTracks = originalTextTracks;
});
QUnit.test('Snapshot object is cleaned up', function(assert) {
assert.equal(typeof this.player.ads.snapshot, 'undefined', 'no snapshot before ad');
this.player.ads.snapshot = snapshot.getPlayerSnapshot(this.player);
assert.equal(typeof this.player.ads.snapshot, 'object', 'snapshot during ad');
snapshot.restorePlayerSnapshot(this.player);
assert.equal(typeof this.player.ads.snapshot, 'undefined', 'no snapshot after ad');
});
QUnit.test('Call play after live preroll on iOS', function(assert) {
let played = 0;
this.player.duration(Infinity);
this.player.ads.videoElementRecycled = () => true;
videojs.browser = {IS_IOS: true};
this.player.play = () => {
played++;
};
this.player.trigger('loadstart');
this.player.trigger('adsready');
this.player.trigger('play');
this.player.ads.startLinearAdMode();
this.player.ads.endLinearAdMode();
this.player.el().querySelector = query => {
if (query === '.vjs-tech') {
return {seekable: {length: 1}};
}
};
assert.strictEqual(played, 0, 'No play yet');
this.player.trigger('contentcanplay');
assert.strictEqual(played, 1, 'Play happened');
});

View file

@ -0,0 +1,151 @@
/*
TODO:
* timeupdate, adtimeupdate, contenttimeupdate
* loadstart, adloadstart, contentloadstart
* play, adplay, contentplay
* loadeddata, adloadeddata, contentloadeddata
* loadedmetadata, adloadedmetadata, contentloadedmetadata
*/
import videojs from 'video.js';
import QUnit from 'qunit';
import document from 'global/document';
import '../../examples/stitched-ad-plugin/plugin.js';
const originalTestTimeout = QUnit.config.testTimeout;
QUnit.module('Events and Stitched Ads', {
before() {
QUnit.config.testTimeout = 30000;
},
beforeEach() {
this.video = document.createElement('video');
this.fixture = document.querySelector('#qunit-fixture');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.exampleStitchedAds();
this.player.src({
src: 'http://vjs.zencdn.net/v/oceans.webm',
type: 'video/webm'
});
// Mute the player to allow playback without user interaction
this.player.muted(true);
},
afterEach() {
this.player.dispose();
},
after() {
QUnit.config.testTimeout = originalTestTimeout;
}
});
QUnit.test('Stitched Ads', function(assert) {
const done = assert.async();
const seenBeforePreroll = [];
const seenDuringPreroll = [];
const seenAfterPreroll = [];
const seenDuringMidroll = [];
const seenAfterMidroll = [];
let currentEventLog = seenBeforePreroll;
let finish;
let events = [
'suspend',
'abort',
'error',
'emptied',
'stalled',
'canplay',
'canplaythrough',
'waiting',
'seeking',
'durationchange',
'progress',
'pause',
'ratechange',
'volumechange',
'firstplay',
'suspend',
'playing',
'ended'
];
events = events
.concat(events.map(e => 'ad' + e))
.concat(events.map(e => 'content' + e));
const {player} = this;
player.on('adstart', () => {
if (currentEventLog === seenBeforePreroll) {
currentEventLog = seenDuringPreroll;
} else {
currentEventLog = seenDuringMidroll;
}
});
player.on('adend', () => {
if (currentEventLog === seenDuringPreroll) {
currentEventLog = seenAfterPreroll;
} else {
currentEventLog = seenAfterMidroll;
}
});
player.on(events, (e) => currentEventLog.push(e.type));
const onError = () => {
assert.ok(false, 'no errors');
finish();
};
const onTimeupdate = () => {
videojs.log(player.currentTime(), player.currentSrc());
if (player.currentTime() > 21) {
seenBeforePreroll.forEach(event => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix before preroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix before preroll');
});
seenDuringPreroll.forEach(event => {
assert.ok(/^ad/.test(event), event + ' has ad prefix during preroll');
});
seenAfterPreroll.forEach(event => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix after preroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix after preroll');
});
seenDuringMidroll.forEach(event => {
assert.ok(/^ad/.test(event), event + ' has ad prefix during midroll');
});
seenAfterMidroll.forEach(event => {
assert.ok(!/^ad/.test(event), event + ' has no ad prefix after midroll');
assert.ok(!/^content/.test(event), event + ' has no content prefix after midroll');
});
finish();
}
};
finish = () => {
player.off('timeupdate', onTimeupdate);
player.off(['error', 'aderror'], onError);
done();
};
player.on(['error', 'aderror'], onError);
player.on('timeupdate', onTimeupdate);
player.ready(player.play);
});

View file

@ -0,0 +1,61 @@
import QUnit from 'qunit';
import AdState from '../../../../src/states/abstract/AdState.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('AdState', {
beforeEach() {
this.player = {
ads: {}
};
this.adState = new AdState(this.player);
this.adState.transitionTo = (newState) => {
this.newState = newState.name;
};
}
});
QUnit.test('does not start out with content resuming', function(assert) {
assert.equal(this.adState.contentResuming, false);
});
QUnit.test('is an ad state', function(assert) {
assert.equal(this.adState.isAdState(), true);
});
QUnit.test('transitions to ContentPlayback on playing if content resuming', function(assert) {
this.adState.contentResuming = true;
this.adState.onPlaying();
assert.equal(this.newState, 'ContentPlayback');
});
QUnit.test('doesn\'t transition on playing if content not resuming', function(assert) {
this.adState.onPlaying();
assert.equal(this.newState, undefined, 'no transition');
});
QUnit.test('transitions to ContentPlayback on contentresumed if content resuming', function(assert) {
this.adState.contentResuming = true;
this.adState.onContentResumed();
assert.equal(this.newState, 'ContentPlayback');
});
QUnit.test('doesn\'t transition on contentresumed if content not resuming', function(assert) {
this.adState.onContentResumed();
assert.equal(this.newState, undefined, 'no transition');
});
QUnit.test('can check if content is resuming', function(assert) {
assert.equal(this.adState.isContentResuming(), false, 'not resuming');
this.adState.contentResuming = true;
assert.equal(this.adState.isContentResuming(), true, 'resuming');
});
QUnit.test('can check if in ad break', function(assert) {
assert.equal(this.adState.inAdBreak(), false, 'not in ad break');
this.player.ads._inLinearAdMode = true;
assert.equal(this.adState.inAdBreak(), true, 'in ad break');
});

View file

@ -0,0 +1,46 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import ContentState from '../../../../src/states/abstract/ContentState.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('ContentState', {
beforeEach() {
this.player = {
ads: {
debug: () => {}
}
};
this.contentState = new ContentState(this.player);
this.contentState.transitionTo = (newState) => {
this.newState = newState.name;
};
}
});
QUnit.test('is not an ad state', function(assert) {
assert.equal(this.contentState.isAdState(), false);
});
QUnit.test('handles content changed when not playing', function(assert) {
this.player.paused = () => true;
this.player.pause = sinon.stub();
this.contentState.onContentChanged(this.player);
assert.equal(this.newState, 'BeforePreroll');
assert.equal(this.player.pause.callCount, 0, 'did not pause player');
assert.ok(!this.player.ads._pausedOnContentupdate, 'did not set _pausedOnContentupdate');
});
QUnit.test('handles content changed when playing', function(assert) {
this.player.paused = () => false;
this.player.pause = sinon.stub();
this.contentState.onContentChanged(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.player.pause.callCount, 1, 'paused player');
assert.equal(this.player.ads._pausedOnContentupdate, true, 'set _pausedOnContentupdate');
});

View file

@ -0,0 +1,68 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import State from '../../../../src/states/abstract/State.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('State', {
beforeEach() {
this.player = {
ads: {
debug: () => {}
}
};
this.state = new State(this.player);
}
});
QUnit.test('sets this.player', function(assert) {
assert.equal(this.state.player, this.player);
});
QUnit.test('can transition to another state', function(assert) {
let mockStateInit = false;
class MockState {
static _getName() {
return 'MockState';
}
init() {
mockStateInit = true;
}
}
this.state.cleanup = sinon.stub();
this.state.transitionTo(MockState);
assert.ok(this.state.cleanup.calledOnce, 'cleaned up old state');
assert.equal(this.player.ads._state.constructor.name, 'MockState', 'set ads._state');
assert.equal(mockStateInit, true, 'initialized new state');
});
QUnit.test('throws error if isAdState is not implemented', function(assert) {
let error;
try {
this.state.isAdState();
} catch (e) {
error = e;
}
assert.equal(error.message, 'isAdState unimplemented for Anonymous State');
});
QUnit.test('is not resuming content by default', function(assert) {
assert.equal(this.state.isContentResuming(), false);
});
QUnit.test('is not in an ad break by default', function(assert) {
assert.equal(this.state.inAdBreak(), false);
});
QUnit.test('handles events', function(assert) {
this.state.onPlay = sinon.stub();
this.state.handleEvent('play');
assert.ok(this.state.onPlay.calledOnce);
});

View file

@ -0,0 +1,39 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import AdsDone from '../../../src/states/AdsDone.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('AdsDone', {
beforeEach() {
this.events = [];
this.player = {
trigger: (event) => {
this.events.push(event);
},
ads: {}
};
this.adsDone = new AdsDone(this.player);
}
});
QUnit.test('sets _contentHasEnded on init', function(assert) {
this.adsDone.init(this.player);
assert.equal(this.player.ads._contentHasEnded, true, 'content has ended');
});
QUnit.test('ended event on init', function(assert) {
this.adsDone.init(this.player);
assert.equal(this.events[0], 'ended', 'content has ended');
});
QUnit.test('does not play midrolls', function(assert) {
this.adsDone.transitionTo = sinon.spy();
this.adsDone.init(this.player);
this.adsDone.startLinearAdMode();
assert.equal(this.adsDone.transitionTo.callCount, 0, 'no transition');
});

View file

@ -0,0 +1,152 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import BeforePreroll from '../../../src/states/BeforePreroll.js';
import * as CancelContentPlay from '../../../src/cancelContentPlay.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('BeforePreroll', {
beforeEach() {
this.events = [];
this.player = {
ads: {
debug: () => {},
_shouldBlockPlay: false,
settings: {}
},
setTimeout: () => {},
trigger: (event) => {
this.events.push(event);
}
};
this.beforePreroll = new BeforePreroll(this.player);
this.beforePreroll.transitionTo = (newState, arg, arg2) => {
this.newState = newState.name;
this.transitionArg = arg;
this.transitionArg2 = arg2;
};
this.cancelContentPlayStub = sinon.stub(CancelContentPlay, 'cancelContentPlay');
},
afterEach() {
this.cancelContentPlayStub.restore();
}
});
QUnit.test('transitions to Preroll (adsready first)', function(assert) {
this.beforePreroll.init(this.player);
assert.equal(this.beforePreroll.adsReady, false);
this.beforePreroll.onAdsReady(this.player);
assert.equal(this.beforePreroll.adsReady, true);
this.beforePreroll.onPlay(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.transitionArg, true);
});
QUnit.test('transitions to Preroll (play first)', function(assert) {
this.beforePreroll.init(this.player);
assert.equal(this.beforePreroll.adsReady, false);
this.beforePreroll.onPlay(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.transitionArg, false);
});
QUnit.test('cancels ads', function(assert) {
this.beforePreroll.init(this.player);
this.beforePreroll.onAdsCanceled(this.player);
assert.equal(this.beforePreroll.shouldResumeToContent, true);
this.beforePreroll.onPlay(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.transitionArg, false);
assert.equal(this.transitionArg2, true);
});
QUnit.test('transitions to content resuming in preroll on error', function(assert) {
this.beforePreroll.init(this.player);
this.beforePreroll.onAdsError(this.player);
assert.equal(this.beforePreroll.shouldResumeToContent, true);
this.beforePreroll.onPlay(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.transitionArg, false);
assert.equal(this.transitionArg2, true);
});
QUnit.test('has no preroll', function(assert) {
this.beforePreroll.init(this.player);
this.beforePreroll.onNoPreroll(this.player);
assert.equal(this.beforePreroll.shouldResumeToContent, true);
this.beforePreroll.onPlay(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.transitionArg, false);
assert.equal(this.transitionArg2, true);
});
QUnit.test('skips the preroll', function(assert) {
this.beforePreroll.init(this.player);
this.beforePreroll.skipLinearAdMode();
assert.equal(this.events[0], 'adskip');
assert.equal(this.beforePreroll.shouldResumeToContent, true);
this.beforePreroll.onPlay(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.transitionArg, false);
assert.equal(this.transitionArg2, true);
});
QUnit.test('handles content change', function(assert) {
sinon.spy(this.beforePreroll, 'init');
this.beforePreroll.onContentChanged(this.player);
assert.equal(this.beforePreroll.init.calledOnce, true);
});
QUnit.test('sets _shouldBlockPlay to true by default', function(assert) {
this.beforePreroll.init(this.player);
assert.equal(this.player.ads._shouldBlockPlay, true);
});
QUnit.test('sets _shouldBlockPlay to true if allowVjsAutoplay option is true and player.autoplay() is false', function(assert) {
this.player.ads.settings.allowVjsAutoplay = true;
this.player.autoplay = () => false;
this.beforePreroll.init(this.player);
assert.equal(this.player.ads._shouldBlockPlay, true);
});
QUnit.test('sets _shouldBlockPlay to false if allowVjsAutoplay option is true and player.autoplay() is truthy', function(assert) {
this.player.ads.settings.allowVjsAutoplay = true;
this.player.autoplay = () => 'play';
this.beforePreroll.init(this.player);
assert.equal(this.player.ads._shouldBlockPlay, false);
});
QUnit.test('updates `shouldResumeToContent` on `nopreroll`', function(assert) {
this.beforePreroll.init(this.player);
this.beforePreroll.onNoPreroll();
assert.strictEqual(this.beforePreroll.shouldResumeToContent, true);
});
QUnit.test('updates `shouldResumeToContent` on `adserror`', function(assert) {
this.beforePreroll.init(this.player);
this.beforePreroll.onAdsError();
assert.strictEqual(this.beforePreroll.shouldResumeToContent, true);
});
QUnit.test('updates `shouldResumeToContent` on `adscanceled`', function(assert) {
this.beforePreroll.init(this.player);
this.beforePreroll.onAdsCanceled(this.player);
assert.strictEqual(this.beforePreroll.shouldResumeToContent, true);
});
QUnit.test('updates `shouldResumeToContent` on `skipLinearAdMode`', function(assert) {
this.beforePreroll.init(this.player);
this.beforePreroll.skipLinearAdMode();
assert.strictEqual(this.beforePreroll.shouldResumeToContent, true);
});

View file

@ -0,0 +1,62 @@
import QUnit from 'qunit';
import ContentPlayback from '../../../src/states/ContentPlayback.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('ContentPlayback', {
beforeEach() {
this.events = [];
this.playTriggered = false;
this.player = {
paused: () => false,
play: () => {},
trigger: (event) => {
this.events.push(event);
},
ads: {
debug: () => {},
_shouldBlockPlay: true
}
};
this.contentPlayback = new ContentPlayback(this.player);
this.contentPlayback.transitionTo = (newState) => {
this.newState = newState.name;
};
}
});
QUnit.test('adsready triggers readyforpreroll', function(assert) {
this.contentPlayback.init(this.player);
this.contentPlayback.onAdsReady(this.player);
assert.equal(this.events[0], 'readyforpreroll');
});
QUnit.test('no readyforpreroll if nopreroll_', function(assert) {
this.player.ads.nopreroll_ = true;
this.contentPlayback.init(this.player);
this.contentPlayback.onAdsReady(this.player);
assert.equal(this.events.length, 0, 'no events triggered');
});
QUnit.test('transitions to Postroll on readyforpostroll', function(assert) {
this.contentPlayback.init(this.player, false);
this.contentPlayback.onReadyForPostroll(this.player);
assert.equal(this.newState, 'Postroll', 'transitioned to Postroll');
});
QUnit.test('transitions to Midroll on startlinearadmode', function(assert) {
this.contentPlayback.init(this.player, false);
this.contentPlayback.startLinearAdMode();
assert.equal(this.newState, 'Midroll', 'transitioned to Midroll');
});
QUnit.test('sets _shouldBlockPlay to false on init', function(assert) {
assert.equal(this.player.ads._shouldBlockPlay, true);
this.contentPlayback.init(this.player);
assert.equal(this.player.ads._shouldBlockPlay, false);
});

View file

@ -0,0 +1,51 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import Midroll from '../../../src/states/Midroll.js';
import adBreak from '../../../src/adBreak.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('Midroll', {
beforeEach() {
this.player = {
addClass: () => {},
removeClass: () => {},
ads: {
_inLinearAdMode: true,
endLinearAdMode: () => {
this.calledEndLinearAdMode = true;
}
}
};
this.midroll = new Midroll(this.player);
this.adBreakStartStub = sinon.stub(adBreak, 'start');
this.adBreakEndStub = sinon.stub(adBreak, 'end');
},
afterEach() {
this.adBreakStartStub.restore();
this.adBreakEndStub.restore();
}
});
QUnit.test('starts an ad break on init', function(assert) {
this.midroll.init(this.player);
assert.equal(this.player.ads.adType, 'midroll', 'ad type is midroll');
assert.equal(this.adBreakStartStub.callCount, 1, 'ad break started');
});
QUnit.test('ends an ad break on endLinearAdMode', function(assert) {
this.midroll.init(this.player);
this.midroll.endLinearAdMode();
assert.equal(this.adBreakEndStub.callCount, 1, 'ad break ended');
});
QUnit.test('adserror during ad break ends ad break', function(assert) {
this.midroll.init(this.player);
this.midroll.onAdsError(this.player);
assert.equal(this.calledEndLinearAdMode, true, 'linear ad mode ended');
});

View file

@ -0,0 +1,151 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import Postroll from '../../../src/states/Postroll.js';
import adBreak from '../../../src/adBreak.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('Postroll', {
beforeEach() {
this.events = [];
this.player = {
ads: {
settings: {},
debug: () => {},
inAdBreak: () => false
},
addClass: () => {},
removeClass: () => {},
setTimeout: () => {},
trigger: (event) => {
this.events.push(event);
},
clearTimeout: () => {}
};
this.postroll = new Postroll(this.player);
this.postroll.transitionTo = (newState) => {
this.newState = newState.name;
};
this.adBreakStartStub = sinon.stub(adBreak, 'start');
this.adBreakEndStub = sinon.stub(adBreak, 'end');
},
afterEach() {
this.adBreakStartStub.restore();
this.adBreakEndStub.restore();
}
});
QUnit.test('sets _contentEnding on init', function(assert) {
this.postroll.init(this.player);
assert.equal(this.player.ads._contentEnding, true, 'content is ending');
});
QUnit.test('startLinearAdMode starts ad break', function(assert) {
this.postroll.init(this.player);
this.postroll.startLinearAdMode();
assert.equal(this.adBreakStartStub.callCount, 1, 'ad break started');
assert.equal(this.player.ads.adType, 'postroll', 'ad type is postroll');
});
QUnit.test('removes ad loading class on ad started', function(assert) {
this.player.removeClass = sinon.spy();
this.postroll.init(this.player);
this.postroll.onAdStarted(this.player);
assert.ok(this.player.removeClass.calledWith('vjs-ad-loading'));
});
QUnit.test('ends linear ad mode & ended event on ads error', function(assert) {
this.player.ads.endLinearAdMode = sinon.spy();
this.postroll.init(this.player);
this.player.ads.inAdBreak = () => true;
this.postroll.onAdsError(this.player);
assert.equal(this.player.ads.endLinearAdMode.callCount, 1, 'linear ad mode ended');
});
QUnit.test('no endLinearAdMode on adserror if not in ad break', function(assert) {
this.player.ads.endLinearAdMode = sinon.spy();
this.postroll.init(this.player);
this.player.ads.inAdBreak = () => false;
this.postroll.onAdsError(this.player);
assert.equal(this.player.ads.endLinearAdMode.callCount, 0, 'linear ad mode ended');
});
QUnit.test('does not transition to AdsDone unless content resuming', function(assert) {
this.postroll.init(this.player);
this.postroll.onEnded(this.player);
assert.equal(this.newState, undefined, 'no transition');
});
QUnit.test('transitions to BeforePreroll on content changed after ad break', function(assert) {
this.postroll.isContentResuming = () => true;
this.postroll.init(this.player);
this.postroll.onContentChanged(this.player);
assert.equal(this.newState, 'BeforePreroll');
});
QUnit.test('transitions to Preroll on content changed before ad break', function(assert) {
this.postroll.init(this.player);
this.postroll.onContentChanged(this.player);
assert.equal(this.newState, 'Preroll');
});
QUnit.test('doesn\'t transition on content changed during ad break', function(assert) {
this.postroll.inAdBreak = () => true;
this.postroll.init(this.player);
this.postroll.onContentChanged(this.player);
assert.equal(this.newState, undefined, 'no transition');
});
QUnit.test('transitions to AdsDone on nopostroll before ad break', function(assert) {
this.postroll.init(this.player);
this.postroll.onNoPostroll(this.player);
assert.equal(this.newState, 'AdsDone');
});
QUnit.test('no transition on nopostroll during ad break', function(assert) {
this.postroll.inAdBreak = () => true;
this.postroll.init(this.player);
this.postroll.onNoPostroll(this.player);
assert.equal(this.newState, undefined, 'no transition');
});
QUnit.test('no transition on nopostroll after ad break', function(assert) {
this.postroll.isContentResuming = () => true;
this.postroll.init(this.player);
this.postroll.onNoPostroll(this.player);
assert.equal(this.newState, undefined, 'no transition');
});
QUnit.test('can abort', function(assert) {
const removeClassSpy = sinon.spy(this.player, 'removeClass');
this.postroll.init(this.player);
this.postroll.abort(this.player);
assert.equal(this.postroll.contentResuming, true, 'contentResuming');
assert.ok(removeClassSpy.calledWith('vjs-ad-loading'), 'loading class removed');
});
QUnit.test('can clean up', function(assert) {
const clearSpy = sinon.spy(this.player, 'clearTimeout');
this.postroll.init(this.player);
this.postroll.cleanup(this.player);
assert.equal(this.player.ads._contentEnding, false, '_contentEnding');
assert.ok(clearSpy.calledWith(this.postroll._postrollTimeout), 'cleared timeout');
});
QUnit.test('can tell if waiting for ad break', function(assert) {
this.postroll.init(this.player);
assert.equal(this.postroll.isWaitingForAdBreak(), true, 'waiting for ad break');
this.postroll.startLinearAdMode();
assert.equal(this.postroll.isWaitingForAdBreak(), false, 'not waiting for ad break');
});

View file

@ -0,0 +1,265 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import Preroll from '../../../src/states/Preroll.js';
import adBreak from '../../../src/adBreak.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('Preroll', {
beforeEach() {
this.events = [];
this.playTriggered = false;
this.classes = [];
this.player = {
ads: {
debug: () => {},
settings: {},
inAdBreak: () => false,
isContentResuming: () => false,
_shouldBlockPlay: true
},
setTimeout: () => {},
clearTimeout: () => {},
addClass: (name) => this.classes.push(name),
removeClass: (name) => this.classes.splice(this.classes.indexOf(name), 1),
hasClass: (name) => this.classes.indexOf(name) !== -1,
one: () => {},
trigger: (event) => {
this.events.push(event);
},
paused: () => {},
play: () => {
this.playTriggered = true;
}
};
this.preroll = new Preroll(this.player);
this.preroll.transitionTo = (newState, arg) => {
this.newState = newState.name;
this.transitionArg = arg;
};
this.preroll.afterLoadStart = (callback) => {
callback();
};
this.adBreakStartStub = sinon.stub(adBreak, 'start');
this.adBreakEndStub = sinon.stub(adBreak, 'end');
},
afterEach() {
this.adBreakStartStub.restore();
this.adBreakEndStub.restore();
}
});
QUnit.test('plays a preroll (adsready true)', function(assert) {
this.preroll.init(this.player, true);
assert.equal(this.preroll.adsReady, true, 'adsready from init');
assert.equal(this.events[0], 'readyforpreroll', 'readyforpreroll from init');
assert.equal(this.preroll.inAdBreak(), false, 'not in ad break');
assert.equal(this.preroll.isWaitingForAdBreak(), true, 'waiting for ad break');
this.preroll.startLinearAdMode();
// Because adBreak.start is mocked.
this.player.ads._inLinearAdMode = true;
assert.equal(this.adBreakStartStub.callCount, 1, 'ad break started');
assert.equal(this.player.ads.adType, 'preroll', 'adType is preroll');
assert.equal(this.preroll.isContentResuming(), false, 'content not resuming');
assert.equal(this.preroll.inAdBreak(), true, 'in ad break');
assert.equal(this.preroll.isWaitingForAdBreak(), false, 'not waiting for ad break');
this.preroll.endLinearAdMode();
assert.equal(this.adBreakEndStub.callCount, 1, 'ad break ended');
assert.equal(this.preroll.isContentResuming(), true, 'content resuming');
assert.equal(this.preroll.isWaitingForAdBreak(), false, 'not waiting for ad break');
this.preroll.onPlaying();
assert.equal(this.newState, 'ContentPlayback', 'transitioned to ContentPlayback');
});
QUnit.test('plays a preroll (adsready false)', function(assert) {
this.preroll.init(this.player, false);
assert.equal(this.preroll.adsReady, false, 'not adsReady yet');
this.preroll.onAdsReady(this.player);
assert.equal(this.preroll.adsReady, true, 'adsready from init');
assert.equal(this.events[0], 'readyforpreroll', 'readyforpreroll from init');
assert.equal(this.preroll.inAdBreak(), false, 'not in ad break');
assert.equal(this.preroll.isWaitingForAdBreak(), true, 'waiting for ad break');
this.preroll.startLinearAdMode();
// Because adBreak.start is mocked.
this.player.ads._inLinearAdMode = true;
assert.equal(this.adBreakStartStub.callCount, 1, 'ad break started');
assert.equal(this.player.ads.adType, 'preroll', 'adType is preroll');
assert.equal(this.preroll.isContentResuming(), false, 'content not resuming');
assert.equal(this.preroll.inAdBreak(), true, 'in ad break');
assert.equal(this.preroll.isWaitingForAdBreak(), false, 'not waiting for ad break');
this.preroll.endLinearAdMode();
assert.equal(this.adBreakEndStub.callCount, 1, 'ad break ended');
assert.equal(this.preroll.isContentResuming(), true, 'content resuming');
assert.equal(this.preroll.isWaitingForAdBreak(), false, 'not waiting for ad break');
this.preroll.onPlaying();
assert.equal(this.newState, 'ContentPlayback', 'transitioned to ContentPlayback');
});
QUnit.test('can handle nopreroll event', function(assert) {
this.preroll.init(this.player, false, false);
this.preroll.onNoPreroll(this.player);
assert.equal(this.preroll.isContentResuming(), true);
this.preroll.onPlaying(this.player);
assert.equal(this.newState, 'ContentPlayback', 'transitioned to ContentPlayback');
});
QUnit.test('can handle adscanceled', function(assert) {
this.preroll.init(this.player, false, false);
this.preroll.onAdsCanceled(this.player);
assert.equal(this.preroll.isContentResuming(), true);
assert.notOk(this.player.hasClass('vjs-ad-loading'));
assert.notOk(this.player.hasClass('vjs-ad-content-resuming'));
assert.notOk(this.preroll._timeout);
this.preroll.onPlaying(this.player);
assert.equal(this.newState, 'ContentPlayback', 'transitioned to ContentPlayback');
});
QUnit.test('can handle adserror', function(assert) {
this.preroll.init(this.player, false, false);
this.preroll.onAdsError(this.player);
assert.equal(this.preroll.isContentResuming(), true);
assert.notOk(this.player.hasClass('vjs-ad-loading'));
assert.notOk(this.player.hasClass('vjs-ad-content-resuming'));
assert.notOk(this.preroll._timeout);
this.preroll.onPlaying(this.player);
assert.equal(this.newState, 'ContentPlayback', 'transitioned to ContentPlayback');
});
QUnit.test('can skip linear ad mode', function(assert) {
this.preroll.init(this.player, false, false);
this.preroll.skipLinearAdMode();
assert.equal(this.preroll.isContentResuming(), true);
this.preroll.onPlaying(this.player);
assert.equal(this.newState, 'ContentPlayback', 'transitioned to ContentPlayback');
});
QUnit.test('can handle adtimeout', function(assert) {
this.preroll.init(this.player, false, false);
this.preroll.onAdTimeout(this.player);
assert.equal(this.preroll.isContentResuming(), true);
assert.notOk(this.player.hasClass('vjs-ad-loading'));
assert.notOk(this.player.hasClass('vjs-ad-content-resuming'));
assert.notOk(this.preroll._timeout);
this.preroll.onPlaying(this.player);
assert.equal(this.newState, 'ContentPlayback', 'transitioned to ContentPlayback');
});
QUnit.test('removes ad loading class on ads started', function(assert) {
this.preroll.init(this.player, false);
const removeClassSpy = sinon.spy(this.player, 'removeClass');
this.preroll.onAdStarted(this.player);
assert.ok(removeClassSpy.calledWith('vjs-ad-loading'), 'loading class removed');
});
QUnit.test('only plays after no ad in correct conditions', function(assert) {
this.preroll.init(this.player, false, false);
this.player.ads._playRequested = false;
this.player.ads._pausedOnContentupdate = false;
this.player.paused = () => false;
this.preroll.resumeAfterNoPreroll(this.player);
assert.equal(
this.playTriggered, false,
'should not call play when playing already'
);
this.player.ads._playRequested = true;
this.player.ads._pausedOnContentupdate = false;
this.player.paused = () => false;
this.preroll.resumeAfterNoPreroll(this.player);
assert.equal(
this.playTriggered, false,
'should not call play when playing already 2'
);
this.player.ads._playRequested = false;
this.player.ads._pausedOnContentupdate = true;
this.player.paused = () => false;
this.preroll.resumeAfterNoPreroll(this.player);
assert.equal(
this.playTriggered, false,
'should not call play when playing already 3'
);
this.player.ads._playRequested = false;
this.player.ads._pausedOnContentupdate = false;
this.player.paused = () => true;
this.preroll.resumeAfterNoPreroll(this.player);
assert.equal(
this.playTriggered, false,
'should not call play when playback has never started'
);
this.player.ads._playRequested = true;
this.player.ads._pausedOnContentupdate = false;
this.player.paused = () => true;
this.preroll.resumeAfterNoPreroll(this.player);
assert.equal(
this.playTriggered, true,
'should call play when playback had been started and the player is paused'
);
this.player.ads._playRequested = false;
this.player.ads._pausedOnContentupdate = true;
this.player.paused = () => true;
this.preroll.resumeAfterNoPreroll(this.player);
assert.equal(
this.playTriggered, true,
'should call play when playback had been started on the last source and the player is paused'
);
});
QUnit.test('remove ad loading class on cleanup', function(assert) {
this.preroll.init(this.player, false);
const removeClassSpy = sinon.spy(this.player, 'removeClass');
this.preroll.cleanup(this.player);
assert.ok(removeClassSpy.calledWith('vjs-ad-loading'), 'loading class removed');
});
QUnit.test('resets _shouldBlockPlay to false when ad break starts', function(assert) {
this.preroll.init(this.player, true);
this.preroll.startLinearAdMode();
assert.equal(this.player.ads._shouldBlockPlay, false);
});
QUnit.test('resets _shouldBlockPlay to false when no preroll', function(assert) {
this.preroll.init(this.player, true, false);
this.preroll.resumeAfterNoPreroll(this.player);
assert.equal(this.player.ads._shouldBlockPlay, false);
});
QUnit.test('adserror (Preroll) will trigger play & playing if its already playing', function(assert) {
this.preroll.init(this.player, false, false);
this.playingTriggered = false;
// due AdError 1009: The VAST response document is empty
this.player.ads._playRequested = true;
this.player.ads._pausedOnContentupdate = false;
this.player.paused = () => false;
this.preroll.onAdsError(this.player);
assert.equal(this.preroll.adType, null);
assert.equal(this.events[0], 'play', 'playing from adserror');
assert.equal(this.events[1], 'playing', 'playing from adserror');
assert.equal(this.player.ads._shouldBlockPlay, false);
assert.equal(this.preroll.isContentResuming(), true);
});

View file

@ -0,0 +1,54 @@
import QUnit from 'qunit';
import sinon from 'sinon';
import StitchedAdRoll from '../../../src/states/StitchedAdRoll.js';
import adBreak from '../../../src/adBreak.js';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('StitchedAdRoll', {
beforeEach() {
this.player = {
addClass: () => {},
removeClass: () => {},
trigger: sinon.spy(),
ads: {
_inLinearAdMode: true,
debug: () => {}
}
};
this.adroll = new StitchedAdRoll(this.player);
this.adBreakStartStub = sinon.stub(adBreak, 'start');
this.adBreakEndStub = sinon.stub(adBreak, 'end');
},
afterEach() {
this.adBreakStartStub.restore();
this.adBreakEndStub.restore();
}
});
QUnit.test('starts an ad break on init', function(assert) {
this.adroll.init();
assert.equal(this.player.ads.adType, 'stitched', 'ad type is stitched');
assert.equal(this.adBreakStartStub.callCount, 1, 'ad break started');
});
QUnit.test('ends an ad break on endLinearAdMode', function(assert) {
this.adroll.init();
this.adroll.endLinearAdMode();
assert.equal(this.adBreakEndStub.callCount, 1, 'ad break ended');
});
QUnit.test('adended during ad break leaves linear ad mode and re-triggers ended', function(assert) {
sinon.spy(this.adroll, 'endLinearAdMode');
this.adroll.init();
this.adroll.onAdEnded();
assert.ok(this.player.trigger.calledOnce, 'the player fired one event');
assert.ok(this.player.trigger.calledWith('ended'), 'the event it fired was ended');
assert.ok(this.adroll.endLinearAdMode.calledOnce, 'the ad roll called endLinearAdMode');
});

View file

@ -0,0 +1,44 @@
import StitchedContentPlayback from '../../../src/states/StitchedContentPlayback.js';
import QUnit from 'qunit';
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit.module('StitchedContentPlayback', {
beforeEach() {
this.events = [];
this.playTriggered = false;
this.player = {
paused: () => false,
play: () => {},
trigger: (event) => {
this.events.push(event);
},
ads: {
debug: () => {},
_contentHasEnded: false,
_shouldBlockPlay: true
}
};
this.stitchedContentPlayback = new StitchedContentPlayback(this.player);
this.stitchedContentPlayback.transitionTo = (newState) => {
this.newState = newState.name;
};
}
});
QUnit.test('transitions to StitchedAdRoll when startLinearAdMode is called', function(assert) {
this.stitchedContentPlayback.init();
this.stitchedContentPlayback.startLinearAdMode();
assert.equal(this.newState, 'StitchedAdRoll', 'transitioned to StitchedAdRoll');
});
QUnit.test('sets _shouldBlockPlay to false on init', function(assert) {
assert.equal(this.player.ads._shouldBlockPlay, true);
this.stitchedContentPlayback.init();
assert.equal(this.player.ads._shouldBlockPlay, false);
});

164
node_modules/videojs-contrib-ads/test/unit/test.ads.js generated vendored Normal file
View file

@ -0,0 +1,164 @@
import videojs from 'video.js';
import getAds from '../../src/ads.js';
import QUnit from 'qunit';
import sinon from 'sinon';
QUnit.module('Ads Object', {
beforeEach() {
this.player = {
currentSrc: () => {},
duration: () => {},
on: () => {},
one: () => {},
ready: () => {},
setTimeout: () => {}
};
this.player.ads = getAds(this.player);
this.player.ads.settings = {};
}
}, function() {
/*
* Basic live detection
*/
QUnit.test('isLive', function(assert) {
this.player.duration = () => 5;
assert.equal(this.player.ads.isLive(this.player), false);
this.player.duration = () => Infinity;
assert.equal(this.player.ads.isLive(this.player), true);
});
/*
* `contentIsLive` setting overrides live detection
*/
QUnit.test('isLive and contentIsLive', function(assert) {
this.player.duration = () => 5;
this.player.ads.settings.contentIsLive = true;
assert.equal(this.player.ads.isLive(this.player), true);
this.player.duration = () => 5;
this.player.ads.settings.contentIsLive = false;
assert.equal(this.player.ads.isLive(this.player), false);
this.player.duration = () => Infinity;
this.player.ads.settings.contentIsLive = true;
assert.equal(this.player.ads.isLive(this.player), true);
this.player.duration = () => Infinity;
this.player.ads.settings.contentIsLive = false;
assert.equal(this.player.ads.isLive(this.player), false);
});
QUnit.test('stitchedAds', function(assert) {
assert.notOk(this.player.ads.stitchedAds());
this.player.ads.settings.stitchedAds = true;
assert.ok(this.player.ads.stitchedAds());
sinon.spy(videojs.log, 'warn');
this.player.ads.stitchedAds(false);
assert.ok(videojs.log.warn.calledOnce, 'using as a setter is deprecated');
assert.notOk(this.player.ads.stitchedAds());
assert.notOk(this.player.ads.settings.stitchedAds);
videojs.log.warn.restore();
});
QUnit.test('shouldPlayContentBehindAd', function(assert) {
// liveCuePoints true + finite duration
this.player.ads.settings.liveCuePoints = true;
this.player.duration = () => 60;
videojs.browser.IS_IOS = false;
videojs.browser.IS_ANDROID = false;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
this.player.ads.settings.liveCuePoints = true;
this.player.duration = () => 60;
videojs.browser.IS_IOS = true;
videojs.browser.IS_ANDROID = false;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
this.player.ads.settings.liveCuePoints = true;
this.player.duration = () => 60;
videojs.browser.IS_IOS = false;
videojs.browser.IS_ANDROID = true;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
// liveCuePoints true + infinite duration
this.player.ads.settings.liveCuePoints = true;
this.player.duration = () => Infinity;
videojs.browser.IS_IOS = false;
videojs.browser.IS_ANDROID = false;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), true);
this.player.ads.settings.liveCuePoints = true;
this.player.duration = () => Infinity;
videojs.browser.IS_IOS = true;
videojs.browser.IS_ANDROID = false;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
this.player.ads.settings.liveCuePoints = true;
this.player.duration = () => Infinity;
videojs.browser.IS_IOS = false;
videojs.browser.IS_ANDROID = true;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
// liveCuePoints false + finite duration
this.player.ads.settings.liveCuePoints = false;
this.player.duration = () => 60;
videojs.browser.IS_IOS = false;
videojs.browser.IS_ANDROID = false;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
this.player.ads.settings.liveCuePoints = false;
this.player.duration = () => 60;
videojs.browser.IS_IOS = true;
videojs.browser.IS_ANDROID = false;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
this.player.ads.settings.liveCuePoints = false;
this.player.duration = () => 60;
videojs.browser.IS_IOS = false;
videojs.browser.IS_ANDROID = true;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
// liveCuePoints false + infinite duration
this.player.ads.settings.liveCuePoints = false;
this.player.duration = () => Infinity;
videojs.browser.IS_IOS = false;
videojs.browser.IS_ANDROID = false;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
this.player.ads.settings.liveCuePoints = false;
this.player.duration = () => Infinity;
videojs.browser.IS_IOS = true;
videojs.browser.IS_ANDROID = false;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
this.player.ads.settings.liveCuePoints = false;
this.player.duration = () => Infinity;
videojs.browser.IS_IOS = false;
videojs.browser.IS_ANDROID = true;
assert.equal(this.player.ads.shouldPlayContentBehindAd(this.player), false);
});
QUnit.test('shouldTakeSnapshots', function(assert) {
this.player.ads.shouldPlayContentBehindAd = () => false;
this.player.ads.stitchedAds = () => false;
assert.ok(this.player.ads.shouldTakeSnapshots());
this.player.ads.shouldPlayContentBehindAd = () => true;
assert.notOk(this.player.ads.shouldTakeSnapshots());
this.player.ads.shouldPlayContentBehindAd = () => false;
this.player.ads.stitchedAds = () => true;
assert.notOk(this.player.ads.shouldTakeSnapshots());
});
});

View file

@ -0,0 +1,245 @@
import pm from '../../src/playMiddleware.js';
import QUnit from 'qunit';
import sinon from 'sinon';
import videojs from 'video.js';
QUnit.module('Play Middleware', {}, function() {
const baseMockedVjsNotSupported = {
use: () => {},
VERSION: '5.0.0',
browser: {
}
};
const baseMockedVjsIsSupported = {
use: () => {},
VERSION: '6.7.3',
browser: {
IS_IOS: false,
IS_ANDROID: false
},
middleware: {
TERMINATOR: {fake: 'terminator'}
}
};
QUnit.module('Not supported unit tests', {
beforeEach() {
this.videojs = videojs.mergeOptions({}, baseMockedVjsNotSupported);
},
afterEach() {
this.videojs = null;
}
}, function() {
QUnit.test('isMiddlewareMediatorSupported is false if old video.js version', function(assert) {
// Mock videojs.browser to mock an older videojs version
pm.testHook(this.videojs);
assert.equal(
pm.isMiddlewareMediatorSupported(), false,
'old video.js does not support middleware mediators'
);
});
QUnit.test('isMiddlewareMediatorSupported is false if on mobile', function(assert) {
// Mock videojs.browser to fake being on Android
this.videojs.browser.IS_ANDROID = true;
this.videojs.browser.IS_IOS = false;
pm.testHook(this.videojs);
assert.equal(
pm.isMiddlewareMediatorSupported(), false,
'is not supported on Android'
);
// Mock videojs.browser to fake being on iOS
this.videojs.browser.IS_ANDROID = false;
this.videojs.browser.IS_IOS = true;
pm.testHook(this.videojs);
assert.equal(
pm.isMiddlewareMediatorSupported(), false,
'is not supported on iOS'
);
});
});
QUnit.module('Supported unit tests', {
beforeEach() {
// Stub videojs to force playMiddleware to be used
this.videojs = videojs.mergeOptions({}, baseMockedVjsIsSupported);
pm.testHook(this.videojs);
this.triggeredEvent = null;
this.addedClass = null;
// Stub the player
this.player = {
ads: {
_shouldBlockPlay: false,
_playBlocked: false,
debug: () => {}
},
trigger: (event) => {
this.triggeredEvent = event;
},
addClass: (className) => {
this.addedClass = className;
}
};
this.sandbox = sinon.sandbox.create();
},
afterEach() {
// Reset variables
this.videojs = null;
this.sandbox.restore();
}
});
QUnit.test('isMiddlewareMediatorSupported is true if middleware mediators exist on desktop', function(assert) {
assert.equal(
pm.isMiddlewareMediatorSupported(), true,
'is supported if middleware mediators exist and not mobile'
);
});
QUnit.test('playMiddleware returns with a setSource, callPlay and play method', function(assert) {
const m = pm.playMiddleware(this.player);
this.sandbox.stub(pm, 'isMiddlewareMediatorSupported').returns(true);
assert.equal(typeof m, 'object', 'returns an object');
assert.equal(typeof m.setSource, 'function', 'has setSource');
assert.equal(typeof m.callPlay, 'function', 'has callPlay');
assert.equal(typeof m.play, 'function', 'has play');
});
QUnit.test('playMiddleware callPlay will terminate if _shouldBlockPlay is true', function(assert) {
const m = pm.playMiddleware(this.player);
this.sandbox.stub(pm, 'isMiddlewareMediatorSupported').returns(true);
this.player.ads._shouldBlockPlay = true;
assert.equal(
m.callPlay(), this.videojs.middleware.TERMINATOR,
'callPlay returns terminator'
);
assert.strictEqual(
this.player.ads._playBlocked, true,
'_playBlocked is set'
);
});
QUnit.test('playMiddleware callPlay will not terminate if _shouldBlockPlay is false', function(assert) {
const m = pm.playMiddleware(this.player);
this.sandbox.stub(pm, 'isMiddlewareMediatorSupported').returns(true);
this.player.ads._shouldBlockPlay = false;
assert.equal(
m.callPlay(), undefined,
'callPlay should not return an object'
);
assert.notEqual(
m.callPlay(), this.videojs.middleware.TERMINATOR,
'callPlay should not return the terminator'
);
assert.strictEqual(
this.player.ads._playBlocked, false,
'_playBlocked should not be set'
);
});
QUnit.test("playMiddleware callPlay will not terminate if the player doesn't have this plugin", function(assert) {
const nonAdsPlayer = {
trigger: (event) => {
this.triggeredEvent = event;
},
addClass: (className) => {
this.addedClass = className;
}
};
const m = pm.playMiddleware(nonAdsPlayer);
this.sandbox.stub(pm, 'isMiddlewareMediatorSupported').returns(true);
this.player.ads._shouldBlockPlay = true;
assert.equal(
m.callPlay(), undefined,
'callPlay should not return an object'
);
assert.strictEqual(
this.player.ads._playBlocked, false,
'_playBlocked should not be set'
);
});
QUnit.test('playMiddleware play will trigger play event if callPlay terminates', function(assert) {
const m = pm.playMiddleware(this.player);
this.sandbox.stub(pm, 'isMiddlewareMediatorSupported').returns(true);
this.player.ads._shouldBlockPlay = true;
// Mock that the callPlay method terminated
this.player.ads._playBlocked = true;
// Play terminates, there's no value returned
m.play(true, null);
assert.equal(this.triggeredEvent, 'play');
assert.equal(this.addedClass, 'vjs-has-started');
assert.equal(
this.player.ads._playBlocked, false,
'_playBlocked is reset'
);
});
QUnit.test('playMiddleware play will not trigger play event if another middleware terminated', function(assert) {
const m = pm.playMiddleware(this.player);
this.sandbox.stub(pm, 'isMiddlewareMediatorSupported').returns(true);
// Mock that another middleware terminated but the playMiddleware did not
this.player.ads._shouldBlockPlay = true;
this.player.ads._playBlocked = false;
this.sandbox.stub(m, 'callPlay').returns(undefined);
// Another middleware terminated so the first argument is true
m.play(true, null);
assert.equal(this.triggeredEvent, null, 'no events should be triggered');
assert.equal(this.addedClass, null, 'no classes should be added');
assert.equal(this.player.ads._playBlocked, false, '_playBlocked has not changed');
});
QUnit.test("playMiddleware play will not trigger play event if the player doesn't have this plugin", function(assert) {
let evt = null;
let cnm = null;
const nonAdsPlayer = {
trigger: (event) => {
evt = event;
},
addClass: (className) => {
cnm = className;
}
};
const m = pm.playMiddleware(nonAdsPlayer);
this.sandbox.stub(pm, 'isMiddlewareMediatorSupported').returns(true);
m.play(true, null);
assert.equal(evt, null, 'the play event should not have been triggered');
assert.equal(cnm, null, 'the class should not have been added');
});
QUnit.test("playMiddleware won't trigger play event if callPlay doesn't terminate", function(assert) {
const m = pm.playMiddleware(this.player);
const originalPlayBlocked = this.player.ads._playBlocked;
this.sandbox.stub(pm, 'isMiddlewareMediatorSupported').returns(true);
m.play(false, {});
assert.equal(this.triggeredEvent, null, 'no events should be triggered');
assert.equal(this.addedClass, null, 'no classes should be added');
assert.strictEqual(
this.player.ads._playBlocked, originalPlayBlocked,
'_playBlocked remains unchanged'
);
});
});

View file

@ -0,0 +1,130 @@
import redispatch from '../../src/redispatch.js';
import QUnit from 'qunit';
QUnit.module('Redispatch', {
beforeEach(assert) {
// Player event buffer.
// Mocked player pushes events here when they are triggered.
// redispatch helper returns event buffer after each redispatch.
let eventBuffer = [];
// Mocked player
this.player = {
trigger(event) {
eventBuffer.push(event);
},
currentSrc() {
return 'my vid';
},
ads: {
snapshot: {
ended: false,
currentSrc: 'my vid'
},
videoElementRecycled() {
return false;
},
stitchedAds() {
return false;
},
isResumingAfterNoPreroll() {
return false;
}
}
};
// Redispatch helper for tests
this.redispatch = function(type) {
const event = {type};
eventBuffer = [];
redispatch.call(this.player, event);
if (eventBuffer.length === 1) {
return eventBuffer[0].type;
} else if (event.cancelBubble) {
return 'cancelled';
} else if (eventBuffer.length === 0) {
return 'ignored';
}
throw new Error('Event buffer has more than 1 event');
};
},
afterEach(assert) {
// Cleanup
this.player = null;
this.redispatch = null;
}
});
QUnit.test('playing event in different ad states', function(assert) {
this.player.ads.isInAdMode = () => false;
this.player.ads.isContentResuming = () => false;
assert.equal(this.redispatch('playing'), 'ignored');
this.player.ads.isInAdMode = () => true;
this.player.ads.isContentResuming = () => false;
assert.equal(this.redispatch('playing'), 'adplaying');
this.player.ads.isInAdMode = () => true;
this.player.ads.isContentResuming = () => true;
assert.equal(this.redispatch('playing'), 'ignored');
});
QUnit.test('play events in different states', function(assert) {
this.player.ads.inAdBreak = () => false;
this.player.ads.isInAdMode = () => true;
this.player.ads.isContentResuming = () => true;
assert.equal(
this.redispatch('play'), 'contentplay',
'should be contentplay when content is resuming'
);
this.player.ads.inAdBreak = () => false;
this.player.ads.isInAdMode = () => false;
this.player.ads.isContentResuming = () => false;
this.player.ads._playRequested = false;
assert.strictEqual(
this.redispatch('play'), 'ignored',
"should not be redispatched if play hasn't been requested yet"
);
this.player.ads.inAdBreak = () => false;
this.player.ads.isInAdMode = () => false;
this.player.ads.isContentResuming = () => false;
this.player.ads._playRequested = true;
assert.strictEqual(
this.redispatch('play'), 'ignored',
'should not be redispatched if in content state'
);
this.player.ads.inAdBreak = () => false;
this.player.ads.isInAdMode = () => true;
this.player.ads.isContentResuming = () => false;
this.player.ads._playRequested = true;
assert.strictEqual(
this.redispatch('play'), 'ignored',
'should not prefix when not in an ad break'
);
this.player.ads.inAdBreak = () => true;
this.player.ads.isInAdMode = () => true;
this.player.ads.isContentResuming = () => false;
this.player.ads._playRequested = true;
assert.strictEqual(
this.redispatch('play'), 'adplay',
'should be adplay when in an ad break'
);
});

View file

@ -0,0 +1,47 @@
import videojs from 'video.js';
import contribAdsPlugin from '../../src/plugin.js';
import register from '../../src/register.js';
import { hasAdsPlugin} from '../../src/register.js';
import QUnit from 'qunit';
// Cross-compatible plugin de-registration.
const deregister = () => {
// Video.js 7.2+
if (videojs.deregisterPlugin) {
return videojs.deregisterPlugin('ads');
}
// Video.js 6.0 thru 7.1
if (videojs.getPlugin) {
const Plugin = videojs.getPlugin('plugin');
if (Plugin && Plugin.deregisterPlugin) {
return Plugin.deregisterPlugin('ads');
}
}
// Video.js 5
const Player = videojs.getComponent('Player');
if (Player && Player.prototype.ads) {
delete Player.prototype.ads;
}
};
QUnit.module('Register');
QUnit.test('registration fails if plugin exists, succeeds otherwise', function(assert) {
// The plugin is already registered here.
assert.notOk(register(contribAdsPlugin), 'plugin was already registered');
assert.ok(hasAdsPlugin(), 'plugin exists');
// De-register the plugin and verify that it no longer exists.
deregister();
assert.notOk(hasAdsPlugin(), 'plugin does not exist');
// Re-register the plugin and verify that it exists.
assert.ok(register(contribAdsPlugin), 'plugin was registered');
assert.ok(hasAdsPlugin(), 'plugin exists');
});