2015-02-14 01:18:07 +02:00
|
|
|
var noop = function() {}, clock, oldTextTracks;
|
|
|
|
|
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 Flash from '../../../src/js/tech/flash.js';
|
|
|
|
import Button from '../../../src/js/button.js';
|
2015-07-21 23:12:24 +02:00
|
|
|
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
|
2015-09-22 17:19:04 +02:00
|
|
|
import extendFn from '../../../src/js/extend.js';
|
2015-08-31 20:21:53 +02:00
|
|
|
import MediaError from '../../../src/js/media-error.js';
|
2015-03-11 03:01:11 +02:00
|
|
|
|
2015-03-26 06:43:41 +02:00
|
|
|
q.module('Media Tech', {
|
2014-08-14 02:44:36 +03:00
|
|
|
'setup': function() {
|
2014-12-03 21:31:39 +02:00
|
|
|
this.noop = function() {};
|
|
|
|
this.clock = sinon.useFakeTimers();
|
2015-04-14 22:08:32 +02:00
|
|
|
this.featuresProgessEvents = Tech.prototype['featuresProgessEvents'];
|
|
|
|
Tech.prototype['featuresProgressEvents'] = false;
|
|
|
|
Tech.prototype['featuresNativeTextTracks'] = true;
|
|
|
|
oldTextTracks = Tech.prototype.textTracks;
|
|
|
|
Tech.prototype.textTracks = function() {
|
2015-02-14 01:18:07 +02:00
|
|
|
return {
|
|
|
|
addEventListener: Function.prototype,
|
|
|
|
removeEventListener: Function.prototype
|
|
|
|
};
|
|
|
|
};
|
2014-08-14 02:44:36 +03:00
|
|
|
},
|
|
|
|
'teardown': function() {
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.restore();
|
2015-04-14 22:08:32 +02:00
|
|
|
Tech.prototype['featuresProgessEvents'] = this.featuresProgessEvents;
|
|
|
|
Tech.prototype['featuresNativeTextTracks'] = false;
|
|
|
|
Tech.prototype.textTracks = oldTextTracks;
|
2014-08-14 02:44:36 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should synthesize timeupdate events by default', function() {
|
2015-05-06 20:01:52 +02:00
|
|
|
var timeupdates = 0, tech;
|
|
|
|
|
|
|
|
tech = new Tech();
|
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);
|
2015-05-06 20:01:52 +02:00
|
|
|
equal(timeupdates, 1, 'triggered at least one timeupdate');
|
2014-08-14 02:44:36 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
test('stops manual timeupdates while paused', function() {
|
2015-05-06 20:01:52 +02:00
|
|
|
var timeupdates = 0, tech, expected;
|
|
|
|
tech = new Tech();
|
|
|
|
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);
|
2014-08-14 02:44:36 +03:00
|
|
|
ok(timeupdates > 0, 'timeupdates fire during playback');
|
|
|
|
|
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);
|
2014-08-14 02:44:36 +03:00
|
|
|
equal(timeupdates, 0, 'timeupdates do not fire when paused');
|
|
|
|
|
2015-05-06 20:01:52 +02:00
|
|
|
tech.trigger('play');
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.tick(10 * 250);
|
2014-08-14 02:44:36 +03:00
|
|
|
ok(timeupdates > 0, 'timeupdates fire when playback resumes');
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should synthesize progress events by default', function() {
|
2015-07-07 20:35:17 +02:00
|
|
|
var progresses = 0, bufferedPercent = 0.5, tech;
|
2015-05-06 20:01:52 +02:00
|
|
|
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);
|
|
|
|
equal(progresses, 0, 'waits until ready');
|
|
|
|
|
|
|
|
tech.trigger('ready');
|
2014-12-03 21:31:39 +02:00
|
|
|
this.clock.tick(500);
|
2014-08-14 02:44:36 +03:00
|
|
|
equal(progresses, 1, 'triggered one event');
|
2015-07-07 20:35:17 +02:00
|
|
|
|
|
|
|
tech.trigger('ready');
|
|
|
|
bufferedPercent = 0.75;
|
|
|
|
this.clock.tick(500);
|
|
|
|
equal(progresses, 2, 'repeated readies are ok');
|
2014-08-14 02:44:36 +03:00
|
|
|
});
|
2014-08-20 21:39:02 +03:00
|
|
|
|
|
|
|
test('dispose() should stop time tracking', function() {
|
2015-05-06 20:01:52 +02:00
|
|
|
var 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) {
|
|
|
|
return equal(e, undefined, 'threw an exception');
|
|
|
|
}
|
|
|
|
ok(true, 'no exception was thrown');
|
|
|
|
});
|
2014-12-03 00:22:34 +02:00
|
|
|
|
2015-07-21 23:12:24 +02:00
|
|
|
test('should add the source handler interface to a tech', function(){
|
2014-12-03 00:22:34 +02:00
|
|
|
var sourceA = { src: 'foo.mp4', type: 'video/mp4' };
|
|
|
|
var sourceB = { src: 'no-support', type: 'no-support' };
|
|
|
|
|
|
|
|
// Define a new tech class
|
2015-09-22 17:19:04 +02:00
|
|
|
var MyTech = extendFn(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
|
2015-04-14 22:08:32 +02:00
|
|
|
ok(MyTech.registerSourceHandler, 'added a registerSourceHandler function to the Tech');
|
|
|
|
ok(MyTech.selectSourceHandler, 'added a selectSourceHandler function to the Tech');
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Create an instance of Tech
|
2015-05-06 20:01:52 +02:00
|
|
|
var tech = new MyTech();
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// 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
|
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
|
|
|
|
var disposeCalled = false;
|
|
|
|
var handlerInternalState = function(){};
|
|
|
|
handlerInternalState.prototype.dispose = function(){
|
|
|
|
disposeCalled = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Create source handlers
|
|
|
|
var handlerOne = {
|
2015-10-27 19:46:05 +02:00
|
|
|
canPlayType: function(type){
|
|
|
|
if (type !=='no-support') {
|
|
|
|
return 'probably';
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
},
|
2014-12-03 00:22:34 +02:00
|
|
|
canHandleSource: function(source){
|
|
|
|
if (source.type !=='no-support') {
|
|
|
|
return 'probably';
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
},
|
2016-04-13 20:28:09 +02:00
|
|
|
handleSource: function(s, t, o){
|
2014-12-03 00:22:34 +02:00
|
|
|
strictEqual(tech, t, 'the tech instance was passed to the source handler');
|
|
|
|
strictEqual(sourceA, s, 'the tech instance was passed to the source handler');
|
2016-04-13 20:28:09 +02:00
|
|
|
strictEqual(tech.options_, o, 'the tech options were passed to the source handler');
|
2014-12-03 00:22:34 +02:00
|
|
|
return new handlerInternalState();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var handlerTwo = {
|
2015-10-27 19:46:05 +02:00
|
|
|
canPlayType: function(type){
|
|
|
|
return ''; // no support
|
|
|
|
},
|
2014-12-03 00:22:34 +02:00
|
|
|
canHandleSource: function(source){
|
|
|
|
return ''; // no support
|
|
|
|
},
|
2016-04-13 20:28:09 +02:00
|
|
|
handleSource: function(source, tech, options){
|
2014-12-03 00:22:34 +02:00
|
|
|
ok(false, 'handlerTwo supports nothing and should never be called');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Test registering source handlers
|
2015-04-14 22:08:32 +02:00
|
|
|
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)');
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// Test handler selection
|
2015-04-14 22:08:32 +02:00
|
|
|
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');
|
2014-12-03 00:22:34 +02:00
|
|
|
|
2015-10-27 19:46:05 +02:00
|
|
|
// Test canPlayType return values
|
|
|
|
strictEqual(MyTech.canPlayType(sourceA.type), 'probably', 'the Tech returned probably for the valid source');
|
|
|
|
strictEqual(MyTech.canPlayType(sourceB.type), '', 'the Tech returned an empty string for the invalid source');
|
|
|
|
|
2014-12-03 00:22:34 +02:00
|
|
|
// Test canPlaySource return values
|
2015-04-14 22:08:32 +02:00
|
|
|
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');
|
2014-12-03 00:22:34 +02:00
|
|
|
|
|
|
|
// 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');
|
|
|
|
});
|
2015-02-28 01:50:13 +02:00
|
|
|
|
2015-07-21 23:12:24 +02:00
|
|
|
test('should handle unsupported sources with the source handler API', function(){
|
2015-02-28 01:50:13 +02:00
|
|
|
// Define a new tech class
|
2015-09-22 17:19:04 +02:00
|
|
|
var MyTech = extendFn(Tech);
|
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
|
2015-05-06 20:01:52 +02:00
|
|
|
var tech = new MyTech();
|
2015-02-28 01:50:13 +02:00
|
|
|
|
|
|
|
var usedNative;
|
2015-04-14 22:08:32 +02:00
|
|
|
MyTech.nativeSourceHandler = {
|
2015-02-28 01:50:13 +02:00
|
|
|
handleSource: function(){ usedNative = true; }
|
|
|
|
};
|
|
|
|
|
|
|
|
tech.setSource('');
|
|
|
|
ok(usedNative, 'native source handler was used when an unsupported source was set');
|
2015-03-11 03:01:11 +02:00
|
|
|
});
|
2015-07-21 22:56:23 +02:00
|
|
|
|
2015-08-31 20:21:53 +02:00
|
|
|
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');
|
|
|
|
});
|
|
|
|
|
2015-07-21 22:56:23 +02:00
|
|
|
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');
|
|
|
|
});
|
2015-07-21 23:12:24 +02:00
|
|
|
|
|
|
|
test('delegates seekable to the source handler', function(){
|
2015-09-22 17:19:04 +02:00
|
|
|
let MyTech = extendFn(Tech, {
|
2015-07-21 23:12:24 +02:00
|
|
|
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({
|
2015-10-27 19:46:05 +02:00
|
|
|
canPlayType: function() {
|
|
|
|
return true;
|
|
|
|
},
|
2015-07-21 23:12:24 +02:00
|
|
|
canHandleSource: function() {
|
|
|
|
return true;
|
|
|
|
},
|
2016-04-13 20:28:09 +02:00
|
|
|
handleSource: function(source, tech, options) {
|
2015-07-21 23:12:24 +02:00
|
|
|
return handler;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let tech = new MyTech();
|
|
|
|
tech.setSource({
|
|
|
|
src: 'example.mp4',
|
|
|
|
type: 'video/mp4'
|
|
|
|
});
|
|
|
|
tech.seekable();
|
|
|
|
equal(seekableCount, 1, 'called the source handler');
|
|
|
|
});
|
2015-11-06 23:42:19 +02:00
|
|
|
|
|
|
|
test('Tech.isTech returns correct answers for techs and components', function() {
|
|
|
|
let isTech = Tech.isTech;
|
|
|
|
|
|
|
|
ok(isTech(Tech), 'Tech is a Tech');
|
|
|
|
ok(isTech(Html5), 'Html5 is a Tech');
|
|
|
|
ok(isTech(new Html5({}, {})), 'An html5 instance is a Tech');
|
|
|
|
ok(isTech(Flash), 'Flash is a Tech');
|
|
|
|
ok(!isTech(5), 'A number is not a Tech');
|
|
|
|
ok(!isTech('this is a tech'), 'A string is not a Tech');
|
|
|
|
ok(!isTech(Button), 'A Button is not a Tech');
|
|
|
|
ok(!isTech(new Button({}, {})), 'A Button instance is not a Tech');
|
|
|
|
ok(!isTech(isTech), 'A function is not a Tech');
|
|
|
|
});
|