Skip to content

Commit

Permalink
Fix seeking into and over multiple EXT-X-GAP segments
Browse files Browse the repository at this point in the history
Fixes #6814
  • Loading branch information
robwalch committed Feb 1, 2025
1 parent 305a3a7 commit a9863a5
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 13 deletions.
2 changes: 1 addition & 1 deletion api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1866,7 +1866,7 @@ export class FragmentTracker implements ComponentAPI {
getBufferedFrag(position: number, levelType: PlaylistLevelType): MediaFragment | null;
// (undocumented)
getFragAtPos(position: number, levelType: PlaylistLevelType, buffered?: boolean): MediaFragment | null;
getPartialFragment(time: number): Fragment | null;
getPartialFragment(time: number): MediaFragment | null;
// (undocumented)
getState(fragment: Fragment): FragmentState;
// (undocumented)
Expand Down
8 changes: 4 additions & 4 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1215,11 +1215,11 @@ export default class BaseStreamController
bufferedFragAtPos &&
(bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
) {
return BufferHelper.bufferInfo(
bufferable,
pos,
Math.max(bufferInfo.nextStart, maxBufferHole),
const gapDuration = Math.max(
Math.min(bufferInfo.nextStart, bufferedFragAtPos.end) - pos,
maxBufferHole,
);
return BufferHelper.bufferInfo(bufferable, pos, gapDuration);
}
}
return bufferInfo;
Expand Down
2 changes: 1 addition & 1 deletion src/controller/fragment-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export class FragmentTracker implements ComponentAPI {
/**
* Gets the partial fragment for a certain time
*/
public getPartialFragment(time: number): Fragment | null {
public getPartialFragment(time: number): MediaFragment | null {
let bestFragment: Fragment | null = null;
let timePadding: number;
let startTime: number;
Expand Down
21 changes: 17 additions & 4 deletions src/controller/gap-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { InFlightData } from './base-stream-controller';
import type { InFlightFragments } from '../hls';
import type Hls from '../hls';
import type { FragmentTracker } from './fragment-tracker';
import type { Fragment } from '../loader/fragment';
import type { Fragment, MediaFragment } from '../loader/fragment';
import type { SourceBufferName } from '../types/buffer';
import type {
BufferAppendedData,
Expand Down Expand Up @@ -503,7 +503,7 @@ export default class GapController extends TaskLoop {
* @param partial - The partial fragment found at the current time (where playback is stalling).
* @private
*/
private _trySkipBufferHole(partial: Fragment | null): number {
private _trySkipBufferHole(partial: MediaFragment | null): number {
const { fragmentTracker, media } = this;
const config = this.hls?.config;
if (!media || !fragmentTracker || !config) {
Expand All @@ -515,7 +515,7 @@ export default class GapController extends TaskLoop {
const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
const startTime =
currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
if (startTime) {
if (startTime && this.hls) {
const bufferStarved = bufferInfo.len <= config.maxBufferHole;
const waiting =
bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
Expand All @@ -541,6 +541,19 @@ export default class GapController extends TaskLoop {
PlaylistLevelType.MAIN,
);
if (startProvisioned) {
// Do not seek when switching variants
if (!this.hls.levels[this.hls.loadLevel]?.details) {
return 0;
}
// Do not seek when loading frags
const inFlightDependency = getInFlightDependency(
this.hls.inFlightFragments,
startTime,
);
if (inFlightDependency) {
return 0;
}
// Do not seek if we can't walk tracked fragments to end of gap
let moreToLoad = false;
let pos = startProvisioned.end;
while (pos < startTime) {
Expand All @@ -567,7 +580,7 @@ export default class GapController extends TaskLoop {
);
this.moved = true;
media.currentTime = targetTime;
if (!partial?.gap && this.hls) {
if (!partial?.gap) {
const error = new Error(
`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`,
);
Expand Down
2 changes: 1 addition & 1 deletion src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export default class StreamController

protected onTickEnd() {
super.onTickEnd();
if (this.media?.readyState) {
if (this.media?.readyState && this.media.seeking === false) {
this.lastCurrentTime = this.media.currentTime;
}
this.checkFragmentChanged();
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/controller/gap-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { MockMediaElement, MockMediaSource } from '../utils/mock-media';
import type { HlsConfig } from '../../../src/config';
import type StreamController from '../../../src/controller/stream-controller';
import type { Fragment } from '../../../src/loader/fragment';
import type { Fragment, MediaFragment } from '../../../src/loader/fragment';

chai.use(sinonChai);
const expect = chai.expect;
Expand Down Expand Up @@ -202,7 +202,7 @@ describe('GapController', function () {
const bufferInfo = BufferHelper.bufferedInfo([], 0, 0);
sandbox
.stub(gapController.fragmentTracker, 'getPartialFragment')
.returns({} as unknown as Fragment);
.returns({} as unknown as MediaFragment);
const skipHoleStub = sandbox.stub(gapController, '_trySkipBufferHole');
gapController._tryFixBufferStall(bufferInfo, 100);
expect(skipHoleStub).to.have.been.calledOnce;
Expand Down

0 comments on commit a9863a5

Please sign in to comment.