mirror of
https://github.com/video-dev/hls.js.git
synced 2026-05-17 13:30:38 +00:00
PLAYLIST_UNCHANGED_ERROR should not be fatal so that playback is allowed to reach the end of the playlist
This commit is contained in:
@@ -150,6 +150,7 @@ export default class BasePlaylistController
|
||||
previousDetails?: LevelDetails,
|
||||
) {
|
||||
const { details, stats } = data;
|
||||
const fragments = details.fragments;
|
||||
|
||||
// Set last updated date-time
|
||||
const now = self.performance.now();
|
||||
@@ -163,8 +164,8 @@ export default class BasePlaylistController
|
||||
if (timelineOffset !== details.appliedTimelineOffset) {
|
||||
const offset = Math.max(timelineOffset || 0, 0);
|
||||
details.appliedTimelineOffset = offset;
|
||||
details.fragments.forEach((frag) => {
|
||||
frag.setStart(frag.playlistOffset + offset);
|
||||
fragments.forEach((frag) => {
|
||||
frag?.setStart(frag.playlistOffset + offset);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,11 +173,12 @@ export default class BasePlaylistController
|
||||
if (details.live || previousDetails?.live) {
|
||||
const levelOrTrack = 'levelInfo' in data ? data.levelInfo : data.track;
|
||||
details.reloaded(previousDetails);
|
||||
const parent = fragments[fragments.length - 1]?.type;
|
||||
|
||||
// TODO: consider a separate flow for low-latency blocking reload requests with delivery directives
|
||||
if (details.misses >= this.hls.config.liveMaxUnchangedPlaylistRefresh) {
|
||||
const error = new Error(
|
||||
`levelOrTrack (${levelOrTrack.id}) hits max allowed unchanged reloads.`,
|
||||
`${parent} playlist ${levelOrTrack.id} hit max allowed unchanged reloads.`,
|
||||
);
|
||||
this.warn(error);
|
||||
const { networkDetails, context } = data;
|
||||
@@ -187,8 +189,8 @@ export default class BasePlaylistController
|
||||
url: details.url,
|
||||
error,
|
||||
reason: error.message,
|
||||
level: (data as LevelLoadedData).level ?? undefined,
|
||||
parent: details.fragments[0]?.type,
|
||||
level: (data as LevelLoadedData).level,
|
||||
parent,
|
||||
context,
|
||||
networkDetails,
|
||||
stats,
|
||||
@@ -197,7 +199,7 @@ export default class BasePlaylistController
|
||||
}
|
||||
|
||||
// Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments
|
||||
if (previousDetails && details.fragments.length > 0) {
|
||||
if (previousDetails) {
|
||||
mergeDetails(previousDetails, details, this);
|
||||
const error = details.playlistParsingError;
|
||||
if (error) {
|
||||
@@ -212,8 +214,8 @@ export default class BasePlaylistController
|
||||
url: details.url,
|
||||
error,
|
||||
reason: error.message,
|
||||
level: (data as any).level || undefined,
|
||||
parent: details.fragments[0]?.type,
|
||||
level: (data as LevelLoadedData).level,
|
||||
parent,
|
||||
networkDetails,
|
||||
stats,
|
||||
});
|
||||
|
||||
@@ -2341,7 +2341,7 @@ export default class BaseStreamController
|
||||
}
|
||||
|
||||
private playlistLabel() {
|
||||
return this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track';
|
||||
return `${this.playlistType} playlist`;
|
||||
}
|
||||
|
||||
private fragInfo(
|
||||
|
||||
@@ -1013,8 +1013,8 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe
|
||||
};
|
||||
this.log(
|
||||
`queuing "${type}" append sn: ${sn}${part ? ' p: ' + part.index : ''} of ${
|
||||
parent === PlaylistLevelType.MAIN ? 'level' : 'track'
|
||||
} ${frag.level} cc: ${cc} offset: ${offset} bytes: ${data.byteLength}`,
|
||||
parent
|
||||
} playlist ${frag.level} cc: ${cc} offset: ${offset} bytes: ${data.byteLength}`,
|
||||
);
|
||||
this.append(operation, type, this.isPending(this.tracks[type]));
|
||||
}
|
||||
|
||||
@@ -481,7 +481,8 @@ export default class ErrorController
|
||||
this.sendAlternateToPenaltyBox(data);
|
||||
if (
|
||||
!data.errorAction.resolved &&
|
||||
data.details !== ErrorDetails.FRAG_GAP
|
||||
data.details !== ErrorDetails.FRAG_GAP &&
|
||||
data.details !== ErrorDetails.PLAYLIST_UNCHANGED_ERROR
|
||||
) {
|
||||
data.fatal = true;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import Transmuxer, {
|
||||
} from '../demux/transmuxer';
|
||||
import { ErrorDetails, ErrorTypes } from '../errors';
|
||||
import { Events } from '../events';
|
||||
import { PlaylistLevelType } from '../types/loader';
|
||||
import { getM2TSSupportedAudioTypes } from '../utils/codecs';
|
||||
import { stringify } from '../utils/safe-json-stringify';
|
||||
import type { WorkerContext } from './inject-worker';
|
||||
@@ -20,6 +19,7 @@ import type { HlsEventEmitter, HlsListeners } from '../events';
|
||||
import type Hls from '../hls';
|
||||
import type { MediaFragment, Part } from '../loader/fragment';
|
||||
import type { ErrorData, FragDecryptedData } from '../types/events';
|
||||
import type { PlaylistLevelType } from '../types/loader';
|
||||
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
|
||||
import type { TimestampOffset } from '../utils/timescale-conversion';
|
||||
|
||||
@@ -237,7 +237,7 @@ export default class TransmuxerInterface {
|
||||
this.hls.logger
|
||||
.log(`[transmuxer-interface]: Starting new transmux session for ${frag.type} sn: ${chunkMeta.sn}${
|
||||
chunkMeta.part > -1 ? ' part: ' + chunkMeta.part : ''
|
||||
} ${this.id === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${chunkMeta.level} id: ${chunkMeta.id}
|
||||
} ${this.id} playlist: ${chunkMeta.level} id: ${chunkMeta.id}
|
||||
discontinuity: ${discontinuity}
|
||||
trackSwitch: ${trackSwitch}
|
||||
contiguous: ${contiguous}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { ErrorDetails, ErrorTypes } from '../errors';
|
||||
import { Events } from '../events';
|
||||
import MP4Remuxer from '../remux/mp4-remuxer';
|
||||
import PassThroughRemuxer from '../remux/passthrough-remuxer';
|
||||
import { PlaylistLevelType } from '../types/loader';
|
||||
import {
|
||||
getAesModeFromFullSegmentMethod,
|
||||
isFullSegmentEncryption,
|
||||
@@ -17,6 +16,7 @@ import type { HlsConfig } from '../config';
|
||||
import type { HlsEventEmitter } from '../events';
|
||||
import type { DecryptData } from '../loader/level-key';
|
||||
import type { Demuxer, DemuxerResult, KeyData } from '../types/demuxer';
|
||||
import type { PlaylistLevelType } from '../types/loader';
|
||||
import type { Remuxer } from '../types/remuxer';
|
||||
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
|
||||
import type { TypeSupported } from '../utils/codecs';
|
||||
@@ -287,7 +287,7 @@ export default class Transmuxer {
|
||||
this.logger.log(
|
||||
`[transmuxer.ts]: Flushed ${this.id} sn: ${chunkMeta.sn}${
|
||||
chunkMeta.part > -1 ? ' part: ' + chunkMeta.part : ''
|
||||
} of ${this.id === PlaylistLevelType.MAIN ? 'level' : 'track'} ${chunkMeta.level}`,
|
||||
} of ${this.id} playlist ${chunkMeta.level}`,
|
||||
);
|
||||
const remuxResult = this.remuxer!.remux(
|
||||
audioTrack,
|
||||
|
||||
@@ -457,12 +457,12 @@ segment101.ts`;
|
||||
expect(data.details).to.equal(ErrorDetails.PLAYLIST_UNCHANGED_ERROR);
|
||||
expect(data.fatal).to.equal(false, 'Error should not be fatal');
|
||||
expect(data.error.message).to.include(
|
||||
'hits max allowed unchanged reloads',
|
||||
'hit max allowed unchanged reloads',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('PLAYLIST_UNCHANGED_ERROR becomes fatal when no alternate levels are available', function () {
|
||||
it('PLAYLIST_UNCHANGED_ERROR is not fatal when no alternate levels are available', function () {
|
||||
(hls.config as any).liveMaxUnchangedPlaylistRefresh = 2;
|
||||
|
||||
server.respondWith('singleLevelMultivariant.m3u8', [
|
||||
@@ -502,13 +502,12 @@ segment101.ts`;
|
||||
}).then((data: ErrorData) => {
|
||||
expect(data.details).to.equal(ErrorDetails.PLAYLIST_UNCHANGED_ERROR);
|
||||
expect(data.fatal).to.equal(
|
||||
true,
|
||||
'Error should be fatal with no alternates',
|
||||
false,
|
||||
'Error should not be fatal with no alternates',
|
||||
);
|
||||
expect(data.error.message).to.include(
|
||||
'hits max allowed unchanged reloads',
|
||||
'hit max allowed unchanged reloads',
|
||||
);
|
||||
expect(hls.stopLoad).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -572,7 +571,7 @@ audio101.ts`;
|
||||
expect(data.details).to.equal(ErrorDetails.PLAYLIST_UNCHANGED_ERROR);
|
||||
expect(data.fatal).to.equal(false, 'Error should not be fatal');
|
||||
expect(data.error.message).to.include(
|
||||
'hits max allowed unchanged reloads',
|
||||
'hit max allowed unchanged reloads',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user