Skip to content
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

Bugfix/live start bugs #4303

Merged
merged 4 commits into from
Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 5 additions & 2 deletions src/controller/audio-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ChunkCache from '../demux/chunk-cache';
import TransmuxerInterface from '../demux/transmuxer-interface';
import { ChunkMetadata } from '../types/transmuxer';
import { fragmentWithinToleranceTest } from './fragment-finders';
import { alignPDT } from '../utils/discontinuities';
import { alignByPDT } from '../utils/discontinuities';
import { ErrorDetails } from '../errors';
import { logger } from '../utils/logger';
import type { NetworkComponentAPI } from '../types/component-api';
Expand Down Expand Up @@ -144,6 +144,7 @@ class AudioStreamController
this.startPosition =
this.lastCurrentTime =
startPosition;

this.tick();
}

Expand Down Expand Up @@ -440,7 +441,9 @@ class AudioStreamController
newDetails.hasProgramDateTime &&
mainDetails.hasProgramDateTime
) {
alignPDT(newDetails, mainDetails);
// Make sure our audio rendition is aligned with the "main" rendition, using
// pdt as our reference times.
alignByPDT(newDetails, mainDetails);
sliding = newDetails.fragments[0].start;
} else {
sliding = this.alignPlaylists(newDetails, track.details);
Expand Down
6 changes: 5 additions & 1 deletion src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,11 @@ export default class StreamController
const buffered = BufferHelper.getBuffered(media);
const bufferStart = buffered.length ? buffered.start(0) : 0;
const delta = bufferStart - startPosition;
if (delta > 0 && delta < this.config.maxBufferHole) {
if (
delta > 0 &&
(delta < this.config.maxBufferHole ||
delta < this.config.maxFragLookUpTolerance)
) {
logger.log(
`adjusting start position by ${delta} to match buffer start`
);
Expand Down
42 changes: 42 additions & 0 deletions src/utils/discontinuities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,45 @@ export function alignPDT(details: LevelDetails, lastDetails: LevelDetails) {
adjustSlidingStart(sliding, details);
}
}

export function alignFragmentByPDTDelta(frag: Fragment, delta: number) {
const { programDateTime } = frag;
if (!programDateTime) return;
const start = (programDateTime - delta) / 1000;
frag.start = frag.startPTS = start;
frag.endPTS = start + frag.duration;
}

/**
* Ensures appropriate time-alignment between renditions based on PDT.
* @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition).
* @param refDetails - The details of the reference rendition with start and PDT times for alignment.
*/
export function alignByPDT(details: LevelDetails, refDetails: LevelDetails) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you document the difference between alignByPDT and alignPDT above. Given the closeness in the implementation, having a description for them would be helpful.

That or a better name for each.

Perhaps alignByPDT could be something like; alignMediaPlaylistByPDT.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup can do both.

// This check protects the unsafe "!" usage below for null program date time access.
if (
!refDetails.fragments.length ||
!details.hasProgramDateTime ||
!refDetails.hasProgramDateTime
) {
return;
}
const refPDT = refDetails.fragments[0].programDateTime!; // hasProgramDateTime check above makes this safe.
const refStart = refDetails.fragments[0].start;
// Use the delta between the reference details' presentation timeline's start time and its PDT
// to align the other rendtion's timeline.
const delta = refPDT - refStart * 1000;
// Per spec: "If any Media Playlist in a Master Playlist contains an EXT-X-PROGRAM-DATE-TIME tag, then all
// Media Playlists in that Master Playlist MUST contain EXT-X-PROGRAM-DATE-TIME tags with consistent mappings
// of date and time to media timestamps."
// So we should be able to use each rendition's PDT as a reference time and use the delta to compute our relevant
// start and end times.
// NOTE: This code assumes each level/details timelines have already been made "internally consistent"
details.fragments.forEach((frag) => {
alignFragmentByPDTDelta(frag, delta);
});
if (details.fragmentHint) {
alignFragmentByPDTDelta(details.fragmentHint, delta);
}
details.alignedSliding = true;
}
101 changes: 101 additions & 0 deletions tests/unit/utils/discontinuities.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
findDiscontinuousReferenceFrag,
adjustSlidingStart,
alignPDT,
alignByPDT,
} from '../../../src/utils/discontinuities';

const mockReferenceFrag = {
Expand Down Expand Up @@ -73,6 +74,106 @@ describe('level-helper', function () {
expect(details.alignedSliding).to.be.true;
});

it('aligns level fragments times based on PDT and start time of reference level details', function () {
const lastLevel = {
details: {
PTSKnown: false,
alignedSliding: false,
hasProgramDateTime: true,
fragments: [
{
start: 18,
startPTS: undefined,
endPTS: undefined,
duration: 2,
programDateTime: 1629821766107,
},
{
start: 20,
startPTS: undefined,
endPTS: 22,
duration: 2,
programDateTime: 1629821768107,
},
{
start: 22,
startPTS: 22,
endPTS: 30,
duration: 8,
programDateTime: 1629821770107,
},
],
fragmentHint: {
start: 30,
startPTS: 30,
endPTS: 32,
duration: 2,
programDateTime: 1629821778107,
},
},
};

const refDetails = {
fragments: [
{
start: 18,
startPTS: undefined,
endPTS: undefined,
duration: 2,
programDateTime: 1629821768107,
},
],
PTSKnown: false,
alignedSliding: false,
hasProgramDateTime: true,
};

const detailsExpected = {
fragments: [
{
start: 16,
startPTS: 16,
endPTS: 18,
duration: 2,
programDateTime: 1629821766107,
},
{
start: 18,
startPTS: 18,
endPTS: 20,
duration: 2,
programDateTime: 1629821768107,
},
{
start: 20,
startPTS: 20,
endPTS: 28,
duration: 8,
programDateTime: 1629821770107,
},
],
fragmentHint: {
start: 28,
startPTS: 28,
endPTS: 30,
duration: 2,
programDateTime: 1629821778107,
},
PTSKnown: false,
alignedSliding: true,
hasProgramDateTime: true,
};
alignByPDT(lastLevel.details, refDetails);
expect(
lastLevel.details,
`actual:\n\n${JSON.stringify(
lastLevel.details,
null,
2
)}\n\nexpected\n\n${JSON.stringify(detailsExpected, null, 2)}`
).to.deep.equal(detailsExpected);
});

it('adjusts level fragments without overlapping CC range but with programDateTime info', function () {
const lastLevel = {
details: {
Expand Down