feat: update examplees
This commit is contained in:
parent
53f2bc8ca7
commit
42e36e3c68
Binary file not shown.
Binary file not shown.
BIN
apps/mock/public/video/test-av1.mkv
Normal file
BIN
apps/mock/public/video/test-av1.mkv
Normal file
Binary file not shown.
BIN
apps/mock/public/video/test-avc.mkv
Normal file
BIN
apps/mock/public/video/test-avc.mkv
Normal file
Binary file not shown.
BIN
apps/mock/public/video/test-hevc.mkv
Normal file
BIN
apps/mock/public/video/test-hevc.mkv
Normal file
Binary file not shown.
BIN
apps/mock/public/video/test-theora.mkv
Normal file
BIN
apps/mock/public/video/test-theora.mkv
Normal file
Binary file not shown.
BIN
apps/mock/public/video/test-vp8.mkv
Normal file
BIN
apps/mock/public/video/test-vp8.mkv
Normal file
Binary file not shown.
BIN
apps/mock/public/video/test-vp9.mkv
Normal file
BIN
apps/mock/public/video/test-vp9.mkv
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -9,7 +9,7 @@
|
|||||||
"preview": "rsbuild preview"
|
"preview": "rsbuild preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"konoebml": "0.1.2-rc.5",
|
"konoebml": "0.1.2",
|
||||||
"lit": "^3.2.1"
|
"lit": "^3.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export class UnsupportCodecError extends Error {
|
export class UnsupportedCodecError extends Error {
|
||||||
constructor(codec: string, context: string) {
|
constructor(codec: string, context: string) {
|
||||||
super(`codec ${codec} is not supported in ${context} context`);
|
super(`codec ${codec} is not supported in ${context} context`);
|
||||||
}
|
}
|
||||||
@ -9,3 +9,17 @@ export class ParseCodecPrivateError extends Error {
|
|||||||
super(`code ${codec} private parse failed: ${detail}`);
|
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');
|
||||||
|
}
|
||||||
|
}
|
0
apps/playground/src/media/mkv/codecs/av1.ts
Normal file
0
apps/playground/src/media/mkv/codecs/av1.ts
Normal file
@ -1,5 +1,5 @@
|
|||||||
import { AudioCodec } from '../../base/audio_codecs';
|
import { AudioCodec } from '../../base/audio_codecs';
|
||||||
import { UnsupportCodecError } from '../../base/errors';
|
import { UnsupportedCodecError } from '../../base/errors';
|
||||||
import { VideoCodec } from '../../base/video_codecs';
|
import { VideoCodec } from '../../base/video_codecs';
|
||||||
import type { TrackEntryType } from '../schema';
|
import type { TrackEntryType } from '../schema';
|
||||||
import {
|
import {
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
genCodecIdByAVCDecoderConfigurationRecord,
|
genCodecIdByAVCDecoderConfigurationRecord,
|
||||||
parseAVCDecoderConfigurationRecord,
|
parseAVCDecoderConfigurationRecord,
|
||||||
} from './avc';
|
} from './avc';
|
||||||
|
import type {ProbeInfo} from "@/media/mkv/enhance/probe.ts";
|
||||||
|
|
||||||
export const VideoCodecId = {
|
export const VideoCodecId = {
|
||||||
VCM: 'V_MS/VFW/FOURCC',
|
VCM: 'V_MS/VFW/FOURCC',
|
||||||
@ -107,123 +108,183 @@ export type SubtitleCodecIdType =
|
|||||||
| `${(typeof SubtitleCodecId)[keyof typeof SubtitleCodecId]}`
|
| `${(typeof SubtitleCodecId)[keyof typeof SubtitleCodecId]}`
|
||||||
| string;
|
| string;
|
||||||
|
|
||||||
export function videoCodecIdToWebCodecsVideoDecoder(
|
|
||||||
track: TrackEntryType
|
export interface VideoDecoderConfigExt extends VideoDecoderConfig {
|
||||||
): [VideoCodec, string] {
|
codecType: VideoCodec,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function videoCodecIdToWebCodecs(
|
||||||
|
track: TrackEntryType,
|
||||||
|
_probeInfo?: ProbeInfo
|
||||||
|
): VideoDecoderConfigExt {
|
||||||
const codecId = track.CodecID;
|
const codecId = track.CodecID;
|
||||||
const codecPrivate = track.CodecPrivate;
|
const codecPrivate = track.CodecPrivate;
|
||||||
|
const shareOptions = {
|
||||||
|
description: codecPrivate
|
||||||
|
}
|
||||||
switch (codecId) {
|
switch (codecId) {
|
||||||
case VideoCodecId.HEVC:
|
case VideoCodecId.HEVC:
|
||||||
return [VideoCodec.HEVC, 'hevc'];
|
return { ...shareOptions, codecType: VideoCodec.HEVC, codec: 'hevc' };
|
||||||
case VideoCodecId.VP9:
|
case VideoCodecId.VP9:
|
||||||
return [VideoCodec.VP9, 'vp09'];
|
return { ...shareOptions, codecType: VideoCodec.VP9, codec: 'vp09' };
|
||||||
case VideoCodecId.AV1:
|
case VideoCodecId.AV1:
|
||||||
return [VideoCodec.AV1, 'av1'];
|
return { ...shareOptions, codecType: VideoCodec.AV1, codec: 'av1' };
|
||||||
case VideoCodecId.H264:
|
case VideoCodecId.H264:
|
||||||
if (!codecPrivate) {
|
if (!codecPrivate) {
|
||||||
throw new UnsupportCodecError(
|
throw new UnsupportedCodecError(
|
||||||
'h264(without codec_private profile)',
|
'h264(without codec_private profile)',
|
||||||
'web codecs audio decoder'
|
'web codecs audio decoder'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [
|
return {
|
||||||
VideoCodec.H264,
|
...shareOptions,
|
||||||
genCodecIdByAVCDecoderConfigurationRecord(
|
codecType: VideoCodec.H264,
|
||||||
|
codec: genCodecIdByAVCDecoderConfigurationRecord(
|
||||||
parseAVCDecoderConfigurationRecord(codecPrivate)
|
parseAVCDecoderConfigurationRecord(codecPrivate)
|
||||||
),
|
)
|
||||||
];
|
};
|
||||||
case VideoCodecId.THEORA:
|
case VideoCodecId.THEORA:
|
||||||
return [VideoCodec.Theora, 'theora'];
|
return { ...shareOptions, codecType: VideoCodec.Theora, codec: 'theora' };
|
||||||
case VideoCodecId.VP8:
|
case VideoCodecId.VP8:
|
||||||
return [VideoCodec.VP8, 'vp8'];
|
return { ...shareOptions, codecType: VideoCodec.VP8, codec: 'vp8' };
|
||||||
case VideoCodecId.MPEG4_ISO_SP:
|
case VideoCodecId.MPEG4_ISO_SP:
|
||||||
return [VideoCodec.MPEG4, 'mp4v.01.3'];
|
return { ...shareOptions, codecType: VideoCodec.MPEG4, codec: 'mp4v.01.3' };
|
||||||
case VideoCodecId.MPEG4_ISO_ASP:
|
case VideoCodecId.MPEG4_ISO_ASP:
|
||||||
return [VideoCodec.MPEG4, 'mp4v.20.9'];
|
return { ...shareOptions, codecType: VideoCodec.MPEG4, codec: 'mp4v.20.9' };
|
||||||
case VideoCodecId.MPEG4_ISO_AP:
|
case VideoCodecId.MPEG4_ISO_AP:
|
||||||
return [VideoCodec.MPEG4, 'mp4v.20.9'];
|
return { ...shareOptions, codecType: VideoCodec.MPEG4, codec: 'mp4v.20.9' };
|
||||||
default:
|
default:
|
||||||
throw new UnsupportCodecError(codecId, 'web codecs video decoder');
|
throw new UnsupportedCodecError(codecId, 'web codecs video decoder');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function videoCodecIdToWebCodecsAudioDecoder(
|
export interface AudioDecoderConfigExt extends AudioDecoderConfig {
|
||||||
track: TrackEntryType
|
codecType: AudioCodec,
|
||||||
): [AudioCodec, string] {
|
}
|
||||||
|
|
||||||
|
export function audioCodecIdToWebCodecs(
|
||||||
|
track: TrackEntryType,
|
||||||
|
_probeInfo?: ProbeInfo
|
||||||
|
): AudioDecoderConfigExt {
|
||||||
const codecId = track.CodecID;
|
const codecId = track.CodecID;
|
||||||
const codecPrivate = track.CodecPrivate;
|
const codecPrivate = track.CodecPrivate;
|
||||||
const bitDepth = track.Audio?.BitDepth;
|
const bitDepth = track.Audio?.BitDepth;
|
||||||
|
const numberOfChannels = Number(track.Audio?.Channels);
|
||||||
|
const sampleRate = Number(track.Audio?.SamplingFrequency);
|
||||||
|
|
||||||
|
const shareOptions = {
|
||||||
|
numberOfChannels,
|
||||||
|
sampleRate,
|
||||||
|
description: codecPrivate
|
||||||
|
}
|
||||||
|
|
||||||
switch (track.CodecID) {
|
switch (track.CodecID) {
|
||||||
case AudioCodecId.AAC_MPEG4_MAIN:
|
case AudioCodecId.AAC_MPEG4_MAIN:
|
||||||
case AudioCodecId.AAC_MPEG2_MAIN:
|
case AudioCodecId.AAC_MPEG2_MAIN:
|
||||||
return [AudioCodec.AAC, 'mp4a.40.1'];
|
return {
|
||||||
|
...shareOptions,
|
||||||
|
codecType: AudioCodec.AAC,
|
||||||
|
codec: 'mp4a.40.1'
|
||||||
|
};
|
||||||
case AudioCodecId.AAC_MPEG2_LC:
|
case AudioCodecId.AAC_MPEG2_LC:
|
||||||
case AudioCodecId.AAC_MPEG4_LC:
|
case AudioCodecId.AAC_MPEG4_LC:
|
||||||
return [AudioCodec.AAC, 'mp4a.40.2'];
|
return {
|
||||||
|
...shareOptions,
|
||||||
|
codecType: AudioCodec.AAC,
|
||||||
|
codec: 'mp4a.40.2'
|
||||||
|
};
|
||||||
case AudioCodecId.AAC_MPEG2_SSR:
|
case AudioCodecId.AAC_MPEG2_SSR:
|
||||||
case AudioCodecId.AAC_MPEG4_SSR:
|
case AudioCodecId.AAC_MPEG4_SSR:
|
||||||
return [AudioCodec.AAC, 'mp4a.40.3'];
|
return {
|
||||||
|
...shareOptions,
|
||||||
|
codecType: AudioCodec.AAC,
|
||||||
|
codec: 'mp4a.40.3'
|
||||||
|
};
|
||||||
case AudioCodecId.AAC_MPEG4_LTP:
|
case AudioCodecId.AAC_MPEG4_LTP:
|
||||||
return [AudioCodec.AAC, 'mp4a.40.4'];
|
return {
|
||||||
|
...shareOptions,
|
||||||
|
codecType: AudioCodec.AAC,
|
||||||
|
codec: 'mp4a.40.4'
|
||||||
|
};
|
||||||
case AudioCodecId.AAC_MPEG2_LC_SBR:
|
case AudioCodecId.AAC_MPEG2_LC_SBR:
|
||||||
case AudioCodecId.AAC_MPEG4_SBR:
|
case AudioCodecId.AAC_MPEG4_SBR:
|
||||||
return [AudioCodec.AAC, 'mp4a.40.5'];
|
return {
|
||||||
|
...shareOptions,
|
||||||
|
codecType: AudioCodec.AAC,
|
||||||
|
codec: 'mp4a.40.5'
|
||||||
|
};
|
||||||
case AudioCodecId.AAC:
|
case AudioCodecId.AAC:
|
||||||
return [
|
return {
|
||||||
AudioCodec.AAC,
|
...shareOptions,
|
||||||
codecPrivate
|
codecType: AudioCodec.AAC,
|
||||||
? genCodecIdByAudioSpecificConfig(
|
codec: codecPrivate
|
||||||
parseAudioSpecificConfig(codecPrivate)
|
? genCodecIdByAudioSpecificConfig(
|
||||||
)
|
parseAudioSpecificConfig(codecPrivate)
|
||||||
: 'mp4a.40.2',
|
) : 'mp4a.40.2',
|
||||||
];
|
};
|
||||||
case AudioCodecId.AC3:
|
case AudioCodecId.AC3:
|
||||||
case AudioCodecId.AC3_BSID9:
|
case AudioCodecId.AC3_BSID9:
|
||||||
return [AudioCodec.AC3, 'ac-3'];
|
return {
|
||||||
|
...shareOptions,
|
||||||
|
codecType: AudioCodec.AC3,
|
||||||
|
codec: 'ac-3'
|
||||||
|
};
|
||||||
case AudioCodecId.EAC3:
|
case AudioCodecId.EAC3:
|
||||||
case AudioCodecId.AC3_BSID10:
|
case AudioCodecId.AC3_BSID10:
|
||||||
return [AudioCodec.EAC3, 'ec-3'];
|
return {
|
||||||
|
...shareOptions,
|
||||||
|
codecType: AudioCodec.EAC3,
|
||||||
|
codec: 'ec-3'
|
||||||
|
};
|
||||||
case AudioCodecId.MPEG_L3:
|
case AudioCodecId.MPEG_L3:
|
||||||
return [AudioCodec.MP3, 'mp3'];
|
return {
|
||||||
|
...shareOptions,
|
||||||
|
codecType: AudioCodec.MP3,
|
||||||
|
codec: 'mp3'
|
||||||
|
};
|
||||||
case AudioCodecId.VORBIS:
|
case AudioCodecId.VORBIS:
|
||||||
return [AudioCodec.Vorbis, 'vorbis'];
|
return { ...shareOptions, codecType: AudioCodec.Vorbis, codec: 'vorbis' }
|
||||||
|
;
|
||||||
case AudioCodecId.FLAC:
|
case AudioCodecId.FLAC:
|
||||||
return [AudioCodec.FLAC, 'flac'];
|
return { ...shareOptions, codecType: AudioCodec.FLAC, codec: 'flac' }
|
||||||
|
;
|
||||||
case AudioCodecId.OPUS:
|
case AudioCodecId.OPUS:
|
||||||
return [AudioCodec.Opus, 'opus'];
|
return { ...shareOptions, codecType: AudioCodec.Opus, codec: 'opus' }
|
||||||
|
;
|
||||||
case AudioCodecId.ALAC:
|
case AudioCodecId.ALAC:
|
||||||
return [AudioCodec.ALAC, 'alac'];
|
return { ...shareOptions, codecType: AudioCodec.ALAC, codec: 'alac' }
|
||||||
|
;
|
||||||
case AudioCodecId.PCM_INT_BIG:
|
case AudioCodecId.PCM_INT_BIG:
|
||||||
if (bitDepth === 16) {
|
if (bitDepth === 16) {
|
||||||
return [AudioCodec.PCM_S16BE, 'pcm-s16be'];
|
return { ...shareOptions, codecType: AudioCodec.PCM_S16BE, codec: 'pcm-s16be' };
|
||||||
}
|
}
|
||||||
if (bitDepth === 24) {
|
if (bitDepth === 24) {
|
||||||
return [AudioCodec.PCM_S24BE, 'pcm-s24be'];
|
return { ...shareOptions, codecType: AudioCodec.PCM_S24BE, codec: 'pcm-s24be' };
|
||||||
}
|
}
|
||||||
if (bitDepth === 32) {
|
if (bitDepth === 32) {
|
||||||
return [AudioCodec.PCM_S32BE, 'pcm-s32be'];
|
return { ...shareOptions, codecType: AudioCodec.PCM_S32BE, codec: 'pcm-s32be' };
|
||||||
}
|
}
|
||||||
throw new UnsupportCodecError(
|
throw new UnsupportedCodecError(
|
||||||
`${codecId}(${bitDepth}b)`,
|
`${codecId}(${bitDepth}b)`,
|
||||||
'web codecs audio decoder'
|
'web codecs audio decoder'
|
||||||
);
|
);
|
||||||
case AudioCodecId.PCM_INT_LIT:
|
case AudioCodecId.PCM_INT_LIT:
|
||||||
if (bitDepth === 16) {
|
if (bitDepth === 16) {
|
||||||
return [AudioCodec.PCM_S16LE, 'pcm-s16le'];
|
return { ...shareOptions, codecType: AudioCodec.PCM_S16LE, codec: 'pcm-s16le' };
|
||||||
}
|
}
|
||||||
if (bitDepth === 24) {
|
if (bitDepth === 24) {
|
||||||
return [AudioCodec.PCM_S24LE, 'pcm-s24le'];
|
return { ...shareOptions, codecType: AudioCodec.PCM_S24LE, codec: 'pcm-s24le' };
|
||||||
}
|
}
|
||||||
if (bitDepth === 32) {
|
if (bitDepth === 32) {
|
||||||
return [AudioCodec.PCM_S32LE, 'pcm-s32le'];
|
return { ...shareOptions, codecType: AudioCodec.PCM_S32LE, codec: 'pcm-s32le' };
|
||||||
}
|
}
|
||||||
throw new UnsupportCodecError(
|
throw new UnsupportedCodecError(
|
||||||
`${codecId}(${bitDepth}b)`,
|
`${codecId}(${bitDepth}b)`,
|
||||||
'web codecs audio decoder'
|
'web codecs audio decoder'
|
||||||
);
|
);
|
||||||
case AudioCodecId.PCM_FLOAT_IEEE:
|
case AudioCodecId.PCM_FLOAT_IEEE:
|
||||||
return [AudioCodec.PCM_F32LE, 'pcm-f32le'];
|
return { ...shareOptions, codecType: AudioCodec.PCM_F32LE, codec: 'pcm-f32le' };
|
||||||
default:
|
default:
|
||||||
throw new UnsupportCodecError(codecId, 'web codecs audio decoder');
|
throw new UnsupportedCodecError(codecId, 'web codecs audio decoder');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
apps/playground/src/media/mkv/codecs/vp9.ts
Normal file
21
apps/playground/src/media/mkv/codecs/vp9.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { type } from 'arktype';
|
||||||
|
import type {TrackEntryType} from "@/media/mkv/schema.ts";
|
||||||
|
|
||||||
|
export const VP9DecoderProfileSchema = type('0 | 1 | 2 | 3');
|
||||||
|
|
||||||
|
export const VP9DecoderConfigurationRecordSchema = type({
|
||||||
|
profile: VP9DecoderProfileSchema,
|
||||||
|
level: type.number,
|
||||||
|
bitDepth: type.number,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type VP9DecoderConfigurationRecordType =
|
||||||
|
typeof VP9DecoderConfigurationRecordSchema.infer;
|
||||||
|
|
||||||
|
export function parseVP9DecoderConfigurationRecord(track: TrackEntryType) {
|
||||||
|
const pixelWidth = Number(track.Video?.PixelWidth);
|
||||||
|
const pixelHeight = Number(track.Video?.PixelHeight);
|
||||||
|
const pixels = pixelWidth * pixelHeight;
|
||||||
|
const bitDepth = Number(track.Video?.Colour?.BitsPerChannel) || 10;
|
||||||
|
|
||||||
|
}
|
3
apps/playground/src/media/mkv/enhance/probe.ts
Normal file
3
apps/playground/src/media/mkv/enhance/probe.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface ProbeInfo {
|
||||||
|
|
||||||
|
}
|
@ -30,8 +30,14 @@ import {
|
|||||||
TagSchema,
|
TagSchema,
|
||||||
type TagType,
|
type TagType,
|
||||||
TrackEntrySchema,
|
TrackEntrySchema,
|
||||||
type TrackEntryType,
|
type TrackEntryType, TrackTypeRestrictionEnum,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
import {concatBufs} from "konoebml/lib/tools";
|
||||||
|
import {ParseCodecErrors, UnreachableOrLogicError, UnsupportedCodecError} from "@/media/base/errors.ts";
|
||||||
|
import type {ProbeInfo} from "@/media/mkv/enhance/probe.ts";
|
||||||
|
import {audioCodecIdToWebCodecs, videoCodecIdToWebCodecs} from "@/media/mkv/codecs";
|
||||||
|
import {Queue} from "mnemonist";
|
||||||
|
import {BehaviorSubject} from "rxjs";
|
||||||
|
|
||||||
export const SEEK_ID_KAX_INFO = new Uint8Array([0x15, 0x49, 0xa9, 0x66]);
|
export const SEEK_ID_KAX_INFO = new Uint8Array([0x15, 0x49, 0xa9, 0x66]);
|
||||||
export const SEEK_ID_KAX_TRACKS = new Uint8Array([0x16, 0x54, 0xae, 0x6b]);
|
export const SEEK_ID_KAX_TRACKS = new Uint8Array([0x16, 0x54, 0xae, 0x6b]);
|
||||||
@ -41,6 +47,10 @@ export const SEEK_ID_KAX_TAGS = new Uint8Array([0x12, 0x54, 0xc3, 0x67]);
|
|||||||
export class SegmentSystem {
|
export class SegmentSystem {
|
||||||
startTag: EbmlSegmentTagType;
|
startTag: EbmlSegmentTagType;
|
||||||
headTags: EbmlTagType[] = [];
|
headTags: EbmlTagType[] = [];
|
||||||
|
teeStream: ReadableStream<Uint8Array>
|
||||||
|
teeBufferTask: Promise<Uint8Array>;
|
||||||
|
firstCluster: EbmlClusterTagType | undefined;
|
||||||
|
probInfo?: ProbeInfo;
|
||||||
|
|
||||||
cue: CueSystem;
|
cue: CueSystem;
|
||||||
cluster: ClusterSystem;
|
cluster: ClusterSystem;
|
||||||
@ -49,7 +59,7 @@ export class SegmentSystem {
|
|||||||
track: TrackSystem;
|
track: TrackSystem;
|
||||||
tag: TagSystem;
|
tag: TagSystem;
|
||||||
|
|
||||||
constructor(startNode: EbmlSegmentTagType) {
|
constructor(startNode: EbmlSegmentTagType, teeStream: ReadableStream<Uint8Array>) {
|
||||||
this.startTag = startNode;
|
this.startTag = startNode;
|
||||||
this.cue = new CueSystem(this);
|
this.cue = new CueSystem(this);
|
||||||
this.cluster = new ClusterSystem(this);
|
this.cluster = new ClusterSystem(this);
|
||||||
@ -57,17 +67,35 @@ export class SegmentSystem {
|
|||||||
this.info = new InfoSystem(this);
|
this.info = new InfoSystem(this);
|
||||||
this.track = new TrackSystem(this);
|
this.track = new TrackSystem(this);
|
||||||
this.tag = new TagSystem(this);
|
this.tag = new TagSystem(this);
|
||||||
|
this.teeStream = teeStream;
|
||||||
|
this.teeBufferTask = this.teeWaitingProbingData(teeStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async teeWaitingProbingData (teeStream: ReadableStream<Uint8Array>): Promise<Uint8Array> {
|
||||||
|
const reader = teeStream.getReader();
|
||||||
|
const list: Uint8Array<ArrayBufferLike>[] = [];
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
list.push(value);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.name === 'AbortError') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return concatBufs(...list)
|
||||||
}
|
}
|
||||||
|
|
||||||
get contentStartOffset() {
|
get contentStartOffset() {
|
||||||
return this.startTag.startOffset + this.startTag.headerLength;
|
return this.startTag.startOffset + this.startTag.headerLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
get startOffset() {
|
private seekLocal () {
|
||||||
return this.startTag.startOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
completeHeads() {
|
|
||||||
const infoTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_INFO);
|
const infoTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_INFO);
|
||||||
const tracksTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_TRACKS);
|
const tracksTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_TRACKS);
|
||||||
const cuesTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_CUES);
|
const cuesTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_CUES);
|
||||||
@ -83,13 +111,11 @@ export class SegmentSystem {
|
|||||||
this.track.prepareTracksWithTag(tracksTag);
|
this.track.prepareTracksWithTag(tracksTag);
|
||||||
}
|
}
|
||||||
if (tagsTag?.id === EbmlTagIdEnum.Tags) {
|
if (tagsTag?.id === EbmlTagIdEnum.Tags) {
|
||||||
this.tag.prepareTagsWIthTag(tagsTag);
|
this.tag.prepareTagsWithTag(tagsTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scanHead(tag: EbmlTagType) {
|
scanMeta(tag: EbmlTagType) {
|
||||||
if (
|
if (
|
||||||
tag.id === EbmlTagIdEnum.SeekHead &&
|
tag.id === EbmlTagIdEnum.SeekHead &&
|
||||||
tag.position === EbmlTagPosition.End
|
tag.position === EbmlTagPosition.End
|
||||||
@ -98,8 +124,62 @@ export class SegmentSystem {
|
|||||||
}
|
}
|
||||||
this.headTags.push(tag);
|
this.headTags.push(tag);
|
||||||
this.seek.memoTag(tag);
|
this.seek.memoTag(tag);
|
||||||
|
if (tag.id === EbmlTagIdEnum.Cluster && !this.firstCluster) {
|
||||||
|
this.firstCluster = tag;
|
||||||
|
this.seekLocal();
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async completeMeta () {
|
||||||
|
this.seekLocal();
|
||||||
|
|
||||||
|
await this.parseCodes();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchProbeInfo (_payload: Uint8Array): Promise<ProbeInfo> {
|
||||||
|
// call local or remote ff-probe
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseCodes () {
|
||||||
|
const candidates = this.track.tracks.filter(c => c.TrackType === TrackTypeRestrictionEnum.AUDIO || c.TrackType === TrackTypeRestrictionEnum.VIDEO);
|
||||||
|
const parseErrors = new ParseCodecErrors();
|
||||||
|
|
||||||
|
if (!this.probInfo) {
|
||||||
|
for (const t of candidates) {
|
||||||
|
try {
|
||||||
|
await this.track.initTrack(t, undefined)
|
||||||
|
} catch (e: unknown) {
|
||||||
|
parseErrors.cause.push(e as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parseErrors.cause.length > 0) {
|
||||||
|
try {
|
||||||
|
const teeBuffer = await this.teeBufferTask;
|
||||||
|
this.probInfo = await this.fetchProbeInfo(teeBuffer);
|
||||||
|
} catch (e) {
|
||||||
|
parseErrors.cause.push(e as Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const t of candidates) {
|
||||||
|
try {
|
||||||
|
await this.track.initTrack(t, this.probInfo)
|
||||||
|
} catch (e) {
|
||||||
|
parseErrors.cause.push(e as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parseErrors.cause.length > 0) {
|
||||||
|
console.error(parseErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SegmentComponent<T> = T & {
|
export type SegmentComponent<T> = T & {
|
||||||
@ -157,7 +237,7 @@ export class SeekSystem extends SegmentComponentSystemTrait<
|
|||||||
}
|
}
|
||||||
|
|
||||||
seekHeads: SeekHeadType[] = [];
|
seekHeads: SeekHeadType[] = [];
|
||||||
offsetToTagMemo: Map<number, EbmlTagType> = new Map();
|
private offsetToTagMemo: Map<number, EbmlTagType> = new Map();
|
||||||
|
|
||||||
memoTag(tag: EbmlTagType) {
|
memoTag(tag: EbmlTagType) {
|
||||||
this.offsetToTagMemo.set(tag.startOffset, tag);
|
this.offsetToTagMemo.set(tag.startOffset, tag);
|
||||||
@ -193,6 +273,13 @@ export class SeekSystem extends SegmentComponentSystemTrait<
|
|||||||
seekTagBySeekId(seekId: Uint8Array): EbmlTagType | undefined {
|
seekTagBySeekId(seekId: Uint8Array): EbmlTagType | undefined {
|
||||||
return this.seekTagByStartOffset(this.seekOffsetBySeekId(seekId));
|
return this.seekTagByStartOffset(this.seekOffsetBySeekId(seekId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get firstClusterOffset () {
|
||||||
|
if (!this.segment.firstCluster) {
|
||||||
|
throw new UnreachableOrLogicError("first cluster not found")
|
||||||
|
}
|
||||||
|
return this.segment.firstCluster.startOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InfoSystem extends SegmentComponentSystemTrait<
|
export class InfoSystem extends SegmentComponentSystemTrait<
|
||||||
@ -228,6 +315,18 @@ export class ClusterSystem extends SegmentComponentSystemTrait<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetTrackEntryOptions {
|
||||||
|
priority?: (v: SegmentComponent<TrackEntryType>) => number;
|
||||||
|
predicate?: (v: SegmentComponent<TrackEntryType>) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface TrackState<Decoder, Config, Frame> {
|
||||||
|
decoder: Decoder,
|
||||||
|
configuration?: Config,
|
||||||
|
frameBuffer$: BehaviorSubject<Queue<Frame>>
|
||||||
|
}
|
||||||
|
|
||||||
export class TrackSystem extends SegmentComponentSystemTrait<
|
export class TrackSystem extends SegmentComponentSystemTrait<
|
||||||
EbmlTrackEntryTagType,
|
EbmlTrackEntryTagType,
|
||||||
typeof TrackEntrySchema
|
typeof TrackEntrySchema
|
||||||
@ -237,15 +336,14 @@ export class TrackSystem extends SegmentComponentSystemTrait<
|
|||||||
}
|
}
|
||||||
|
|
||||||
tracks: SegmentComponent<TrackEntryType>[] = [];
|
tracks: SegmentComponent<TrackEntryType>[] = [];
|
||||||
|
videoTrackState = new WeakMap<TrackEntryType, TrackState< VideoDecoder, VideoDecoderConfig, VideoFrame>>();
|
||||||
|
audioTrackState = new WeakMap<TrackEntryType, TrackState<AudioDecoder, AudioDecoderConfig, AudioData>>();
|
||||||
|
|
||||||
getTrackEntry({
|
getTrackEntry({
|
||||||
priority = (track) =>
|
priority = (track) =>
|
||||||
(Number(!!track.FlagForced) << 4) + Number(!!track.FlagDefault),
|
(Number(!!track.FlagForced) << 4) + Number(!!track.FlagDefault),
|
||||||
predicate = (track) => track.FlagEnabled !== 0,
|
predicate = (track) => track.FlagEnabled !== 0,
|
||||||
}: {
|
}: GetTrackEntryOptions) {
|
||||||
priority?: (v: SegmentComponent<TrackEntryType>) => number;
|
|
||||||
predicate?: (v: SegmentComponent<TrackEntryType>) => boolean;
|
|
||||||
}) {
|
|
||||||
return this.tracks
|
return this.tracks
|
||||||
.filter(predicate)
|
.filter(predicate)
|
||||||
.toSorted((a, b) => priority(b) - priority(a))
|
.toSorted((a, b) => priority(b) - priority(a))
|
||||||
@ -258,6 +356,52 @@ export class TrackSystem extends SegmentComponentSystemTrait<
|
|||||||
.map((c) => this.componentFromTag(c));
|
.map((c) => this.componentFromTag(c));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initTrack (track: TrackEntryType, probe?: ProbeInfo) {
|
||||||
|
if (track.TrackType === TrackTypeRestrictionEnum.AUDIO) {
|
||||||
|
const configuration = audioCodecIdToWebCodecs(track, probe);
|
||||||
|
if (await AudioDecoder.isConfigSupported(configuration)) {
|
||||||
|
throw new UnsupportedCodecError(configuration.codec, 'audio decoder')
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue$ = new BehaviorSubject(new Queue<AudioData>());
|
||||||
|
this.audioTrackState.set(track, {
|
||||||
|
configuration,
|
||||||
|
decoder: new AudioDecoder({
|
||||||
|
output: (audioData) => {
|
||||||
|
const queue = queue$.getValue();
|
||||||
|
queue.enqueue(audioData);
|
||||||
|
queue$.next(queue);
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
queue$.error(e);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
frameBuffer$: queue$,
|
||||||
|
})
|
||||||
|
} else if (track.TrackType === TrackTypeRestrictionEnum.VIDEO) {
|
||||||
|
const configuration = videoCodecIdToWebCodecs(track, probe);
|
||||||
|
if (await VideoDecoder.isConfigSupported(configuration)) {
|
||||||
|
throw new UnsupportedCodecError(configuration.codec, 'audio decoder')
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue$ = new BehaviorSubject(new Queue<VideoFrame>());
|
||||||
|
this.videoTrackState.set(track, {
|
||||||
|
configuration,
|
||||||
|
decoder: new VideoDecoder({
|
||||||
|
output: (audioData) => {
|
||||||
|
const queue = queue$.getValue();
|
||||||
|
queue.enqueue(audioData);
|
||||||
|
queue$.next(queue);
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
queue$.error(e);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
frameBuffer$: queue$,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CueSystem extends SegmentComponentSystemTrait<
|
export class CueSystem extends SegmentComponentSystemTrait<
|
||||||
@ -350,7 +494,7 @@ export class TagSystem extends SegmentComponentSystemTrait<
|
|||||||
|
|
||||||
tags: SegmentComponent<TagType>[] = [];
|
tags: SegmentComponent<TagType>[] = [];
|
||||||
|
|
||||||
prepareWithTagsTag(tag: EbmlTagsTagType) {
|
prepareTagsWithTag(tag: EbmlTagsTagType) {
|
||||||
this.tags = tag.children
|
this.tags = tag.children
|
||||||
.filter((c) => c.id === EbmlTagIdEnum.Tag)
|
.filter((c) => c.id === EbmlTagIdEnum.Tag)
|
||||||
.map((c) => this.componentFromTag(c));
|
.map((c) => this.componentFromTag(c));
|
||||||
|
@ -1,62 +1,64 @@
|
|||||||
|
import {EbmlStreamDecoder, EbmlTagIdEnum, EbmlTagPosition, type EbmlTagType,} from 'konoebml';
|
||||||
import {
|
import {
|
||||||
type EbmlTagType,
|
|
||||||
EbmlStreamDecoder,
|
|
||||||
EbmlTagIdEnum,
|
|
||||||
EbmlTagPosition,
|
|
||||||
} from 'konoebml';
|
|
||||||
import {
|
|
||||||
Observable,
|
|
||||||
from,
|
|
||||||
switchMap,
|
|
||||||
share,
|
|
||||||
defer,
|
defer,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
of,
|
|
||||||
filter,
|
filter,
|
||||||
finalize,
|
finalize,
|
||||||
|
from,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
map,
|
map,
|
||||||
merge,
|
merge,
|
||||||
raceWith,
|
Observable,
|
||||||
|
of,
|
||||||
reduce,
|
reduce,
|
||||||
scan,
|
scan,
|
||||||
|
share,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
|
switchMap,
|
||||||
take,
|
take,
|
||||||
takeUntil,
|
takeWhile,
|
||||||
withLatestFrom,
|
withLatestFrom,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { createRangedStream, type CreateRangedStreamOptions } from '@/fetch';
|
import {createRangedStream, type CreateRangedStreamOptions} from '@/fetch';
|
||||||
import {
|
import {type CueSystem, SEEK_ID_KAX_CUES, SEEK_ID_KAX_TAGS, type SegmentComponent, SegmentSystem,} from './model';
|
||||||
SegmentSystem,
|
import {isTagIdPos, waitTick} from './util';
|
||||||
SEEK_ID_KAX_CUES,
|
import type {ClusterType} from './schema';
|
||||||
type CueSystem,
|
|
||||||
type SegmentComponent,
|
|
||||||
SEEK_ID_KAX_TAGS,
|
|
||||||
} from './model';
|
|
||||||
import { isTagIdPos, waitTick } from './util';
|
|
||||||
import type { ClusterType } from './schema';
|
|
||||||
|
|
||||||
export interface CreateRangedEbmlStreamOptions
|
export interface CreateRangedEbmlStreamOptions
|
||||||
extends CreateRangedStreamOptions {}
|
extends CreateRangedStreamOptions {
|
||||||
|
tee?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function createRangedEbmlStream({
|
export function createRangedEbmlStream({
|
||||||
url,
|
url,
|
||||||
byteStart = 0,
|
byteStart = 0,
|
||||||
byteEnd,
|
byteEnd,
|
||||||
|
tee = false
|
||||||
}: CreateRangedEbmlStreamOptions): Observable<{
|
}: CreateRangedEbmlStreamOptions): Observable<{
|
||||||
ebml$: Observable<EbmlTagType>;
|
ebml$: Observable<EbmlTagType>;
|
||||||
totalSize?: number;
|
totalSize?: number;
|
||||||
response: Response;
|
response: Response;
|
||||||
body: ReadableStream<Uint8Array>;
|
body: ReadableStream<Uint8Array>;
|
||||||
controller: AbortController;
|
controller: AbortController;
|
||||||
|
teeBody: ReadableStream<Uint8Array> | undefined;
|
||||||
}> {
|
}> {
|
||||||
const stream$ = from(createRangedStream({ url, byteStart, byteEnd }));
|
const stream$ = from(createRangedStream({ url, byteStart, byteEnd }));
|
||||||
|
|
||||||
return stream$.pipe(
|
return stream$.pipe(
|
||||||
switchMap(({ controller, body, totalSize, response }) => {
|
switchMap(({ controller, body, totalSize, response }) => {
|
||||||
let requestCompleted = false;
|
let requestCompleted = false;
|
||||||
|
let teeStream: ReadableStream<Uint8Array> | undefined;
|
||||||
|
|
||||||
|
let stream: ReadableStream<Uint8Array>;
|
||||||
|
|
||||||
|
if (tee) {
|
||||||
|
[stream, teeStream] = body.tee();
|
||||||
|
} else {
|
||||||
|
stream = body;
|
||||||
|
}
|
||||||
|
|
||||||
const originRequest$ = new Observable<EbmlTagType>((subscriber) => {
|
const originRequest$ = new Observable<EbmlTagType>((subscriber) => {
|
||||||
body
|
stream
|
||||||
.pipeThrough(
|
.pipeThrough(
|
||||||
new EbmlStreamDecoder({
|
new EbmlStreamDecoder({
|
||||||
streamStartOffset: byteStart,
|
streamStartOffset: byteStart,
|
||||||
@ -114,7 +116,8 @@ export function createRangedEbmlStream({
|
|||||||
ebml$,
|
ebml$,
|
||||||
totalSize,
|
totalSize,
|
||||||
response,
|
response,
|
||||||
body,
|
body: stream,
|
||||||
|
teeBody: teeStream,
|
||||||
controller,
|
controller,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -128,14 +131,16 @@ export function createEbmlController({
|
|||||||
url,
|
url,
|
||||||
...options
|
...options
|
||||||
}: CreateEbmlControllerOptions) {
|
}: CreateEbmlControllerOptions) {
|
||||||
const request$ = createRangedEbmlStream({
|
const metaRequest$ = createRangedEbmlStream({
|
||||||
...options,
|
...options,
|
||||||
url,
|
url,
|
||||||
byteStart: 0,
|
byteStart: 0,
|
||||||
|
tee: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const controller$ = request$.pipe(
|
const controller$ = metaRequest$.pipe(
|
||||||
map(({ totalSize, ebml$, response, controller }) => {
|
map(({ totalSize, ebml$, response, controller, teeBody }) => {
|
||||||
|
|
||||||
const head$ = ebml$.pipe(
|
const head$ = ebml$.pipe(
|
||||||
filter(isTagIdPos(EbmlTagIdEnum.EBML, EbmlTagPosition.End)),
|
filter(isTagIdPos(EbmlTagIdEnum.EBML, EbmlTagPosition.End)),
|
||||||
take(1),
|
take(1),
|
||||||
@ -147,8 +152,7 @@ export function createEbmlController({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const segmentStart$ = ebml$.pipe(
|
const segmentStart$ = ebml$.pipe(
|
||||||
filter((s) => s.position === EbmlTagPosition.Start),
|
filter(isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.Start))
|
||||||
filter((tag) => tag.id === EbmlTagIdEnum.Segment)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,24 +161,24 @@ export function createEbmlController({
|
|||||||
*/
|
*/
|
||||||
const segments$ = segmentStart$.pipe(
|
const segments$ = segmentStart$.pipe(
|
||||||
map((startTag) => {
|
map((startTag) => {
|
||||||
const segment = new SegmentSystem(startTag);
|
const segment = new SegmentSystem(startTag, teeBody!);
|
||||||
const clusterSystem = segment.cluster;
|
const clusterSystem = segment.cluster;
|
||||||
const seekSystem = segment.seek;
|
const seekSystem = segment.seek;
|
||||||
|
|
||||||
const segmentEnd$ = ebml$.pipe(
|
|
||||||
filter(isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End)),
|
|
||||||
filter((tag) => tag.id === EbmlTagIdEnum.Segment),
|
|
||||||
take(1)
|
|
||||||
);
|
|
||||||
|
|
||||||
const clusterStart$ = ebml$.pipe(
|
|
||||||
filter(isTagIdPos(EbmlTagIdEnum.Cluster, EbmlTagPosition.Start)),
|
|
||||||
take(1),
|
|
||||||
shareReplay(1)
|
|
||||||
);
|
|
||||||
|
|
||||||
const meta$ = ebml$.pipe(
|
const meta$ = ebml$.pipe(
|
||||||
takeUntil(clusterStart$.pipe(raceWith(segmentEnd$))),
|
scan((acc, tag) => {
|
||||||
|
// avoid object recreation
|
||||||
|
acc.hasKeyframe = acc.hasKeyframe || (tag.id === EbmlTagIdEnum.SimpleBlock && tag.keyframe) || (tag.id === EbmlTagIdEnum.BlockGroup && tag.children.every(c => c.id !== EbmlTagIdEnum.ReferenceBlock));
|
||||||
|
acc.tag = tag;
|
||||||
|
return acc;
|
||||||
|
}, { hasKeyframe: false, tag: undefined as unknown as EbmlTagType }),
|
||||||
|
takeWhile(
|
||||||
|
({ tag, hasKeyframe }) => {
|
||||||
|
return !isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End)(tag) && !(isTagIdPos(EbmlTagIdEnum.Cluster, EbmlTagPosition.End)(tag) && hasKeyframe);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
),
|
||||||
|
map(({ tag }) => tag),
|
||||||
share({
|
share({
|
||||||
resetOnComplete: false,
|
resetOnComplete: false,
|
||||||
resetOnError: false,
|
resetOnError: false,
|
||||||
@ -183,8 +187,8 @@ export function createEbmlController({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const withMeta$ = meta$.pipe(
|
const withMeta$ = meta$.pipe(
|
||||||
reduce((segment, meta) => segment.scanHead(meta), segment),
|
reduce((segment, meta) => segment.scanMeta(meta), segment),
|
||||||
map(segment.completeHeads.bind(segment)),
|
switchMap(() => segment.completeMeta()),
|
||||||
take(1),
|
take(1),
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
@ -231,6 +235,7 @@ export function createEbmlController({
|
|||||||
if (tagSystem.prepared) {
|
if (tagSystem.prepared) {
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteTagsTagStartOffset =
|
const remoteTagsTagStartOffset =
|
||||||
seekSystem.seekOffsetBySeekId(SEEK_ID_KAX_TAGS);
|
seekSystem.seekOffsetBySeekId(SEEK_ID_KAX_TAGS);
|
||||||
if (remoteTagsTagStartOffset! >= 0) {
|
if (remoteTagsTagStartOffset! >= 0) {
|
||||||
@ -243,7 +248,7 @@ export function createEbmlController({
|
|||||||
filter(isTagIdPos(EbmlTagIdEnum.Tags, EbmlTagPosition.End)),
|
filter(isTagIdPos(EbmlTagIdEnum.Tags, EbmlTagPosition.End)),
|
||||||
withLatestFrom(withMeta$),
|
withLatestFrom(withMeta$),
|
||||||
map(([tags, withMeta]) => {
|
map(([tags, withMeta]) => {
|
||||||
withMeta.tag.prepareWithTagsTag(tags);
|
withMeta.tag.prepareTagsWithTag(tags);
|
||||||
return withMeta;
|
return withMeta;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -280,12 +285,12 @@ export function createEbmlController({
|
|||||||
const seekWithoutCues = (
|
const seekWithoutCues = (
|
||||||
seekTime: number
|
seekTime: number
|
||||||
): Observable<SegmentComponent<ClusterType>> => {
|
): Observable<SegmentComponent<ClusterType>> => {
|
||||||
const request$ = clusterStart$.pipe(
|
const request$ = withMeta$.pipe(
|
||||||
switchMap((startTag) =>
|
switchMap(() =>
|
||||||
createRangedEbmlStream({
|
createRangedEbmlStream({
|
||||||
...options,
|
...options,
|
||||||
url,
|
url,
|
||||||
byteStart: startTag.startOffset,
|
byteStart: seekSystem.firstClusterOffset,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -301,18 +306,16 @@ export function createEbmlController({
|
|||||||
|
|
||||||
return cluster$.pipe(
|
return cluster$.pipe(
|
||||||
scan(
|
scan(
|
||||||
(prev, curr) =>
|
(acc, curr) => {
|
||||||
[prev?.[1], curr] as [
|
// avoid object recreation
|
||||||
SegmentComponent<ClusterType> | undefined,
|
acc.prev = acc.next;
|
||||||
SegmentComponent<ClusterType> | undefined,
|
acc.next = curr;
|
||||||
],
|
return acc;
|
||||||
[undefined, undefined] as [
|
},
|
||||||
SegmentComponent<ClusterType> | undefined,
|
({ prev: undefined as (SegmentComponent<ClusterType> | undefined), next: undefined as SegmentComponent<ClusterType> | undefined })
|
||||||
SegmentComponent<ClusterType> | undefined,
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
filter((c) => c[1]?.Timestamp! > seekTime),
|
filter((c) => c.next?.Timestamp! > seekTime),
|
||||||
map((c) => c[0] ?? c[1]!)
|
map((c) => c.prev ?? c.next!)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -394,6 +397,6 @@ export function createEbmlController({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
controller$,
|
controller$,
|
||||||
request$,
|
request$: metaRequest$,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ import { TrackTypeRestrictionEnum, type ClusterType } from './media/mkv/schema';
|
|||||||
import type { SegmentComponent } from './media/mkv/model';
|
import type { SegmentComponent } from './media/mkv/model';
|
||||||
import { createRef, ref, type Ref } from 'lit/directives/ref.js';
|
import { createRef, ref, type Ref } from 'lit/directives/ref.js';
|
||||||
import { Queue } from 'mnemonist';
|
import { Queue } from 'mnemonist';
|
||||||
import { dataViewSliceToBuf } from 'konoebml';
|
|
||||||
|
|
||||||
export class VideoPipelineDemo extends LitElement {
|
export class VideoPipelineDemo extends LitElement {
|
||||||
static styles = css``;
|
static styles = css``;
|
||||||
@ -45,7 +44,6 @@ export class VideoPipelineDemo extends LitElement {
|
|||||||
videoFrameBuffer$ = new BehaviorSubject(new Queue<VideoFrame>());
|
videoFrameBuffer$ = new BehaviorSubject(new Queue<VideoFrame>());
|
||||||
audioFrameBuffer$ = new BehaviorSubject(new Queue<AudioData>());
|
audioFrameBuffer$ = new BehaviorSubject(new Queue<AudioData>());
|
||||||
pipeline$$?: Subscription;
|
pipeline$$?: Subscription;
|
||||||
bridge$$?: Subscription;
|
|
||||||
private startTime = 0;
|
private startTime = 0;
|
||||||
|
|
||||||
paused$ = new BehaviorSubject<boolean>(false);
|
paused$ = new BehaviorSubject<boolean>(false);
|
||||||
|
20
pnpm-lock.yaml
generated
20
pnpm-lock.yaml
generated
@ -89,8 +89,8 @@ importers:
|
|||||||
apps/playground:
|
apps/playground:
|
||||||
dependencies:
|
dependencies:
|
||||||
konoebml:
|
konoebml:
|
||||||
specifier: 0.1.2-rc.5
|
specifier: 0.1.2
|
||||||
version: 0.1.2-rc.5(arktype@2.1.10)
|
version: 0.1.2(arktype@2.1.10)
|
||||||
lit:
|
lit:
|
||||||
specifier: ^3.2.1
|
specifier: ^3.2.1
|
||||||
version: 3.2.1
|
version: 3.2.1
|
||||||
@ -1885,6 +1885,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
konoebml@0.1.2:
|
||||||
|
resolution: {integrity: sha512-ZPibYe5KLu+fhd3ZgKiz2xyitTY24VhhG+DHw+hCUwJppaQbEJDLTkbUcWQ6A3JkSkL5RiZASNocFvqfudASNA==}
|
||||||
|
engines: {node: '>= 18.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
arktype: ^2.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
arktype:
|
||||||
|
optional: true
|
||||||
|
|
||||||
konoebml@0.1.2-rc.5:
|
konoebml@0.1.2-rc.5:
|
||||||
resolution: {integrity: sha512-VsXIlsXby0OzSzLER6ERRZE+9kLkqrYUF7Wr9MKAt8qvmUc3/YStf2SdpC2gMOtCjoyxDi7bXCQPIOHziUu4nw==}
|
resolution: {integrity: sha512-VsXIlsXby0OzSzLER6ERRZE+9kLkqrYUF7Wr9MKAt8qvmUc3/YStf2SdpC2gMOtCjoyxDi7bXCQPIOHziUu4nw==}
|
||||||
engines: {node: '>= 18.0.0'}
|
engines: {node: '>= 18.0.0'}
|
||||||
@ -4518,6 +4527,13 @@ snapshots:
|
|||||||
|
|
||||||
kind-of@6.0.3: {}
|
kind-of@6.0.3: {}
|
||||||
|
|
||||||
|
konoebml@0.1.2(arktype@2.1.10):
|
||||||
|
dependencies:
|
||||||
|
mnemonist: 0.40.3
|
||||||
|
type-fest: 4.37.0
|
||||||
|
optionalDependencies:
|
||||||
|
arktype: 2.1.10
|
||||||
|
|
||||||
konoebml@0.1.2-rc.5(arktype@2.1.10):
|
konoebml@0.1.2-rc.5(arktype@2.1.10):
|
||||||
dependencies:
|
dependencies:
|
||||||
mnemonist: 0.40.3
|
mnemonist: 0.40.3
|
||||||
|
Loading…
Reference in New Issue
Block a user