Skip to content

Fix Interstitial asset inline buffering with other items buffered ahead #6913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,21 @@ export default class BaseStreamController
bufferInfo: BufferInfo,
levelDetails: LevelDetails,
): boolean {
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
// of nothing loading/loaded return false
const hasTimelineOffset = this.config.timelineOffset !== undefined;
// Stream is never "ended" when playlist is live or media is detached
if (levelDetails.live || !this.media) {
return false;
}
// Stream is not "ended" when nothing is buffered past the start
const bufferEnd = bufferInfo.end || 0;
const timelineStart = this.config.timelineOffset || 0;
if (bufferEnd <= timelineStart) {
return false;
}
// Stream is not "ended" when there is a second buffered range starting before the end of the playlist
const nextStart = bufferInfo.nextStart;
const hasSecondBufferedRange =
nextStart && (!hasTimelineOffset || nextStart < levelDetails.edge);
if (
levelDetails.live ||
hasSecondBufferedRange ||
!bufferInfo.end ||
!this.media
) {
nextStart && nextStart > timelineStart && nextStart < levelDetails.edge;
if (hasSecondBufferedRange) {
return false;
}
const partList = levelDetails.partList;
Expand Down
55 changes: 35 additions & 20 deletions tests/unit/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import sinonChai from 'sinon-chai';
import { hlsDefaultConfig } from '../../../src/config';
import BaseStreamController from '../../../src/controller/stream-controller';
import Hls from '../../../src/hls';
import { Fragment } from '../../../src/loader/fragment';
import KeyLoader from '../../../src/loader/key-loader';
import { LevelDetails } from '../../../src/loader/level-details';
import { PlaylistLevelType } from '../../../src/types/loader';
import { TimeRangesMock } from '../../mocks/time-ranges.mock';
import type { Fragment, Part } from '../../../src/loader/fragment';
import type { LevelDetails } from '../../../src/loader/level-details';
import type { MediaFragment, Part } from '../../../src/loader/fragment';
import type { BufferInfo } from '../../../src/utils/buffer-helper';

chai.use(sinonChai);
Expand All @@ -24,7 +26,6 @@ describe('BaseStreamController', function () {
let hls: Hls;
let baseStreamController: BaseStreamControllerTestable;
let bufferInfo: BufferInfo;
let levelDetails: LevelDetails;
let fragmentTracker;
let media;
beforeEach(function () {
Expand All @@ -49,41 +50,55 @@ describe('BaseStreamController', function () {
start: 0,
end: 1,
};
levelDetails = {
endSN: 0,
live: false,
get fragments() {
const frags: Fragment[] = [];
for (let i = 0; i < this.endSN; i++) {
frags.push({ sn: i, type: 'main' } as unknown as Fragment);
}
return frags;
},
} as unknown as LevelDetails;
media = {
duration: 0,
buffered: new TimeRangesMock(),
} as unknown as HTMLMediaElement;
baseStreamController.media = media;
});

function levelDetailsWithEndSequenceVodOrLive(
endSN: number = 1,
live: boolean = false,
) {
const details = new LevelDetails('');
for (let i = 0; i < endSN; i++) {
const frag = new Fragment(PlaylistLevelType.MAIN, '') as MediaFragment;
frag.duration = 5;
frag.sn = i;
frag.start = i * 5;
details.fragments.push(frag);
}
details.live = live;
return details;
}

describe('_streamEnded', function () {
it('returns false if the stream is live', function () {
levelDetails.live = true;
const levelDetails = levelDetailsWithEndSequenceVodOrLive(3, true);
expect(baseStreamController._streamEnded(bufferInfo, levelDetails)).to.be
.false;
});

it('returns false if there is subsequently buffered range', function () {
levelDetails.endSN = 10;
bufferInfo.nextStart = 100;
it('returns false if there is subsequently buffered range within program range', function () {
const levelDetails = levelDetailsWithEndSequenceVodOrLive(10);
expect(levelDetails.edge).to.eq(50);
bufferInfo.nextStart = 45;
expect(baseStreamController._streamEnded(bufferInfo, levelDetails)).to.be
.false;
});

it('returns true if complete and subsequently buffered range is outside program range', function () {
const levelDetails = levelDetailsWithEndSequenceVodOrLive(10);
expect(levelDetails.edge).to.eq(50);
bufferInfo.nextStart = 100;
expect(baseStreamController._streamEnded(bufferInfo, levelDetails)).to.be
.true;
});

it('returns true if parts are buffered for low latency content', function () {
media.buffered = new TimeRangesMock([0, 1]);
levelDetails.endSN = 10;
const levelDetails = levelDetailsWithEndSequenceVodOrLive(10);
levelDetails.partList = [{ start: 0, duration: 1 } as unknown as Part];

expect(baseStreamController._streamEnded(bufferInfo, levelDetails)).to.be
Expand All @@ -92,7 +107,7 @@ describe('BaseStreamController', function () {

it('depends on fragment-tracker to determine if last fragment is buffered', function () {
media.buffered = new TimeRangesMock([0, 1]);
levelDetails.endSN = 10;
const levelDetails = levelDetailsWithEndSequenceVodOrLive(10);

expect(baseStreamController._streamEnded(bufferInfo, levelDetails)).to.be
.true;
Expand Down
Loading