1
0
mirror of https://github.com/videojs/video.js.git synced 2024-12-14 11:23:30 +02:00
video.js/test/unit/tech/tech.test.js

258 lines
7.7 KiB
JavaScript

var noop = function() {}, clock, oldTextTracks;
import Tech from '../../../src/js/tech/tech.js';
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
import extendFn from '../../../src/js/extend.js';
import MediaError from '../../../src/js/media-error.js';
q.module('Media Tech', {
'setup': function() {
this.noop = function() {};
this.clock = sinon.useFakeTimers();
this.featuresProgessEvents = Tech.prototype['featuresProgessEvents'];
Tech.prototype['featuresProgressEvents'] = false;
Tech.prototype['featuresNativeTextTracks'] = true;
oldTextTracks = Tech.prototype.textTracks;
Tech.prototype.textTracks = function() {
return {
addEventListener: Function.prototype,
removeEventListener: Function.prototype
};
};
},
'teardown': function() {
this.clock.restore();
Tech.prototype['featuresProgessEvents'] = this.featuresProgessEvents;
Tech.prototype['featuresNativeTextTracks'] = false;
Tech.prototype.textTracks = oldTextTracks;
}
});
test('should synthesize timeupdate events by default', function() {
var timeupdates = 0, tech;
tech = new Tech();
tech.on('timeupdate', function() {
timeupdates++;
});
tech.trigger('play');
this.clock.tick(250);
equal(timeupdates, 1, 'triggered at least one timeupdate');
});
test('stops manual timeupdates while paused', function() {
var timeupdates = 0, tech, expected;
tech = new Tech();
tech.on('timeupdate', function() {
timeupdates++;
});
tech.trigger('play');
this.clock.tick(10 * 250);
ok(timeupdates > 0, 'timeupdates fire during playback');
tech.trigger('pause');
timeupdates = 0;
this.clock.tick(10 * 250);
equal(timeupdates, 0, 'timeupdates do not fire when paused');
tech.trigger('play');
this.clock.tick(10 * 250);
ok(timeupdates > 0, 'timeupdates fire when playback resumes');
});
test('should synthesize progress events by default', function() {
var progresses = 0, bufferedPercent = 0.5, tech;
tech = new Tech();
tech.on('progress', function() {
progresses++;
});
tech.bufferedPercent = function() {
return bufferedPercent;
};
this.clock.tick(500);
equal(progresses, 0, 'waits until ready');
tech.trigger('ready');
this.clock.tick(500);
equal(progresses, 1, 'triggered one event');
tech.trigger('ready');
bufferedPercent = 0.75;
this.clock.tick(500);
equal(progresses, 2, 'repeated readies are ok');
});
test('dispose() should stop time tracking', function() {
var tech = new Tech();
tech.dispose();
// progress and timeupdate events will throw exceptions after the
// tech is disposed
try {
this.clock.tick(10 * 1000);
} catch (e) {
return equal(e, undefined, 'threw an exception');
}
ok(true, 'no exception was thrown');
});
test('should add the source handler interface to a tech', function(){
var sourceA = { src: 'foo.mp4', type: 'video/mp4' };
var sourceB = { src: 'no-support', type: 'no-support' };
// Define a new tech class
var MyTech = extendFn(Tech);
// Extend Tech with source handlers
Tech.withSourceHandlers(MyTech);
// Check for the expected class methods
ok(MyTech.registerSourceHandler, 'added a registerSourceHandler function to the Tech');
ok(MyTech.selectSourceHandler, 'added a selectSourceHandler function to the Tech');
// Create an instance of Tech
var tech = new MyTech();
// Check for the expected instance methods
ok(tech.setSource, 'added a setSource function to the tech instance');
// Create an internal state class for the source handler
// The internal class would be used by a source handler to maintain state
// and provde a dispose method for the handler.
// This is optional for source handlers
var disposeCalled = false;
var handlerInternalState = function(){};
handlerInternalState.prototype.dispose = function(){
disposeCalled = true;
};
// Create source handlers
var handlerOne = {
canHandleSource: function(source){
if (source.type !=='no-support') {
return 'probably';
}
return '';
},
handleSource: function(s, t){
strictEqual(tech, t, 'the tech instance was passed to the source handler');
strictEqual(sourceA, s, 'the tech instance was passed to the source handler');
return new handlerInternalState();
}
};
var handlerTwo = {
canHandleSource: function(source){
return ''; // no support
},
handleSource: function(source, tech){
ok(false, 'handlerTwo supports nothing and should never be called');
}
};
// Test registering source handlers
MyTech.registerSourceHandler(handlerOne);
strictEqual(MyTech.sourceHandlers[0], handlerOne, 'handlerOne was added to the source handler array');
MyTech.registerSourceHandler(handlerTwo, 0);
strictEqual(MyTech.sourceHandlers[0], handlerTwo, 'handlerTwo was registered at the correct index (0)');
// Test handler selection
strictEqual(MyTech.selectSourceHandler(sourceA), handlerOne, 'handlerOne was selected to handle the valid source');
strictEqual(MyTech.selectSourceHandler(sourceB), null, 'no handler was selected to handle the invalid source');
// Test canPlaySource return values
strictEqual(MyTech.canPlaySource(sourceA), 'probably', 'the Tech returned probably for the valid source');
strictEqual(MyTech.canPlaySource(sourceB), '', 'the Tech returned an empty string for the invalid source');
// Pass a source through the source handler process of a tech instance
tech.setSource(sourceA);
strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
// Check that the handler dipose method works
ok(!disposeCalled, 'dispose has not been called for the handler yet');
tech.dispose();
ok(disposeCalled, 'the handler dispose method was called when the tech was disposed');
});
test('should handle unsupported sources with the source handler API', function(){
// Define a new tech class
var MyTech = extendFn(Tech);
// Extend Tech with source handlers
Tech.withSourceHandlers(MyTech);
// Create an instance of Tech
var tech = new MyTech();
var usedNative;
MyTech.nativeSourceHandler = {
handleSource: function(){ usedNative = true; }
};
tech.setSource('');
ok(usedNative, 'native source handler was used when an unsupported source was set');
});
test('should allow custom error events to be set', function() {
let tech = new Tech();
let errors = [];
tech.on('error', function() {
errors.push(tech.error());
});
equal(tech.error(), null, 'error is null by default');
tech.error(new MediaError(1));
equal(errors.length, 1, 'triggered an error event');
equal(errors[0].code, 1, 'set the proper code');
tech.error(2);
equal(errors.length, 2, 'triggered an error event');
equal(errors[1].code, 2, 'wrapped the error code');
});
test('should track whether a video has played', function() {
let tech = new Tech();
equal(tech.played().length, 0, 'starts with zero length');
tech.trigger('playing');
equal(tech.played().length, 1, 'has length after playing');
});
test('delegates seekable to the source handler', function(){
let MyTech = extendFn(Tech, {
seekable: function() {
throw new Error('You should not be calling me!');
}
});
Tech.withSourceHandlers(MyTech);
let seekableCount = 0;
let handler = {
seekable: function() {
seekableCount++;
return createTimeRange(0, 0);
}
};
MyTech.registerSourceHandler({
canHandleSource: function() {
return true;
},
handleSource: function(source, tech) {
return handler;
}
});
let tech = new MyTech();
tech.setSource({
src: 'example.mp4',
type: 'video/mp4'
});
tech.seekable();
equal(seekableCount, 1, 'called the source handler');
});