@jxstjh/jhvideo
Version:
HTML5 jhvideo base on MPEG2-TS Stream Player
404 lines (367 loc) • 11.3 kB
JavaScript
const MAXREQCOUNT = 100
const MINREQCOUNT = 10
const MAXREQCOUNT_LIVE = 50
//Decoder request.
const kInitDecoderReq = 0;
const kUninitDecoderReq = 1;
const kOpenDecoderReq = 2;
const kCloseDecoderReq = 3;
const kFeedDataReq = 4;
const kStartDecodingReq = 5;
const kPauseDecodingReq = 6;
const kSeekDecoderReq = 7;
const kClearDecoderReq = 8;
//Decoder response.
const kInitDecoderRsp = 0;
const kUninitDecoderRsp = 1;
const kOpenDecoderRsp = 2;
const kCloseDecoderRsp = 3;
const kVideoFrame = 4;
const kAudioFrame = 5;
const kStartDecodingRsp = 6;
const kPauseDecodingRsp = 7;
const kDecodeFinishedEvt = 8;
const kStartFeedingEvt = 9;
const kPauseFeedingEvt = 10;
const kDecodeErrorEvt = 11;
const kWamsLoadErrorEvt = 12;
const kAudioInfo = 13;
self.Module = {
onRuntimeInitialized: function () {
onWasmLoaded();
}
};
var g_loadjs = true;
self.onmessage = function (evt) {
if (!g_loadjs) {
var objData = {
t: kWamsLoadErrorEvt,
};
self.postMessage(objData);
console.log("[ER] importScripts(libffmpeg.js)");
return false;
}
if (!self.decoder) {
console.log("[ER] Decoder not initialized!");
return;
}
var req = evt.data;
self.decoder.cacheAndProReq(req);
};
try {
self.importScripts('libffmpeg.js');
} catch (e) {
console.log(e)
g_loadjs = false;
}
function Decoder() {
this.coreLogLevel = 1;
this.wasmLoaded = false;
this.tmpReqQue = [];
this.tmpDataQue = [];
this.cacheBuffer = null;
this.cacheSize = 0;
this.decodeTimer = null;
this.videoCallback = null;
this.audioCallback = null;
this.decoderID = 0;
this.feedstatus = 0;
this._frameDur = 40;
this._startDec = 0;
this._lastPts = 0;
this._totalDur = 0;
this._count = 0;
this._speed = 1;
this._inputCount = 0;
}
Decoder.prototype.initDecoder = function (chunkSize) {
var ret = Module._initDecoder(this.coreLogLevel);
if (0 == ret) {
chunkSize = chunkSize || 65536;
this.cacheBuffer = Module._malloc(chunkSize);
this.cacheSize = chunkSize
}
var objData = {
t: kInitDecoderRsp,
e: ret
};
self.postMessage(objData);
};
Decoder.prototype.uninitDecoder = function () {
var ret = Module._uninitDecoder();
if (this.cacheBuffer != null) {
Module._free(this.cacheBuffer);
this.cacheBuffer = null;
this.cacheSize = 0;
}
};
Decoder.prototype.openDecoder = function (cd, islive) {
var paramCount = 1, paramSize = 4;
var paramByteBuffer = Module._malloc(paramCount * paramSize);
var codeId = 3;
if (cd == 9) { //mpeg4
codeId = 2;
} else if (cd == 12) { //265
codeId = 3;
} else if (cd == 7) { //264
codeId = 1;
} else if (cd == 13) { //svac
codeId = 4;
} else if (cd == 14) { //mjpeg
codeId = 5;
} else if (cd == 15) { //mjpegb
codeId = 6;
}
console.log('Decoder.openDecoder: code[', cd, '], codeId[', codeId, ']');
var ret = Module._openDecoder(paramByteBuffer, codeId, 1, this.videoCallback, this.audioCallback);
if (ret == 0) {
var paramIntBuff = paramByteBuffer >> 2;
var paramArray = Module.HEAP32.subarray(paramIntBuff, paramIntBuff + paramCount);
this.decoderID = paramArray[0];
this.islive = islive;
// Send audio config to main thread
var sampleRate = 8000;
var channels = 1;
var format = 1; // 1 = 16-bit signed int
try {
if (typeof Module._getAudioSampleRate === 'function') {
sampleRate = Module._getAudioSampleRate(this.decoderID);
}
if (typeof Module._getAudioChannels === 'function') {
channels = Module._getAudioChannels(this.decoderID);
}
if (typeof Module._getAudioSampleFormat === 'function') {
format = Module._getAudioSampleFormat(this.decoderID);
}
} catch (e) {
// WASM module doesn't expose audio query functions, use defaults
}
self.postMessage({
t: kAudioInfo,
f: format,
c: channels,
r: sampleRate
});
}
var objData = {
t: kOpenDecoderRsp,
e: ret
};
self.postMessage(objData);
Module._free(paramByteBuffer);
};
Decoder.prototype.closeDecoder = function () {
this.tmpDataQue = [];
if (this.decodeTimer) {
clearInterval(this.decodeTimer);
this.decodeTimer = null;
}
var ret = Module._closeDecoder(this.decoderID);
this.decoderID = 0
var objData = {
t: kCloseDecoderRsp,
e: 0
};
self.postMessage(objData);
};
Decoder.prototype.startDecoding = function (speed, bdel) {
if (this.decodeTimer) {
clearInterval(this.decodeTimer);
}
if (typeof speed === 'number') {
this._speed = speed;
}
if (bdel === true) {
var dq = this.tmpDataQue;
dq.splice(0, dq.length);
}
this.decodeTimer = setInterval(this.decode.bind(this), this._frameDur / this._speed);
};
Decoder.prototype.pauseDecoding = function () {
if (this.decodeTimer) {
clearInterval(this.decodeTimer);
this.decodeTimer = null;
}
};
Decoder.prototype.SeekDecoder = function (pts) {
var dq = this.tmpDataQue;
var del = 0;
if (pts == -1) {
dq.splice(0, dq.length);
} else {
for (var i = 0, len = dq.length; i < len; i++) {
if (dq[i].k == 1) {
if (dq[i].s < pts) {
del = i;
} else if (dq[i].s = pts) {
del = i;
break;
} else {
break;
}
}
}
if (del > 0) {
dq.splice(0, del);
}
}
};
Decoder.prototype.decode = function () {
if (this.feedstatus == 1 && this.tmpDataQue.length < MINREQCOUNT) {
this.feedstatus = 0;
self.postMessage({
t: kStartFeedingEvt
});
}
if (this.tmpDataQue.length > 0) {
var dataReq = this.tmpDataQue.shift();
this.decodeFrameData(dataReq.d, dataReq.s, dataReq.k);
}
};
Decoder.prototype.decodeFrameData = function (data, pts, key) {
var typedArray = data;
if (typedArray.length > this.cacheSize) {
Module._free(this.cacheBuffer);
this.cacheSize = typedArray.length + 1000;
this.cacheBuffer = Module._malloc(this.cacheSize);
}
Module.HEAPU8.set(typedArray, this.cacheBuffer);
this._getFrame = false;
var error = Module._decodeOnePacket(this.decoderID, this.cacheBuffer, typedArray.length, pts, pts);
this._inputCount += 1;
if (this._inputCount > 200) {
self.postMessage({
t: kDecodeErrorEvt
});
this._inputCount = 0;
console.log("wasm deocdeError 200 times");
} else {
if (!this._getFrame && this.tmpDataQue.length > 0) {
this.decode();
}
}
};
Decoder.prototype.processReq = function (req) {
switch (req.t) {
case kInitDecoderReq:
this.initDecoder(req.s, req.c);
break;
case kUninitDecoderReq:
this.uninitDecoder();
break;
case kOpenDecoderReq:
this.openDecoder(req.cd, req.l);
break;
case kCloseDecoderReq:
this.closeDecoder();
break;
case kStartDecodingReq:
this.startDecoding(req.i, req.d);
break;
case kPauseDecodingReq:
this.pauseDecoding();
break;
case kSeekDecoderReq:
this.SeekDecoder(req.s);
break;
default:
}
};
Decoder.prototype.cacheAndProReq = function (req) {
if (req) {
if (req.t === kFeedDataReq) {
this.tmpDataQue.push(req);
if (this._count >= 0) {
var dif = req.s - this._lastPts,
frame = this._frameDur;
this._totalDur += (dif <= 0 || dif >= 2000) ? frame : dif;
if (this._count > 20) {
var val = this._totalDur / this._count;
if (frame - val > 3) {
this._frameDur = val;
this.startDecoding();
this._count = -1;
}
}
}
this._lastPts = req.s;
this._count++
var dq = this.tmpDataQue;
if (this.islive) {
if (req.k == 1 && dq.length > MAXREQCOUNT_LIVE) {
dq.splice(0, dq.length - 1);
}
} else {
if (this.feedstatus == 0 && this.tmpDataQue.length > MAXREQCOUNT) {
this.feedstatus = 1;
self.postMessage({
t: kPauseFeedingEvt
});
} else if (this.feedstatus == 1 && this.tmpDataQue.length < MINREQCOUNT) {
this.feedstatus = 0;
self.postMessage({
t: kStartFeedingEvt
});
}
}
} else {
if (!this.wasmLoaded || this.tmpReqQue.length > 0) {
this.tmpReqQue.push(req);
} else {
this.processReq(req);
}
}
}
if (this.wasmLoaded && this.tmpReqQue.length > 0) {
var proReq = this.tmpReqQue.shift();
this.processReq(proReq);
if (this.tmpReqQue.length > 0) {
setTimeout(this.cacheAndProReq, 0);
}
}
};
Decoder.prototype.onWasmLoaded = function () {
this.wasmLoaded = true;
var _this = this;
this.videoCallback = Module.addFunction(function (y, yline, u, uline, v, vline, width, height, timestamp) {
_this._inputCount = 0;
_this._getFrame = true;
var outArray = Module.HEAPU8.subarray(y, y + yline * height);
var ydata = new Uint8Array(outArray);
outArray = Module.HEAPU8.subarray(u, u + uline * height / 2);
var udata = new Uint8Array(outArray);
outArray = Module.HEAPU8.subarray(v, v + vline * height / 2);
var vdata = new Uint8Array(outArray);
var objData = {
t: kVideoFrame,
w: width,
h: height,
ly: yline,
lu: uline,
lv: vline,
s: timestamp,
y: ydata,
u: udata,
v: vdata
};
self.postMessage(objData, [objData.y.buffer], [objData.u.buffer], [objData.v.buffer]);
});
this.audioCallback = Module.addFunction(function (buff, size) {
var outArray = Module.HEAPU8.subarray(buff, buff + size);
var data = new Uint8Array(outArray);
var objData = {
t: kAudioFrame,
d: data
};
self.postMessage(objData, [objData.d.buffer]);
});
this.cacheAndProReq();
};
self.decoder = new Decoder;
function onWasmLoaded() {
if (self.decoder) {
self.decoder.onWasmLoaded();
} else {
console.log("[ER] No decoder!");
}
}