homepagePHP/www/Public/Addons/Qiniu/videojs-media-sources.js

265 lines
8.4 KiB
JavaScript
Executable File

(function(window){
var urlCount = 0,
NativeMediaSource = window.MediaSource || window.WebKitMediaSource || {},
nativeUrl = window.URL || {},
EventEmitter,
flvCodec = /video\/flv(;\s*codecs=["']vp6,aac["'])?$/,
objectUrlPrefix = 'blob:vjs-media-source/';
EventEmitter = function(){};
EventEmitter.prototype.init = function(){
this.listeners = [];
};
EventEmitter.prototype.addEventListener = function(type, listener){
if (!this.listeners[type]){
this.listeners[type] = [];
}
this.listeners[type].unshift(listener);
};
EventEmitter.prototype.removeEventListener = function(type, listener){
var listeners = this.listeners[type],
i = listeners.length;
while (i--) {
if (listeners[i] === listener) {
return listeners.splice(i, 1);
}
}
};
EventEmitter.prototype.trigger = function(event){
var listeners = this.listeners[event.type] || [],
i = listeners.length;
while (i--) {
listeners[i](event);
}
};
// extend the media source APIs
// Media Source
videojs.MediaSource = function(){
var self = this;
videojs.MediaSource.prototype.init.call(this);
this.sourceBuffers = [];
this.readyState = 'closed';
this.listeners = {
sourceopen: [function(event){
// find the swf where we will push media data
self.swfObj = document.getElementById(event.swfId);
self.readyState = 'open';
// trigger load events
if (self.swfObj) {
self.swfObj.vjs_load();
}
}],
webkitsourceopen: [function(event){
self.trigger({
type: 'sourceopen'
});
}]
};
};
videojs.MediaSource.prototype = new EventEmitter();
/**
* The maximum size in bytes for append operations to the video.js
* SWF. Calling through to Flash blocks and can be expensive so
* tuning this parameter may improve playback on slower
* systems. There are two factors to consider:
* - Each interaction with the SWF must be quick or you risk dropping
* video frames. To maintain 60fps for the rest of the page, each append
* cannot take longer than 16ms. Given the likelihood that the page will
* be executing more javascript than just playback, you probably want to
* aim for ~8ms.
* - Bigger appends significantly increase throughput. The total number of
* bytes over time delivered to the SWF must exceed the video bitrate or
* playback will stall.
*
* The default is set so that a 4MB/s stream should playback
* without stuttering.
*/
videojs.MediaSource.BYTES_PER_SECOND_GOAL = 4 * 1024 * 1024;
videojs.MediaSource.TICKS_PER_SECOND = 60;
// create a new source buffer to receive a type of media data
videojs.MediaSource.prototype.addSourceBuffer = function(type){
var sourceBuffer;
// if this is an FLV type, we'll push data to flash
if (flvCodec.test(type)) {
// Flash source buffers
sourceBuffer = new videojs.SourceBuffer(this);
} else if (this.nativeSource) {
// native source buffers
sourceBuffer = this.nativeSource.addSourceBuffer.apply(this.nativeSource, arguments);
} else {
throw new Error('NotSupportedError (Video.js)');
}
this.sourceBuffers.push(sourceBuffer);
return sourceBuffer;
};
videojs.MediaSource.prototype.endOfStream = function(){
this.swfObj.vjs_endOfStream();
this.readyState = 'ended';
};
// store references to the media sources so they can be connected
// to a video element (a swf object)
videojs.mediaSources = {};
// provide a method for a swf object to notify JS that a media source is now open
videojs.MediaSource.open = function(msObjectURL, swfId){
var mediaSource = videojs.mediaSources[msObjectURL];
if (mediaSource) {
mediaSource.trigger({
type: 'sourceopen',
swfId: swfId
});
} else {
throw new Error('Media Source not found (Video.js)');
}
};
// Source Buffer
videojs.SourceBuffer = function(source){
var self = this,
// byte arrays queued to be appended
buffer = [],
// the total number of queued bytes
bufferSize = 0,
scheduleTick = function(func) {
// Chrome doesn't invoke requestAnimationFrame callbacks
// in background tabs, so use setTimeout.
window.setTimeout(func,
Math.ceil(1000 / videojs.MediaSource.TICKS_PER_SECOND));
},
append = function() {
var chunk, i, length, payload, maxSize,
binary = '';
if (!buffer.length) {
// do nothing if the buffer is empty
return;
}
if (document.hidden) {
// When the document is hidden, the browser will likely
// invoke callbacks less frequently than we want. Just
// append a whole second's worth of data. It doesn't
// matter if the video janks, since the user can't see it.
maxSize = videojs.MediaSource.BYTES_PER_SECOND_GOAL;
} else {
maxSize = Math.ceil(videojs.MediaSource.BYTES_PER_SECOND_GOAL/
videojs.MediaSource.TICKS_PER_SECOND);
}
// concatenate appends up to the max append size
payload = new Uint8Array(Math.min(maxSize, bufferSize));
i = payload.byteLength;
while (i) {
chunk = buffer[0].subarray(0, i);
payload.set(chunk, payload.byteLength - i);
// requeue any bytes that won't make it this round
if (chunk.byteLength < buffer[0].byteLength) {
buffer[0] = buffer[0].subarray(i);
} else {
buffer.shift();
}
i -= chunk.byteLength;
}
bufferSize -= payload.byteLength;
// schedule another append if necessary
if (bufferSize !== 0) {
scheduleTick(append);
} else {
self.trigger({ type: 'updateend' });
}
// base64 encode the bytes
for (i = 0, length = payload.byteLength; i < length; i++) {
binary += String.fromCharCode(payload[i]);
}
b64str = window.btoa(binary);
// bypass normal ExternalInterface calls and pass xml directly
// EI can be slow by default
self.source.swfObj.CallFunction('<invoke name="vjs_appendBuffer"' +
'returntype="javascript"><arguments><string>' +
b64str +
'</string></arguments></invoke>');
};
videojs.SourceBuffer.prototype.init.call(this);
this.source = source;
// accept video data and pass to the video (swf) object
this.appendBuffer = function(uint8Array){
if (buffer.length === 0) {
scheduleTick(append);
}
this.trigger({ type: 'update' });
buffer.push(uint8Array);
bufferSize += uint8Array.byteLength;
};
// reset the parser and remove any data queued to be sent to the swf
this.abort = function() {
buffer = [];
bufferSize = 0;
this.source.swfObj.vjs_abort();
};
};
videojs.SourceBuffer.prototype = new EventEmitter();
// URL
videojs.URL = {
createObjectURL: function(object){
var url = objectUrlPrefix + urlCount;
urlCount++;
// setup the mapping back to object
videojs.mediaSources[url] = object;
return url;
}
};
// plugin
videojs.plugin('mediaSource', function(options){
var player = this;
player.on('loadstart', function(){
var url = player.currentSrc(),
trigger = function(event){
mediaSource.trigger(event);
},
mediaSource;
if (player.techName === 'Html5' && url.indexOf(objectUrlPrefix) === 0) {
// use the native media source implementation
mediaSource = videojs.mediaSources[url];
if (!mediaSource.nativeUrl) {
// initialize the native source
mediaSource.nativeSource = new NativeMediaSource();
mediaSource.nativeSource.addEventListener('sourceopen', trigger, false);
mediaSource.nativeSource.addEventListener('webkitsourceopen', trigger, false);
mediaSource.nativeUrl = nativeUrl.createObjectURL(mediaSource.nativeSource);
}
player.src(mediaSource.nativeUrl);
}
});
});
})(this);