2016-08-03 21:27:03 +02:00
|
|
|
/* eslint-env qunit */
|
2015-05-04 01:12:38 +02:00
|
|
|
import Tech from '../../../src/js/tech/tech.js';
|
2015-11-06 23:42:19 +02:00
|
|
|
import Html5 from '../../../src/js/tech/html5.js';
|
|
|
|
import Button from '../../../src/js/button.js';
|
2022-05-23 22:23:13 +02:00
|
|
|
import { createTimeRange } from '../../../src/js/utils/time.js';
|
2015-08-31 20:21:53 +02:00
|
|
|
import MediaError from '../../../src/js/media-error.js';
|
2016-04-22 20:31:12 +02:00
|
|
|
import AudioTrack from '../../../src/js/tracks/audio-track';
|
|
|
|
import VideoTrack from '../../../src/js/tracks/video-track';
|
|
|
|
import TextTrack from '../../../src/js/tracks/text-track';
|
|
|
|
import AudioTrackList from '../../../src/js/tracks/audio-track-list';
|
|
|
|
import VideoTrackList from '../../../src/js/tracks/video-track-list';
|
|
|
|
import TextTrackList from '../../../src/js/tracks/text-track-list';
|
2016-08-03 21:27:03 +02:00
|
|
|
import sinon from 'sinon';
|
2016-12-15 22:48:19 +02:00
|
|
|
import log from '../../../src/js/utils/log.js';
|
2020-11-11 01:09:37 +02:00
|
|
|
import TestHelpers from '../test-helpers.js';
|
2016-04-22 20:31:12 +02:00
|
|
|
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
function stubbedSourceHandler(handler) {
|
|
|
|
return {
|
|
|
|
canPlayType() {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
canHandleSource() {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
handleSource(source, tech, options) {
|
|
|
|
return handler;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-08-03 21:27:03 +02:00
|
|
|
QUnit.module('Media Tech', {
|
2016-08-12 19:51:31 +02:00
|
|
|
beforeEach(assert) {
|
2014-12-03 21:31:39 +02:00
|
|
|
this.noop = function() {};
|
|
|
|
this.clock = sinon.useFakeTimers();
|
2016-08-03 21:27:03 +02:00
|
|
|
this.featuresProgessEvents = Tech.prototype.featuresProgessEvents;
|
|
|
|
Tech.prototype.featuresProgressEvents = false;
|
2014-08-14 02:44:36 +03:00
|
|
|
},
|
2016-08-12 19:51:31 +02:00
|
|
|
afterEach(assert) {
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.restore();
|
2019-04-29 18:01:37 +02:00
|
|
|
Tech.prototype.featuresProgressEvents = this.featuresProgessEvents;
|
2014-08-14 02:44:36 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-02-02 21:34:33 +02:00
|
|
|
QUnit.test('Tech.registerTech and Tech.getTech', function(assert) {
|
2022-06-02 18:05:57 +02:00
|
|
|
class MyTech extends Tech {}
|
2017-02-02 21:34:33 +02:00
|
|
|
const oldTechs = Tech.techs_;
|
|
|
|
const oldDefaultTechOrder = Tech.defaultTechOrder_;
|
|
|
|
|
|
|
|
Tech.registerTech('MyTech', MyTech);
|
|
|
|
|
|
|
|
assert.ok(Tech.techs_.MyTech, 'Tech is stored in the global list');
|
|
|
|
assert.notEqual(Tech.defaultTechOrder_.indexOf('MyTech'), -1, 'Tech is stored in the defaultTechOrder array');
|
|
|
|
assert.strictEqual(Tech.getTech('myTech'), MyTech, 'can get a tech using `camelCase` name');
|
|
|
|
assert.strictEqual(Tech.getTech('MyTech'), MyTech, 'can get a tech using `titleCase` name');
|
|
|
|
|
|
|
|
// reset techs and defaultTechOrder
|
|
|
|
Tech.techs_ = oldTechs;
|
|
|
|
Tech.defaultTechOrder_ = oldDefaultTechOrder;
|
|
|
|
});
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('should synthesize timeupdate events by default', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
let timeupdates = 0;
|
|
|
|
const tech = new Tech();
|
2015-05-06 20:01:52 +02:00
|
|
|
|
2014-08-14 02:44:36 +03:00
|
|
|
tech.on('timeupdate', function() {
|
|
|
|
timeupdates++;
|
|
|
|
});
|
|
|
|
|
2015-05-06 20:01:52 +02:00
|
|
|
tech.trigger('play');
|
|
|
|
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.tick(250);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(timeupdates, 1, 'triggered at least one timeupdate');
|
2019-03-18 21:49:48 +02:00
|
|
|
|
|
|
|
tech.dispose();
|
2014-08-14 02:44:36 +03:00
|
|
|
});
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('stops manual timeupdates while paused', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
let timeupdates = 0;
|
|
|
|
const tech = new Tech();
|
|
|
|
|
2015-05-06 20:01:52 +02:00
|
|
|
tech.on('timeupdate', function() {
|
|
|
|
timeupdates++;
|
2014-08-14 02:44:36 +03:00
|
|
|
});
|
2015-05-06 20:01:52 +02:00
|
|
|
|
|
|
|
tech.trigger('play');
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.tick(10 * 250);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(timeupdates > 0, 'timeupdates fire during playback');
|
2014-08-14 02:44:36 +03:00
|
|
|
|
2015-05-06 20:01:52 +02:00
|
|
|
tech.trigger('pause');
|
2014-08-14 02:44:36 +03:00
|
|
|
timeupdates = 0;
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.tick(10 * 250);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(timeupdates, 0, 'timeupdates do not fire when paused');
|
2014-08-14 02:44:36 +03:00
|
|
|
|
2015-05-06 20:01:52 +02:00
|
|
|
tech.trigger('play');
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.tick(10 * 250);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(timeupdates > 0, 'timeupdates fire when playback resumes');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2014-08-14 02:44:36 +03:00
|
|
|
});
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('should synthesize progress events by default', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
let progresses = 0;
|
|
|
|
let bufferedPercent = 0.5;
|
|
|
|
const tech = new Tech();
|
|
|
|
|
2014-08-14 02:44:36 +03:00
|
|
|
tech.on('progress', function() {
|
|
|
|
progresses++;
|
|
|
|
});
|
2015-07-07 20:35:17 +02:00
|
|
|
tech.bufferedPercent = function() {
|
|
|
|
return bufferedPercent;
|
|
|
|
};
|
2014-08-14 02:44:36 +03:00
|
|
|
|
2015-07-07 20:35:17 +02:00
|
|
|
this.clock.tick(500);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(progresses, 0, 'waits until ready');
|
2015-07-07 20:35:17 +02:00
|
|
|
|
|
|
|
tech.trigger('ready');
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.tick(500);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(progresses, 1, 'triggered one event');
|
2015-07-07 20:35:17 +02:00
|
|
|
|
|
|
|
tech.trigger('ready');
|
|
|
|
bufferedPercent = 0.75;
|
|
|
|
this.clock.tick(500);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(progresses, 2, 'repeated readies are ok');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2014-08-14 02:44:36 +03:00
|
|
|
});
|
2014-08-20 21:39:02 +03:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('dispose() should stop time tracking', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new Tech();
|
|
|
|
|
2014-08-20 21:39:02 +03:00
|
|
|
tech.dispose();
|
|
|
|
|
|
|
|
// progress and timeupdate events will throw exceptions after the
|
|
|
|
// tech is disposed
|
|
|
|
try {
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.tick(10 * 1000);
|
2014-08-20 21:39:02 +03:00
|
|
|
} catch (e) {
|
2016-08-12 19:51:31 +02:00
|
|
|
return assert.equal(e, undefined, 'threw an exception');
|
2014-08-20 21:39:02 +03:00
|
|
|
}
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(true, 'no exception was thrown');
|
2014-08-20 21:39:02 +03:00
|
|
|
});
|
2014-12-03 00:22:34 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('dispose() should clear all tracks that are passed when its created', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
const audioTracks = new AudioTrackList([new AudioTrack(), new AudioTrack()]);
|
|
|
|
const videoTracks = new VideoTrackList([new VideoTrack(), new VideoTrack()]);
|
2021-01-22 00:02:15 +02:00
|
|
|
const pretech = new Tech();
|
|
|
|
const textTracks = new TextTrackList([new TextTrack({tech: pretech}),
|
|
|
|
new TextTrack({tech: pretech})]);
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(audioTracks.length, 2, 'should have two audio tracks at the start');
|
|
|
|
assert.equal(videoTracks.length, 2, 'should have two video tracks at the start');
|
|
|
|
assert.equal(textTracks.length, 2, 'should have two text tracks at the start');
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new Tech({audioTracks, videoTracks, textTracks});
|
|
|
|
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.videoTracks().length,
|
|
|
|
videoTracks.length,
|
|
|
|
'should hold video tracks that we passed'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
tech.audioTracks().length,
|
|
|
|
audioTracks.length,
|
|
|
|
'should hold audio tracks that we passed'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
tech.textTracks().length,
|
|
|
|
textTracks.length,
|
|
|
|
'should hold text tracks that we passed'
|
|
|
|
);
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2021-01-22 00:02:15 +02:00
|
|
|
pretech.dispose();
|
2016-04-22 20:31:12 +02:00
|
|
|
tech.dispose();
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(audioTracks.length, 0, 'should have zero audio tracks after dispose');
|
|
|
|
assert.equal(videoTracks.length, 0, 'should have zero video tracks after dispose');
|
|
|
|
assert.equal(textTracks.length, 0, 'should have zero text tracks after dispose');
|
2016-04-22 20:31:12 +02:00
|
|
|
});
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('dispose() should clear all tracks that are added after creation', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new Tech();
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2016-12-15 22:48:19 +02:00
|
|
|
tech.addRemoteTextTrack({}, true);
|
|
|
|
tech.addRemoteTextTrack({}, true);
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2017-01-19 22:16:28 +02:00
|
|
|
tech.audioTracks().addTrack(new AudioTrack());
|
|
|
|
tech.audioTracks().addTrack(new AudioTrack());
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2017-01-19 22:16:28 +02:00
|
|
|
tech.videoTracks().addTrack(new VideoTrack());
|
|
|
|
tech.videoTracks().addTrack(new VideoTrack());
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
|
|
|
assert.equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
2016-11-09 23:07:59 +02:00
|
|
|
assert.equal(tech.textTracks().length, 2, 'should have two text tracks at the start');
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.remoteTextTrackEls().length,
|
|
|
|
2,
|
|
|
|
'should have two remote text tracks els'
|
|
|
|
);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
2016-04-22 20:31:12 +02:00
|
|
|
|
|
|
|
tech.dispose();
|
|
|
|
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.audioTracks().length,
|
|
|
|
0,
|
|
|
|
'should have zero audio tracks after dispose'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
tech.videoTracks().length,
|
|
|
|
0,
|
|
|
|
'should have zero video tracks after dispose'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
tech.remoteTextTrackEls().length,
|
|
|
|
0,
|
|
|
|
'should have zero remote text tracks els'
|
|
|
|
);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.remoteTextTracks().length, 0, 'should have zero remote text tracks');
|
|
|
|
assert.equal(tech.textTracks().length, 0, 'should have zero video tracks after dispose');
|
2016-04-22 20:31:12 +02:00
|
|
|
});
|
|
|
|
|
2022-01-13 19:25:26 +02:00
|
|
|
QUnit.test('switching sources should clear all remote tracks that are added with the default manualCleanup = false', function(assert) {
|
2016-12-15 22:48:19 +02:00
|
|
|
const oldLogWarn = log.warn;
|
|
|
|
|
2016-11-09 23:07:59 +02:00
|
|
|
// Define a new tech class
|
2022-06-02 18:05:57 +02:00
|
|
|
class MyTech extends Tech {}
|
2016-11-09 23:07:59 +02:00
|
|
|
|
|
|
|
// Create source handler
|
|
|
|
const handler = {
|
|
|
|
canPlayType: () => 'probably',
|
|
|
|
canHandleSource: () => 'probably',
|
|
|
|
handleSource: () => {
|
|
|
|
return {
|
|
|
|
dispose: () => {}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Extend Tech with source handlers
|
|
|
|
Tech.withSourceHandlers(MyTech);
|
|
|
|
|
|
|
|
MyTech.registerSourceHandler(handler);
|
|
|
|
|
|
|
|
const tech = new MyTech();
|
|
|
|
|
2017-06-28 08:38:29 +02:00
|
|
|
tech.triggerReady();
|
|
|
|
|
2016-11-09 23:07:59 +02:00
|
|
|
// set the initial source
|
|
|
|
tech.setSource({src: 'foo.mp4', type: 'mp4'});
|
|
|
|
|
2022-01-13 19:25:26 +02:00
|
|
|
// should not be automatically cleaned up when source changes
|
|
|
|
tech.addRemoteTextTrack({}, true);
|
2016-11-09 23:07:59 +02:00
|
|
|
// should be automatically cleaned up when source changes
|
2022-01-13 19:25:26 +02:00
|
|
|
tech.addRemoteTextTrack({});
|
2017-06-28 08:38:29 +02:00
|
|
|
this.clock.tick(1);
|
2016-11-09 23:07:59 +02:00
|
|
|
|
|
|
|
assert.equal(tech.textTracks().length, 2, 'should have two text tracks at the start');
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.remoteTextTrackEls().length,
|
|
|
|
2,
|
|
|
|
'should have two remote text tracks els'
|
|
|
|
);
|
2016-11-09 23:07:59 +02:00
|
|
|
assert.equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.autoRemoteTextTracks_.length,
|
|
|
|
1,
|
|
|
|
'should have one auto-cleanup remote text track'
|
|
|
|
);
|
2016-11-09 23:07:59 +02:00
|
|
|
|
|
|
|
// change source to force cleanup of auto remote text tracks
|
|
|
|
tech.setSource({src: 'bar.mp4', type: 'mp4'});
|
2017-06-28 08:38:29 +02:00
|
|
|
this.clock.tick(1);
|
2016-11-09 23:07:59 +02:00
|
|
|
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.textTracks().length,
|
|
|
|
1,
|
|
|
|
'should have one text track after source change'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
tech.remoteTextTrackEls().length,
|
|
|
|
1,
|
|
|
|
'should have one remote remote text track els after source change'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
tech.remoteTextTracks().length,
|
|
|
|
1,
|
|
|
|
'should have one remote text track after source change'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
tech.autoRemoteTextTracks_.length,
|
|
|
|
0,
|
|
|
|
'should have zero auto-cleanup remote text tracks'
|
|
|
|
);
|
2016-12-15 22:48:19 +02:00
|
|
|
|
|
|
|
log.warn = oldLogWarn;
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2016-11-09 23:07:59 +02:00
|
|
|
});
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('should add the source handler interface to a tech', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
const sourceA = { src: 'foo.mp4', type: 'video/mp4' };
|
|
|
|
const sourceB = { src: 'no-support', type: 'no-support' };
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Define a new tech class
|
2022-06-02 18:05:57 +02:00
|
|
|
class MyTech extends Tech {}
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Extend Tech with source handlers
|
2015-04-14 22:08:32 +02:00
|
|
|
Tech.withSourceHandlers(MyTech);
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Check for the expected class methods
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.ok(
|
|
|
|
MyTech.registerSourceHandler,
|
|
|
|
'added a registerSourceHandler function to the Tech'
|
|
|
|
);
|
|
|
|
assert.ok(
|
|
|
|
MyTech.selectSourceHandler,
|
|
|
|
'added a selectSourceHandler function to the Tech'
|
|
|
|
);
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Create an instance of Tech
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new MyTech();
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Check for the expected instance methods
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(tech.setSource, 'added a setSource function to the tech instance');
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Create an internal state class for the source handler
|
2015-07-24 18:07:58 +02:00
|
|
|
// The internal class would be used by a source handler to maintain state
|
2014-12-03 00:22:34 +02:00
|
|
|
// and provde a dispose method for the handler.
|
|
|
|
// This is optional for source handlers
|
2016-08-03 21:27:03 +02:00
|
|
|
let disposeCalled = false;
|
|
|
|
|
2019-04-29 18:01:37 +02:00
|
|
|
class HandlerInternalState {
|
|
|
|
dispose() {
|
|
|
|
disposeCalled = true;
|
|
|
|
}
|
|
|
|
}
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Create source handlers
|
2016-08-03 21:27:03 +02:00
|
|
|
const handlerOne = {
|
|
|
|
canPlayType(type) {
|
|
|
|
if (type !== 'no-support') {
|
2015-10-27 19:46:05 +02:00
|
|
|
return 'probably';
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
},
|
2016-08-03 21:27:03 +02:00
|
|
|
canHandleSource(source, options) {
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.strictEqual(
|
|
|
|
tech.options_,
|
|
|
|
options,
|
|
|
|
'tech options passed to canHandleSource'
|
|
|
|
);
|
2016-08-03 21:27:03 +02:00
|
|
|
if (source.type !== 'no-support') {
|
2014-12-03 00:22:34 +02:00
|
|
|
return 'probably';
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
},
|
2016-08-03 21:27:03 +02:00
|
|
|
handleSource(s, t, o) {
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.strictEqual(
|
|
|
|
tech,
|
|
|
|
t,
|
|
|
|
'tech instance passed to source handler'
|
|
|
|
);
|
|
|
|
assert.strictEqual(
|
|
|
|
sourceA,
|
|
|
|
s,
|
|
|
|
'tech instance passed to the source handler'
|
|
|
|
);
|
|
|
|
assert.strictEqual(
|
|
|
|
tech.options_,
|
|
|
|
o,
|
|
|
|
'tech options passed to the source handler handleSource'
|
|
|
|
);
|
2016-08-03 21:27:03 +02:00
|
|
|
return new HandlerInternalState();
|
2014-12-03 00:22:34 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-03 21:27:03 +02:00
|
|
|
const handlerTwo = {
|
|
|
|
canPlayType(type) {
|
|
|
|
// no support
|
|
|
|
return '';
|
2015-10-27 19:46:05 +02:00
|
|
|
},
|
2016-08-03 21:27:03 +02:00
|
|
|
canHandleSource(source, options) {
|
|
|
|
// no support
|
|
|
|
return '';
|
2014-12-03 00:22:34 +02:00
|
|
|
},
|
2016-08-03 21:27:03 +02:00
|
|
|
handleSource(source, tech_, options) {
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(false, 'handlerTwo supports nothing and should never be called');
|
2014-12-03 00:22:34 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Test registering source handlers
|
2015-04-14 22:08:32 +02:00
|
|
|
MyTech.registerSourceHandler(handlerOne);
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.strictEqual(
|
|
|
|
MyTech.sourceHandlers[0],
|
|
|
|
handlerOne,
|
|
|
|
'handlerOne was added to the source handler array'
|
|
|
|
);
|
2015-04-14 22:08:32 +02:00
|
|
|
MyTech.registerSourceHandler(handlerTwo, 0);
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.strictEqual(
|
|
|
|
MyTech.sourceHandlers[0],
|
|
|
|
handlerTwo,
|
|
|
|
'handlerTwo was registered at the correct index (0)'
|
|
|
|
);
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Test handler selection
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.strictEqual(
|
|
|
|
MyTech.selectSourceHandler(sourceA, tech.options_),
|
|
|
|
handlerOne,
|
|
|
|
'handlerOne was selected to handle the valid source'
|
|
|
|
);
|
|
|
|
assert.strictEqual(
|
|
|
|
MyTech.selectSourceHandler(sourceB, tech.options_),
|
|
|
|
null,
|
|
|
|
'no handler was selected to handle the invalid source'
|
|
|
|
);
|
2014-12-03 00:22:34 +02:00
|
|
|
|
2015-10-27 19:46:05 +02:00
|
|
|
// Test canPlayType return values
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.strictEqual(
|
|
|
|
MyTech.canPlayType(sourceA.type),
|
|
|
|
'probably',
|
|
|
|
'the Tech returned probably for the valid source'
|
|
|
|
);
|
|
|
|
assert.strictEqual(
|
|
|
|
MyTech.canPlayType(sourceB.type),
|
|
|
|
'',
|
|
|
|
'the Tech returned an empty string for the invalid source'
|
|
|
|
);
|
2015-10-27 19:46:05 +02:00
|
|
|
|
2014-12-03 00:22:34 +02:00
|
|
|
// Test canPlaySource return values
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.strictEqual(
|
|
|
|
MyTech.canPlaySource(sourceA, tech.options_),
|
|
|
|
'probably',
|
|
|
|
'the Tech returned probably for the valid source'
|
|
|
|
);
|
|
|
|
assert.strictEqual(
|
|
|
|
MyTech.canPlaySource(sourceB, tech.options_),
|
|
|
|
'',
|
|
|
|
'the Tech returned an empty string for the invalid source'
|
|
|
|
);
|
2014-12-03 00:22:34 +02:00
|
|
|
|
2016-12-15 22:48:19 +02:00
|
|
|
tech.addRemoteTextTrack({}, true);
|
|
|
|
tech.addRemoteTextTrack({}, true);
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2017-01-19 22:16:28 +02:00
|
|
|
tech.audioTracks().addTrack(new AudioTrack());
|
|
|
|
tech.audioTracks().addTrack(new AudioTrack());
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2017-01-19 22:16:28 +02:00
|
|
|
tech.videoTracks().addTrack(new VideoTrack());
|
|
|
|
tech.videoTracks().addTrack(new VideoTrack());
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
|
|
|
assert.equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
|
|
|
assert.equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.remoteTextTrackEls().length,
|
|
|
|
2,
|
|
|
|
'should have two remote text tracks els'
|
|
|
|
);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2014-12-03 00:22:34 +02:00
|
|
|
// Pass a source through the source handler process of a tech instance
|
|
|
|
tech.setSource(sourceA);
|
2016-04-22 20:31:12 +02:00
|
|
|
|
|
|
|
// verify that the Tracks are still there
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
|
|
|
assert.equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
|
|
|
assert.equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.remoteTextTrackEls().length,
|
|
|
|
2,
|
|
|
|
'should have two remote text tracks els'
|
|
|
|
);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
|
|
|
|
assert.ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
|
2014-12-03 00:22:34 +02:00
|
|
|
|
2016-04-22 20:31:12 +02:00
|
|
|
// Pass a second source
|
|
|
|
tech.setSource(sourceA);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
|
|
|
|
assert.ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
|
2016-04-22 20:31:12 +02:00
|
|
|
|
|
|
|
// verify that all the tracks were removed as we got a new source
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.audioTracks().length, 0, 'should have zero audio tracks');
|
|
|
|
assert.equal(tech.videoTracks().length, 0, 'should have zero video tracks');
|
|
|
|
assert.equal(tech.textTracks().length, 2, 'should have two text tracks');
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.equal(
|
|
|
|
tech.remoteTextTrackEls().length,
|
|
|
|
2,
|
|
|
|
'should have two remote text tracks els'
|
|
|
|
);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
2016-04-22 20:31:12 +02:00
|
|
|
|
2023-03-22 16:00:01 +02:00
|
|
|
// Check that the handler dispose method works
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(disposeCalled, 'dispose has been called for the handler yet');
|
2016-04-22 20:31:12 +02:00
|
|
|
disposeCalled = false;
|
2014-12-03 00:22:34 +02:00
|
|
|
tech.dispose();
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.ok(
|
|
|
|
disposeCalled,
|
|
|
|
'the handler dispose method was called when the tech was disposed'
|
|
|
|
);
|
2014-12-03 00:22:34 +02:00
|
|
|
});
|
2015-02-28 01:50:13 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('should handle unsupported sources with the source handler API', function(assert) {
|
2015-02-28 01:50:13 +02:00
|
|
|
// Define a new tech class
|
2022-06-02 18:05:57 +02:00
|
|
|
class MyTech extends Tech {}
|
2016-08-03 21:27:03 +02:00
|
|
|
|
2015-02-28 01:50:13 +02:00
|
|
|
// Extend Tech with source handlers
|
2015-04-14 22:08:32 +02:00
|
|
|
Tech.withSourceHandlers(MyTech);
|
2015-02-28 01:50:13 +02:00
|
|
|
// Create an instance of Tech
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new MyTech();
|
|
|
|
let usedNative;
|
2015-02-28 01:50:13 +02:00
|
|
|
|
2015-04-14 22:08:32 +02:00
|
|
|
MyTech.nativeSourceHandler = {
|
2016-08-03 21:27:03 +02:00
|
|
|
handleSource() {
|
|
|
|
usedNative = true;
|
|
|
|
}
|
2015-02-28 01:50:13 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
tech.setSource('');
|
2018-09-28 20:58:15 +02:00
|
|
|
assert.ok(
|
|
|
|
usedNative,
|
|
|
|
'native source handler was used when an unsupported source was set'
|
|
|
|
);
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2015-03-11 03:01:11 +02:00
|
|
|
});
|
2015-07-21 22:56:23 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('should allow custom error events to be set', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new Tech();
|
|
|
|
const errors = [];
|
|
|
|
|
2015-08-31 20:21:53 +02:00
|
|
|
tech.on('error', function() {
|
|
|
|
errors.push(tech.error());
|
|
|
|
});
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.error(), null, 'error is null by default');
|
2015-08-31 20:21:53 +02:00
|
|
|
|
|
|
|
tech.error(new MediaError(1));
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(errors.length, 1, 'triggered an error event');
|
|
|
|
assert.equal(errors[0].code, 1, 'set the proper code');
|
2015-08-31 20:21:53 +02:00
|
|
|
|
|
|
|
tech.error(2);
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(errors.length, 2, 'triggered an error event');
|
|
|
|
assert.equal(errors[1].code, 2, 'wrapped the error code');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2015-08-31 20:21:53 +02:00
|
|
|
});
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('should track whether a video has played', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new Tech();
|
2015-07-21 22:56:23 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.played().length, 0, 'starts with zero length');
|
2015-07-21 22:56:23 +02:00
|
|
|
tech.trigger('playing');
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(tech.played().length, 1, 'has length after playing');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2015-07-21 22:56:23 +02:00
|
|
|
});
|
2015-07-21 23:12:24 +02:00
|
|
|
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
QUnit.test('delegates deferrables to the source handler', function(assert) {
|
2022-06-02 18:05:57 +02:00
|
|
|
class MyTech extends Tech {
|
2016-08-03 21:27:03 +02:00
|
|
|
seekable() {
|
2015-07-21 23:12:24 +02:00
|
|
|
throw new Error('You should not be calling me!');
|
2022-06-02 18:05:57 +02:00
|
|
|
}
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
seeking() {
|
|
|
|
throw new Error('You should not be calling me!');
|
2022-06-02 18:05:57 +02:00
|
|
|
}
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
duration() {
|
|
|
|
throw new Error('You should not be calling me!');
|
2015-07-21 23:12:24 +02:00
|
|
|
}
|
2022-06-02 18:05:57 +02:00
|
|
|
}
|
2016-08-03 21:27:03 +02:00
|
|
|
|
2015-07-21 23:12:24 +02:00
|
|
|
Tech.withSourceHandlers(MyTech);
|
|
|
|
|
|
|
|
let seekableCount = 0;
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
let seekingCount = 0;
|
|
|
|
let durationCount = 0;
|
2016-08-03 21:27:03 +02:00
|
|
|
const handler = {
|
|
|
|
seekable() {
|
2015-07-21 23:12:24 +02:00
|
|
|
seekableCount++;
|
|
|
|
return createTimeRange(0, 0);
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
},
|
|
|
|
seeking() {
|
|
|
|
seekingCount++;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
duration() {
|
|
|
|
durationCount++;
|
|
|
|
return 0;
|
2015-07-21 23:12:24 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
MyTech.registerSourceHandler(stubbedSourceHandler(handler));
|
|
|
|
|
|
|
|
const tech = new MyTech();
|
|
|
|
|
|
|
|
tech.setSource({
|
|
|
|
src: 'example.mp4',
|
|
|
|
type: 'video/mp4'
|
|
|
|
});
|
|
|
|
tech.seekable();
|
|
|
|
tech.seeking();
|
|
|
|
tech.duration();
|
|
|
|
assert.equal(seekableCount, 1, 'called the source handler');
|
|
|
|
assert.equal(seekingCount, 1, 'called the source handler');
|
|
|
|
assert.equal(durationCount, 1, 'called the source handler');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
QUnit.test('delegates only deferred deferrables to the source handler', function(assert) {
|
|
|
|
let seekingCount = 0;
|
2022-06-02 18:05:57 +02:00
|
|
|
|
|
|
|
class MyTech extends Tech {
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
seekable() {
|
|
|
|
throw new Error('You should not be calling me!');
|
2022-06-02 18:05:57 +02:00
|
|
|
}
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
seeking() {
|
|
|
|
seekingCount++;
|
|
|
|
return false;
|
2022-06-02 18:05:57 +02:00
|
|
|
}
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
duration() {
|
|
|
|
throw new Error('You should not be calling me!');
|
2015-07-21 23:12:24 +02:00
|
|
|
}
|
2022-06-02 18:05:57 +02:00
|
|
|
}
|
2015-07-21 23:12:24 +02:00
|
|
|
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
Tech.withSourceHandlers(MyTech);
|
|
|
|
|
|
|
|
let seekableCount = 0;
|
|
|
|
let durationCount = 0;
|
|
|
|
const handler = {
|
|
|
|
seekable() {
|
|
|
|
seekableCount++;
|
|
|
|
return createTimeRange(0, 0);
|
|
|
|
},
|
|
|
|
duration() {
|
|
|
|
durationCount++;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
MyTech.registerSourceHandler(stubbedSourceHandler(handler));
|
|
|
|
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new MyTech();
|
|
|
|
|
2015-07-21 23:12:24 +02:00
|
|
|
tech.setSource({
|
|
|
|
src: 'example.mp4',
|
|
|
|
type: 'video/mp4'
|
|
|
|
});
|
|
|
|
tech.seekable();
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
tech.seeking();
|
|
|
|
tech.duration();
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(seekableCount, 1, 'called the source handler');
|
feat: Queue playback events when the playback rate is zero and we are seeking (#5024)
SourceHandlers that use MSE have a problem: if they push a segment into a SourceBuffer and then seek close to the end, playback will stall and/or there will be a massive downswitch in quality. The general approach to fixing this that was discussed on slack was by setting the playback rate of the player to zero, buffering all that was required, and then restoring the previous playback rate. In my implementation, I've done this in the source handler (see: videojs/videojs-contrib-hls#1374).
From the video.js perspective, it should ensure that the UI reflects the buffering status and that the player API behaves like you'd expect -- that is to say, that it will fire seeking immediately after a call to currentTime, and it will fire seeked, canplay, canplaythrough, and playing when everything is buffered.
2018-04-17 21:28:05 +02:00
|
|
|
assert.equal(seekingCount, 1, 'called the tech itself');
|
|
|
|
assert.equal(durationCount, 1, 'called the source handler');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2015-07-21 23:12:24 +02:00
|
|
|
});
|
2015-11-06 23:42:19 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('Tech.isTech returns correct answers for techs and components', function(assert) {
|
2016-08-03 21:27:03 +02:00
|
|
|
const isTech = Tech.isTech;
|
2019-03-18 21:49:48 +02:00
|
|
|
const tech = new Html5({}, {});
|
2020-11-11 01:09:37 +02:00
|
|
|
const button = new Button(TestHelpers.makePlayer(), {});
|
2016-08-03 21:27:03 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(isTech(Tech), 'Tech is a Tech');
|
|
|
|
assert.ok(isTech(Html5), 'Html5 is a Tech');
|
2019-03-18 21:49:48 +02:00
|
|
|
assert.ok(isTech(tech), 'An html5 instance is a Tech');
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(!isTech(5), 'A number is not a Tech');
|
|
|
|
assert.ok(!isTech('this is a tech'), 'A string is not a Tech');
|
|
|
|
assert.ok(!isTech(Button), 'A Button is not a Tech');
|
2019-03-18 21:49:48 +02:00
|
|
|
assert.ok(!isTech(button), 'A Button instance is not a Tech');
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(!isTech(isTech), 'A function is not a Tech');
|
2019-03-18 21:49:48 +02:00
|
|
|
|
|
|
|
tech.dispose();
|
|
|
|
button.dispose();
|
2015-11-06 23:42:19 +02:00
|
|
|
});
|
2016-05-03 00:54:34 +02:00
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('setSource after tech dispose should dispose source handler once', function(assert) {
|
2022-06-02 18:05:57 +02:00
|
|
|
class MyTech extends Tech {}
|
2016-08-03 21:27:03 +02:00
|
|
|
|
2016-05-28 00:12:47 +02:00
|
|
|
Tech.withSourceHandlers(MyTech);
|
|
|
|
|
|
|
|
let disposeCount = 0;
|
2016-08-03 21:27:03 +02:00
|
|
|
const handler = {
|
2016-05-28 00:12:47 +02:00
|
|
|
dispose() {
|
|
|
|
disposeCount++;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
MyTech.registerSourceHandler({
|
2016-08-03 21:27:03 +02:00
|
|
|
canPlayType() {
|
2016-05-28 00:12:47 +02:00
|
|
|
return true;
|
|
|
|
},
|
2016-08-03 21:27:03 +02:00
|
|
|
canHandleSource() {
|
2016-05-28 00:12:47 +02:00
|
|
|
return true;
|
|
|
|
},
|
2016-08-03 21:27:03 +02:00
|
|
|
handleSource(source, tech, options) {
|
2016-05-28 00:12:47 +02:00
|
|
|
return handler;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new MyTech();
|
|
|
|
|
2016-05-28 00:12:47 +02:00
|
|
|
tech.setSource('test');
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(disposeCount, 0, 'did not call sourceHandler_ dispose for initial dispose');
|
2016-05-28 00:12:47 +02:00
|
|
|
tech.dispose();
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.ok(!tech.sourceHandler_, 'sourceHandler should be unset');
|
|
|
|
assert.equal(disposeCount, 1, 'called the source handler dispose');
|
2016-05-28 00:12:47 +02:00
|
|
|
|
|
|
|
// this would normally be done above tech on src after dispose
|
|
|
|
tech.el_ = tech.createEl();
|
|
|
|
|
|
|
|
tech.setSource('test');
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(disposeCount, 1, 'did not dispose after initial setSource');
|
2016-05-28 00:12:47 +02:00
|
|
|
|
|
|
|
tech.setSource('test');
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(disposeCount, 2, 'did dispose on second setSource');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2016-05-28 00:12:47 +02:00
|
|
|
|
|
|
|
});
|
|
|
|
|
2016-08-12 19:51:31 +02:00
|
|
|
QUnit.test('setSource after previous setSource should dispose source handler once', function(assert) {
|
2022-06-02 18:05:57 +02:00
|
|
|
class MyTech extends Tech {}
|
2016-08-03 21:27:03 +02:00
|
|
|
|
2016-05-28 00:12:47 +02:00
|
|
|
Tech.withSourceHandlers(MyTech);
|
|
|
|
|
|
|
|
let disposeCount = 0;
|
2016-08-03 21:27:03 +02:00
|
|
|
const handler = {
|
2016-05-28 00:12:47 +02:00
|
|
|
dispose() {
|
|
|
|
disposeCount++;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
MyTech.registerSourceHandler({
|
2016-08-03 21:27:03 +02:00
|
|
|
canPlayType() {
|
2016-05-28 00:12:47 +02:00
|
|
|
return true;
|
|
|
|
},
|
2016-08-03 21:27:03 +02:00
|
|
|
canHandleSource() {
|
2016-05-28 00:12:47 +02:00
|
|
|
return true;
|
|
|
|
},
|
2016-08-03 21:27:03 +02:00
|
|
|
handleSource(source, tech, options) {
|
2016-05-28 00:12:47 +02:00
|
|
|
return handler;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-08-03 21:27:03 +02:00
|
|
|
const tech = new MyTech();
|
2016-05-28 00:12:47 +02:00
|
|
|
|
|
|
|
tech.setSource('test');
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(disposeCount, 0, 'did not call dispose for initial setSource');
|
2016-05-28 00:12:47 +02:00
|
|
|
|
|
|
|
tech.setSource('test');
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(disposeCount, 1, 'did dispose for second setSource');
|
2016-05-28 00:12:47 +02:00
|
|
|
|
|
|
|
tech.setSource('test');
|
2016-08-12 19:51:31 +02:00
|
|
|
assert.equal(disposeCount, 2, 'did dispose for third setSource');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2016-05-28 00:12:47 +02:00
|
|
|
|
|
|
|
});
|
2017-05-12 21:22:02 +02:00
|
|
|
|
|
|
|
QUnit.test('returns an empty object for getVideoPlaybackQuality', function(assert) {
|
|
|
|
const tech = new Tech();
|
|
|
|
|
|
|
|
assert.deepEqual(tech.getVideoPlaybackQuality(), {}, 'returns an empty object');
|
2019-03-18 21:49:48 +02:00
|
|
|
tech.dispose();
|
2017-05-12 21:22:02 +02:00
|
|
|
});
|
2022-09-09 19:52:34 +02:00
|
|
|
|
|
|
|
QUnit.test('requestVideoFrameCallback waits if tech not ready', function(assert) {
|
|
|
|
const tech = new Tech();
|
|
|
|
const cbSpy = sinon.spy();
|
|
|
|
|
|
|
|
tech.paused = sinon.spy();
|
|
|
|
tech.isReady_ = false;
|
|
|
|
|
|
|
|
tech.requestVideoFrameCallback(cbSpy);
|
|
|
|
|
|
|
|
assert.notOk(tech.paused.called, 'paused not called on tech that is not ready');
|
|
|
|
|
|
|
|
tech.trigger('playing');
|
|
|
|
|
|
|
|
assert.ok(cbSpy.called, 'callback was called on tech playing');
|
|
|
|
|
|
|
|
tech.dispose();
|
|
|
|
});
|