@vikasietum_tecknology/record-rtc
Version:
record-rtc is a library based on recordrtc library. In this forked version of the original library we have optimized the memory management. The video recording is stored in IndexDB in chunks.
1,183 lines (1,174 loc) • 244 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.EBML = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tools_1 = require("./tools");
var int64_buffer_1 = require("int64-buffer");
var tools = require("./tools");
var schema = require("matroska/lib/schema");
var byEbmlID = schema.byEbmlID;
// https://www.matroska.org/technical/specs/index.html
var State;
(function (State) {
State[State["STATE_TAG"] = 1] = "STATE_TAG";
State[State["STATE_SIZE"] = 2] = "STATE_SIZE";
State[State["STATE_CONTENT"] = 3] = "STATE_CONTENT";
})(State || (State = {}));
var EBMLDecoder = /** @class */ (function () {
function EBMLDecoder() {
this._buffer = new tools_1.Buffer(0);
this._tag_stack = [];
this._state = State.STATE_TAG;
this._cursor = 0;
this._total = 0;
this._schema = byEbmlID;
this._result = [];
}
EBMLDecoder.prototype.decode = function (chunk) {
this.readChunk(chunk);
var diff = this._result;
this._result = [];
return diff;
};
EBMLDecoder.prototype.readChunk = function (chunk) {
// 読みかけの(読めなかった) this._buffer と 新しい chunk を合わせて読み直す
this._buffer = tools.concat([this._buffer, new tools_1.Buffer(chunk)]);
while (this._cursor < this._buffer.length) {
// console.log(this._cursor, this._total, this._tag_stack);
if (this._state === State.STATE_TAG && !this.readTag()) {
break;
}
if (this._state === State.STATE_SIZE && !this.readSize()) {
break;
}
if (this._state === State.STATE_CONTENT && !this.readContent()) {
break;
}
}
};
EBMLDecoder.prototype.getSchemaInfo = function (tagNum) {
return this._schema[tagNum] || {
name: "unknown",
level: -1,
type: "unknown",
description: "unknown"
};
};
/**
* vint された parsing tag
* @return - return false when waiting for more data
*/
EBMLDecoder.prototype.readTag = function () {
// tag.length が buffer の外にある
if (this._cursor >= this._buffer.length) {
return false;
}
// read ebml id vint without first byte
var tag = tools_1.readVint(this._buffer, this._cursor);
// tag が読めなかった
if (tag == null) {
return false;
}
// >>>>>>>>>
// tag 識別子
//const tagStr = this._buffer.toString("hex", this._cursor, this._cursor + tag.length);
//const tagNum = parseInt(tagStr, 16);
// 上と等価
var buf = this._buffer.slice(this._cursor, this._cursor + tag.length);
var tagNum = buf.reduce(function (o, v, i, arr) { return o + v * Math.pow(16, 2 * (arr.length - 1 - i)); }, 0);
var schema = this.getSchemaInfo(tagNum);
var tagObj = {
EBML_ID: tagNum.toString(16),
schema: schema,
type: schema.type,
name: schema.name,
level: schema.level,
tagStart: this._total,
tagEnd: this._total + tag.length,
sizeStart: this._total + tag.length,
sizeEnd: null,
dataStart: null,
dataEnd: null,
dataSize: null,
data: null
};
// | tag: vint | size: vint | data: Buffer(size) |
this._tag_stack.push(tagObj);
// <<<<<<<<
// ポインタを進める
this._cursor += tag.length;
this._total += tag.length;
// 読み込み状態変更
this._state = State.STATE_SIZE;
return true;
};
/**
* vint された現在のタグの内容の大きさを読み込む
* @return - return false when waiting for more data
*/
EBMLDecoder.prototype.readSize = function () {
// tag.length が buffer の外にある
if (this._cursor >= this._buffer.length) {
return false;
}
// read ebml datasize vint without first byte
var size = tools_1.readVint(this._buffer, this._cursor);
// まだ読めない
if (size == null) {
return false;
}
// >>>>>>>>>
// current tag の data size 決定
var tagObj = this._tag_stack[this._tag_stack.length - 1];
tagObj.sizeEnd = tagObj.sizeStart + size.length;
tagObj.dataStart = tagObj.sizeEnd;
tagObj.dataSize = size.value;
if (size.value === -1) {
// unknown size
tagObj.dataEnd = -1;
if (tagObj.type === "m") {
tagObj.unknownSize = true;
}
}
else {
tagObj.dataEnd = tagObj.sizeEnd + size.value;
}
// <<<<<<<<
// ポインタを進める
this._cursor += size.length;
this._total += size.length;
this._state = State.STATE_CONTENT;
return true;
};
/**
* データ読み込み
*/
EBMLDecoder.prototype.readContent = function () {
var tagObj = this._tag_stack[this._tag_stack.length - 1];
// master element は子要素を持つので生データはない
if (tagObj.type === 'm') {
// console.log('content should be tags');
tagObj.isEnd = false;
this._result.push(tagObj);
this._state = State.STATE_TAG;
// この Mastert Element は空要素か
if (tagObj.dataSize === 0) {
// 即座に終了タグを追加
var elm = Object.assign({}, tagObj, { isEnd: true });
this._result.push(elm);
this._tag_stack.pop(); // スタックからこのタグを捨てる
}
return true;
}
// waiting for more data
if (this._buffer.length < this._cursor + tagObj.dataSize) {
return false;
}
// タグの中身の生データ
var data = this._buffer.slice(this._cursor, this._cursor + tagObj.dataSize);
// 読み終わったバッファを捨てて読み込んでいる部分のバッファのみ残す
this._buffer = this._buffer.slice(this._cursor + tagObj.dataSize);
tagObj.data = data;
// >>>>>>>>>
switch (tagObj.type) {
//case "m": break;
// Master-Element - contains other EBML sub-elements of the next lower level
case "u":
tagObj.value = data.readUIntBE(0, data.length);
break;
// Unsigned Integer - Big-endian, any size from 1 to 8 octets
case "i":
tagObj.value = data.readIntBE(0, data.length);
break;
// Signed Integer - Big-endian, any size from 1 to 8 octets
case "f":
tagObj.value = tagObj.dataSize === 4 ? data.readFloatBE(0) :
tagObj.dataSize === 8 ? data.readDoubleBE(0) :
(console.warn("cannot read " + tagObj.dataSize + " octets float. failback to 0"), 0);
break;
// Float - Big-endian, defined for 4 and 8 octets (32, 64 bits)
case "s":
tagObj.value = data.toString("ascii");
break; // ascii
// Printable ASCII (0x20 to 0x7E), zero-padded when needed
case "8":
tagObj.value = data.toString("utf8");
break;
// Unicode string, zero padded when needed (RFC 2279)
case "b":
tagObj.value = data;
break;
// Binary - not interpreted by the parser
case "d":
tagObj.value = tools_1.convertEBMLDateToJSDate(new int64_buffer_1.Int64BE(data).toNumber());
break;
// nano second; Date.UTC(2001,1,1,0,0,0,0) === 980985600000
// Date - signed 8 octets integer in nanoseconds with 0 indicating
// the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC)
}
if (tagObj.value === null) {
throw new Error("unknown tag type:" + tagObj.type);
}
this._result.push(tagObj);
// <<<<<<<<
// ポインタを進める
this._total += tagObj.dataSize;
// タグ待ちモードに変更
this._state = State.STATE_TAG;
this._cursor = 0;
this._tag_stack.pop(); // remove the object from the stack
while (this._tag_stack.length > 0) {
var topEle = this._tag_stack[this._tag_stack.length - 1];
// 親が不定長サイズなので閉じタグは期待できない
if (topEle.dataEnd < 0) {
this._tag_stack.pop(); // 親タグを捨てる
return true;
}
// 閉じタグの来るべき場所まで来たかどうか
if (this._total < topEle.dataEnd) {
break;
}
// 閉じタグを挿入すべきタイミングが来た
if (topEle.type !== "m") {
throw new Error("parent element is not master element");
}
var elm = Object.assign({}, topEle, { isEnd: true });
this._result.push(elm);
this._tag_stack.pop();
}
return true;
};
return EBMLDecoder;
}());
exports.default = EBMLDecoder;
},{"./tools":5,"int64-buffer":15,"matroska/lib/schema":17}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tools = require("./tools");
var tools_1 = require("./tools");
var schema = require("matroska/lib/schema");
var byEbmlID = schema.byEbmlID;
var EBMLEncoder = /** @class */ (function () {
function EBMLEncoder() {
this._schema = byEbmlID;
this._buffers = [];
this._stack = [];
}
EBMLEncoder.prototype.encode = function (elms) {
var _this = this;
return tools.concat(elms.reduce(function (lst, elm) {
return lst.concat(_this.encodeChunk(elm));
}, [])).buffer;
};
EBMLEncoder.prototype.encodeChunk = function (elm) {
if (elm.type === "m") {
if (!elm.isEnd) {
this.startTag(elm);
}
else {
this.endTag(elm);
}
}
else {
this.writeTag(elm);
}
return this.flush();
};
EBMLEncoder.prototype.flush = function () {
var ret = this._buffers;
this._buffers = [];
return ret;
};
EBMLEncoder.prototype.getSchemaInfo = function (tagName) {
var tagNums = Object.keys(this._schema).map(Number);
for (var i = 0; i < tagNums.length; i++) {
var tagNum = tagNums[i];
if (this._schema[tagNum].name === tagName) {
return new tools_1.Buffer(tagNum.toString(16), 'hex');
}
}
return null;
};
EBMLEncoder.prototype.writeTag = function (elm) {
var tagName = elm.name;
var tagId = this.getSchemaInfo(tagName);
var tagData = elm.data;
if (tagId == null) {
throw new Error('No schema entry found for ' + tagName);
}
var data = tools.encodeTag(tagId, tagData);
/**
* 親要素が閉じタグあり(isEnd)なら閉じタグが来るまで待つ(children queに入る)
*/
if (this._stack.length > 0) {
var last = this._stack[this._stack.length - 1];
last.children.push({
tagId: tagId,
elm: elm,
children: [],
data: data
});
return;
}
this._buffers = this._buffers.concat(data);
return;
};
EBMLEncoder.prototype.startTag = function (elm) {
var tagName = elm.name;
var tagId = this.getSchemaInfo(tagName);
if (tagId == null) {
throw new Error('No schema entry found for ' + tagName);
}
/**
* 閉じタグ不定長の場合はスタックに積まずに即時バッファに書き込む
*/
if (elm.unknownSize) {
var data = tools.encodeTag(tagId, new tools_1.Buffer(0), elm.unknownSize);
this._buffers = this._buffers.concat(data);
return;
}
var tag = {
tagId: tagId,
elm: elm,
children: [],
data: null
};
if (this._stack.length > 0) {
this._stack[this._stack.length - 1].children.push(tag);
}
this._stack.push(tag);
};
EBMLEncoder.prototype.endTag = function (elm) {
var tagName = elm.name;
var tag = this._stack.pop();
if (tag == null) {
throw new Error("EBML structure is broken");
}
if (tag.elm.name !== elm.name) {
throw new Error("EBML structure is broken");
}
var childTagDataBuffers = tag.children.reduce(function (lst, child) {
if (child.data === null) {
throw new Error("EBML structure is broken");
}
return lst.concat(child.data);
}, []);
var childTagDataBuffer = tools.concat(childTagDataBuffers);
if (tag.elm.type === "m") {
tag.data = tools.encodeTag(tag.tagId, childTagDataBuffer, tag.elm.unknownSize);
}
else {
tag.data = tools.encodeTag(tag.tagId, childTagDataBuffer);
}
if (this._stack.length < 1) {
this._buffers = this._buffers.concat(tag.data);
}
};
return EBMLEncoder;
}());
exports.default = EBMLEncoder;
},{"./tools":5,"matroska/lib/schema":17}],3:[function(require,module,exports){
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var events_1 = require("events");
var tools = require("./tools");
/**
* This is an informal code for reference.
* EBMLReader is a class for getting information to enable seeking Webm recorded by MediaRecorder.
* So please do not use for regular WebM files.
*/
var EBMLReader = /** @class */ (function (_super) {
__extends(EBMLReader, _super);
function EBMLReader() {
var _this = _super.call(this) || this;
_this.logGroup = "";
_this.hasLoggingStarted = false;
_this.metadataloaded = false;
_this.chunks = [];
_this.stack = [];
_this.segmentOffset = 0;
_this.last2SimpleBlockVideoTrackTimecode = [0, 0];
_this.last2SimpleBlockAudioTrackTimecode = [0, 0];
_this.lastClusterTimecode = 0;
_this.lastClusterPosition = 0;
_this.timecodeScale = 1000000; // webm default TimecodeScale is 1ms
_this.metadataSize = 0;
_this.metadatas = [];
_this.cues = [];
_this.firstVideoBlockRead = false;
_this.firstAudioBlockRead = false;
_this.currentTrack = { TrackNumber: -1, TrackType: -1, DefaultDuration: null, CodecDelay: null };
_this.trackTypes = [];
_this.trackDefaultDuration = [];
_this.trackCodecDelay = [];
_this.trackInfo = { type: "nothing" };
_this.ended = false;
_this.logging = false;
_this.use_duration_every_simpleblock = false;
_this.use_webp = false;
_this.use_segment_info = true;
_this.drop_default_duration = true;
return _this;
}
/**
* emit final state.
*/
EBMLReader.prototype.stop = function () {
this.ended = true;
this.emit_segment_info();
// clean up any unclosed Master Elements at the end of the stream.
while (this.stack.length) {
this.stack.pop();
if (this.logging) {
console.groupEnd();
}
}
// close main group if set, logging is enabled, and has actually logged anything.
if (this.logging && this.hasLoggingStarted && this.logGroup) {
console.groupEnd();
}
};
/**
* emit chunk info
*/
EBMLReader.prototype.emit_segment_info = function () {
var data = this.chunks;
this.chunks = [];
if (!this.metadataloaded) {
this.metadataloaded = true;
this.metadatas = data;
var videoTrackNum = this.trackTypes.indexOf(1); // find first video track
var audioTrackNum = this.trackTypes.indexOf(2); // find first audio track
this.trackInfo = videoTrackNum >= 0 && audioTrackNum >= 0 ? { type: "both", trackNumber: videoTrackNum }
: videoTrackNum >= 0 ? { type: "video", trackNumber: videoTrackNum }
: audioTrackNum >= 0 ? { type: "audio", trackNumber: audioTrackNum }
: { type: "nothing" };
if (!this.use_segment_info) {
return;
}
this.emit("metadata", { data: data, metadataSize: this.metadataSize });
}
else {
if (!this.use_segment_info) {
return;
}
var timecode = this.lastClusterTimecode;
var duration = this.duration;
var timecodeScale = this.timecodeScale;
this.emit("cluster", { timecode: timecode, data: data });
this.emit("duration", { timecodeScale: timecodeScale, duration: duration });
}
};
EBMLReader.prototype.read = function (elm) {
var _this = this;
var drop = false;
if (this.ended) {
// reader is finished
return;
}
if (elm.type === "m") {
// 閉じタグの自動挿入
if (elm.isEnd) {
this.stack.pop();
}
else {
var parent_1 = this.stack[this.stack.length - 1];
if (parent_1 != null && parent_1.level >= elm.level) {
// 閉じタグなしでレベルが下がったら閉じタグを挿入
this.stack.pop();
// From http://w3c.github.io/media-source/webm-byte-stream-format.html#webm-media-segments
// This fixes logging for webm streams with Cluster of unknown length and no Cluster closing elements.
if (this.logging) {
console.groupEnd();
}
parent_1.dataEnd = elm.dataEnd;
parent_1.dataSize = elm.dataEnd - parent_1.dataStart;
parent_1.unknownSize = false;
var o = Object.assign({}, parent_1, { name: parent_1.name, type: parent_1.type, isEnd: true });
this.chunks.push(o);
}
this.stack.push(elm);
}
}
if (elm.type === "m" && elm.name == "Segment") {
if (this.segmentOffset != 0) {
console.warn("Multiple segments detected!");
}
this.segmentOffset = elm.dataStart;
this.emit("segment_offset", this.segmentOffset);
}
else if (elm.type === "b" && elm.name === "SimpleBlock") {
var _a = tools.ebmlBlock(elm.data), timecode = _a.timecode, trackNumber = _a.trackNumber, frames_1 = _a.frames;
if (this.trackTypes[trackNumber] === 1) { // trackType === 1 => video track
if (!this.firstVideoBlockRead) {
this.firstVideoBlockRead = true;
if (this.trackInfo.type === "both" || this.trackInfo.type === "video") {
var CueTime = this.lastClusterTimecode + timecode;
this.cues.push({ CueTrack: trackNumber, CueClusterPosition: this.lastClusterPosition, CueTime: CueTime });
this.emit("cue_info", { CueTrack: trackNumber, CueClusterPosition: this.lastClusterPosition, CueTime: this.lastClusterTimecode });
this.emit("cue", { CueTrack: trackNumber, CueClusterPosition: this.lastClusterPosition, CueTime: CueTime });
}
}
this.last2SimpleBlockVideoTrackTimecode = [this.last2SimpleBlockVideoTrackTimecode[1], timecode];
}
else if (this.trackTypes[trackNumber] === 2) { // trackType === 2 => audio track
if (!this.firstAudioBlockRead) {
this.firstAudioBlockRead = true;
if (this.trackInfo.type === "audio") {
var CueTime = this.lastClusterTimecode + timecode;
this.cues.push({ CueTrack: trackNumber, CueClusterPosition: this.lastClusterPosition, CueTime: CueTime });
this.emit("cue_info", { CueTrack: trackNumber, CueClusterPosition: this.lastClusterPosition, CueTime: this.lastClusterTimecode });
this.emit("cue", { CueTrack: trackNumber, CueClusterPosition: this.lastClusterPosition, CueTime: CueTime });
}
}
this.last2SimpleBlockAudioTrackTimecode = [this.last2SimpleBlockAudioTrackTimecode[1], timecode];
}
if (this.use_duration_every_simpleblock) {
this.emit("duration", { timecodeScale: this.timecodeScale, duration: this.duration });
}
if (this.use_webp) {
frames_1.forEach(function (frame) {
var startcode = frame.slice(3, 6).toString("hex");
if (startcode !== "9d012a") {
return;
}
; // VP8 の場合
var webpBuf = tools.VP8BitStreamToRiffWebPBuffer(frame);
var webp = new Blob([webpBuf], { type: "image/webp" });
var currentTime = _this.duration;
_this.emit("webp", { currentTime: currentTime, webp: webp });
});
}
}
else if (elm.type === "m" && elm.name === "Cluster" && elm.isEnd === false) {
this.firstVideoBlockRead = false;
this.firstAudioBlockRead = false;
this.emit_segment_info();
this.emit("cluster_ptr", elm.tagStart);
this.lastClusterPosition = elm.tagStart;
}
else if (elm.type === "u" && elm.name === "Timecode") {
this.lastClusterTimecode = elm.value;
}
else if (elm.type === "u" && elm.name === "TimecodeScale") {
this.timecodeScale = elm.value;
}
else if (elm.type === "m" && elm.name === "TrackEntry") {
if (elm.isEnd) {
this.trackTypes[this.currentTrack.TrackNumber] = this.currentTrack.TrackType;
this.trackDefaultDuration[this.currentTrack.TrackNumber] = this.currentTrack.DefaultDuration;
this.trackCodecDelay[this.currentTrack.TrackNumber] = this.currentTrack.CodecDelay;
}
else {
this.currentTrack = { TrackNumber: -1, TrackType: -1, DefaultDuration: null, CodecDelay: null };
}
}
else if (elm.type === "u" && elm.name === "TrackType") {
this.currentTrack.TrackType = elm.value;
}
else if (elm.type === "u" && elm.name === "TrackNumber") {
this.currentTrack.TrackNumber = elm.value;
}
else if (elm.type === "u" && elm.name === "CodecDelay") {
this.currentTrack.CodecDelay = elm.value;
}
else if (elm.type === "u" && elm.name === "DefaultDuration") {
// media source api は DefaultDuration を計算するとバグる。
// https://bugs.chromium.org/p/chromium/issues/detail?id=606000#c22
// chrome 58 ではこれを回避するために DefaultDuration 要素を抜き取った。
// chrome 58 以前でもこのタグを抜き取ることで回避できる
if (this.drop_default_duration) {
console.warn("DefaultDuration detected!, remove it");
drop = true;
}
else {
this.currentTrack.DefaultDuration = elm.value;
}
}
else if (elm.name === "unknown") {
console.warn(elm);
}
if (!this.metadataloaded && elm.dataEnd > 0) {
this.metadataSize = elm.dataEnd;
}
if (!drop) {
this.chunks.push(elm);
}
if (this.logging) {
this.put(elm);
}
};
Object.defineProperty(EBMLReader.prototype, "duration", {
/**
* DefaultDuration が定義されている場合は最後のフレームのdurationも考慮する
* 単位 timecodeScale
*
* !!! if you need duration with seconds !!!
* ```js
* const nanosec = reader.duration * reader.timecodeScale;
* const sec = nanosec / 1000 / 1000 / 1000;
* ```
*/
get: function () {
if (this.trackInfo.type === "nothing") {
console.warn("no video, no audio track");
return 0;
}
// defaultDuration は 生の nano sec
var defaultDuration = 0;
// nanoseconds
var codecDelay = 0;
var lastTimecode = 0;
var _defaultDuration = this.trackDefaultDuration[this.trackInfo.trackNumber];
if (typeof _defaultDuration === "number") {
defaultDuration = _defaultDuration;
}
else {
// https://bugs.chromium.org/p/chromium/issues/detail?id=606000#c22
// default duration がないときに使う delta
if (this.trackInfo.type === "both") {
if (this.last2SimpleBlockAudioTrackTimecode[1] > this.last2SimpleBlockVideoTrackTimecode[1]) {
// audio diff
defaultDuration = (this.last2SimpleBlockAudioTrackTimecode[1] - this.last2SimpleBlockAudioTrackTimecode[0]) * this.timecodeScale;
// audio delay
var delay = this.trackCodecDelay[this.trackTypes.indexOf(2)]; // 2 => audio
if (typeof delay === "number") {
codecDelay = delay;
}
// audio timecode
lastTimecode = this.last2SimpleBlockAudioTrackTimecode[1];
}
else {
// video diff
defaultDuration = (this.last2SimpleBlockVideoTrackTimecode[1] - this.last2SimpleBlockVideoTrackTimecode[0]) * this.timecodeScale;
// video delay
var delay = this.trackCodecDelay[this.trackTypes.indexOf(1)]; // 1 => video
if (typeof delay === "number") {
codecDelay = delay;
}
// video timecode
lastTimecode = this.last2SimpleBlockVideoTrackTimecode[1];
}
}
else if (this.trackInfo.type === "video") {
defaultDuration = (this.last2SimpleBlockVideoTrackTimecode[1] - this.last2SimpleBlockVideoTrackTimecode[0]) * this.timecodeScale;
var delay = this.trackCodecDelay[this.trackInfo.trackNumber]; // 2 => audio
if (typeof delay === "number") {
codecDelay = delay;
}
lastTimecode = this.last2SimpleBlockVideoTrackTimecode[1];
}
else if (this.trackInfo.type === "audio") {
defaultDuration = (this.last2SimpleBlockAudioTrackTimecode[1] - this.last2SimpleBlockAudioTrackTimecode[0]) * this.timecodeScale;
var delay = this.trackCodecDelay[this.trackInfo.trackNumber]; // 1 => video
if (typeof delay === "number") {
codecDelay = delay;
}
lastTimecode = this.last2SimpleBlockAudioTrackTimecode[1];
} // else { not reached }
}
// convert to timecodescale
var duration_nanosec = ((this.lastClusterTimecode + lastTimecode) * this.timecodeScale) + defaultDuration - codecDelay;
var duration = duration_nanosec / this.timecodeScale;
return Math.floor(duration);
},
enumerable: true,
configurable: true
});
EBMLReader.prototype.addListener = function (event, listener) {
return _super.prototype.addListener.call(this, event, listener);
};
EBMLReader.prototype.put = function (elm) {
if (!this.hasLoggingStarted) {
this.hasLoggingStarted = true;
if (this.logging && this.logGroup) {
console.groupCollapsed(this.logGroup);
}
}
if (elm.type === "m") {
if (elm.isEnd) {
console.groupEnd();
}
else {
console.group(elm.name + ":" + elm.tagStart);
}
}
else if (elm.type === "b") {
// for debug
//if(elm.name === "SimpleBlock"){
//const o = EBML.tools.ebmlBlock(elm.value);
//console.log(elm.name, elm.type, o.trackNumber, o.timecode);
//}else{
console.log(elm.name, elm.type);
//}
}
else {
console.log(elm.name, elm.tagStart, elm.type, elm.value);
}
};
return EBMLReader;
}(events_1.EventEmitter));
exports.default = EBMLReader;
;
;
;
;
},{"./tools":5,"events":13}],4:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var EBMLDecoder_1 = require("./EBMLDecoder");
exports.Decoder = EBMLDecoder_1.default;
var EBMLEncoder_1 = require("./EBMLEncoder");
exports.Encoder = EBMLEncoder_1.default;
var EBMLReader_1 = require("./EBMLReader");
exports.Reader = EBMLReader_1.default;
var tools = require("./tools");
exports.tools = tools;
var version = require("../package.json").version;
exports.version = version;
},{"../package.json":18,"./EBMLDecoder":1,"./EBMLEncoder":2,"./EBMLReader":3,"./tools":5}],5:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/// <reference types="node"/>
var int64_buffer_1 = require("int64-buffer");
var EBMLEncoder_1 = require("./EBMLEncoder");
var _Buffer = require("buffer/");
var _tools = require("ebml/lib/ebml/tools");
var _block = require("ebml-block");
exports.Buffer = _Buffer.Buffer;
exports.readVint = _tools.readVint;
exports.writeVint = _tools.writeVint;
exports.ebmlBlock = _block;
function readBlock(buf) {
return exports.ebmlBlock(new exports.Buffer(buf));
}
exports.readBlock = readBlock;
/**
* @param end - if end === false then length is unknown
*/
function encodeTag(tagId, tagData, unknownSize) {
if (unknownSize === void 0) { unknownSize = false; }
return concat([
tagId,
unknownSize ?
new exports.Buffer('01ffffffffffffff', 'hex') :
exports.writeVint(tagData.length),
tagData
]);
}
exports.encodeTag = encodeTag;
/**
* @return - SimpleBlock to WebP Filter
*/
function WebPFrameFilter(elms) {
return WebPBlockFilter(elms).reduce(function (lst, elm) {
var o = exports.ebmlBlock(elm.data);
return o.frames.reduce(function (lst, frame) {
// https://developers.Blob.com/speed/webp/docs/riff_container
var webpBuf = VP8BitStreamToRiffWebPBuffer(frame);
var webp = new Blob([webpBuf], { type: "image/webp" });
return lst.concat(webp);
}, lst);
}, []);
}
exports.WebPFrameFilter = WebPFrameFilter;
/**
* WebP ファイルにできる SimpleBlock の パスフィルタ
*/
function WebPBlockFilter(elms) {
return elms.reduce(function (lst, elm) {
if (elm.type !== "b") {
return lst;
}
if (elm.name !== "SimpleBlock") {
return lst;
}
var o = exports.ebmlBlock(elm.data);
var hasWebP = o.frames.some(function (frame) {
// https://tools.ietf.org/html/rfc6386#section-19.1
var startcode = frame.slice(3, 6).toString("hex");
return startcode === "9d012a";
});
if (!hasWebP) {
return lst;
}
return lst.concat(elm);
}, []);
}
exports.WebPBlockFilter = WebPBlockFilter;
/**
* @param frame - VP8 BitStream のうち startcode をもつ frame
* @return - WebP ファイルの ArrayBuffer
*/
function VP8BitStreamToRiffWebPBuffer(frame) {
var VP8Chunk = createRIFFChunk("VP8 ", frame);
var WebPChunk = concat([
new exports.Buffer("WEBP", "ascii"),
VP8Chunk
]);
return createRIFFChunk("RIFF", WebPChunk);
}
exports.VP8BitStreamToRiffWebPBuffer = VP8BitStreamToRiffWebPBuffer;
/**
* RIFF データチャンクを作る
*/
function createRIFFChunk(FourCC, chunk) {
var chunkSize = new exports.Buffer(4);
chunkSize.writeUInt32LE(chunk.byteLength, 0);
return concat([
new exports.Buffer(FourCC.substr(0, 4), "ascii"),
chunkSize,
chunk,
new exports.Buffer(chunk.byteLength % 2 === 0 ? 0 : 1) // padding
]);
}
exports.createRIFFChunk = createRIFFChunk;
/* Original Metadata
m 0 EBML
u 1 EBMLVersion 1
u 1 EBMLReadVersion 1
u 1 EBMLMaxIDLength 4
u 1 EBMLMaxSizeLength 8
s 1 DocType webm
u 1 DocTypeVersion 4
u 1 DocTypeReadVersion 2
m 0 Segment
m 1 Info segmentContentStartPos, all CueClusterPositions provided in info.cues will be relative to here and will need adjusted
u 2 TimecodeScale 1000000
8 2 MuxingApp Chrome
8 2 WritingApp Chrome
m 1 Tracks tracksStartPos
m 2 TrackEntry
u 3 TrackNumber 1
u 3 TrackUID 31790271978391090
u 3 TrackType 2
s 3 CodecID A_OPUS
b 3 CodecPrivate <Buffer 19>
m 3 Audio
f 4 SamplingFrequency 48000
u 4 Channels 1
m 2 TrackEntry
u 3 TrackNumber 2
u 3 TrackUID 24051277436254136
u 3 TrackType 1
s 3 CodecID V_VP8
m 3 Video
u 4 PixelWidth 1024
u 4 PixelHeight 576
m 1 Cluster clusterStartPos
u 2 Timecode 0
b 2 SimpleBlock track:2 timecode:0 keyframe:true invisible:false discardable:false lacing:1
*/
/* Desired Metadata
m 0 EBML
u 1 EBMLVersion 1
u 1 EBMLReadVersion 1
u 1 EBMLMaxIDLength 4
u 1 EBMLMaxSizeLength 8
s 1 DocType webm
u 1 DocTypeVersion 4
u 1 DocTypeReadVersion 2
m 0 Segment
m 1 SeekHead -> This is SeekPosition 0, so all SeekPositions can be calculated as (bytePos - segmentContentStartPos), which is 44 in this case
m 2 Seek
b 3 SeekID -> Buffer([0x15, 0x49, 0xA9, 0x66]) Info
u 3 SeekPosition -> infoStartPos =
m 2 Seek
b 3 SeekID -> Buffer([0x16, 0x54, 0xAE, 0x6B]) Tracks
u 3 SeekPosition { tracksStartPos }
m 2 Seek
b 3 SeekID -> Buffer([0x1C, 0x53, 0xBB, 0x6B]) Cues
u 3 SeekPosition { cuesStartPos }
m 1 Info
f 2 Duration 32480 -> overwrite, or insert if it doesn't exist
u 2 TimecodeScale 1000000
8 2 MuxingApp Chrome
8 2 WritingApp Chrome
m 1 Tracks
m 2 TrackEntry
u 3 TrackNumber 1
u 3 TrackUID 31790271978391090
u 3 TrackType 2
s 3 CodecID A_OPUS
b 3 CodecPrivate <Buffer 19>
m 3 Audio
f 4 SamplingFrequency 48000
u 4 Channels 1
m 2 TrackEntry
u 3 TrackNumber 2
u 3 TrackUID 24051277436254136
u 3 TrackType 1
s 3 CodecID V_VP8
m 3 Video
u 4 PixelWidth 1024
u 4 PixelHeight 576
m 1 Cues -> cuesStartPos
m 2 CuePoint
u 3 CueTime 0
m 3 CueTrackPositions
u 4 CueTrack 1
u 4 CueClusterPosition 3911
m 2 CuePoint
u 3 CueTime 600
m 3 CueTrackPositions
u 4 CueTrack 1
u 4 CueClusterPosition 3911
m 1 Cluster
u 2 Timecode 0
b 2 SimpleBlock track:2 timecode:0 keyframe:true invisible:false discardable:false lacing:1
*/
/**
* convert the metadata from a streaming webm bytestream to a seekable file by inserting Duration, Seekhead and Cues
* @param originalMetadata - orginal metadata (everything before the clusters start) from media recorder
* @param duration - Duration (TimecodeScale)
* @param cues - cue points for clusters
*/
function makeMetadataSeekable(originalMetadata, duration, cuesInfo) {
// extract the header, we can reuse this as-is
var header = extractElement("EBML", originalMetadata);
var headerSize = encodedSizeOfEbml(header);
//console.error("Header size: " + headerSize);
//printElementIds(header);
// After the header comes the Segment open tag, which in this implementation is always 12 bytes (4 byte id, 8 byte 'unknown length')
// After that the segment content starts. All SeekPositions and CueClusterPosition must be relative to segmentContentStartPos
var segmentContentStartPos = headerSize + 12;
//console.error("segmentContentStartPos: " + segmentContentStartPos);
// find the original metadata size, and adjust it for header size and Segment start element so we can keep all positions relative to segmentContentStartPos
var originalMetadataSize = originalMetadata[originalMetadata.length - 1].dataEnd - segmentContentStartPos;
//console.error("Original Metadata size: " + originalMetadataSize);
//printElementIds(originalMetadata);
// extract the segment info, remove the potentially existing Duration element, and add our own one.
var info = extractElement("Info", originalMetadata);
removeElement("Duration", info);
info.splice(1, 0, { name: "Duration", type: "f", data: createFloatBuffer(duration, 8) });
var infoSize = encodedSizeOfEbml(info);
//console.error("Info size: " + infoSize);
//printElementIds(info);
// extract the track info, we can re-use this as is
var tracks = extractElement("Tracks", originalMetadata);
var tracksSize = encodedSizeOfEbml(tracks);
//console.error("Tracks size: " + tracksSize);
//printElementIds(tracks);
var seekHeadSize = 47; // Initial best guess, but could be slightly larger if the Cues element is huge.
var seekHead = [];
var cuesSize = 5 + cuesInfo.length * 15; // very rough initial approximation, depends a lot on file size and number of CuePoints
var cues = [];
var lastSizeDifference = -1; //
// The size of SeekHead and Cues elements depends on how many bytes the offsets values can be encoded in.
// The actual offsets in CueClusterPosition depend on the final size of the SeekHead and Cues elements
// We need to iteratively converge to a stable solution.
var maxIterations = 10;
var _loop_1 = function (i) {
// SeekHead starts at 0
var infoStart = seekHeadSize; // Info comes directly after SeekHead
var tracksStart = infoStart + infoSize; // Tracks comes directly after Info
var cuesStart = tracksStart + tracksSize; // Cues starts directly after
var newMetadataSize = cuesStart + cuesSize; // total size of metadata
// This is the offset all CueClusterPositions should be adjusted by due to the metadata size changing.
var sizeDifference = newMetadataSize - originalMetadataSize;
// console.error(`infoStart: ${infoStart}, infoSize: ${infoSize}`);
// console.error(`tracksStart: ${tracksStart}, tracksSize: ${tracksSize}`);
// console.error(`cuesStart: ${cuesStart}, cuesSize: ${cuesSize}`);
// console.error(`originalMetadataSize: ${originalMetadataSize}, newMetadataSize: ${newMetadataSize}, sizeDifference: ${sizeDifference}`);
// create the SeekHead element
seekHead = [];
seekHead.push({ name: "SeekHead", type: "m", isEnd: false });
seekHead.push({ name: "Seek", type: "m", isEnd: false });
seekHead.push({ name: "SeekID", type: "b", data: new exports.Buffer([0x15, 0x49, 0xA9, 0x66]) }); // Info
seekHead.push({ name: "SeekPosition", type: "u", data: createUIntBuffer(infoStart) });
seekHead.push({ name: "Seek", type: "m", isEnd: true });
seekHead.push({ name: "Seek", type: "m", isEnd: false });
seekHead.push({ name: "SeekID", type: "b", data: new exports.Buffer([0x16, 0x54, 0xAE, 0x6B]) }); // Tracks
seekHead.push({ name: "SeekPosition", type: "u", data: createUIntBuffer(tracksStart) });
seekHead.push({ name: "Seek", type: "m", isEnd: true });
seekHead.push({ name: "Seek", type: "m", isEnd: false });
seekHead.push({ name: "SeekID", type: "b", data: new exports.Buffer([0x1C, 0x53, 0xBB, 0x6B]) }); // Cues
seekHead.push({ name: "SeekPosition", type: "u", data: createUIntBuffer(cuesStart) });
seekHead.push({ name: "Seek", type: "m", isEnd: true });
seekHead.push({ name: "SeekHead", type: "m", isEnd: true });
seekHeadSize = encodedSizeOfEbml(seekHead);
//console.error("SeekHead size: " + seekHeadSize);
//printElementIds(seekHead);
// create the Cues element
cues = [];
cues.push({ name: "Cues", type: "m", isEnd: false });
cuesInfo.forEach(function (_a) {
var CueTrack = _a.CueTrack, CueClusterPosition = _a.CueClusterPosition, CueTime = _a.CueTime;
cues.push({ name: "CuePoint", type: "m", isEnd: false });
cues.push({ name: "CueTime", type: "u", data: createUIntBuffer(CueTime) });
cues.push({ name: "CueTrackPositions", type: "m", isEnd: false });
cues.push({ name: "CueTrack", type: "u", data: createUIntBuffer(CueTrack) });
//console.error(`CueClusterPosition: ${CueClusterPosition}, Corrected to: ${CueClusterPosition - segmentContentStartPos} , offset by ${sizeDifference} to become ${(CueClusterPosition - segmentContentStartPos) + sizeDifference - segmentContentStartPos}`);
// EBMLReader returns CueClusterPosition with absolute byte offsets. The Cues section expects them as offsets from the first level 1 element of the Segment, so we need to adjust it.
CueClusterPosition -= segmentContentStartPos;
// We also need to adjust to take into account the change in metadata size from when EBMLReader read the original metadata.
CueClusterPosition += sizeDifference;
cues.push({ name: "CueClusterPosition", type: "u", data: createUIntBuffer(CueClusterPosition) });
cues.push({ name: "CueTrackPositions", type: "m", isEnd: true });
cues.push({ name: "CuePoint", type: "m", isEnd: true });
});
cues.push({ name: "Cues", type: "m", isEnd: true });
cuesSize = encodedSizeOfEbml(cues);
//console.error("Cues size: " + cuesSize);
//console.error("Cue count: " + cuesInfo.length);
//printElementIds(cues);
// If the new MetadataSize is not the same as the previous iteration, we need to run once more.
if (lastSizeDifference !== sizeDifference) {
lastSizeDifference = sizeDifference;
if (i === maxIterations - 1) {
throw new Error("Failed to converge to a stable metadata size");
}
}
else {
return "break";
}
};
for (var i = 0; i < maxIterations; i++) {
var state_1 = _loop_1(i);
if (state_1 === "break")
break;
}
var finalMetadata = [].concat.apply([], [
header,
{ name: "Segment", type: "m", isEnd: false, unknownSize: true },
seekHead,
info,
tracks,
cues
]);
var result = new EBMLEncoder_1.default().encode(finalMetadata);
//printElementIds(finalMetadata);
//console.error(`Final metadata buffer size: ${result.byteLength}`);
//console.error(`Final metadata buffer size without header and segment: ${result.byteLength-segmentContentStartPos}`);
return result;
}
exports.makeMetadataSeekable = makeMetadataSeekable;
/**
* print all element id names in a list
* @param metadata - array of EBML elements to print
*
export function printElementIds(metadata: EBML.EBMLElementBuffer[]) {
let result: EBML.EBMLElementBuffer[] = [];
let start: number = -1;
for (let i = 0; i < metadata.length; i++) {
console.error("\t id: " + metadata[i].name);
}
}
*/
/**
* remove all occurances of an EBML element from an array of elements
* If it's a MasterElement you will also remove the content. (everything between start and end)
* @param idName - name of the EBML Element to remove.
* @param metadata - array of EBML elements to search
*/
function removeElement(idName, metadata) {
var result = [];
var start = -1;
for (var i = 0; i < metadata.length; i++) {
var element = metadata[i];
if (element.name === idName) {
// if it's a Master element, extract the start and end element, and everything in between
if (element.type === "m") {
if (!element.isEnd) {
start = i;
}
else {
// we've reached the end, extract the whole thing
if (start == -1)
throw new Error("Detected " + idName + " closing element before finding the start");
metadata.splice(start, i - start + 1);
return;
}
}
else {
// not a Master element, so we've found what we're looking for.
metadata.splice(i, 1);
return;
}
}
}
}
exports.removeElement = removeElement;
/**
* extract the first occurance of an EBML tag from a flattened array of EBML data.
* If it's a MasterElement you will also get the content. (everything between start and end)
* @param idName - name of the EBML Element to extract.
* @param metadata - array of EBML elements to search
*/
function extractElement(idName, metadata) {
var result = [];
var start = -1;
for (var i = 0; i < metadata.length; i++) {
var element = metadata[i];
if (element.name === idName) {
// if it's a Master element, extract the start and end element, and everything in between
if (element.type === "m") {
if (!element.isEnd) {
start = i;
}
else {
// we've reached the end, extract the whole thing
if (start == -1)
throw new Error("Detected " + idName + " closing element before finding the start");
result = metadata.slice(start, i + 1);
break;
}
}
else {
// not a Master element, so we've found what we're looking for.
result.push(metadata[i]);
break;
}
}
}
return result;
}
exports.extractElement = extractElement;
/**
* @deprecated
* metadata に対して duration と seekhead を追加した metadata を返す
* @param metadata - 変更前の webm における ファイル先頭から 最初の Cluster 要素までの 要素
* @param duration - Duration (TimecodeScale)
* @param cues - cue points for clusters
* @deprecated @param clusterPtrs - 変更前の webm における SeekHead に追加する Cluster 要素 への start pointer
* @deprecated @param cueInfos - please use cues.
*/
function putRefinedMetaData(metadata, info) {
if (Array.isArray(info.cueInfos) && !Array.isArray(info.cues)) {
console.warn("putRefinedMetaData: info.cueInfos property is deprecated. please use info.cues");
info.cues = info.cueInfos;
}
var ebml = [];
var payload = [];
for (var i_1 = 0; i_1 < metadata.length; i_1++) {
var elm = metadata[i_1];
if (elm.type === "m" && elm.name === "Segment") {
ebml = metadata.slice(0, i_1);
payload = metadata.slice(i_1);
if (elm.unknownSize) {
payload.shift(); // remove segment tag
break;
}
throw new Error("this metadata is not streaming webm file");
}
}
// *0 *4 *5 *36 *40 *48=segmentOffset *185=originalPayloadOffsetEnd
// | | | | | | |
// [EBML][size]....[Segment][size][Info][size][Duration][size]...[Cluster]
// | | |^inf | |
// | +segmentSiz(12)+ |
// +-ebmlSize(36)--+ | +-payloadSize(137)-------------+offsetEndDiff+
// | | +-newPayloadSize(??)-------------------------+
// | | | |
// [Segment][size][Info][size][Duration][size]....[size][value][Cluster]
// ^ |
//