rx-player
Version:
Canal+ HTML5 Video Player
527 lines (493 loc) • 12.3 kB
text/typescript
/**
* Copyright 2015 CANAL+ Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createBox } from "../../../parsers/containers/isobmff";
import {
be2toi,
be4toi,
concat,
itobe2,
itobe4,
itobe8,
} from "../../../utils/byte_parsing";
import { hexToBytes, strToUtf8 } from "../../../utils/string_parsing";
/**
* @param {Number} width
* @param {Number} height
* @param {Number} hRes - horizontal resolution, eg 72
* @param {Number} vRes - vertical resolution, eg 72
* @param {string} encName
* @param {Number} colorDepth - eg 24
* @param {Uint8Array} avcc - Uint8Array representing the avcC atom
* @returns {Uint8Array}
*/
function createAVC1Box(
width: number,
height: number,
hRes: number,
vRes: number,
encName: string,
colorDepth: number,
avcc: Uint8Array,
): Uint8Array {
return createBox(
"avc1",
concat(
6, // 6 bytes reserved
itobe2(1),
16, // drefIdx + QuickTime reserved, zeroes
itobe2(width), // size 2 w
itobe2(height), // size 2 h
itobe2(hRes),
2, // reso 4 h
itobe2(vRes),
2 + 4, // reso 4 v + QuickTime reserved, zeroes
[0, 1, encName.length], // frame count (default 1)
strToUtf8(encName), // 1byte len + encoder name str
31 - encName.length, // + padding
itobe2(colorDepth), // color depth
[0xff, 0xff], // reserved ones
avcc, // avcc atom,
),
);
}
/**
* @param {Number} width
* @param {Number} height
* @param {Number} hRes - horizontal resolution, eg 72
* @param {Number} vRes - vertical resolution, eg 72
* @param {string} encName
* @param {Number} colorDepth - eg 24
* @param {Uint8Array} avcc - Uint8Array representing the avcC atom
* @param {Uint8Array} sinf - Uint8Array representing the sinf atom
* @returns {Uint8Array}
*/
function createENCVBox(
width: number,
height: number,
hRes: number,
vRes: number,
encName: string,
colorDepth: number,
avcc: Uint8Array,
sinf: Uint8Array,
): Uint8Array {
return createBox(
"encv",
concat(
6, // 6 bytes reserved
itobe2(1),
16, // drefIdx + QuickTime reserved, zeroes
itobe2(width), // size 2 w
itobe2(height), // size 2 h
itobe2(hRes),
2, // reso 4 h
itobe2(vRes),
2 + 4, // reso 4 v + QuickTime reserved, zeroes
[0, 1, encName.length], // frame count (default 1)
strToUtf8(encName), // 1byte len + encoder name str
31 - encName.length, // + padding
itobe2(colorDepth), // color depth
[0xff, 0xff], // reserved ones
avcc, // avcc atom,
sinf,
),
);
}
/**
* @param {Number} drefIdx
* @param {Number} channelsCount
* @param {Number} sampleSize
* @param {Number} packetSize
* @param {Number} sampleRate
* @param {Uint8Array} esds - Uint8Array representing the esds atom
* @returns {Uint8Array}
*/
function createMP4ABox(
drefIdx: number,
channelsCount: number,
sampleSize: number,
packetSize: number,
sampleRate: number,
esds: Uint8Array,
): Uint8Array {
return createBox(
"mp4a",
concat(
6,
itobe2(drefIdx),
8,
itobe2(channelsCount),
itobe2(sampleSize),
2,
itobe2(packetSize),
itobe2(sampleRate),
2,
esds,
),
);
}
/**
* @param {Number} drefIdx
* @param {Number} channelsCount
* @param {Number} sampleSize
* @param {Number} packetSize
* @param {Number} sampleRate
* @param {Uint8Array} esds - Uint8Array representing the esds atom
* @param {Uint8Array} [sinf] - Uint8Array representing the sinf atom,
* only if name == "enca"
* @returns {Uint8Array}
*/
function createENCABox(
drefIdx: number,
channelsCount: number,
sampleSize: number,
packetSize: number,
sampleRate: number,
esds: Uint8Array,
sinf: Uint8Array,
): Uint8Array {
return createBox(
"enca",
concat(
6,
itobe2(drefIdx),
8,
itobe2(channelsCount),
itobe2(sampleSize),
2,
itobe2(packetSize),
itobe2(sampleRate),
2,
esds,
sinf,
),
);
}
/**
* @param {Uint8Array} url
* @returns {Uint8Array}
*/
function createDREFBox(url: Uint8Array): Uint8Array {
// only one description here... FIXME
return createBox("dref", concat(7, [1], url));
}
/**
* @param {string} majorBrand
* @param {Array.<string>} brands
* @returns {Uint8Array}
*/
function createFTYPBox(majorBrand: string, brands: string[]): Uint8Array {
const content = concat(
...[strToUtf8(majorBrand), [0, 0, 0, 1]].concat(brands.map(strToUtf8)),
);
return createBox("ftyp", content);
}
/**
* @param {string} schemeType - four letters (eg "cenc" for Common Encryption)
* @param {Number} schemeVersion - eg 65536
* @returns {Uint8Array}
*/
function createSCHMBox(schemeType: string, schemeVersion: number): Uint8Array {
return createBox("schm", concat(4, strToUtf8(schemeType), itobe4(schemeVersion)));
}
/**
* Create tfdt box from a decoding time.
* @param {number} decodeTime
* @returns {Uint8Array}
*/
function createTfdtBox(decodeTime: number): Uint8Array {
return createBox("tfdt", concat([1, 0, 0, 0], itobe8(decodeTime)));
}
/**
* @returns {Uint8Array}
*/
function createVMHDBox(): Uint8Array {
const arr = new Uint8Array(12);
arr[3] = 1; // QuickTime...
return createBox("vmhd", arr);
}
/**
* @param {Number} trackId
* @returns {Uint8Array}
*/
function createTREXBox(trackId: number): Uint8Array {
// default sample desc idx = 1
return createBox("trex", concat(4, itobe4(trackId), [0, 0, 0, 1], 12));
}
/**
* @param {Number} length
* @returns {Uint8Array}
*/
function createFreeBox(length: number): Uint8Array {
return createBox("free", new Uint8Array(length - 8));
}
/**
* @param {Number} stream
* @param {string} codecPrivateData - hex string
* @returns {Uint8Array}
*/
function createESDSBox(stream: number, codecPrivateData: string): Uint8Array {
return createBox(
"esds",
concat(
4,
[0x03, 0x19],
itobe2(stream),
[0x00, 0x04, 0x11, 0x40, 0x15],
11,
[0x05, 0x02],
hexToBytes(codecPrivateData),
[0x06, 0x01, 0x02],
),
);
}
/**
* @param {string} dataFormat - four letters (eg "avc1")
* @returns {Uint8Array}
*/
function createFRMABox(dataFormat: string): Uint8Array {
return createBox("frma", strToUtf8(dataFormat));
}
/**
* @param {Uint8Array} sps
* @param {Uint8Array} pps
* @param {Number} nalLen - NAL Unit length: 1, 2 or 4 bytes
* eg: avcc(0x4d, 0x40, 0x0d, 4, 0xe1, "674d400d96560c0efcb80a70505050a0",
* 1, "68ef3880")
* @returns {Uint8Array}
*/
function createAVCCBox(sps: Uint8Array, pps: Uint8Array, nalLen: number): Uint8Array {
let nal;
if (nalLen === 2) {
nal = 0x1;
} else if (nalLen === 4) {
nal = 0x3;
} else {
nal = 0x0;
}
// Deduce AVC Profile from SPS
const h264Profile = sps[1];
const h264CompatibleProfile = sps[2];
const h264Level = sps[3];
return createBox(
"avcC",
concat(
[1, h264Profile, h264CompatibleProfile, h264Level, (0x3f << 2) | nal, 0xe0 | 1],
itobe2(sps.length),
sps,
[1],
itobe2(pps.length),
pps,
),
);
}
/**
* @param {string} type - "video"/"audio"/"hint"
* @returns {Uint8Array}
*/
function createHDLRBox(type: "audio" | "video" | "hint"): Uint8Array {
let name;
let handlerName;
switch (type) {
case "video":
name = "vide";
handlerName = "VideoHandler";
break;
case "audio":
name = "soun";
handlerName = "SoundHandler";
break;
default:
name = "hint";
handlerName = "";
break;
}
return createBox(
"hdlr",
concat(
8,
strToUtf8(name),
12,
strToUtf8(handlerName),
1, // handler name is C-style string (0 terminated)
),
);
}
/**
* @param {number} timescale
* @returns {Uint8Array}
*/
function createMDHDBox(timescale: number): Uint8Array {
return createBox("mdhd", concat(12, itobe4(timescale), 8));
}
/**
* @param {Number} timescale
* @param {Number} trackId
* @returns {Uint8Array}
*/
function createMVHDBox(timescale: number, trackId: number): Uint8Array {
return createBox(
"mvhd",
concat(
12,
itobe4(timescale),
4,
[0, 1],
2, // we assume rate = 1;
[1, 0],
10, // we assume volume = 100%;
[0, 1],
14, // default matrix
[0, 1],
14, // default matrix
[64, 0, 0, 0],
26,
itobe2(trackId + 1), // next trackId (=trackId + 1);
),
);
}
/**
* @param {Uint8Array} mfhd
* @param {Uint8Array} tfhd
* @param {Uint8Array} tfdt
* @param {Uint8Array} trun
* @returns {Uint8Array}
*/
function createSAIOBox(
mfhd: Uint8Array,
tfhd: Uint8Array,
tfdt: Uint8Array,
trun: Uint8Array,
): Uint8Array {
return createBox(
"saio",
concat(
4,
[0, 0, 0, 1], // ??
itobe4(mfhd.length + tfhd.length + tfdt.length + trun.length + 8 + 8 + 8 + 8),
),
);
}
/**
* @param {Uint8Array} sencContent - including 8 bytes flags and entries count
* @returns {Uint8Array}
*/
function createSAIZBox(sencContent: Uint8Array): Uint8Array {
if (sencContent.length === 0) {
return createBox("saiz", new Uint8Array(0));
}
const flags = be4toi(sencContent, 0);
const entries = be4toi(sencContent, 4);
const arr = new Uint8Array(entries + 9);
arr.set(itobe4(entries), 5);
let i = 9;
let j = 8;
let pairsCnt;
let pairsLen;
while (j < sencContent.length) {
j += 8; // assuming IV is 8 bytes TODO handle 16 bytes IV
// if we have extradata for each entry
if ((flags & 0x2) === 0x2) {
pairsLen = 2;
pairsCnt = be2toi(sencContent, j);
j += pairsCnt * 6 + 2;
} else {
pairsCnt = 0;
pairsLen = 0;
}
arr[i] = pairsCnt * 6 + 8 + pairsLen;
i++;
}
return createBox("saiz", arr);
}
/**
* @returns {Uint8Array}
*/
function createSMHDBox(): Uint8Array {
return createBox("smhd", new Uint8Array(8));
}
/**
* @param {Array.<Uint8Array>} reps - arrays of Uint8Array,
* typically [avc1] or [encv, avc1]
* @returns {Uint8Array}
*/
function createSTSDBox(reps: Uint8Array[]): Uint8Array {
// only one description here... FIXME
const arrBase: Array<Uint8Array | number[] | number> = [7, [reps.length]];
return createBox("stsd", concat(...arrBase.concat(reps)));
}
/**
* @param {Number} width
* @param {Number} height
* @param {Number} trackId
* @returns {Uint8Array}
*/
function createTKHDBox(width: number, height: number, trackId: number): Uint8Array {
return createBox(
"tkhd",
concat(
itobe4(1 + 2 + 4),
8, // we assume track is enabled,
// in media and in preview.
itobe4(trackId),
20, // we assume trackId = 1;
[1, 0, 0, 0], // we assume volume = 100%;
[0, 1, 0, 0],
12, // default matrix
[0, 1, 0, 0],
12, // default matrix
[64, 0, 0, 0], // ??
itobe2(width),
2, // width (TODO handle fixed)
itobe2(height),
2, // height (TODO handle fixed)
),
);
}
/**
* @param {Number} algId - eg 1
* @param {Number} ivSize - eg 8
* @param {string} keyId - Hex KID 93789920e8d6520098577df8f2dd5546
* @returns {Uint8Array}
*/
function createTENCBox(algId: number, ivSize: number, keyId: Uint8Array): Uint8Array {
return createBox("tenc", concat(6, [algId, ivSize], keyId));
}
export {
createAVC1Box,
createAVCCBox,
createDREFBox,
createENCABox,
createENCVBox,
createESDSBox,
createFRMABox,
createFTYPBox,
createFreeBox,
createHDLRBox,
createMDHDBox,
createMP4ABox,
createMVHDBox,
createSAIOBox,
createSAIZBox,
createSCHMBox,
createSMHDBox,
createSTSDBox,
createTENCBox,
createTKHDBox,
createTREXBox,
createTfdtBox,
createVMHDBox,
};