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:
parent
8a019ee046
commit
76e662a756
@ -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))
|
||||
|
||||
--------------------
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
||||
|
88
src/js/media/flash.rtmp.js
Normal file
88
src/js/media/flash.rtmp.js
Normal 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);
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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
151
src/js/xhr.js
Normal 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;
|
||||
};
|
@ -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",
|
||||
|
@ -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');
|
||||
});
|
||||
|
@ -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');
|
||||
});
|
||||
|
@ -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');
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user