Skip to content

Commit

Permalink
Merge branch 'patch/v1.5.x'
Browse files Browse the repository at this point in the history
* patch/v1.5.x:
  Don't append over first fragment when next fragment aligns with playlist within 1/200s tolerance (#6471)
  Fix exception reading metadata.channelCount with HE-AAC when changeType is not supported (#6472)
  Fix TSDemuxer parsing error handling in sync path (#6469)
  when `this.stats` is called, add optional chaining or check for existence (#6459)
  • Loading branch information
robwalch committed Jun 1, 2024
2 parents f379442 + e1c2904 commit 1a19b97
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 45 deletions.
7 changes: 4 additions & 3 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ export default class BaseStreamController
if (this.state === State.STOPPED || this.state === State.ERROR) {
return;
}
this.warn(reason);
this.warn(`Frag error: ${reason?.message || reason}`);
this.resetFragmentLoading(frag);
});
}
Expand Down Expand Up @@ -1398,7 +1398,7 @@ export default class BaseStreamController
let { fragPrevious } = this;
let { fragments, endSN } = levelDetails;
const { fragmentHint } = levelDetails;
const tolerance = config.maxFragLookUpTolerance;
const { maxFragLookUpTolerance } = config;
const partList = levelDetails.partList;

const loadingParts = !!(
Expand All @@ -1414,7 +1414,8 @@ export default class BaseStreamController

let frag;
if (bufferEnd < end) {
const lookupTolerance = bufferEnd > end - tolerance ? 0 : tolerance;
const lookupTolerance =
bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
// Remove the tolerance if it would put the bufferEnd past the actual end of stream
// Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
frag = findFragmentByPTS(
Expand Down
37 changes: 34 additions & 3 deletions src/controller/fragment-finders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function findFragmentByPTS(
fragments: Array<Fragment>,
bufferEnd: number = 0,
maxFragLookUpTolerance: number = 0,
nextFragLookupTolerance: number = 0.005,
): Fragment | null {
let fragNext: Fragment | null = null;
if (fragPrevious) {
Expand All @@ -76,9 +77,17 @@ export function findFragmentByPTS(
// Prefer the next fragment if it's within tolerance
if (
fragNext &&
(!fragPrevious || fragPrevious.level === fragNext.level) &&
fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) ===
0
(((!fragPrevious || fragPrevious.level === fragNext.level) &&
fragmentWithinToleranceTest(
bufferEnd,
maxFragLookUpTolerance,
fragNext,
) === 0) ||
fragmentWithinFastStartSwitch(
fragNext,
fragPrevious,
Math.min(nextFragLookupTolerance, maxFragLookUpTolerance),
))
) {
return fragNext;
}
Expand All @@ -94,6 +103,28 @@ export function findFragmentByPTS(
return fragNext;
}

function fragmentWithinFastStartSwitch(
fragNext: Fragment,
fragPrevious: Fragment | null,
nextFragLookupTolerance: number,
): boolean {
if (
fragPrevious &&
fragPrevious.start === 0 &&
fragPrevious.level < fragNext.level &&
(fragPrevious.endPTS || 0) > 0
) {
const firstDuration = fragPrevious.tagList.reduce((duration, tag) => {
if (tag[0] === 'INF') {
duration += parseFloat(tag[1]);
}
return duration;
}, nextFragLookupTolerance);
return fragNext.start <= firstDuration;
}
return false;
}

/**
* The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions.
* @param candidate - The fragment to test
Expand Down
8 changes: 7 additions & 1 deletion src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1268,7 +1268,13 @@ export default class StreamController
// In the case that AAC and HE-AAC audio codecs are signalled in manifest,
// force HE-AAC, as it seems that most browsers prefers it.
// don't force HE-AAC if mono stream, or in Firefox
if (audio.metadata.channelCount !== 1 && ua.indexOf('firefox') === -1) {
const audioMetadata = audio.metadata;
if (
audioMetadata &&
'channelCount' in audioMetadata &&
(audioMetadata.channelCount || 1) !== 1 &&
ua.indexOf('firefox') === -1
) {
audioCodec = 'mp4a.40.5';
}
}
Expand Down
42 changes: 26 additions & 16 deletions src/demux/transmuxer-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Fragment, Part } from '../loader/fragment';
import { getM2TSSupportedAudioTypes } from '../utils/codecs';
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
import type Hls from '../hls';
import type { HlsEventEmitter } from '../events';
import type { HlsEventEmitter, HlsListeners } from '../events';
import type { PlaylistLevelType } from '../types/loader';
import type { RationalTimestamp } from '../utils/timescale-conversion';

Expand All @@ -30,7 +30,9 @@ export default class TransmuxerInterface {
private part: Part | null = null;
private useWorker: boolean;
private workerContext: WorkerContext | null = null;
private onwmsg?: Function;
private onwmsg?: (
event: MessageEvent<{ event: string; data?: any } | null>,
) => void;
private transmuxer: Transmuxer | null = null;
private onTransmuxComplete: (transmuxResult: TransmuxerResult) => void;
private onFlush: (chunkMeta: ChunkMetadata) => void;
Expand Down Expand Up @@ -67,9 +69,6 @@ export default class TransmuxerInterface {
config.preferManagedMediaSource,
);

// navigator.vendor is not always available in Web Worker
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
const vendor = navigator.vendor;
if (this.useWorker && typeof Worker !== 'undefined') {
const canCreateWorker = config.workerPath || hasUMDWorker();
if (canCreateWorker) {
Expand All @@ -81,9 +80,9 @@ export default class TransmuxerInterface {
logger.log(`injecting Web Worker for "${id}"`);
this.workerContext = injectWorker();
}
this.onwmsg = (ev: any) => this.onWorkerMessage(ev);
this.onwmsg = (event) => this.onWorkerMessage(event);
const { worker } = this.workerContext;
worker.addEventListener('message', this.onwmsg as any);
worker.addEventListener('message', this.onwmsg);
worker.onerror = (event) => {
const error = new Error(
`${event.message} (${event.filename}:${event.lineno})`,
Expand All @@ -101,7 +100,7 @@ export default class TransmuxerInterface {
worker.postMessage({
cmd: 'init',
typeSupported: m2tsTypeSupported,
vendor: vendor,
vendor: '',
id: id,
config: JSON.stringify(config),
});
Expand All @@ -116,7 +115,7 @@ export default class TransmuxerInterface {
this.observer,
m2tsTypeSupported,
config,
vendor,
'',
id,
);
}
Expand All @@ -128,12 +127,12 @@ export default class TransmuxerInterface {
this.observer,
m2tsTypeSupported,
config,
vendor,
'',
id,
);
}

resetWorker(): void {
resetWorker() {
if (this.workerContext) {
const { worker, objectURL } = this.workerContext;
if (objectURL) {
Expand All @@ -147,7 +146,7 @@ export default class TransmuxerInterface {
}
}

destroy(): void {
destroy() {
if (this.workerContext) {
this.resetWorker();
this.onwmsg = undefined;
Expand Down Expand Up @@ -180,7 +179,7 @@ export default class TransmuxerInterface {
accurateTimeOffset: boolean,
chunkMeta: ChunkMetadata,
defaultInitPTS?: RationalTimestamp,
): void {
) {
chunkMeta.transmuxing.start = self.performance.now();
const { transmuxer } = this;
const timeOffset = part ? part.start : frag.start;
Expand Down Expand Up @@ -347,9 +346,20 @@ export default class TransmuxerInterface {
this.onFlush(chunkMeta);
}

private onWorkerMessage(ev: any): void {
const data = ev.data;
private onWorkerMessage(
event: MessageEvent<{ event: string; data?: any } | null>,
) {
const data = event.data;
if (!data?.event) {
logger.warn(
`worker message received with no ${data ? 'event name' : 'data'}`,
);
return;
}
const hls = this.hls;
if (!this.hls) {
return;
}
switch (data.event) {
case 'init': {
const objectURL = this.workerContext?.objectURL;
Expand Down Expand Up @@ -381,7 +391,7 @@ export default class TransmuxerInterface {
data.data = data.data || {};
data.data.frag = this.frag;
data.data.id = this.id;
hls.trigger(data.event, data.data);
hls.trigger(data.event as keyof HlsListeners, data.data);
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/demux/transmuxer-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function startWorker(self) {
observer,
data.typeSupported,
config,
data.vendor,
'',
data.id,
);
const logger = enableLogs(config.debug, data.id);
Expand Down
52 changes: 31 additions & 21 deletions src/demux/tsdemuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ class TSDemuxer implements Demuxer {
offset,
this.typeSupported,
isSampleAes,
this.observer,
);

// only update track id if track PID found while parsing PMT
Expand Down Expand Up @@ -422,16 +423,12 @@ class TSDemuxer implements Demuxer {
}

if (tsPacketErrors > 0) {
const error = new Error(
`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`,
emitParsingError(
this.observer,
new Error(
`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`,
),
);
this.observer.emit(Events.ERROR, Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
details: ErrorDetails.FRAG_PARSING_ERROR,
fatal: false,
error,
reason: error.message,
});
}

videoTrack.pesData = videoData;
Expand Down Expand Up @@ -628,16 +625,7 @@ class TSDemuxer implements Demuxer {
} else {
reason = 'No ADTS header found in AAC PES';
}
const error = new Error(reason);
logger.warn(`parsing error: ${reason}`);
this.observer.emit(Events.ERROR, Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
details: ErrorDetails.FRAG_PARSING_ERROR,
fatal: false,
levelRetry: recoverable,
error,
reason,
});
emitParsingError(this.observer, new Error(reason), recoverable);
if (!recoverable) {
return;
}
Expand Down Expand Up @@ -768,6 +756,7 @@ function parsePMT(
offset: number,
typeSupported: TypeSupported,
isSampleAes: boolean,
observer: HlsEventEmitter,
) {
const result = {
audioPid: -1,
Expand Down Expand Up @@ -897,7 +886,8 @@ function parsePMT(
case 0xc2: // SAMPLE-AES EC3
/* falls through */
case 0x87:
throw new Error('Unsupported EC-3 in M2TS found');
emitParsingError(observer, new Error('Unsupported EC-3 in M2TS found'));
return result;

case 0x24: // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
if (__USE_M2TS_ADVANCED_CODECS__) {
Expand All @@ -907,7 +897,11 @@ function parsePMT(
logger.log('HEVC in M2TS found');
}
} else {
throw new Error('Unsupported HEVC in M2TS found');
emitParsingError(
observer,
new Error('Unsupported HEVC in M2TS found'),
);
return result;
}
break;

Expand All @@ -922,6 +916,22 @@ function parsePMT(
return result;
}

function emitParsingError(
observer: HlsEventEmitter,
error: Error,
levelRetry?: boolean,
) {
logger.warn(`parsing error: ${error.message}`);
observer.emit(Events.ERROR, Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
details: ErrorDetails.FRAG_PARSING_ERROR,
fatal: false,
levelRetry,
error,
reason: error.message,
});
}

function logEncryptedSamplesFoundInUnencryptedStream(type: string) {
logger.log(`${type} with AES-128-CBC encryption found in unencrypted stream`);
}
Expand Down

0 comments on commit 1a19b97

Please sign in to comment.