feat: refactor folder structure & add new codec parser and gen & add unit tests
This commit is contained in:
8
packages/core/package.json
Normal file
8
packages/core/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@konoplayer/core",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {},
|
||||
"dependencies": {}
|
||||
}
|
||||
32
packages/core/src/codecs/audio-codecs.ts
Normal file
32
packages/core/src/codecs/audio-codecs.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export enum AudioCodec {
|
||||
Unknown = 0,
|
||||
AAC = 1,
|
||||
MP3 = 2,
|
||||
PCM = 3,
|
||||
Vorbis = 4,
|
||||
FLAC = 5,
|
||||
AMR_NB = 6,
|
||||
AMR_WB = 7,
|
||||
PCM_MULAW = 8,
|
||||
GSM_MS = 9,
|
||||
PCM_S16BE = 10,
|
||||
PCM_S24BE = 11,
|
||||
Opus = 12,
|
||||
EAC3 = 13,
|
||||
PCM_ALAW = 14,
|
||||
ALAC = 15,
|
||||
AC3 = 16,
|
||||
MpegHAudio = 17,
|
||||
DTS = 18,
|
||||
DTSXP2 = 19,
|
||||
DTSE = 20,
|
||||
AC4 = 21,
|
||||
IAMF = 22,
|
||||
PCM_S32BE = 23,
|
||||
PCM_S32LE = 24,
|
||||
PCM_S24LE = 25,
|
||||
PCM_S16LE = 26,
|
||||
PCM_F32BE = 27,
|
||||
PCM_F32LE = 28,
|
||||
MaxValue = PCM_F32LE, // Must equal the last "real" codec above.
|
||||
}
|
||||
2
packages/core/src/codecs/index.ts
Normal file
2
packages/core/src/codecs/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { AudioCodec } from './audio-codecs';
|
||||
export { VideoCodec } from './video-codecs';
|
||||
97
packages/core/src/codecs/video-codecs.ts
Normal file
97
packages/core/src/codecs/video-codecs.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
export enum VideoCodec {
|
||||
Unknown = 0,
|
||||
H264 = 1,
|
||||
VC1 = 2,
|
||||
MPEG2 = 3,
|
||||
MPEG4 = 4,
|
||||
Theora = 5,
|
||||
VP8 = 6,
|
||||
VP9 = 7,
|
||||
HEVC = 8,
|
||||
DolbyVision = 9,
|
||||
AV1 = 10,
|
||||
MaxValue = AV1, // Must equal the last "real" codec above.
|
||||
}
|
||||
|
||||
export enum VideoCodecProfile {
|
||||
VIDEO_CODEC_PROFILE_UNKNOWN = -1,
|
||||
VIDEO_CODEC_PROFILE_MIN = VIDEO_CODEC_PROFILE_UNKNOWN,
|
||||
H264PROFILE_MIN = 0,
|
||||
H264PROFILE_BASELINE = H264PROFILE_MIN,
|
||||
H264PROFILE_MAIN = 1,
|
||||
H264PROFILE_EXTENDED = 2,
|
||||
H264PROFILE_HIGH = 3,
|
||||
H264PROFILE_HIGH10PROFILE = 4,
|
||||
H264PROFILE_HIGH422PROFILE = 5,
|
||||
H264PROFILE_HIGH444PREDICTIVEPROFILE = 6,
|
||||
H264PROFILE_SCALABLEBASELINE = 7,
|
||||
H264PROFILE_SCALABLEHIGH = 8,
|
||||
H264PROFILE_STEREOHIGH = 9,
|
||||
H264PROFILE_MULTIVIEWHIGH = 10,
|
||||
H264PROFILE_MAX = H264PROFILE_MULTIVIEWHIGH,
|
||||
VP8PROFILE_MIN = 11,
|
||||
VP8PROFILE_ANY = VP8PROFILE_MIN,
|
||||
VP8PROFILE_MAX = VP8PROFILE_ANY,
|
||||
VP9PROFILE_MIN = 12,
|
||||
VP9PROFILE_PROFILE0 = VP9PROFILE_MIN,
|
||||
VP9PROFILE_PROFILE1 = 13,
|
||||
VP9PROFILE_PROFILE2 = 14,
|
||||
VP9PROFILE_PROFILE3 = 15,
|
||||
VP9PROFILE_MAX = VP9PROFILE_PROFILE3,
|
||||
HEVCPROFILE_MIN = 16,
|
||||
HEVCPROFILE_MAIN = HEVCPROFILE_MIN,
|
||||
HEVCPROFILE_MAIN10 = 17,
|
||||
HEVCPROFILE_MAIN_STILL_PICTURE = 18,
|
||||
HEVCPROFILE_MAX = HEVCPROFILE_MAIN_STILL_PICTURE,
|
||||
DOLBYVISION_PROFILE0 = 19,
|
||||
// Deprecated: DOLBYVISION_PROFILE4 = 20,
|
||||
DOLBYVISION_PROFILE5 = 21,
|
||||
DOLBYVISION_PROFILE7 = 22,
|
||||
THEORAPROFILE_MIN = 23,
|
||||
THEORAPROFILE_ANY = THEORAPROFILE_MIN,
|
||||
THEORAPROFILE_MAX = THEORAPROFILE_ANY,
|
||||
AV1PROFILE_MIN = 24,
|
||||
AV1PROFILE_PROFILE_MAIN = AV1PROFILE_MIN,
|
||||
AV1PROFILE_PROFILE_HIGH = 25,
|
||||
AV1PROFILE_PROFILE_PRO = 26,
|
||||
AV1PROFILE_MAX = AV1PROFILE_PROFILE_PRO,
|
||||
DOLBYVISION_PROFILE8 = 27,
|
||||
DOLBYVISION_PROFILE9 = 28,
|
||||
HEVCPROFILE_EXT_MIN = 29,
|
||||
HEVCPROFILE_REXT = HEVCPROFILE_EXT_MIN,
|
||||
HEVCPROFILE_HIGH_THROUGHPUT = 30,
|
||||
HEVCPROFILE_MULTIVIEW_MAIN = 31,
|
||||
HEVCPROFILE_SCALABLE_MAIN = 32,
|
||||
HEVCPROFILE_3D_MAIN = 33,
|
||||
HEVCPROFILE_SCREEN_EXTENDED = 34,
|
||||
HEVCPROFILE_SCALABLE_REXT = 35,
|
||||
HEVCPROFILE_HIGH_THROUGHPUT_SCREEN_EXTENDED = 36,
|
||||
HEVCPROFILE_EXT_MAX = HEVCPROFILE_HIGH_THROUGHPUT_SCREEN_EXTENDED,
|
||||
VVCPROFILE_MIN = 37,
|
||||
VVCPROFILE_MAIN10 = VVCPROFILE_MIN,
|
||||
VVCPROFILE_MAIN12 = 38,
|
||||
VVCPROFILE_MAIN12_INTRA = 39,
|
||||
VVCPROIFLE_MULTILAYER_MAIN10 = 40,
|
||||
VVCPROFILE_MAIN10_444 = 41,
|
||||
VVCPROFILE_MAIN12_444 = 42,
|
||||
VVCPROFILE_MAIN16_444 = 43,
|
||||
VVCPROFILE_MAIN12_444_INTRA = 44,
|
||||
VVCPROFILE_MAIN16_444_INTRA = 45,
|
||||
VVCPROFILE_MULTILAYER_MAIN10_444 = 46,
|
||||
VVCPROFILE_MAIN10_STILL_PICTURE = 47,
|
||||
VVCPROFILE_MAIN12_STILL_PICTURE = 48,
|
||||
VVCPROFILE_MAIN10_444_STILL_PICTURE = 49,
|
||||
VVCPROFILE_MAIN12_444_STILL_PICTURE = 50,
|
||||
VVCPROFILE_MAIN16_444_STILL_PICTURE = 51,
|
||||
VVCPROFILE_MAX = VVCPROFILE_MAIN16_444_STILL_PICTURE,
|
||||
VIDEO_CODEC_PROFILE_MAX = VVCPROFILE_MAIN16_444_STILL_PICTURE,
|
||||
}
|
||||
|
||||
export type VideoCodecLevel = number; // uint32
|
||||
export const NoVideoCodecLevel: VideoCodecLevel = 0;
|
||||
|
||||
export type VideoCodecProfileLevel = {
|
||||
codec: VideoCodec;
|
||||
profile: VideoCodecProfile;
|
||||
level: VideoCodecLevel;
|
||||
};
|
||||
39
packages/core/src/data/bit.ts
Normal file
39
packages/core/src/data/bit.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export class BitReader {
|
||||
private data: Uint8Array;
|
||||
private byteOffset = 0;
|
||||
private bitOffset = 0;
|
||||
|
||||
constructor(data: Uint8Array) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
readBits(numBits: number): number {
|
||||
let value = 0;
|
||||
for (let i = 0; i < numBits; i++) {
|
||||
const bit = (this.data[this.byteOffset] >> (7 - this.bitOffset)) & 1;
|
||||
value = (value << 1) | bit;
|
||||
this.bitOffset++;
|
||||
if (this.bitOffset === 8) {
|
||||
this.bitOffset = 0;
|
||||
this.byteOffset++;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
skipBits(numBits: number): void {
|
||||
this.bitOffset += numBits;
|
||||
while (this.bitOffset >= 8) {
|
||||
this.bitOffset -= 8;
|
||||
this.byteOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
hasData(): boolean {
|
||||
return this.byteOffset < this.data.length;
|
||||
}
|
||||
|
||||
getRemainingBytes(): Uint8Array {
|
||||
return this.data.slice(this.byteOffset);
|
||||
}
|
||||
}
|
||||
65
packages/core/src/data/fetch.ts
Normal file
65
packages/core/src/data/fetch.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export interface RangedStream {
|
||||
controller: AbortController;
|
||||
response: Response;
|
||||
body: ReadableStream;
|
||||
totalSize?: number;
|
||||
}
|
||||
|
||||
export interface CreateRangedStreamOptions {
|
||||
url: string;
|
||||
byteStart?: number;
|
||||
byteEnd?: number;
|
||||
}
|
||||
|
||||
export async function createRangedStream({
|
||||
url,
|
||||
byteStart = 0,
|
||||
byteEnd,
|
||||
}: CreateRangedStreamOptions) {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
const headers = new Headers();
|
||||
headers.append(
|
||||
'Range',
|
||||
typeof byteEnd === 'number'
|
||||
? `bytes=${byteStart}-${byteEnd}`
|
||||
: `bytes=${byteStart}-`
|
||||
);
|
||||
|
||||
const response = await fetch(url, { signal, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('fetch video stream failed');
|
||||
}
|
||||
|
||||
const acceptRanges = response.headers.get('Accept-Ranges');
|
||||
|
||||
if (acceptRanges !== 'bytes') {
|
||||
throw new Error('video server does not support byte ranges');
|
||||
}
|
||||
|
||||
const body = response.body;
|
||||
|
||||
if (!(body instanceof ReadableStream)) {
|
||||
throw new Error('can not get readable stream from response.body');
|
||||
}
|
||||
|
||||
const contentRange = response.headers.get('Content-Range');
|
||||
|
||||
//
|
||||
// Content-Range Header Syntax:
|
||||
// Content-Range: <unit> <range-start>-<range-end>/<size>
|
||||
// Content-Range: <unit> <range-start>-<range-end>/*
|
||||
// Content-Range: <unit> */<size>
|
||||
//
|
||||
const totalSize = contentRange
|
||||
? Number.parseInt(contentRange.split('/')[1], 10)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
controller,
|
||||
response,
|
||||
body,
|
||||
totalSize,
|
||||
};
|
||||
}
|
||||
6
packages/core/src/data/index.ts
Normal file
6
packages/core/src/data/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
type RangedStream,
|
||||
type CreateRangedStreamOptions,
|
||||
createRangedStream,
|
||||
} from './fetch';
|
||||
export { BitReader } from './bit';
|
||||
25
packages/core/src/errors.ts
Normal file
25
packages/core/src/errors.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export class UnsupportedCodecError extends Error {
|
||||
constructor(codec: string, context: string) {
|
||||
super(`codec ${codec} is not supported in ${context} context`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ParseCodecError extends Error {
|
||||
constructor(codec: string, detail: string) {
|
||||
super(`code ${codec} private parse failed: ${detail}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnreachableOrLogicError extends Error {
|
||||
constructor(detail: string) {
|
||||
super(`unreachable or logic error: ${detail}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ParseCodecErrors extends Error {
|
||||
cause: Error[] = [];
|
||||
|
||||
constructor() {
|
||||
super('failed to parse codecs');
|
||||
}
|
||||
}
|
||||
10
packages/core/tsconfig.json
Normal file
10
packages/core/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user