UNPKG

videojs-per-source-behaviors

Version:

A Video.js plugin for enhancing a player with behaviors related to changing media sources.

506 lines (414 loc) 14 kB
import document from 'global/document'; import QUnit from 'qunitjs'; import sinon from 'sinon'; import videojs from 'video.js'; import plugin from '../src/plugin'; /** * Assertion for testing a subset of event data. * * @param {Object} data * @param {Object} expected * @param {string} [message] */ QUnit.assert.eventDataMatches = function(data, expected, message) { this.deepEqual({ from: data.from, to: data.to, // Convert interimEvents to extract only `time` and `type`. interimEvents: data.interimEvents.map(o => ({time: o.time, type: o.event.type})) }, expected, message); }; QUnit.test('the environment is sane', function(assert) { assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists'); assert.strictEqual(typeof sinon, 'object', 'sinon exists'); assert.strictEqual(typeof videojs, 'function', 'videojs exists'); assert.strictEqual(typeof plugin, 'function', 'plugin is a function'); }); QUnit.module('videojs-per-source-behaviors', { beforeEach() { // Mock the environment's timers because certain things - particularly // player readiness - are asynchronous in video.js 5. This MUST come // before any player is created; otherwise, timers could get created // with the actual timer methods! this.clock = sinon.useFakeTimers(); this.fixture = document.getElementById('qunit-fixture'); this.video = document.createElement('video'); this.fixture.appendChild(this.video); this.player = videojs(this.video); this.player.perSourceBehaviors(); // Tick forward enough to ready the player. this.clock.tick(1); }, afterEach() { this.player.dispose(); this.clock.restore(); } }); QUnit.test('disable(), disabled(), enable()', function(assert) { const psb = this.player.perSourceBehaviors; assert.notOk(psb.disabled(), 'by default, per-source behaviors are not disabled'); psb.disable(); assert.ok(psb.disabled(), 'per-source behaviors can be disabled'); psb.enable(); assert.notOk(psb.disabled(), 'per-source behaviors can be enabled'); }); QUnit.test('"sourceunstable" event', function(assert) { const spy = sinon.spy(); this.player.on('sourceunstable', spy); this.player.trigger('abort'); // For each assertion, tick 10ms to be sure multiple timeouts do not happen! this.clock.tick(10); assert.strictEqual( spy.callCount, 1, '"sourceunstable" events can be triggered by "abort"' ); this.player.trigger('emptied'); this.clock.tick(10); assert.strictEqual( spy.callCount, 2, '"sourceunstable" events can be triggered by "emptied"' ); this.player.trigger('abort'); this.player.trigger('emptied'); this.player.trigger('abort'); this.player.trigger('emptied'); this.clock.tick(10); assert.strictEqual( spy.callCount, 3, '"sourceunstable" events will only trigger once during a stack' ); }); QUnit.test('"sourcechanged" event', function(assert) { const spy = sinon.spy(); this.player.on('sourcechanged', spy); this.player.trigger('loadstart'); this.player.trigger('canplay'); this.player.trigger('play'); this.player.trigger('playing'); // For each assertion, tick 10ms to be sure multiple timeouts do not happen! this.clock.tick(10); assert.strictEqual(spy.callCount, 0, 'no source, no "sourcechanged" event'); this.player.currentSrc = () => 'x-1.mp4'; this.player.trigger('play'); this.player.trigger('playing'); this.player.trigger('loadstart'); this.player.trigger('canplay'); this.clock.tick(10); assert.strictEqual(spy.callCount, 1, 'with a source, got a "sourcechanged" event'); assert.eventDataMatches(spy.getCall(0).args[1], { from: undefined, to: 'x-1.mp4', interimEvents: [{ time: 11, type: 'play' }, { time: 11, type: 'playing' }, { time: 11, type: 'loadstart' }, { time: 11, type: 'canplay' }] }); this.player.trigger('pause'); this.player.trigger('emptied'); this.player.trigger('abort'); this.player.trigger('loadstart'); this.clock.tick(10); assert.strictEqual( spy.callCount, 1, 'subsequent events with same source do not trigger "sourcechanged"' ); this.player.currentSrc = () => 'x-2.mp4'; this.player.trigger('loadstart'); this.player.trigger('loadedmetadata'); this.player.trigger('loadeddata'); this.clock.tick(10); assert.strictEqual(spy.callCount, 2, 'with a new source, got a "sourcechanged" event'); assert.eventDataMatches(spy.getCall(1).args[1], { from: 'x-1.mp4', to: 'x-2.mp4', interimEvents: [{ time: 31, type: 'loadstart' }, { time: 31, type: 'loadedmetadata' }, { time: 31, type: 'loadeddata' }] }); this.player.currentSrc = () => 'x-1.mp4'; this.player.trigger('play'); this.player.trigger('canplay'); this.player.trigger('loadstart'); this.clock.tick(10); assert.strictEqual( spy.callCount, 3, 'with a changed, but repeated, source, got a "sourcechanged" event' ); assert.eventDataMatches(spy.getCall(2).args[1], { from: 'x-2.mp4', to: 'x-1.mp4', interimEvents: [{ time: 41, type: 'play' }, { time: 41, type: 'canplay' }, { time: 41, type: 'loadstart' }] }); // The "play" will trigger a listener this.player.trigger('play'); this.player.trigger('canplay'); this.player.currentSrc = () => 'x-2.mp4'; this.player.trigger('playing'); this.player.trigger('loadstart'); this.clock.tick(10); assert.strictEqual( spy.callCount, 4, 'changing the source while a timeout was queued triggered a "sourcechanged" event' ); assert.eventDataMatches(spy.getCall(3).args[1], { from: 'x-1.mp4', to: 'x-2.mp4', interimEvents: [{ time: 51, type: 'play' }, { time: 51, type: 'canplay' }, { time: 51, type: 'playing' }, { time: 51, type: 'loadstart' }] }); this.player.perSourceBehaviors.disable(); this.player.currentSrc = () => 'x-1.mp4'; this.player.trigger('loadedmetadata'); this.player.trigger('loadeddata'); this.player.trigger('loadstart'); this.clock.tick(10); assert.strictEqual( spy.callCount, 4, 'changing the source while per-source behaviors are disabled does NOT ' + 'trigger a "sourcechanged" event' ); this.player.perSourceBehaviors.enable(); this.player.trigger('play'); this.player.trigger('loadstart'); this.player.trigger('canplay'); this.player.trigger('playing'); this.clock.tick(10); assert.strictEqual( spy.callCount, 5, 're-enabling per-source behaviors will start triggering "sourcechanged" ' + 'events once again' ); assert.eventDataMatches(spy.getCall(4).args[1], { from: 'x-2.mp4', to: 'x-1.mp4', interimEvents: [{ time: 71, type: 'play' }, { time: 71, type: 'loadstart' }, { time: 71, type: 'canplay' }, { time: 71, type: 'playing' }] }); this.player.currentSrc = () => 'x-1.mp4'; this.player.addClass('vjs-ad-loading'); this.player.trigger('loadstart'); this.clock.tick(10); assert.strictEqual( spy.callCount, 5, 'changing the source with the vjs-ad-loading class on the player does NOT ' + 'trigger the "sourcechanged" event' ); this.player.removeClass('vjs-ad-loading'); this.player.addClass('vjs-ad-playing'); this.player.trigger('play'); this.clock.tick(10); assert.strictEqual( spy.callCount, 5, 'changing the source with the vjs-ad-playing class on the player does NOT ' + 'trigger the "sourcechanged" event' ); }); QUnit.test('onPerSrc() event binding', function(assert) { const spy = sinon.spy(); this.player.currentSrc = () => 'x-1.mp4'; this.player.onPerSrc('foo', spy); this.player.trigger('foo'); this.player.trigger('foo'); assert.strictEqual( spy.callCount, 2, 'an onPerSrc listener is called each time the event is triggered ' + 'while source is unchanged' ); this.player.currentSrc = () => 'x-2.mp4'; this.player.trigger('foo'); assert.strictEqual( spy.callCount, 2, 'an onPerSrc listener is not called if the event is triggered for ' + 'a new source' ); this.player.currentSrc = () => 'x-1.mp4'; this.player.trigger('foo'); assert.strictEqual( spy.callCount, 2, 'restoring an old source, which had a listener does not trigger - ' + 'the binding is gone' ); this.player.currentSrc = () => {}; this.player.onPerSrc('foo', spy); this.player.trigger('foo'); assert.strictEqual( spy.callCount, 3, 'an onPerSrc listener does not care if there actually is a source' ); this.player.currentSrc = () => 'x-3.mp4'; this.player.trigger('foo'); assert.strictEqual( spy.callCount, 3, 'but gaining a source still clears the previous listener' ); // Bind a new onPerSrc listener for the latest source, then disable per- // source behaviors. this.player.onPerSrc('foo', spy); this.player.perSourceBehaviors.disable(); this.player.trigger('foo'); assert.strictEqual( spy.callCount, 3, 'when per-source behaviors are disabled, listeners are not triggered' ); this.player.perSourceBehaviors.enable(); this.player.trigger('foo'); assert.strictEqual( spy.callCount, 4, 'when per-source behaviors are re-enabled, listeners are triggered' ); }); QUnit.test('removing per-source listeners does not remove other per-source listeners OR normal listeners', function(assert) { const onSpy = sinon.spy(); const onPerSrcSpyA = sinon.spy(); const onPerSrcSpyB = sinon.spy(); this.player.currentSrc = () => 'x-1.mp4'; this.player.on('foo', onSpy); this.player.onPerSrc('foo', onPerSrcSpyA); this.player.onPerSrc('foo', onPerSrcSpyB); this.player.trigger('foo'); assert.strictEqual(onSpy.callCount, 1, 'the "on" listener was called once'); assert.strictEqual(onPerSrcSpyA.callCount, 1, 'the first "onPerSrc" listener was called once'); assert.strictEqual(onPerSrcSpyB.callCount, 1, 'the second "onPerSrc" listener was called once'); this.player.off('foo', onPerSrcSpyA); this.player.trigger('foo'); assert.strictEqual(onSpy.callCount, 2, 'the "on" listener was called once'); assert.strictEqual(onPerSrcSpyA.callCount, 1, 'the first "onPerSrc" listener was NOT called'); assert.strictEqual(onPerSrcSpyB.callCount, 2, 'the second "onPerSrc" listener was called once'); }); QUnit.test('removing normal listeners does not remove per-source listeners OR other normal listeners', function(assert) { const onSpyA = sinon.spy(); const onSpyB = sinon.spy(); const onPerSrcSpy = sinon.spy(); this.player.currentSrc = () => 'x-1.mp4'; this.player.on('foo', onSpyA); this.player.on('foo', onSpyB); this.player.onPerSrc('foo', onPerSrcSpy); this.player.trigger('foo'); assert.strictEqual(onSpyA.callCount, 1, 'the first "on" listener was called once'); assert.strictEqual(onSpyB.callCount, 1, 'the second "on" listener was called once'); assert.strictEqual(onPerSrcSpy.callCount, 1, 'the "onPerSrc" listener was called once'); this.player.off('foo', onSpyB); this.player.trigger('foo'); assert.strictEqual(onSpyA.callCount, 2, 'the first "on" listener was called again'); assert.strictEqual(onSpyB.callCount, 1, 'the second "on" listener was NOT called'); assert.strictEqual(onPerSrcSpy.callCount, 2, 'the "onPerSrc" listener was called again'); }); QUnit.test('onePerSrc() event binding', function(assert) { const spy = sinon.spy(); this.player.currentSrc = () => 'x-1.mp4'; this.player.onePerSrc('foo', spy); this.player.trigger('foo'); this.player.trigger('foo'); this.player.trigger('foo'); this.player.trigger('foo'); assert.strictEqual( spy.callCount, 1, 'an onePerSrc listener is called only once no matter how often the ' + 'event is triggered while source is unchanged' ); this.player.currentSrc = () => 'x-2.mp4'; this.player.trigger('foo'); assert.strictEqual( spy.callCount, 1, 'an onePerSrc listener is not called if the event is triggered for' + 'a new source' ); this.player.currentSrc = () => 'x-1.mp4'; this.player.trigger('foo'); assert.strictEqual( spy.callCount, 1, 'restoring an old source, which had a listener does not trigger - ' + 'the binding is gone' ); this.player.currentSrc = () => {}; this.player.onePerSrc('foo', spy); this.player.trigger('foo'); assert.strictEqual( spy.callCount, 2, 'an onePerSrc listener does not care if there actually is a source' ); this.player.currentSrc = () => 'x-3.mp4'; this.player.trigger('foo'); assert.strictEqual( spy.callCount, 2, 'but gaining a source still clears the previous listener' ); // Bind a new onePerSrc listener for the latest source, then disable per- // source behaviors. this.player.onePerSrc('foo', spy); this.player.perSourceBehaviors.disable(); this.player.trigger('foo'); assert.strictEqual( spy.callCount, 2, 'when per-source behaviors are disabled, listeners are not triggered' ); this.player.perSourceBehaviors.enable(); this.player.trigger('foo'); assert.strictEqual( spy.callCount, 3, 'when per-source behaviors are re-enabled, listeners are triggered' ); }); QUnit.test('"sourcechanged" removes per-source listeners', function(assert) { const spy = sinon.spy(); this.player.currentSrc = () => 'x-1.mp4'; this.player.onPerSrc('foo', spy); this.player.onePerSrc('foo', spy); // Cause a "sourcechanged" event to trigger. This won't work by simply // triggering the event. It needs to happen before the event. this.player.currentSrc = () => 'x-2.mp4'; this.player.trigger('loadstart'); this.clock.tick(10); this.player.trigger('foo'); assert.strictEqual(spy.callCount, 0, 'per source listeners were not called'); });