Pro/public/static/jsmpeg/player.js
2026-02-25 01:50:31 +08:00

295 lines
7.6 KiB
JavaScript

JSMpeg.Player = (function(){ "use strict";
var Player = function(url, options) {
options = options || {};
this.options = options;
if (options.source) {
this.source = new options.source(url, options);
options.streaming = !!this.source.streaming;
}
else if (url.match(/^wss?:\/\//)) {
this.source = new JSMpeg.Source.WebSocket(url, options);
options.streaming = true;
}
else if (options.progressive !== false) {
this.source = new JSMpeg.Source.AjaxProgressive(url, options);
options.streaming = false;
}
else {
this.source = new JSMpeg.Source.Ajax(url, options);
options.streaming = false;
}
this.maxAudioLag = options.maxAudioLag || 0.25;
this.loop = options.loop !== false;
this.autoplay = !!options.autoplay || options.streaming;
this.demuxer = new JSMpeg.Demuxer.TS(options);
this.source.connect(this.demuxer);
if (options.video !== false) {
this.video = new JSMpeg.Decoder.MPEG1Video(options);
this.renderer = !options.disableGl && JSMpeg.Renderer.WebGL.IsSupported()
? new JSMpeg.Renderer.WebGL(options)
: new JSMpeg.Renderer.Canvas2D(options);
this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.VIDEO_1, this.video);
this.video.connect(this.renderer);
}
if (options.audio !== false && JSMpeg.AudioOutput.WebAudio.IsSupported()) {
this.audio = new JSMpeg.Decoder.MP2Audio(options);
this.audioOut = new JSMpeg.AudioOutput.WebAudio(options);
this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.AUDIO_1, this.audio);
this.audio.connect(this.audioOut);
}
//注册事件
var events = [
'onended', //当媒介已到达结尾时
'oncanplay', //当文件就绪可以开始播放时(缓冲已足够开始时)
'onpreload', //预加载时,实时回调
'onplay' //每次从暂停或者加载完成开始播放时
];
for(var i=0,eNum=events.length;i<eNum;i++){
if(typeof options[events[i]]==='function'){
this[events[i]] = options[events[i]];
}
}
Object.defineProperty(this, 'currentTime', {
get: this.getCurrentTime,
set: this.setCurrentTime
});
Object.defineProperty(this, 'volume', {
get: this.getVolume,
set: this.setVolume
});
this.unpauseOnShow = false;
if (options.pauseWhenHidden !== false) {
document.addEventListener('visibilitychange', this.showHide.bind(this));
}
this.source.start();
this.wantsToPlay = this.lastWantsToPlay = false;
if(options.preload||this.autoplay){
this.preload();
if (this.autoplay) {
this.play();
}
}
};
Player.prototype.showHide = function(ev) {
if (document.visibilityState === 'hidden') {
this.unpauseOnShow = this.wantsToPlay;
this.pause();
}
else if (this.unpauseOnShow) {
this.play();
}
};
Player.prototype.play = function(ev) {
this.lastWantsToPlay = this.wantsToPlay;
this.wantsToPlay = true;
this.animationId = requestAnimationFrame(this.update.bind(this));
};
Player.prototype.preload = function(ev) {
if (!this.source.established) {
requestAnimationFrame(this.preload.bind(this));
if (this.renderer) {
if(this.onpreload){
this.onpreload(this.source.progress)
}
this.renderer.renderProgress(this.source.progress);
}
}else if(this.oncanplay){
this.oncanplay();
this.oncanplay = false;
}
};
Player.prototype.pause = function(ev) {
cancelAnimationFrame(this.animationId);
this.lastWantsToPlay = this.wantsToPlay;
this.wantsToPlay = false;
this.isPlaying = false;
if (this.audio && this.audio.canPlay) {
// Seek to the currentTime again - audio may already be enqueued a bit
// further, so we have to rewind it.
this.audioOut.stop();
this.seek(this.currentTime);
}
};
Player.prototype.getVolume = function() {
return this.audioOut ? this.audioOut.volume : 0;
};
Player.prototype.setVolume = function(volume) {
if (this.audioOut) {
this.audioOut.volume = volume;
}
};
Player.prototype.stop = function(ev) {
this.pause();
this.seek(0);
if (this.video && this.options.decodeFirstFrame !== false) {
this.video.decode();
}
};
Player.prototype.destroy = function() {
this.pause();
this.source.destroy();
this.renderer.destroy();
this.audioOut.destroy();
};
Player.prototype.seek = function(time) {
var startOffset = this.audio && this.audio.canPlay
? this.audio.startTime
: this.video.startTime;
if (this.video) {
this.video.seek(time + startOffset);
}
if (this.audio) {
this.audio.seek(time + startOffset);
}
this.startTime = JSMpeg.Now() - time;
};
Player.prototype.getCurrentTime = function() {
return this.audio && this.audio.canPlay
? this.audio.currentTime - this.audio.startTime
: this.video.currentTime - this.video.startTime;
};
Player.prototype.setCurrentTime = function(time) {
this.seek(time);
};
Player.prototype.update = function() {
this.animationId = requestAnimationFrame(this.update.bind(this));
//还没加载好或者还不想开始
if(!this.wantsToPlay||!this.source.established)
return;
//从暂停到播放,要调用事件
if(!this.lastWantsToPlay&&this.onplay){
this.onplay();
}
if (!this.isPlaying) {
this.isPlaying = true;
this.startTime = JSMpeg.Now() - this.currentTime;
}
if (this.options.streaming) {
this.updateForStreaming();
}
else {
this.updateForStaticFile();
}
};
Player.prototype.updateForStreaming = function() {
// When streaming, immediately decode everything we have buffered up until
// now to minimize playback latency.
if (this.video) {
this.video.decode();
}
if (this.audio) {
var decoded = false;
do {
// If there's a lot of audio enqueued already, disable output and
// catch up with the encoding.
if (this.audioOut.enqueuedTime > this.maxAudioLag) {
this.audioOut.resetEnqueuedTime();
this.audioOut.enabled = false;
}
decoded = this.audio.decode();
} while (decoded);
this.audioOut.enabled = true;
}
};
Player.prototype.updateForStaticFile = function() {
var notEnoughData = false,
headroom = 0;
// If we have an audio track, we always try to sync the video to the audio.
// Gaps and discontinuities are far more percetable in audio than in video.
if (this.audio && this.audio.canPlay) {
// Do we have to decode and enqueue some more audio data?
while (
!notEnoughData &&
this.audio.decodedTime - this.audio.currentTime < 0.25
) {
notEnoughData = !this.audio.decode();
}
// Sync video to audio
if (this.video && this.video.currentTime < this.audio.currentTime) {
notEnoughData = !this.video.decode();
}
headroom = this.demuxer.currentTime - this.audio.currentTime;
}
else if (this.video) {
// Video only - sync it to player's wallclock
var targetTime = (JSMpeg.Now() - this.startTime) + this.video.startTime,
lateTime = targetTime - this.video.currentTime,
frameTime = 1/this.video.frameRate;
if (this.video && lateTime > 0) {
// If the video is too far behind (>2 frames), simply reset the
// target time to the next frame instead of trying to catch up.
if (lateTime > frameTime * 2) {
this.startTime += lateTime;
}
notEnoughData = !this.video.decode();
}
headroom = this.demuxer.currentTime - targetTime;
}
// Notify the source of the playhead headroom, so it can decide whether to
// continue loading further data.
this.source.resume(headroom);
// If we failed to decode and the source is complete, it means we reached
// the end of our data. We may want to loop.
if (notEnoughData && this.source.completed) {
if(this.onended){
this.onended();
}
if (this.loop) {
this.seek(0);
}
else {
this.pause();
}
}
};
return Player;
})();