fix: fix audio issues

This commit is contained in:
2025-03-26 06:55:37 +08:00
parent 39e17eb6a5
commit 8cc1a2bab1
25 changed files with 371 additions and 306 deletions

View File

@@ -1,18 +1,20 @@
import { Observable } from 'rxjs';
import {map, Observable, Subject} from 'rxjs';
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
export function createAudioDecodeStream(configuration: AudioDecoderConfig): {
export function createAudioDecodeStream(configuration: AudioDecoderConfig): Observable<{
decoder: AudioDecoder;
frame$: Observable<AudioData>;
} {
let decoder!: VideoDecoder;
const frame$ = new Observable<AudioData>((subscriber) => {
}> {
const frame$ = new Subject<AudioData>()
const decoder$ = new Observable<AudioDecoder>((subscriber) => {
let isFinalized = false;
decoder = new AudioDecoder({
output: (frame) => subscriber.next(frame),
const decoder = new AudioDecoder({
output: (frame) => frame$.next(frame),
error: (e) => {
if (!isFinalized) {
isFinalized = true;
frame$.error(e);
subscriber.error(e);
}
},
@@ -20,16 +22,19 @@ export function createAudioDecodeStream(configuration: AudioDecoderConfig): {
decoder.configure(configuration);
subscriber.next(decoder);
return () => {
if (!isFinalized) {
isFinalized = true;
frame$.complete();
decoder.close();
}
};
});
})
return {
return decoder$.pipe(map((decoder) => ({
decoder,
frame$,
};
frame$
})));
}

View File

@@ -1,4 +1,4 @@
import { Observable } from 'rxjs';
import {map, Observable, Subject} from 'rxjs';
export type RenderingContext =
| ImageBitmapRenderingContext
@@ -42,18 +42,19 @@ export function captureCanvasAsVideoSrcObject(
video.srcObject = canvas.captureStream(frameRate);
}
export function createVideoDecodeStream(configuration: VideoDecoderConfig): {
export function createVideoDecodeStream(configuration: VideoDecoderConfig): Observable<{
decoder: VideoDecoder;
frame$: Observable<VideoFrame>;
} {
let decoder!: VideoDecoder;
const frame$ = new Observable<VideoFrame>((subscriber) => {
}> {
const frame$ = new Subject<VideoFrame>()
const decoder$ = new Observable<VideoDecoder>((subscriber) => {
let isFinalized = false;
decoder = new VideoDecoder({
output: (frame) => subscriber.next(frame),
const decoder = new VideoDecoder({
output: (frame) => frame$.next(frame),
error: (e) => {
if (!isFinalized) {
isFinalized = true;
frame$.error(e);
subscriber.error(e);
}
},
@@ -61,16 +62,19 @@ export function createVideoDecodeStream(configuration: VideoDecoderConfig): {
decoder.configure(configuration);
subscriber.next(decoder);
return () => {
if (!isFinalized) {
isFinalized = true;
frame$.complete();
decoder.close();
}
};
});
})
return {
return decoder$.pipe(map((decoder) => ({
decoder,
frame$,
};
frame$
})));
}

View File

@@ -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',

View File

@@ -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),

View File

@@ -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$
});
})
);

View File

@@ -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$
};
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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";

View File

@@ -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,

View File

@@ -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]);

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

View File

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

View File

@@ -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,

View File

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