Skip to content

Commit bfb6907

Browse files
authored
Handle init segment load context change gracefully (#7935)
* Handle init segment load context change gracefully Fix for error path A of #7931 * Restart loading from startPosition if necessary on live-join attach Fixes #7931 path B
1 parent 0d17a40 commit bfb6907

3 files changed

Lines changed: 65 additions & 10 deletions

File tree

src/controller/base-stream-controller.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
type Part,
1919
} from '../loader/fragment';
2020
import FragmentLoader, { getAESAdjustments } from '../loader/fragment-loader';
21+
import { LoadError } from '../loader/fragment-loader';
2122
import TaskLoop from '../task-loop';
2223
import { PlaylistLevelType } from '../types/loader';
2324
import { ChunkMetadata } from '../types/transmuxer';
@@ -50,10 +51,7 @@ import type { FragmentTracker } from './fragment-tracker';
5051
import type { HlsAssetPlayer } from './interstitial-player';
5152
import type TransmuxerInterface from '../demux/transmuxer-interface';
5253
import type Hls from '../hls';
53-
import type {
54-
FragmentLoadProgressCallback,
55-
LoadError,
56-
} from '../loader/fragment-loader';
54+
import type { FragmentLoadProgressCallback } from '../loader/fragment-loader';
5755
import type KeyLoader from '../loader/key-loader';
5856
import type { LevelDetails } from '../loader/level-details';
5957
import type { SourceBufferName } from '../types/buffer';
@@ -716,13 +714,19 @@ export default class BaseStreamController
716714
return this.initFragmentLoader
717715
.load(initFrag)
718716
.then((data) => {
719-
const frag = data?.frag;
717+
const frag = data.frag;
720718
if (
721-
!frag ||
722-
!fragmentsAreEqual(frag, this.fragCurrent?.initSegment) ||
723-
!this.levels
719+
!this.levels ||
720+
!fragmentsAreEqual(frag, this.fragCurrent?.initSegment)
724721
) {
725-
throw new Error('init load aborted');
722+
throw new LoadError({
723+
type: ErrorTypes.NETWORK_ERROR,
724+
details: ErrorDetails.INTERNAL_ABORTED,
725+
error: new Error('init load aborted'),
726+
fatal: false,
727+
frag,
728+
networkDetails: null,
729+
});
726730
}
727731
return data;
728732
})

src/controller/interstitials-controller.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,11 +891,18 @@ export default class InterstitialsController
891891
const assetPlayer = player as HlsAssetPlayer;
892892
const timelineStart = assetPlayer.assetItem.timelineStart;
893893
if (timelineStart) {
894+
const assetHls = assetPlayer.hls!;
894895
const timePastStart = Math.max(
895896
this.timelinePos - timelineStart,
896897
Number.EPSILON,
897898
);
898-
assetPlayer.hls!.config.startPosition = timePastStart;
899+
if (assetHls.startPosition !== timePastStart) {
900+
this.log(`Set ${assetPlayer} startPosition: ${timePastStart}`);
901+
assetHls.config.startPosition = timePastStart;
902+
if (assetHls.loadingEnabled) {
903+
assetHls.startLoad(timePastStart);
904+
}
905+
}
899906
}
900907
}
901908
player.attachMedia(dataToAttach);

tests/unit/controller/base-stream-controller.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,50 @@ describe('BaseStreamController', function () {
995995
resetFragmentLoadingStub.restore();
996996
});
997997
});
998+
999+
it('loadInitSegmentIfNeeded handles init segment context switch before abort load errors', function () {
1000+
const { mediaFrag, initSegment } = createMediaFragWithInitSegment(false);
1001+
baseStreamController.state = State.FRAG_LOADING;
1002+
(baseStreamController as any).levels = [{}];
1003+
1004+
// The in-flight init request resolves successfully
1005+
sinon
1006+
.stub((baseStreamController as any).initFragmentLoader, 'load')
1007+
.resolves({ frag: initSegment } as any);
1008+
1009+
// context changed before loadInitSegmentIfNeeded is resolved
1010+
baseStreamController.fragCurrent = null;
1011+
1012+
const errorSpy = sinon.spy();
1013+
hls.on(Hls.Events.ERROR, errorSpy);
1014+
1015+
// expect error to be routed through handleFragLoadError
1016+
const initDataPromise = (
1017+
baseStreamController as any
1018+
).loadInitSegmentIfNeeded(mediaFrag);
1019+
expect(
1020+
initDataPromise,
1021+
'loadInitSegmentIfNeeded should return a promise',
1022+
).to.be.a('promise');
1023+
1024+
return initDataPromise
1025+
.catch((error) =>
1026+
(baseStreamController as any).handleFragLoadError(error),
1027+
)
1028+
.then(() => {
1029+
const fatalInternalException = errorSpy
1030+
.getCalls()
1031+
.some(
1032+
(call) =>
1033+
call.args[1]?.details === ErrorDetails.INTERNAL_EXCEPTION &&
1034+
call.args[1]?.fatal === true,
1035+
);
1036+
expect(
1037+
fatalInternalException,
1038+
'init-segment load abort must not be reported as a fatal INTERNAL_EXCEPTION',
1039+
).to.equal(false);
1040+
});
1041+
});
9981042
});
9991043

10001044
describe('backtrackFragment and couldBacktrack properties', function () {

0 commit comments

Comments
 (0)