Skip to content

Commit

Permalink
Fix delta updates with discontinuity tags
Browse files Browse the repository at this point in the history
Fixes #6949
  • Loading branch information
robwalch committed Jan 10, 2025
1 parent 1dee99c commit ce4fad1
Show file tree
Hide file tree
Showing 6 changed files with 517 additions and 56 deletions.
2 changes: 0 additions & 2 deletions api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3105,8 +3105,6 @@ export class LevelDetails {
// (undocumented)
appliedTimelineOffset?: number;
// (undocumented)
availabilityDelay?: number;
// (undocumented)
averagetargetduration?: number;
// (undocumented)
canBlockReload: boolean;
Expand Down
2 changes: 0 additions & 2 deletions src/loader/level-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export class LevelDetails {
public advancedDateTime?: number;
public updated: boolean = true;
public advanced: boolean = true;
public availabilityDelay?: number; // Manifest reload synchronization
public misses: number = 0;
public startCC: number = 0;
public startSN: number = 0;
Expand Down Expand Up @@ -87,7 +86,6 @@ export class LevelDetails {
} else {
this.misses = previous.misses + 1;
}
this.availabilityDelay = previous.availabilityDelay;
}

get hasProgramDateTime(): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/loader/m3u8-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ export default class M3U8Parser {
if (!level.live) {
lastFragment.endList = true;
}
if (firstFragment && !level.startCC) {
if (firstFragment && level.startCC === undefined) {
level.startCC = firstFragment.cc;
}
/**
Expand Down
64 changes: 29 additions & 35 deletions src/utils/level-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import type { Fragment, MediaFragment, Part } from '../loader/fragment';
import type { LevelDetails } from '../loader/level-details';
import type { Level } from '../types/level';

type FragmentIntersection = (oldFrag: Fragment, newFrag: Fragment) => void;
type FragmentIntersection = (
oldFrag: MediaFragment,
newFrag: MediaFragment,
newFragIndex: number,
newFragments: MediaFragment[],
) => void;
type PartIntersection = (oldPart: Part, newPart: Part) => void;

export function updatePTS(
Expand Down Expand Up @@ -107,7 +112,7 @@ export function updateFragPTSDTS(
if (!details || sn < details.startSN || sn > details.endSN) {
return 0;
}
let i;
let i: number;
const fragIdx = sn - details.startSN;
const fragments = details.fragments;
// update frag reference in fragments array
Expand Down Expand Up @@ -156,18 +161,19 @@ export function mergeDetails(
delete oldDetails.fragmentHint.endPTS;
}
// check if old/new playlists have fragments in common
// loop through overlapping SN and update startPTS , cc, and duration if any found
let ccOffset = 0;
let PTSFrag;
// loop through overlapping SN and update startPTS, cc, and duration if any found
let PTSFrag: MediaFragment | undefined;
mapFragmentIntersection(
oldDetails,
newDetails,
(oldFrag: Fragment, newFrag: Fragment) => {
if (oldFrag.relurl) {
// Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts.
// It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end
// of the playlist.
ccOffset = oldFrag.cc - newFrag.cc;
(oldFrag, newFrag, newFragIndex, newFragments) => {
if (newDetails.skippedSegments) {
if (newFrag.cc !== oldFrag.cc) {
const ccOffset = oldFrag.cc - newFrag.cc;
for (let i = newFragIndex; i < newFragments.length; i++) {
newFragments[i].cc += ccOffset;
}
}
}
if (
Number.isFinite(oldFrag.startPTS) &&
Expand Down Expand Up @@ -207,9 +213,10 @@ export function mergeDetails(
},
);

const newFragments = newDetails.fragments;
const fragmentsToCheck = newDetails.fragmentHint
? newDetails.fragments.concat(newDetails.fragmentHint)
: newDetails.fragments;
? newFragments.concat(newDetails.fragmentHint)
: newFragments;
if (currentInitSegment) {
fragmentsToCheck.forEach((frag) => {
if (
Expand All @@ -223,19 +230,17 @@ export function mergeDetails(
}

if (newDetails.skippedSegments) {
newDetails.deltaUpdateFailed = newDetails.fragments.some((frag) => !frag);
newDetails.deltaUpdateFailed = newFragments.some((frag) => !frag);
if (newDetails.deltaUpdateFailed) {
logger.warn(
'[level-helper] Previous playlist missing segments skipped in delta playlist',
);
for (let i = newDetails.skippedSegments; i--; ) {
newDetails.fragments.shift();
}
newDetails.startSN = newDetails.fragments[0].sn;
if (!newDetails.startCC) {
newDetails.startCC = newDetails.fragments[0].cc;
newFragments.shift();
}
newDetails.startSN = newFragments[0].sn;
} else {
newDetails.endCC = newFragments[newFragments.length - 1].cc;
if (newDetails.canSkipDateRanges) {
newDetails.dateRanges = mergeDateRanges(
oldDetails.dateRanges,
Expand All @@ -260,17 +265,6 @@ export function mergeDetails(
}
}

const newFragments = newDetails.fragments;
if (ccOffset) {
logger.warn('discontinuity sliding from playlist, take drift into account');
for (let i = 0; i < newFragments.length; i++) {
newFragments[i].cc += ccOffset;
}
}
if (newDetails.skippedSegments) {
newDetails.startCC = newDetails.fragments[0].cc;
}

// Merge parts
mapPartIntersection(
oldDetails.partList,
Expand All @@ -286,10 +280,10 @@ export function mergeDetails(
updateFragPTSDTS(
newDetails,
PTSFrag,
PTSFrag.startPTS,
PTSFrag.endPTS,
PTSFrag.startDTS,
PTSFrag.endDTS,
PTSFrag.startPTS as number,
PTSFrag.endPTS as number,
PTSFrag.startDTS as number,
PTSFrag.endDTS as number,
);
} else {
// ensure that delta is within oldFragments range
Expand Down Expand Up @@ -414,7 +408,7 @@ export function mapFragmentIntersection(
newFrag = newDetails.fragments[i] = oldFrag;
}
if (oldFrag && newFrag) {
intersectionFn(oldFrag, newFrag);
intersectionFn(oldFrag, newFrag, i, newFrags);
}
}
}
Expand Down
34 changes: 18 additions & 16 deletions tests/unit/controller/cmcd-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,24 @@ https://dummy.url.com/10905.m4s
#EXTINF:2,
https://dummy.url.com/10906.m4s`;

const details = M3U8Parser.parseLevelPlaylist(
playlist,
url,
0,
PlaylistLevelType.MAIN,
0,
null,
);

const uuidRegex =
/[a-f\d]{8}-[a-f\d]{4}-4[a-f\d]{3}-[89ab][a-f\d]{3}-[a-f\d]{12}/;

const level = {
bitrate: 1000,
details,
};

const setupEach = (cmcd?: CMCDControllerConfig) => {
const details = M3U8Parser.parseLevelPlaylist(
playlist,
url,
0,
PlaylistLevelType.MAIN,
0,
null,
);

const level = {
bitrate: 1000,
details,
};

const hls = new Hls({ cmcd }) as any;
hls.networkControllers.forEach((component) => component.destroy());
hls.networkControllers.length = 0;
Expand All @@ -71,6 +71,8 @@ const setupEach = (cmcd?: CMCDControllerConfig) => {
// hls.audioTracks = [];

cmcdController = new CMCDController(hls);

return details;
};

const base = {
Expand Down Expand Up @@ -151,7 +153,7 @@ describe('CMCDController', function () {
});

it('uses fragment data', function () {
setupEach({});
const details = setupEach({});

const { url } = applyFragmentData(details.fragments[0]);

Expand All @@ -162,7 +164,7 @@ describe('CMCDController', function () {
});

it('uses part data when available', function () {
setupEach({});
const details = setupEach({});

const { url } = applyFragmentData(
details.fragments[2],
Expand Down
Loading

0 comments on commit ce4fad1

Please sign in to comment.