fix: fix audio issues
This commit is contained in:
@@ -16,16 +16,16 @@ import {
|
||||
import {
|
||||
genCodecStringByAV1DecoderConfigurationRecord,
|
||||
parseAV1DecoderConfigurationRecord,
|
||||
} from './av1.ts';
|
||||
} from './av1';
|
||||
import {
|
||||
genCodecStringByHEVCDecoderConfigurationRecord,
|
||||
parseHEVCDecoderConfigurationRecord,
|
||||
} from './hevc.ts';
|
||||
} from './hevc';
|
||||
import {
|
||||
genCodecStringByVP9DecoderConfigurationRecord,
|
||||
parseVP9DecoderConfigurationRecord,
|
||||
VP9_CODEC_TYPE,
|
||||
} from './vp9.ts';
|
||||
} from './vp9';
|
||||
|
||||
export const VideoCodecId = {
|
||||
VCM: 'V_MS/VFW/FOURCC',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { CreateRangedStreamOptions } from '@konoplayer/core/data';
|
||||
import { type EbmlEBMLTagType, EbmlTagIdEnum, EbmlTagPosition } from 'konoebml';
|
||||
import {
|
||||
switchMap,
|
||||
@@ -7,14 +6,14 @@ import {
|
||||
shareReplay,
|
||||
map,
|
||||
combineLatest,
|
||||
of,
|
||||
of, type Observable, delayWhen, pipe, finalize, tap, throwIfEmpty,
|
||||
} from 'rxjs';
|
||||
import { isTagIdPos } from '../util';
|
||||
import { createRangedEbmlStream } from './resource';
|
||||
import {createRangedEbmlStream, type CreateRangedEbmlStreamOptions} from './resource';
|
||||
import { type MatroskaSegmentModel, createMatroskaSegment } from './segment';
|
||||
|
||||
export type CreateMatroskaOptions = Omit<
|
||||
CreateRangedStreamOptions,
|
||||
CreateRangedEbmlStreamOptions,
|
||||
'byteStart' | 'byteEnd'
|
||||
>;
|
||||
|
||||
@@ -25,7 +24,7 @@ export interface MatroskaModel {
|
||||
segment: MatroskaSegmentModel;
|
||||
}
|
||||
|
||||
export function createMatroska(options: CreateMatroskaOptions) {
|
||||
export function createMatroska(options: CreateMatroskaOptions): Observable<MatroskaModel> {
|
||||
const metadataRequest$ = createRangedEbmlStream({
|
||||
...options,
|
||||
byteStart: 0,
|
||||
@@ -33,32 +32,34 @@ export function createMatroska(options: CreateMatroskaOptions) {
|
||||
|
||||
return metadataRequest$.pipe(
|
||||
switchMap(({ totalSize, ebml$, response }) => {
|
||||
const head$ = ebml$.pipe(
|
||||
filter(isTagIdPos(EbmlTagIdEnum.EBML, EbmlTagPosition.End)),
|
||||
take(1),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
const segmentStart$ = ebml$.pipe(
|
||||
filter(isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.Start))
|
||||
);
|
||||
|
||||
/**
|
||||
* while [matroska v4](https://www.matroska.org/technical/elements.html) doc tell that there is only one segment in a file
|
||||
* some mkv generated by strange tools will emit several
|
||||
*/
|
||||
const segments$ = segmentStart$.pipe(
|
||||
map((startTag) =>
|
||||
createMatroskaSegment({
|
||||
startTag,
|
||||
matroskaOptions: options,
|
||||
ebml$,
|
||||
})
|
||||
)
|
||||
const segment$ = ebml$.pipe(
|
||||
filter(isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.Start)),
|
||||
map((startTag) => createMatroskaSegment({
|
||||
startTag,
|
||||
matroskaOptions: options,
|
||||
ebml$,
|
||||
})),
|
||||
delayWhen(
|
||||
({ loadedMetadata$ }) => loadedMetadata$
|
||||
),
|
||||
take(1),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
const head$ = ebml$.pipe(
|
||||
filter(isTagIdPos(EbmlTagIdEnum.EBML, EbmlTagPosition.End)),
|
||||
take(1),
|
||||
shareReplay(1),
|
||||
throwIfEmpty(() => new Error("failed to find head tag"))
|
||||
);
|
||||
|
||||
return combineLatest({
|
||||
segment: segments$.pipe(take(1)),
|
||||
segment: segment$,
|
||||
head: head$,
|
||||
totalSize: of(totalSize),
|
||||
initResponse: of(response),
|
||||
|
||||
@@ -3,14 +3,18 @@ import {
|
||||
createRangedStream,
|
||||
} from '@konoplayer/core/data';
|
||||
import { type EbmlTagType, EbmlStreamDecoder, EbmlTagIdEnum } from 'konoebml';
|
||||
import { Observable, from, switchMap, share, defer, EMPTY, of } from 'rxjs';
|
||||
import {Observable, from, switchMap, share, defer, EMPTY, of, tap} from 'rxjs';
|
||||
import { waitTick } from '../util';
|
||||
|
||||
export interface CreateRangedEbmlStreamOptions extends CreateRangedStreamOptions {
|
||||
refCount?: boolean
|
||||
}
|
||||
|
||||
export function createRangedEbmlStream({
|
||||
url,
|
||||
byteStart = 0,
|
||||
byteEnd,
|
||||
}: CreateRangedStreamOptions): Observable<{
|
||||
byteEnd
|
||||
}: CreateRangedEbmlStreamOptions): Observable<{
|
||||
ebml$: Observable<EbmlTagType>;
|
||||
totalSize?: number;
|
||||
response: Response;
|
||||
@@ -23,7 +27,10 @@ export function createRangedEbmlStream({
|
||||
switchMap(({ controller, body, totalSize, response }) => {
|
||||
let requestCompleted = false;
|
||||
|
||||
const originRequest$ = new Observable<EbmlTagType>((subscriber) => {
|
||||
const ebml$ = new Observable<EbmlTagType>((subscriber) => {
|
||||
if (requestCompleted) {
|
||||
subscriber.complete();
|
||||
}
|
||||
body
|
||||
.pipeThrough(
|
||||
new EbmlStreamDecoder({
|
||||
@@ -57,8 +64,10 @@ export function createRangedEbmlStream({
|
||||
});
|
||||
|
||||
return () => {
|
||||
requestCompleted = true;
|
||||
controller.abort();
|
||||
if (!requestCompleted) {
|
||||
requestCompleted = true;
|
||||
controller.abort();
|
||||
}
|
||||
};
|
||||
}).pipe(
|
||||
share({
|
||||
@@ -68,22 +77,12 @@ export function createRangedEbmlStream({
|
||||
})
|
||||
);
|
||||
|
||||
const ebml$ = defer(() =>
|
||||
requestCompleted ? EMPTY : originRequest$
|
||||
).pipe(
|
||||
share({
|
||||
resetOnError: false,
|
||||
resetOnComplete: true,
|
||||
resetOnRefCountZero: true,
|
||||
})
|
||||
);
|
||||
|
||||
return of({
|
||||
ebml$,
|
||||
totalSize,
|
||||
response,
|
||||
body,
|
||||
controller,
|
||||
ebml$
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
takeWhile,
|
||||
share,
|
||||
map,
|
||||
last,
|
||||
switchMap,
|
||||
shareReplay,
|
||||
EMPTY,
|
||||
@@ -23,6 +22,8 @@ import {
|
||||
merge,
|
||||
isEmpty,
|
||||
finalize,
|
||||
delayWhen,
|
||||
from,
|
||||
} from 'rxjs';
|
||||
import type { CreateMatroskaOptions } from '.';
|
||||
import { type ClusterType, TrackTypeRestrictionEnum } from '../schema';
|
||||
@@ -51,7 +52,6 @@ export interface CreateMatroskaSegmentOptions {
|
||||
export interface MatroskaSegmentModel {
|
||||
startTag: EbmlSegmentTagType;
|
||||
segment: SegmentSystem;
|
||||
metadataTags$: Observable<EbmlTagType>;
|
||||
loadedMetadata$: Observable<SegmentSystem>;
|
||||
loadedTags$: Observable<SegmentSystem>;
|
||||
loadedCues$: Observable<SegmentSystem>;
|
||||
@@ -59,19 +59,19 @@ export interface MatroskaSegmentModel {
|
||||
videoTrackDecoder: (
|
||||
track: VideoTrackContext,
|
||||
cluster$: Observable<ClusterType>
|
||||
) => {
|
||||
) => Observable<{
|
||||
track: VideoTrackContext;
|
||||
decoder: VideoDecoder;
|
||||
frame$: Observable<VideoFrame>;
|
||||
};
|
||||
}>;
|
||||
audioTrackDecoder: (
|
||||
track: AudioTrackContext,
|
||||
cluster$: Observable<ClusterType>
|
||||
) => {
|
||||
) => Observable<{
|
||||
track: AudioTrackContext;
|
||||
decoder: AudioDecoder;
|
||||
frame$: Observable<AudioData>;
|
||||
};
|
||||
}>;
|
||||
defaultVideoTrack$: Observable<VideoTrackContext | undefined>;
|
||||
defaultAudioTrack$: Observable<AudioTrackContext | undefined>;
|
||||
}
|
||||
@@ -88,16 +88,20 @@ export function createMatroskaSegment({
|
||||
const metaScan$ = ebml$.pipe(
|
||||
scan(
|
||||
(acc, tag) => {
|
||||
acc.segment.scanMeta(tag);
|
||||
const segment = acc.segment;
|
||||
segment.scanMeta(tag);
|
||||
acc.tag = tag;
|
||||
acc.canComplete = segment.canCompleteMeta();
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
segment,
|
||||
tag: undefined as unknown as EbmlTagType,
|
||||
canComplete: false,
|
||||
}
|
||||
),
|
||||
takeWhile((acc) => acc.segment.canCompleteMeta(), true),
|
||||
takeWhile(({ canComplete }) => !canComplete, true),
|
||||
delayWhen(({ segment }) => from(segment.completeMeta())),
|
||||
share({
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
@@ -105,12 +109,11 @@ export function createMatroskaSegment({
|
||||
})
|
||||
);
|
||||
|
||||
const metadataTags$ = metaScan$.pipe(map(({ tag }) => tag));
|
||||
|
||||
const loadedMetadata$ = metaScan$.pipe(
|
||||
last(),
|
||||
switchMap(({ segment }) => segment.completeMeta()),
|
||||
shareReplay(1)
|
||||
filter(({ canComplete }) => canComplete),
|
||||
map(({ segment }) => segment),
|
||||
take(1),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
const loadedRemoteCues$ = loadedMetadata$.pipe(
|
||||
@@ -297,88 +300,94 @@ export function createMatroskaSegment({
|
||||
track: VideoTrackContext,
|
||||
cluster$: Observable<ClusterType>
|
||||
) => {
|
||||
const { decoder, frame$ } = createVideoDecodeStream(track.configuration);
|
||||
return createVideoDecodeStream(track.configuration).pipe(
|
||||
map(({ decoder, frame$ }) => {
|
||||
const clusterSystem = segment.cluster;
|
||||
const infoSystem = segment.info;
|
||||
const timestampScale = Number(infoSystem.info.TimestampScale) / 1000;
|
||||
|
||||
const clusterSystem = segment.cluster;
|
||||
const decodeSubscription = cluster$.subscribe((cluster) => {
|
||||
for (const block of clusterSystem.enumerateBlocks(
|
||||
cluster,
|
||||
track.trackEntry
|
||||
)) {
|
||||
const blockTime = (Number(cluster.Timestamp) + block.relTime) * timestampScale;
|
||||
const blockDuration =
|
||||
frames.length > 1 ? track.predictBlockDuration(blockTime) * timestampScale : 0;
|
||||
const perFrameDuration =
|
||||
frames.length > 1 && blockDuration
|
||||
? blockDuration / block.frames.length
|
||||
: 0;
|
||||
|
||||
const decodeSubscription = cluster$.subscribe((cluster) => {
|
||||
for (const block of clusterSystem.enumerateBlocks(
|
||||
cluster,
|
||||
track.trackEntry
|
||||
)) {
|
||||
const blockTime = Number(cluster.Timestamp) + block.relTime;
|
||||
const blockDuration =
|
||||
frames.length > 1 ? track.predictBlockDuration(blockTime) : 0;
|
||||
const perFrameDuration =
|
||||
frames.length > 1 && blockDuration
|
||||
? blockDuration / block.frames.length
|
||||
: 0;
|
||||
for (const frame of block.frames) {
|
||||
const chunk = new EncodedVideoChunk({
|
||||
type: block.keyframe ? 'key' : 'delta',
|
||||
data: frame,
|
||||
timestamp: blockTime + perFrameDuration,
|
||||
});
|
||||
|
||||
for (const frame of block.frames) {
|
||||
const chunk = new EncodedVideoChunk({
|
||||
type: block.keyframe ? 'key' : 'delta',
|
||||
data: frame,
|
||||
timestamp: blockTime + perFrameDuration,
|
||||
});
|
||||
decoder.decode(chunk);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
decoder.decode(chunk);
|
||||
return {
|
||||
track,
|
||||
decoder,
|
||||
frame$: frame$
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
decodeSubscription.unsubscribe();
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
track,
|
||||
decoder,
|
||||
frame$: frame$
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
decodeSubscription.unsubscribe();
|
||||
})
|
||||
)
|
||||
.pipe(share()),
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const audioTrackDecoder = (
|
||||
track: AudioTrackContext,
|
||||
cluster$: Observable<ClusterType>
|
||||
) => {
|
||||
const { decoder, frame$ } = createAudioDecodeStream(track.configuration);
|
||||
return createAudioDecodeStream(track.configuration).pipe(
|
||||
map(({ decoder, frame$ }) => {
|
||||
const clusterSystem = segment.cluster;
|
||||
const infoSystem = segment.info;
|
||||
const timestampScale = Number(infoSystem.info.TimestampScale) / 1000;
|
||||
|
||||
const clusterSystem = segment.cluster;
|
||||
const decodeSubscription = cluster$.subscribe((cluster) => {
|
||||
for (const block of clusterSystem.enumerateBlocks(
|
||||
cluster,
|
||||
track.trackEntry
|
||||
)) {
|
||||
const blockTime = (Number(cluster.Timestamp) + block.relTime) * timestampScale;
|
||||
const blockDuration =
|
||||
frames.length > 1 ? track.predictBlockDuration(blockTime) : 0;
|
||||
const perFrameDuration =
|
||||
frames.length > 1 && blockDuration
|
||||
? blockDuration / block.frames.length
|
||||
: 0;
|
||||
|
||||
const decodeSubscription = cluster$.subscribe((cluster) => {
|
||||
for (const block of clusterSystem.enumerateBlocks(
|
||||
cluster,
|
||||
track.trackEntry
|
||||
)) {
|
||||
const blockTime = Number(cluster.Timestamp) + block.relTime;
|
||||
const blockDuration =
|
||||
frames.length > 1 ? track.predictBlockDuration(blockTime) : 0;
|
||||
const perFrameDuration =
|
||||
frames.length > 1 && blockDuration
|
||||
? blockDuration / block.frames.length
|
||||
: 0;
|
||||
let i = 0;
|
||||
for (const frame of block.frames) {
|
||||
const chunk = new EncodedAudioChunk({
|
||||
type: block.keyframe ? 'key' : 'delta',
|
||||
data: frame,
|
||||
timestamp: blockTime + perFrameDuration * i,
|
||||
});
|
||||
i++;
|
||||
|
||||
let i = 0;
|
||||
for (const frame of block.frames) {
|
||||
const chunk = new EncodedAudioChunk({
|
||||
type: block.keyframe ? 'key' : 'delta',
|
||||
data: frame,
|
||||
timestamp: blockTime + perFrameDuration * i,
|
||||
});
|
||||
i++;
|
||||
decoder.decode(chunk);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
decoder.decode(chunk);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
track,
|
||||
decoder,
|
||||
frame$: frame$.pipe(finalize(() => decodeSubscription.unsubscribe())),
|
||||
};
|
||||
return {
|
||||
track,
|
||||
decoder,
|
||||
frame$: frame$.pipe(finalize(() => decodeSubscription.unsubscribe())),
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
const defaultVideoTrack$ = loadedMetadata$.pipe(
|
||||
@@ -406,7 +415,6 @@ export function createMatroskaSegment({
|
||||
return {
|
||||
startTag,
|
||||
segment,
|
||||
metadataTags$,
|
||||
loadedMetadata$,
|
||||
loadedTags$,
|
||||
loadedCues$,
|
||||
@@ -414,6 +422,6 @@ export function createMatroskaSegment({
|
||||
videoTrackDecoder,
|
||||
audioTrackDecoder,
|
||||
defaultVideoTrack$,
|
||||
defaultAudioTrack$,
|
||||
defaultAudioTrack$
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
type BlockGroupType,
|
||||
type TrackEntryType,
|
||||
} from '../schema';
|
||||
import { type SegmentComponent, SegmentComponentSystemTrait } from './segment';
|
||||
import { type SegmentComponent } from './segment';
|
||||
import {SegmentComponentSystemTrait} from "./segment-component";
|
||||
|
||||
export abstract class BlockViewTrait {
|
||||
abstract get keyframe(): boolean;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {type EbmlCuePointTagType, type EbmlCuesTagType, EbmlTagIdEnum} from "konoebml";
|
||||
import {CuePointSchema, type CuePointType, type CueTrackPositionsType} from "../schema.ts";
|
||||
import {CuePointSchema, type CuePointType, type CueTrackPositionsType} from "../schema";
|
||||
import {maxBy} from "lodash-es";
|
||||
import {type SegmentComponent, SegmentComponentSystemTrait} from "./segment.ts";
|
||||
import type {SegmentComponent} from "./segment";
|
||||
import {SegmentComponentSystemTrait} from "./segment-component";
|
||||
|
||||
export class CueSystem extends SegmentComponentSystemTrait<
|
||||
EbmlCuePointTagType,
|
||||
|
||||
@@ -3,5 +3,6 @@ export { CueSystem } from './cue';
|
||||
export { TagSystem } from './tag';
|
||||
export { ClusterSystem } from './cluster';
|
||||
export { InfoSystem } from './info';
|
||||
export { type SegmentComponent, SegmentSystem, SegmentComponentSystemTrait, withSegment } from './segment';
|
||||
export { SeekSystem, SEEK_ID_KAX_CUES, SEEK_ID_KAX_INFO, SEEK_ID_KAX_TAGS, SEEK_ID_KAX_TRACKS } from './seek';
|
||||
export { type SegmentComponent, SegmentSystem, withSegment } from './segment';
|
||||
export { SeekSystem, SEEK_ID_KAX_CUES, SEEK_ID_KAX_INFO, SEEK_ID_KAX_TAGS, SEEK_ID_KAX_TRACKS } from './seek';
|
||||
export {SegmentComponentSystemTrait} from "./segment-component";
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {EbmlInfoTagType} from "konoebml";
|
||||
import {InfoSchema, type InfoType} from "../schema.ts";
|
||||
import {type SegmentComponent, SegmentComponentSystemTrait} from "./segment.ts";
|
||||
import {InfoSchema, type InfoType} from "../schema";
|
||||
import type {SegmentComponent} from "./segment";
|
||||
import {SegmentComponentSystemTrait} from "./segment-component";
|
||||
|
||||
export class InfoSystem extends SegmentComponentSystemTrait<
|
||||
EbmlInfoTagType,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type {EbmlSeekHeadTagType, EbmlTagType} from "konoebml";
|
||||
import {SeekHeadSchema, type SeekHeadType} from "../schema.ts";
|
||||
import {SeekHeadSchema, type SeekHeadType} from "../schema";
|
||||
import {isEqual} from "lodash-es";
|
||||
import {UnreachableOrLogicError} from "@konoplayer/core/errors.ts";
|
||||
|
||||
import {SegmentComponentSystemTrait} from "./segment.ts";
|
||||
import {UnreachableOrLogicError} from "@konoplayer/core/errors";
|
||||
import {SegmentComponentSystemTrait} from "./segment-component";
|
||||
|
||||
export const SEEK_ID_KAX_INFO = new Uint8Array([0x15, 0x49, 0xa9, 0x66]);
|
||||
export const SEEK_ID_KAX_TRACKS = new Uint8Array([0x16, 0x54, 0xae, 0x6b]);
|
||||
|
||||
37
packages/matroska/src/systems/segment-component.ts
Normal file
37
packages/matroska/src/systems/segment-component.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type {EbmlMasterTagType} from "konoebml";
|
||||
import {ArkErrors, type Type} from "arktype";
|
||||
import {convertEbmlTagToComponent, type InferType} from "../util";
|
||||
import type {SegmentComponent, SegmentSystem} from "./segment";
|
||||
|
||||
export class SegmentComponentSystemTrait<
|
||||
E extends EbmlMasterTagType,
|
||||
S extends Type<any>,
|
||||
> {
|
||||
segment: SegmentSystem;
|
||||
|
||||
get schema(): S {
|
||||
throw new Error('unimplemented!');
|
||||
}
|
||||
|
||||
constructor(segment: SegmentSystem) {
|
||||
this.segment = segment;
|
||||
}
|
||||
|
||||
componentFromTag(tag: E): SegmentComponent<InferType<S>> {
|
||||
const extracted = convertEbmlTagToComponent(tag);
|
||||
const result = this.schema(extracted) as
|
||||
| (InferType<S> & { segment: SegmentSystem })
|
||||
| ArkErrors;
|
||||
if (result instanceof ArkErrors) {
|
||||
const errors = result;
|
||||
console.error(
|
||||
'Parse component from tag error:',
|
||||
tag.toDebugRecord(),
|
||||
errors.flatProblemsByPath
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
result.segment = this.segment;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,18 @@
|
||||
import {
|
||||
type EbmlClusterTagType,
|
||||
type EbmlMasterTagType,
|
||||
type EbmlSegmentTagType,
|
||||
EbmlTagIdEnum,
|
||||
EbmlTagPosition,
|
||||
type EbmlTagType
|
||||
} from "konoebml";
|
||||
import {ArkErrors, type Type} from "arktype";
|
||||
import {convertEbmlTagToComponent, type InferType} from "../util.ts";
|
||||
import {CueSystem} from "./cue.ts";
|
||||
import {ClusterSystem} from "./cluster.ts";
|
||||
import {SEEK_ID_KAX_CUES, SEEK_ID_KAX_INFO, SEEK_ID_KAX_TAGS, SEEK_ID_KAX_TRACKS, SeekSystem} from "./seek.ts";
|
||||
import {InfoSystem} from "./info.ts";
|
||||
import {TrackSystem} from "./track.ts";
|
||||
import {TagSystem} from "./tag.ts";
|
||||
import type {BlockGroupType} from "../schema.ts";
|
||||
import {convertEbmlTagToComponent} from "../util";
|
||||
import {CueSystem} from "./cue";
|
||||
import {ClusterSystem} from "./cluster";
|
||||
import {SEEK_ID_KAX_CUES, SEEK_ID_KAX_INFO, SEEK_ID_KAX_TAGS, SEEK_ID_KAX_TRACKS, SeekSystem} from "./seek";
|
||||
import {InfoSystem} from "./info";
|
||||
import {TrackSystem} from "./track";
|
||||
import {TagSystem} from "./tag";
|
||||
import type {BlockGroupType} from "../schema";
|
||||
|
||||
export class SegmentSystem {
|
||||
startTag: EbmlSegmentTagType;
|
||||
@@ -70,7 +68,9 @@ export class SegmentSystem {
|
||||
this.seek.addSeekHeadTag(tag);
|
||||
}
|
||||
this.metaTags.push(tag);
|
||||
this.seek.memoOffset(tag);
|
||||
if (tag.position !== EbmlTagPosition.Start) {
|
||||
this.seek.memoOffset(tag);
|
||||
}
|
||||
if (tag.id === EbmlTagIdEnum.Cluster && !this.firstCluster) {
|
||||
this.firstCluster = tag;
|
||||
this.seekLocal();
|
||||
@@ -97,7 +97,7 @@ export class SegmentSystem {
|
||||
if (lastTag.id === EbmlTagIdEnum.Segment && lastTag.position === EbmlTagPosition.End) {
|
||||
return true;
|
||||
}
|
||||
return !!(this.firstCluster && this.track.preparedToConfigureTracks());
|
||||
return (!!this.firstCluster && this.track.preparedToConfigureTracks());
|
||||
}
|
||||
|
||||
async completeMeta() {
|
||||
@@ -122,35 +122,3 @@ export function withSegment<T extends object>(
|
||||
return component_;
|
||||
}
|
||||
|
||||
export class SegmentComponentSystemTrait<
|
||||
E extends EbmlMasterTagType,
|
||||
S extends Type<any>,
|
||||
> {
|
||||
segment: SegmentSystem;
|
||||
|
||||
get schema(): S {
|
||||
throw new Error('unimplemented!');
|
||||
}
|
||||
|
||||
constructor(segment: SegmentSystem) {
|
||||
this.segment = segment;
|
||||
}
|
||||
|
||||
componentFromTag(tag: E): SegmentComponent<InferType<S>> {
|
||||
const extracted = convertEbmlTagToComponent(tag);
|
||||
const result = this.schema(extracted) as
|
||||
| (InferType<S> & { segment: SegmentSystem })
|
||||
| ArkErrors;
|
||||
if (result instanceof ArkErrors) {
|
||||
const errors = result;
|
||||
console.error(
|
||||
'Parse component from tag error:',
|
||||
tag.toDebugRecord(),
|
||||
errors.flatProblemsByPath
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
result.segment = this.segment;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import {EbmlTagIdEnum, type EbmlTagsTagType, type EbmlTagTagType} from "konoebml";
|
||||
import {TagSchema, type TagType} from "../schema.ts";
|
||||
import {TagSchema, type TagType} from "../schema";
|
||||
|
||||
import {type SegmentComponent, SegmentComponentSystemTrait} from "./segment.ts";
|
||||
import type {SegmentComponent} from "./segment";
|
||||
import {SegmentComponentSystemTrait} from "./segment-component";
|
||||
|
||||
export class TagSystem extends SegmentComponentSystemTrait<
|
||||
EbmlTagTagType,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
ParseCodecErrors,
|
||||
UnsupportedCodecError,
|
||||
} from '@konoplayer/core/errors.ts';
|
||||
} from '@konoplayer/core/errors';
|
||||
import {
|
||||
EbmlTagIdEnum,
|
||||
type EbmlTrackEntryTagType,
|
||||
@@ -19,7 +19,9 @@ import {
|
||||
type TrackEntryType,
|
||||
TrackTypeRestrictionEnum,
|
||||
} from '../schema';
|
||||
import { type SegmentComponent, SegmentComponentSystemTrait } from './segment';
|
||||
import type { SegmentComponent } from './segment';
|
||||
import {SegmentComponentSystemTrait} from "./segment-component";
|
||||
import {pick} from "lodash-es";
|
||||
|
||||
export interface GetTrackEntryOptions {
|
||||
priority?: (v: SegmentComponent<TrackEntryType>) => number;
|
||||
@@ -29,13 +31,13 @@ export interface GetTrackEntryOptions {
|
||||
export abstract class TrackContext {
|
||||
peekingKeyframe?: Uint8Array;
|
||||
trackEntry: TrackEntryType;
|
||||
timecodeScale: number;
|
||||
timestampScale: number;
|
||||
lastBlockTimestamp = Number.NaN;
|
||||
averageBlockDuration = Number.NaN;
|
||||
|
||||
constructor(trackEntry: TrackEntryType, timecodeScale: number) {
|
||||
constructor(trackEntry: TrackEntryType, timestampScale: number) {
|
||||
this.trackEntry = trackEntry;
|
||||
this.timecodeScale = timecodeScale;
|
||||
this.timestampScale = Number(timestampScale);
|
||||
}
|
||||
|
||||
peekKeyframe(payload: Uint8Array) {
|
||||
@@ -87,7 +89,8 @@ export class VideoTrackContext extends TrackContext {
|
||||
this.trackEntry,
|
||||
this.peekingKeyframe
|
||||
);
|
||||
if (await VideoDecoder.isConfigSupported(configuration)) {
|
||||
const checkResult = await VideoDecoder?.isConfigSupported?.(configuration);
|
||||
if (!checkResult?.supported) {
|
||||
throw new UnsupportedCodecError(configuration.codec, 'video decoder');
|
||||
}
|
||||
this.configuration = configuration;
|
||||
@@ -106,7 +109,8 @@ export class AudioTrackContext extends TrackContext {
|
||||
this.trackEntry,
|
||||
this.peekingKeyframe
|
||||
);
|
||||
if (await AudioDecoder.isConfigSupported(configuration)) {
|
||||
const checkResult = await AudioDecoder?.isConfigSupported?.(configuration);
|
||||
if (!checkResult?.supported) {
|
||||
throw new UnsupportedCodecError(configuration.codec, 'audio decoder');
|
||||
}
|
||||
|
||||
@@ -121,8 +125,7 @@ export class AudioTrackContext extends TrackContext {
|
||||
return (
|
||||
Number(
|
||||
this.configuration.samplesPerFrame / this.configuration.sampleRate
|
||||
) *
|
||||
(1_000_000_000 / Number(this.timecodeScale))
|
||||
) * this.timestampScale
|
||||
);
|
||||
}
|
||||
const delta = blockTimestamp - this.lastBlockTimestamp;
|
||||
@@ -203,7 +206,7 @@ export class TrackSystem extends SegmentComponentSystemTrait<
|
||||
}
|
||||
}
|
||||
if (parseErrors.cause.length > 0) {
|
||||
console.error(parseErrors);
|
||||
console.error(parseErrors, parseErrors.cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user