Skip to content

Commit f04b180

Browse files
committed
Add logging for playlist alignment across variants and renditions (different playlist URIs)
#7482
1 parent d698f14 commit f04b180

File tree

6 files changed

+45
-19
lines changed

6 files changed

+45
-19
lines changed

src/controller/audio-stream-controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ class AudioStreamController
581581
if (!newDetails.alignedSliding) {
582582
// Align audio rendition with the "main" playlist on discontinuity change
583583
// or program-date-time (PDT)
584-
alignStream(mainDetails, newDetails);
584+
alignStream(mainDetails, newDetails, this);
585585
sliding = newDetails.fragmentStart;
586586
}
587587
}
@@ -1047,7 +1047,7 @@ class AudioStreamController
10471047
mainDetails &&
10481048
mainDetails.fragmentStart !== track.details.fragmentStart
10491049
) {
1050-
alignStream(mainDetails, track.details);
1050+
alignStream(mainDetails, track.details, this);
10511051
}
10521052
} else {
10531053
super.loadFragment(frag, track, targetBufferTime);

src/controller/base-stream-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1795,7 +1795,7 @@ export default class BaseStreamController
17951795
const firstLevelLoad = !previousDetails;
17961796
const aligned = details.alignedSliding && Number.isFinite(slidingStart);
17971797
if (firstLevelLoad || (!aligned && !slidingStart)) {
1798-
alignStream(switchDetails, details);
1798+
alignStream(switchDetails, details, this);
17991799
const alignedSlidingStart = details.fragmentStart;
18001800
this.log(
18011801
`Live playlist sliding: ${alignedSlidingStart.toFixed(2)} start-sn: ${

src/controller/subtitle-stream-controller.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
getAesModeFromFullSegmentMethod,
1717
isFullSegmentEncryption,
1818
} from '../utils/encryption-methods-util';
19-
import { addSliding } from '../utils/level-helper';
2019
import { subtitleOptionsIdentical } from '../utils/media-option-attributes';
2120
import type { FragmentTracker } from './fragment-tracker';
2221
import type Hls from '../hls';
@@ -315,7 +314,7 @@ export class SubtitleStreamController
315314
}
316315
if (!newDetails.alignedSliding) {
317316
// line up live playlist with main so that fragments in range are loaded
318-
alignStream(mainDetails, newDetails);
317+
alignStream(mainDetails, newDetails, this);
319318
sliding = newDetails.fragmentStart;
320319
}
321320
}

src/utils/discontinuities.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { adjustSliding } from './level-helper';
2-
import { logger } from './logger';
2+
import type { ILogger } from './logger';
33
import type { Fragment } from '../loader/fragment';
44
import type { LevelDetails } from '../loader/level-details';
55

@@ -62,22 +62,23 @@ export function adjustSlidingStart(sliding: number, details: LevelDetails) {
6262
export function alignStream(
6363
switchDetails: LevelDetails | undefined,
6464
details: LevelDetails,
65+
logger: ILogger,
6566
) {
6667
if (!switchDetails) {
6768
return;
6869
}
69-
alignDiscontinuities(details, switchDetails);
70+
alignDiscontinuities(details, switchDetails, logger);
7071
if (!details.alignedSliding) {
7172
// If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level.
7273
// Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same
7374
// discontinuity sequence.
74-
alignMediaPlaylistByPDT(details, switchDetails);
75+
alignMediaPlaylistByPDT(details, switchDetails, logger);
7576
}
7677
if (!details.alignedSliding && !details.skippedSegments) {
7778
// Try to align on sn so that we pick a better start fragment.
7879
// Do not perform this on playlists with delta updates as this is only to align levels on switch
7980
// and adjustSliding only adjusts fragments after skippedSegments.
80-
adjustSliding(switchDetails, details, false);
81+
adjustSliding(switchDetails, details, false, logger);
8182
}
8283
}
8384

@@ -90,6 +91,7 @@ export function alignStream(
9091
export function alignDiscontinuities(
9192
details: LevelDetails,
9293
refDetails: LevelDetails | undefined,
94+
logger: ILogger,
9395
) {
9496
if (!shouldAlignOnDiscontinuities(refDetails, details)) {
9597
return;
@@ -100,8 +102,10 @@ export function alignDiscontinuities(
100102
if (!refFrag || !frag) {
101103
return;
102104
}
103-
logger.log(`Aligning playlist at start of dicontinuity sequence ${targetCC}`);
104105
const delta = refFrag.start - frag.start;
106+
logger.log(
107+
`Aligning playlists using dicontinuity sequence ${targetCC} (diff: ${delta})`,
108+
);
105109
adjustSlidingStart(delta, details);
106110
}
107111

@@ -121,6 +125,7 @@ export function alignDiscontinuities(
121125
export function alignMediaPlaylistByPDT(
122126
details: LevelDetails,
123127
refDetails: LevelDetails,
128+
logger: ILogger,
124129
) {
125130
if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) {
126131
return;
@@ -157,9 +162,13 @@ export function alignMediaPlaylistByPDT(
157162
const dateDifference = (targetPDT - refPDT) / 1000;
158163
if (Math.abs(dateDifference) > Math.max(60, details.totalduration)) {
159164
// Do not align on PDT if ranges differ significantly
165+
logger.log(
166+
`Cannot align playlists using PDT without overlap (${Math.abs(dateDifference)} > ${details.totalduration})`,
167+
);
160168
return;
161169
}
162170

163171
const delta = dateDifference - (frag.start - refFrag.start);
172+
logger.log(`Aligning playlists using PDT (diff: ${delta})`);
164173
adjustSlidingStart(delta, details);
165174
}

src/utils/level-helper.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,27 +475,38 @@ export function adjustSliding(
475475
oldDetails: LevelDetails,
476476
newDetails: LevelDetails,
477477
matchingStableVariantOrRendition: boolean = true,
478-
): void {
478+
logger?: ILogger,
479+
): number {
479480
const delta =
480481
newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN;
481482
const oldFragments = oldDetails.fragments;
482483
const advancedOrStable = delta >= 0;
483484
let sliding = 0;
484485
if (advancedOrStable && delta < oldFragments.length) {
485486
sliding = oldFragments[delta].start;
487+
logger?.log(
488+
`Aligning playlists based on SN ${newDetails.startSN} (diff: ${sliding})`,
489+
);
486490
} else if (advancedOrStable && newDetails.startSN === oldDetails.endSN + 1) {
487491
sliding = oldDetails.fragmentEnd;
492+
logger?.log(
493+
`Aligning playlists based on first/last SN ${newDetails.startSN} (diff: ${sliding})`,
494+
);
488495
} else if (advancedOrStable && matchingStableVariantOrRendition) {
489496
// align with expected position (updated playlist start sequence is past end sequence of last update)
490497
sliding = oldDetails.fragmentStart + delta * newDetails.levelTargetDuration;
491498
} else if (!newDetails.skippedSegments && newDetails.fragmentStart === 0) {
492499
// align new start with old (playlist switch has a sequence with no overlap and should not be used for alignment)
493500
sliding = oldDetails.fragmentStart;
501+
logger?.log(
502+
`Aligning playlists based on first SN ${newDetails.startSN} (diff: ${sliding})`,
503+
);
494504
} else {
495505
// new details already has a sliding offset or has skipped segments
496-
return;
506+
return sliding; // 0
497507
}
498508
addSliding(newDetails, sliding);
509+
return sliding;
499510
}
500511

501512
export function addSliding(details: LevelDetails, sliding: number) {

tests/unit/utils/discontinuities.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
alignMediaPlaylistByPDT,
1212
shouldAlignOnDiscontinuities,
1313
} from '../../../src/utils/discontinuities';
14+
import { logger } from '../../../src/utils/logger';
1415
import type { MediaFragment } from '../../../src/loader/fragment';
1516

1617
chai.use(sinonChai);
@@ -176,7 +177,7 @@ describe('discontinuities', function () {
176177
startCC: 0,
177178
endCC: 0,
178179
});
179-
alignMediaPlaylistByPDT(lastLevel.details, refDetails);
180+
alignMediaPlaylistByPDT(lastLevel.details, refDetails, logger);
180181
expect(
181182
lastLevel.details,
182183
`actual:\n\n${JSON.stringify(
@@ -312,7 +313,7 @@ describe('discontinuities', function () {
312313
startCC: 2,
313314
endCC: 4,
314315
});
315-
alignMediaPlaylistByPDT(details, lastLevel.details as LevelDetails);
316+
alignMediaPlaylistByPDT(details, lastLevel.details as LevelDetails, logger);
316317
expect(details).to.deep.equal(detailsExpected);
317318
});
318319

@@ -420,7 +421,7 @@ describe('discontinuities', function () {
420421
startCC: 2,
421422
endCC: 3,
422423
});
423-
alignMediaPlaylistByPDT(details, lastLevel.details);
424+
alignMediaPlaylistByPDT(details, lastLevel.details, logger);
424425

425426
expect(detailsExpected).to.deep.equal(
426427
details,
@@ -439,7 +440,7 @@ describe('discontinuities', function () {
439440
const curDetails = objToLevelDetails({
440441
fragments: mockFrags.map(objToFragment),
441442
});
442-
alignDiscontinuities(curDetails, prevDetails);
443+
alignDiscontinuities(curDetails, prevDetails, logger);
443444
expect(curDetails).to.have.property('alignedSliding').which.is.true;
444445
expect(curDetails.fragments[0].start).to.equal(20);
445446
});
@@ -455,7 +456,7 @@ describe('discontinuities', function () {
455456
const curDetails = objToLevelDetails({
456457
fragments: mockFrags.map(objToFragment),
457458
});
458-
alignDiscontinuities(curDetails, prevDetails);
459+
alignDiscontinuities(curDetails, prevDetails, logger);
459460
expect(curDetails).to.have.property('alignedSliding').which.is.true;
460461
expect(curDetails.fragments[0].start).to.equal(20);
461462
});
@@ -475,7 +476,7 @@ describe('discontinuities', function () {
475476
{ start: 8.5, startPTS: 4.5, endPTS: 12.5, duration: 4, cc: 3 },
476477
].map(objToFragment),
477478
});
478-
alignDiscontinuities(curDetails, prevDetails);
479+
alignDiscontinuities(curDetails, prevDetails, logger);
479480
expect(curDetails).to.have.property('alignedSliding').which.is.true;
480481
expect(curDetails.fragments[0].start).to.equal(24);
481482
});
@@ -484,7 +485,11 @@ describe('discontinuities', function () {
484485
const curDetails = objToLevelDetails({
485486
fragments: mockFrags.map(objToFragment),
486487
});
487-
alignDiscontinuities(curDetails, objToLevelDetails({ fragments: [] }));
488+
alignDiscontinuities(
489+
curDetails,
490+
objToLevelDetails({ fragments: [] }),
491+
logger,
492+
);
488493
expect(curDetails).to.have.property('alignedSliding').which.is.false;
489494
expect(curDetails.fragments[0].start).to.equal(0);
490495
});
@@ -496,6 +501,7 @@ describe('discontinuities', function () {
496501
alignDiscontinuities(
497502
curDetails,
498503
objToLevelDetails({ fragments: [{ cc: 10 }].map(objToFragment) }),
504+
logger,
499505
);
500506
expect(curDetails).to.have.property('alignedSliding').which.is.false;
501507
expect(curDetails.fragments[0].start).to.equal(0);
@@ -508,6 +514,7 @@ describe('discontinuities', function () {
508514
alignDiscontinuities(
509515
curDetails,
510516
objToLevelDetails({ fragments: [{ cc: 0 }].map(objToFragment) }),
517+
logger,
511518
);
512519
expect(curDetails).to.have.property('alignedSliding').which.is.false;
513520
});

0 commit comments

Comments
 (0)