1
0
mirror of https://github.com/videojs/video.js.git synced 2025-01-10 23:30:03 +02:00

@heff added the Source Handler interface for handling advanced formats including adaptive streaming. closes #1560

This commit is contained in:
Steve Heffernan 2014-12-02 14:22:34 -08:00 committed by heff
parent 8a019ee046
commit 76e662a756
14 changed files with 624 additions and 198 deletions

View File

@ -6,6 +6,7 @@ CHANGELOG
* @toloudis fixed an issue with checking for an existing source on the video element ([view](https://github.com/videojs/video.js/pull/1651))
* @rafalwrzeszcz fixed the Flash object tag markup for strict XML ([view](https://github.com/videojs/video.js/pull/1702))
* @thijstriemstra fixed a number of typos in the docs ([view](https://github.com/videojs/video.js/pull/1704))
* @heff added the Source Handler interface for handling advanced formats including adaptive streaming ([view](https://github.com/videojs/video.js/pull/1560))
--------------------

View File

@ -19,6 +19,7 @@ var sourceFiles = [
"src/js/core-object.js",
"src/js/events.js",
"src/js/lib.js",
"src/js/xhr.js",
"src/js/util.js",
"src/js/component.js",
"src/js/button.js",
@ -44,6 +45,7 @@ var sourceFiles = [
"src/js/media/media.js",
"src/js/media/html5.js",
"src/js/media/flash.js",
"src/js/media/flash.rtmp.js",
"src/js/media/loader.js",
"src/js/tracks.js",
"src/js/json.js",

View File

@ -614,86 +614,6 @@ vjs.createTimeRange = function(start, end){
};
};
/**
* Simple http request for retrieving external files (e.g. text tracks)
* @param {String} url URL of resource
* @param {Function} onSuccess Success callback
* @param {Function=} onError Error callback
* @param {Boolean=} withCredentials Flag which allow credentials
* @private
*/
vjs.get = function(url, onSuccess, onError, withCredentials){
var fileUrl, request, urlInfo, winLoc, crossOrigin;
onError = onError || function(){};
if (typeof XMLHttpRequest === 'undefined') {
// Shim XMLHttpRequest for older IEs
window.XMLHttpRequest = function () {
try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}
try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}
try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {}
throw new Error('This browser does not support XMLHttpRequest.');
};
}
request = new XMLHttpRequest();
urlInfo = vjs.parseUrl(url);
winLoc = window.location;
// check if url is for another domain/origin
// ie8 doesn't know location.origin, so we won't rely on it here
crossOrigin = (urlInfo.protocol + urlInfo.host) !== (winLoc.protocol + winLoc.host);
// Use XDomainRequest for IE if XMLHTTPRequest2 isn't available
// 'withCredentials' is only available in XMLHTTPRequest2
// Also XDomainRequest has a lot of gotchas, so only use if cross domain
if(crossOrigin && window.XDomainRequest && !('withCredentials' in request)) {
request = new window.XDomainRequest();
request.onload = function() {
onSuccess(request.responseText);
};
request.onerror = onError;
// these blank handlers need to be set to fix ie9 http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/
request.onprogress = function() {};
request.ontimeout = onError;
// XMLHTTPRequest
} else {
fileUrl = (urlInfo.protocol == 'file:' || winLoc.protocol == 'file:');
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200 || fileUrl && request.status === 0) {
onSuccess(request.responseText);
} else {
onError(request.responseText);
}
}
};
}
// open the connection
try {
// Third arg is async, or ignored by XDomainRequest
request.open('GET', url, true);
// withCredentials only supported by XMLHttpRequest2
if(withCredentials) {
request.withCredentials = true;
}
} catch(e) {
onError(e);
return;
}
// send the request
try {
request.send();
} catch(e) {
onError(e);
}
};
/**
* Add to local storage (may removable)
* @private

View File

@ -65,14 +65,9 @@ vjs.Flash = vjs.MediaTechController.extend({
// If source was supplied pass as a flash var.
if (source) {
if (source.type && vjs.Flash.isStreamingType(source.type)) {
var parts = vjs.Flash.streamToParts(source.src);
flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);
flashVars['rtmpStream'] = encodeURIComponent(parts.stream);
}
else {
flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
}
this.ready(function(){
this.setSource(source);
});
}
// Add placeholder to player div
@ -124,15 +119,14 @@ vjs.Flash.prototype.src = function(src){
return this['currentSrc']();
}
if (vjs.Flash.isStreamingSrc(src)) {
src = vjs.Flash.streamToParts(src);
this.setRtmpConnection(src.connection);
this.setRtmpStream(src.stream);
} else {
// Make sure source URL is absolute.
src = vjs.getAbsoluteURL(src);
this.el_.vjs_src(src);
}
// Setting src through `src` not `setSrc` will be deprecated
return this.setSrc(src);
};
vjs.Flash.prototype.setSrc = function(src){
// Make sure source URL is absolute.
src = vjs.getAbsoluteURL(src);
this.el_.vjs_src(src);
// Currently the SWF doesn't autoplay if you load a source later.
// e.g. Load player w/ no source, wait 2s, set src.
@ -158,17 +152,11 @@ vjs.Flash.prototype['currentTime'] = function(time){
};
vjs.Flash.prototype['currentSrc'] = function(){
var src = this.el_.vjs_getProperty('currentSrc');
// no src, check and see if RTMP
if (src == null) {
var connection = this['rtmpConnection'](),
stream = this['rtmpStream']();
if (connection && stream) {
src = vjs.Flash.streamFromParts(connection, stream);
}
if (this.currentSource_) {
return this.currentSource_.src;
} else {
return this.el_.vjs_getProperty('currentSrc');
}
return src;
};
vjs.Flash.prototype.load = function(){
@ -229,19 +217,59 @@ vjs.Flash.isSupported = function(){
// return swfobject.hasFlashPlayerVersion('10');
};
vjs.Flash.canPlaySource = function(srcObj){
// Add Source Handler pattern functions to this tech
vjs.MediaTechController.withSourceHandlers(vjs.Flash);
/**
* The default native source handler.
* This simply passes the source to the video element. Nothing fancy.
* @param {Object} source The source object
* @param {vjs.Flash} tech The instance of the Flash tech
*/
vjs.Flash.nativeSourceHandler = {};
/**
* Check Flash can handle the source natively
* @param {Object} source The source object
* @return {String} 'probably', 'maybe', or '' (empty string)
*/
vjs.Flash.nativeSourceHandler.canHandleSource = function(source){
var type;
if (!srcObj.type) {
if (!source.type) {
return '';
}
type = srcObj.type.replace(/;.*/,'').toLowerCase();
if (type in vjs.Flash.formats || type in vjs.Flash.streamingFormats) {
// Strip code information from the type because we don't get that specific
type = source.type.replace(/;.*/,'').toLowerCase();
if (type in vjs.Flash.formats) {
return 'maybe';
}
return '';
};
/**
* Pass the source to the flash object
* Adaptive source handlers will have more complicated workflows before passing
* video data to the video element
* @param {Object} source The source object
* @param {vjs.Flash} tech The instance of the Flash tech
*/
vjs.Flash.nativeSourceHandler.handleSource = function(source, tech){
tech.setSrc(source.src);
};
/**
* Clean up the source handler when disposing the player or switching sources..
* (no cleanup is needed when supporting the format natively)
*/
vjs.Flash.nativeSourceHandler.dispose = function(){};
// Register the native source handler
vjs.Flash.registerSourceHandler(vjs.Flash.nativeSourceHandler);
vjs.Flash.formats = {
'video/flv': 'FLV',
'video/x-flv': 'FLV',
@ -249,11 +277,6 @@ vjs.Flash.formats = {
'video/m4v': 'MP4'
};
vjs.Flash.streamingFormats = {
'rtmp/mp4': 'MP4',
'rtmp/flv': 'FLV'
};
vjs.Flash['onReady'] = function(currSwf){
var el, player;
@ -398,51 +421,3 @@ vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
return objTag + attrsString + '>' + paramsString + '</object>';
};
vjs.Flash.streamFromParts = function(connection, stream) {
return connection + '&' + stream;
};
vjs.Flash.streamToParts = function(src) {
var parts = {
connection: '',
stream: ''
};
if (! src) {
return parts;
}
// Look for the normal URL separator we expect, '&'.
// If found, we split the URL into two pieces around the
// first '&'.
var connEnd = src.indexOf('&');
var streamBegin;
if (connEnd !== -1) {
streamBegin = connEnd + 1;
}
else {
// If there's not a '&', we use the last '/' as the delimiter.
connEnd = streamBegin = src.lastIndexOf('/') + 1;
if (connEnd === 0) {
// really, there's not a '/'?
connEnd = streamBegin = src.length;
}
}
parts.connection = src.substring(0, connEnd);
parts.stream = src.substring(streamBegin, src.length);
return parts;
};
vjs.Flash.isStreamingType = function(srcType) {
return srcType in vjs.Flash.streamingFormats;
};
// RTMP has four variations, any string starting
// with one of these protocols should be valid
vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
vjs.Flash.isStreamingSrc = function(src) {
return vjs.Flash.RTMP_RE.test(src);
};

View File

@ -0,0 +1,88 @@
vjs.Flash.streamingFormats = {
'rtmp/mp4': 'MP4',
'rtmp/flv': 'FLV'
};
vjs.Flash.streamFromParts = function(connection, stream) {
return connection + '&' + stream;
};
vjs.Flash.streamToParts = function(src) {
var parts = {
connection: '',
stream: ''
};
if (! src) {
return parts;
}
// Look for the normal URL separator we expect, '&'.
// If found, we split the URL into two pieces around the
// first '&'.
var connEnd = src.indexOf('&');
var streamBegin;
if (connEnd !== -1) {
streamBegin = connEnd + 1;
}
else {
// If there's not a '&', we use the last '/' as the delimiter.
connEnd = streamBegin = src.lastIndexOf('/') + 1;
if (connEnd === 0) {
// really, there's not a '/'?
connEnd = streamBegin = src.length;
}
}
parts.connection = src.substring(0, connEnd);
parts.stream = src.substring(streamBegin, src.length);
return parts;
};
vjs.Flash.isStreamingType = function(srcType) {
return srcType in vjs.Flash.streamingFormats;
};
// RTMP has four variations, any string starting
// with one of these protocols should be valid
vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
vjs.Flash.isStreamingSrc = function(src) {
return vjs.Flash.RTMP_RE.test(src);
};
/**
* A source handler for RTMP urls
* @type {Object}
*/
vjs.Flash.rtmpSourceHandler = {};
/**
* Check Flash can handle the source natively
* @param {Object} source The source object
* @return {String} 'probably', 'maybe', or '' (empty string)
*/
vjs.Flash.rtmpSourceHandler.canHandleSource = function(source){
if (vjs.Flash.isStreamingType(source.type) || vjs.Flash.isStreamingSrc(source.src)) {
return 'maybe';
}
return '';
};
/**
* Pass the source to the flash object
* Adaptive source handlers will have more complicated workflows before passing
* video data to the video element
* @param {Object} source The source object
* @param {vjs.Flash} tech The instance of the Flash tech
*/
vjs.Flash.rtmpSourceHandler.handleSource = function(source, tech){
var srcParts = vjs.Flash.streamToParts(source.src);
tech.setRtmpConnection(srcParts.connection);
tech.setRtmpStream(srcParts.stream);
};
// Register the native source handler
vjs.Flash.registerSourceHandler(vjs.Flash.rtmpSourceHandler);

View File

@ -12,22 +12,8 @@
vjs.Html5 = vjs.MediaTechController.extend({
/** @constructor */
init: function(player, options, ready){
// volume cannot be changed from 1 on iOS
this['featuresVolumeControl'] = vjs.Html5.canControlVolume();
// just in case; or is it excessively...
this['featuresPlaybackRate'] = vjs.Html5.canControlPlaybackRate();
// In iOS, if you move a video element in the DOM, it breaks video playback.
this['movingMediaElementInDOM'] = !vjs.IS_IOS;
// HTML video is able to automatically resize when going to fullscreen
this['featuresFullscreenResize'] = true;
// HTML video supports progress events
this['featuresProgressEvents'] = true;
vjs.MediaTechController.call(this, player, options, ready);
this.setupTriggers();
var source = options['source'];
@ -36,8 +22,8 @@ vjs.Html5 = vjs.MediaTechController.extend({
// 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
// 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
// anyway so the error gets fired.
if (source && ((this.el_.currentSrc !== source.src) || (player.tag && player.tag.initNetworkState_ === 3))) {
this.el_.src = source.src;
if (source && (this.el_.currentSrc !== source.src || (player.tag && player.tag.initNetworkState_ === 3))) {
this.setSource(source);
}
// Determine if native controls should be used
@ -241,16 +227,25 @@ vjs.Html5.prototype.enterFullScreen = function(){
video.webkitEnterFullScreen();
}
};
vjs.Html5.prototype.exitFullScreen = function(){
this.el_.webkitExitFullScreen();
};
vjs.Html5.prototype.src = function(src) {
if (src === undefined) {
return this.el_.src;
} else {
this.el_.src = src;
// Setting src through `src` instead of `setSrc` will be deprecated
this.setSrc(src);
}
};
vjs.Html5.prototype.setSrc = function(src) {
this.el_.src = src;
};
vjs.Html5.prototype.load = function(){ this.el_.load(); };
vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
@ -279,10 +274,13 @@ vjs.Html5.prototype.setPlaybackRate = function(val){ this.el_.playbackRate = val
vjs.Html5.prototype.networkState = function(){ return this.el_.networkState; };
/* HTML5 Support Testing ---------------------------------------------------- */
/**
* Check if HTML5 video is supported by this browser/device
* @return {Boolean}
*/
vjs.Html5.isSupported = function(){
// ie9 with no Media Player is a LIAR! (#984)
// IE9 with no Media Player is a LIAR! (#984)
try {
vjs.TEST_VID['volume'] = 0.5;
} catch (e) {
@ -292,31 +290,119 @@ vjs.Html5.isSupported = function(){
return !!vjs.TEST_VID.canPlayType;
};
vjs.Html5.canPlaySource = function(srcObj){
// IE9 on Windows 7 without MediaPlayer throws an error here
// https://github.com/videojs/video.js/issues/519
try {
return !!vjs.TEST_VID.canPlayType(srcObj.type);
} catch(e) {
return '';
// Add Source Handler pattern functions to this tech
vjs.MediaTechController.withSourceHandlers(vjs.Html5);
/**
* The default native source handler.
* This simply passes the source to the video element. Nothing fancy.
* @param {Object} source The source object
* @param {vjs.Html5} tech The instance of the HTML5 tech
*/
vjs.Html5.nativeSourceHandler = {};
/**
* Check if the video element can handle the source natively
* @param {Object} source The source object
* @return {String} 'probably', 'maybe', or '' (empty string)
*/
vjs.Html5.nativeSourceHandler.canHandleSource = function(source){
var ext;
function canPlayType(type){
// IE9 on Windows 7 without MediaPlayer throws an error here
// https://github.com/videojs/video.js/issues/519
try {
return !!vjs.TEST_VID.canPlayType(type);
} catch(e) {
return '';
}
}
// If a type was provided we should rely on that
if (source.type) {
return canPlayType(source.type);
} else {
// If no type, fall back to checking 'video/[EXTENSION]'
ext = source.src.match(/\.([^\/\?]+)(\?[^\/]+)?$/i)[1];
return canPlayType('video/'+ext);
}
// TODO: Check Type
// If no Type, check ext
// Check Media Type
};
/**
* Pass the source to the video element
* Adaptive source handlers will have more complicated workflows before passing
* video data to the video element
* @param {Object} source The source object
* @param {vjs.Html5} tech The instance of the Html5 tech
*/
vjs.Html5.nativeSourceHandler.handleSource = function(source, tech){
tech.setSrc(source.src);
};
/**
* Clean up the source handler when disposing the player or switching sources..
* (no cleanup is needed when supporting the format natively)
*/
vjs.Html5.nativeSourceHandler.dispose = function(){};
// Register the native source handler
vjs.Html5.registerSourceHandler(vjs.Html5.nativeSourceHandler);
/**
* Check if the volume can be changed in this browser/device.
* Volume cannot be changed in a lot of mobile devices.
* Specifically, it can't be changed from 1 on iOS.
* @return {Boolean}
*/
vjs.Html5.canControlVolume = function(){
var volume = vjs.TEST_VID.volume;
vjs.TEST_VID.volume = (volume / 2) + 0.1;
return volume !== vjs.TEST_VID.volume;
};
/**
* Check if playbackRate is supported in this browser/device.
* @return {[type]} [description]
*/
vjs.Html5.canControlPlaybackRate = function(){
var playbackRate = vjs.TEST_VID.playbackRate;
vjs.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
return playbackRate !== vjs.TEST_VID.playbackRate;
};
/**
* Set the tech's volume control support status
* @type {Boolean}
*/
vjs.Html5.prototype['featuresVolumeControl'] = vjs.Html5.canControlVolume();
/**
* Set the tech's playbackRate support status
* @type {Boolean}
*/
vjs.Html5.prototype['featuresPlaybackRate'] = vjs.Html5.canControlPlaybackRate();
/**
* Set the tech's status on moving the video element.
* In iOS, if you move a video element in the DOM, it breaks video playback.
* @type {Boolean}
*/
vjs.Html5.prototype['movingMediaElementInDOM'] = !vjs.IS_IOS;
/**
* Set the the tech's fullscreen resize support status.
* HTML video is able to automatically resize when going to fullscreen.
* (No longer appears to be used. Can probably be removed.)
*/
vjs.Html5.prototype['featuresFullscreenResize'] = true;
/**
* Set the tech's progress event support status
* (this disables the manual progress events of the MediaTechController)
*/
vjs.Html5.prototype['featuresProgressEvents'] = true;
// HTML5 Feature detection and Device Fixes --------------------------------- //
(function() {
var canPlayType,

View File

@ -271,4 +271,102 @@ vjs.MediaTechController.prototype['featuresPlaybackRate'] = false;
vjs.MediaTechController.prototype['featuresProgressEvents'] = false;
vjs.MediaTechController.prototype['featuresTimeupdateEvents'] = false;
vjs.media = {};
/**
* A functional mixin for techs that want to use the Source Handler pattern.
*
* ##### EXAMPLE:
*
* videojs.MediaTechController.withSourceHandlers.call(MyTech);
*
*/
vjs.MediaTechController.withSourceHandlers = function(Tech){
/**
* Register a source handler
* Source handlers are scripts for handling specific formats.
* The source handler pattern is used for adaptive formats (HLS, DASH) that
* manually load video data and feed it into a Source Buffer (Media Source Extensions)
* @param {Function} handler The source handler
* @param {Boolean} first Register it before any existing handlers
*/
Tech.registerSourceHandler = function(handler, index){
var handlers = Tech.sourceHandlers;
if (!handlers) {
handlers = Tech.sourceHandlers = [];
}
if (index === undefined) {
// add to the end of the list
index = handlers.length;
}
handlers.splice(index, 0, handler);
};
/**
* Return the first source handler that supports the source
* TODO: Answer question: should 'probably' be prioritized over 'maybe'
* @param {Object} source The source object
* @returns {Object} The first source handler that supports the source
* @returns {null} Null if no source handler is found
*/
Tech.selectSourceHandler = function(source){
var handlers = Tech.sourceHandlers || [];
for (var i = 0; i < handlers.length; i++) {
can = handlers[i].canHandleSource(source);
if (can) {
return handlers[i];
}
}
return null;
};
/**
* Check if the tech can support the given source
* @param {Object} srcObj The source object
* @return {String} 'probably', 'maybe', or '' (empty string)
*/
Tech.canPlaySource = function(srcObj){
var sh = Tech.selectSourceHandler(srcObj);
if (sh) {
return sh.canHandleSource(srcObj);
}
return '';
};
/**
* Create a function for setting the source using a source object
* and source handlers.
* Should never be called unless a source handler was found.
* @param {Object} source A source object with src and type keys
* @return {vjs.MediaTechController} self
*/
Tech.prototype.setSource = function(source){
var sh = Tech.selectSourceHandler(source);
// Dispose any existing source handler
this.disposeSourceHandler();
this.off('dispose', this.disposeSourceHandler);
this.currentSource_ = source;
this.sourceHandler_ = sh.handleSource(source, this);
this.on('dispose', this.disposeSourceHandler);
return this;
};
/**
* Clean up any existing source handler
*/
Tech.prototype.disposeSourceHandler = function(){
if (this.sourceHandler_ && this.sourceHandler_.dispose) {
this.sourceHandler_.dispose();
}
};
};

View File

@ -1084,7 +1084,6 @@ vjs.Player.prototype.exitFullWindow = function(){
};
vjs.Player.prototype.selectSource = function(sources){
// Loop through each playback technology in the options order
for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
var techName = vjs.capitalize(j[i]),
@ -1173,7 +1172,14 @@ vjs.Player.prototype.src = function(source){
// wait until the tech is ready to set the source
this.ready(function(){
this.techCall('src', source.src);
// The setSource tech method was added with source handlers
// so older techs won't support it
if (this.tech['setSource']) {
this.techCall('setSource', source);
} else {
this.techCall('src', source.src);
}
if (this.options_['preload'] == 'auto') {
this.load();

View File

@ -422,7 +422,13 @@ vjs.TextTrack.prototype.load = function(){
// Only load if not loaded yet.
if (this.readyState_ === 0) {
this.readyState_ = 1;
vjs.get(this.src_, vjs.bind(this, this.parseCues), vjs.bind(this, this.onError));
vjs.xhr(this.src_, vjs.bind(function(err, response, responseBody){
if (err) {
return this.onError(err);
}
this.parseCues(responseBody);
}));
}
};

151
src/js/xhr.js Normal file
View File

@ -0,0 +1,151 @@
/**
* Simple http request for retrieving external files (e.g. text tracks)
*
* ##### Example
*
* // using url string
* videojs.xhr('http://example.com/myfile.vtt', function(error, response, responseBody){});
*
* // or options block
* videojs.xhr({
* uri: 'http://example.com/myfile.vtt',
* method: 'GET',
* responseType: 'text'
* }, function(error, response, responseBody){
* if (error) {
* // log the error
* } else {
* // successful, do something with the response
* }
* });
*
*
* API is modeled after the Raynos/xhr, which we hope to use after
* getting browserify implemented.
* https://github.com/Raynos/xhr/blob/master/index.js
*
* @param {Object|String} options Options block or URL string
* @param {Function} callback The callback function
* @returns {Object} The request
*/
vjs.xhr = function(options, callback){
var XHR, request, urlInfo, winLoc, fileUrl, crossOrigin, abortTimeout, successHandler, errorHandler;
// If options is a string it's the url
if (typeof options === 'string') {
options = {
uri: options
};
}
// Merge with default options
videojs.util.mergeOptions({
method: 'GET',
timeout: 45 * 1000
}, options);
callback = callback || function(){};
successHandler = function(){
window.clearTimeout(abortTimeout);
callback(null, request, request.response || request.responseText);
};
errorHandler = function(err){
window.clearTimeout(abortTimeout);
if (!err || typeof err === 'string') {
err = new Error(err);
}
callback(err, request);
};
XHR = window.XMLHttpRequest;
if (typeof XHR === 'undefined') {
// Shim XMLHttpRequest for older IEs
XHR = function () {
try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}
try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}
try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {}
throw new Error('This browser does not support XMLHttpRequest.');
};
}
request = new XHR();
// Store a reference to the url on the request instance
request.uri = options.uri;
urlInfo = vjs.parseUrl(options.uri);
winLoc = window.location;
// Check if url is for another domain/origin
// IE8 doesn't know location.origin, so we won't rely on it here
crossOrigin = (urlInfo.protocol + urlInfo.host) !== (winLoc.protocol + winLoc.host);
// XDomainRequest -- Use for IE if XMLHTTPRequest2 isn't available
// 'withCredentials' is only available in XMLHTTPRequest2
// Also XDomainRequest has a lot of gotchas, so only use if cross domain
if (crossOrigin && window.XDomainRequest && !('withCredentials' in request)) {
request = new window.XDomainRequest();
request.onload = successHandler;
request.onerror = errorHandler;
// These blank handlers need to be set to fix ie9
// http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/
request.onprogress = function(){};
request.ontimeout = function(){};
// XMLHTTPRequest
} else {
fileUrl = (urlInfo.protocol == 'file:' || winLoc.protocol == 'file:');
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.timedout) {
return errorHandler('timeout');
}
if (request.status === 200 || fileUrl && request.status === 0) {
successHandler();
} else {
errorHandler();
}
}
};
if (options.timeout) {
abortTimeout = window.setTimeout(function() {
if (request.readyState !== 4) {
request.timedout = true;
request.abort();
}
}, options.timeout);
}
}
// open the connection
try {
// Third arg is async, or ignored by XDomainRequest
request.open(options.method || 'GET', options.uri, true);
} catch(err) {
return errorHandler(err);
}
// withCredentials only supported by XMLHttpRequest2
if(options.withCredentials) {
request.withCredentials = true;
}
if (options.responseType) {
request.responseType = options.responseType;
}
// send the request
try {
request.send();
} catch(err) {
return errorHandler(err);
}
return request;
};

View File

@ -65,6 +65,7 @@ module.exports = function(config) {
"../src/js/core-object.js",
"../src/js/events.js",
"../src/js/lib.js",
"../src/js/xhr.js",
"../src/js/util.js",
"../src/js/component.js",
"../src/js/button.js",
@ -89,6 +90,7 @@ module.exports = function(config) {
"../src/js/media/media.js",
"../src/js/media/html5.js",
"../src/js/media/flash.js",
"../src/js/media/flash.rtmp.js",
"../src/js/media/loader.js",
"../src/js/tracks.js",
"../src/js/json.js",

View File

@ -143,3 +143,7 @@ test('ready triggering before and after disposing the tech', function() {
vjs.Flash['checkReady'].restore();
});
test('should have the source handler interface', function() {
ok(vjs.Flash.registerSourceHandler, 'has the registerSourceHandler function');
});

View File

@ -130,3 +130,7 @@ test('error events may not set the errors property', function() {
tech.trigger('error');
ok(true, 'no error was thrown');
});
test('should have the source handler interface', function() {
ok(vjs.Html5.registerSourceHandler, 'has the registerSourceHandler function');
});

View File

@ -134,3 +134,86 @@ test('dispose() should stop time tracking', function() {
}
ok(true, 'no exception was thrown');
});
test('should add the source hanlder interface to a tech', function(){
var mockPlayer = {
off: noop,
trigger: noop
};
var sourceA = { src: 'foo.mp4', type: 'video/mp4' };
var sourceB = { src: 'no-support', type: 'no-support' };
// Define a new tech class
var Tech = videojs.MediaTechController.extend();
// Extend Tech with source handlers
vjs.MediaTechController.withSourceHandlers(Tech);
// Check for the expected class methods
ok(Tech.registerSourceHandler, 'added a registerSourceHandler function to the Tech');
ok(Tech.selectSourceHandler, 'added a selectSourceHandler function to the Tech');
// Create an instance of Tech
var tech = new Tech(mockPlayer);
// 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 hanlder 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
Tech.registerSourceHandler(handlerOne);
strictEqual(Tech.sourceHandlers[0], handlerOne, 'handlerOne was added to the source handler array');
Tech.registerSourceHandler(handlerTwo, 0);
strictEqual(Tech.sourceHandlers[0], handlerTwo, 'handlerTwo was registered at the correct index (0)');
// Test handler selection
strictEqual(Tech.selectSourceHandler(sourceA), handlerOne, 'handlerOne was selected to handle the valid source');
strictEqual(Tech.selectSourceHandler(sourceB), null, 'no handler was selected to handle the invalid source');
// Test canPlaySource return values
strictEqual(Tech.canPlaySource(sourceA), 'probably', 'the Tech returned probably for the valid source');
strictEqual(Tech.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');
});