feat: refactor folder structure & add new codec parser and gen & add unit tests

This commit is contained in:
2025-03-25 02:38:00 +08:00
parent 42e36e3c68
commit 39a4cf2773
67 changed files with 2211 additions and 514 deletions

View File

@@ -0,0 +1,8 @@
{
"name": "@konoplayer/core",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {},
"dependencies": {}
}

View 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.
}

View File

@@ -0,0 +1,2 @@
export { AudioCodec } from './audio-codecs';
export { VideoCodec } from './video-codecs';

View 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;
};

View 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);
}
}

View 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,
};
}

View File

@@ -0,0 +1,6 @@
export {
type RangedStream,
type CreateRangedStreamOptions,
createRangedStream,
} from './fetch';
export { BitReader } from './bit';

View 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');
}
}

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist"
},
"include": [
"src"
]
}