av/cmd/mjpeg-player/player.js

134 lines
3.4 KiB
JavaScript

/*
AUTHOR
Trek Hopton <trek@ausocean.org>
LICENSE
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see http://www.gnu.org/licenses.
*/
let frameRate = 25; //Keeps track of the frame rate, default is 25fps.
self.importScripts('./lex-mjpeg.js');
self.importScripts('./hlsjs/mts-demuxer.js');
const codecs = {
MJPEG: 1,
MTS_MJPEG: 2,
}
// onmessage is called whenever the main thread sends this worker a message.
onmessage = e => {
switch (e.data.msg) {
case "setFrameRate":
frameRate = e.data.data;
break;
case "loadMjpeg":
player = new PlayerWorker();
player.init(codecs.MJPEG);
player.append(e.data.data);
player.setFrameRate(frameRate);
player.start();
break;
case "loadMtsMjpeg":
player = new PlayerWorker();
player.init(codecs.MTS_MJPEG);
player.append(e.data.data);
player.start();
break;
case "appendMtsMjpeg":
player.append(e.data.data);
break;
default:
console.error("unknown message from main thread");
break;
}
};
// PlayerWorker has a FrameBuffer to hold frames and once started, passes them one at a time to the main thread.
class PlayerWorker {
init(codec) {
this.frameRate = frameRate;
this.codec = codec;
switch (codec) {
case codecs.MJPEG:
this.frameSrc = new MJPEGLexer();
break;
case codecs.MTS_MJPEG:
this.frameSrc = new FrameBuffer();
break;
}
}
setFrameRate(rate) {
this.frameRate = rate;
}
start() {
let frame = this.frameSrc.read();
if (frame != null) {
postMessage({ msg: "frame", data: frame.buffer }, [frame.buffer]);
setTimeout(() => { this.start(); }, 1000 / this.frameRate);
} else {
postMessage({ msg: "stop" });
}
}
append(data) {
this.frameSrc.append(data);
}
}
// FrameBuffer allows an array of subarrays (MJPEG frames) to be read one at a time.
class FrameBuffer {
constructor() {
this.segments = [];
this.off = { segment: 0, frame: 0 };
this.demuxer = new MTSDemuxer();
}
// read returns the next single frame.
read() {
let off = this.off;
let prevOff = off;
if (this.incrementOff()) {
return this.segments[prevOff.segment][prevOff.frame];
} else {
return null;
}
}
append(data) {
let demuxed = this.demuxer.demux(new Uint8Array(data));
this.segments.push(demuxed.data);
}
incrementOff() {
if (!this.segments || !this.segments[this.off.segment]) {
return false;
}
if (this.off.frame + 1 >= this.segments[this.off.segment].length) {
if (this.off.segment + 1 >= this.segments.length) {
return false;
} else {
this.off.segment++;
this.off.frame = 0;
return true;
}
} else {
this.off.frame++;
return true;
}
}
}