Skip to content

Commit

Permalink
Merge pull request video-dev#3540 from video-dev/bugfix/live-fragment…
Browse files Browse the repository at this point in the history
…-finder

Fix live fragment finder logic
  • Loading branch information
robwalch authored Mar 2, 2021
2 parents ffc8f9e + ff7a5d2 commit 33571bb
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 35 deletions.
20 changes: 10 additions & 10 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ export default class BaseStreamController
levelDetails: LevelDetails,
fragments: Array<Fragment>
): Fragment | null {
const { config, fragPrevious } = this;
const fragPrevious = this.fragPrevious;
let frag: Fragment | null = null;
if (fragPrevious) {
if (levelDetails.hasProgramDateTime) {
Expand All @@ -819,7 +819,7 @@ export default class BaseStreamController
frag = findFragmentByPDT(
fragments,
fragPrevious.endProgramDateTime,
config.maxFragLookUpTolerance
this.config.maxFragLookUpTolerance
);
}
if (!frag) {
Expand Down Expand Up @@ -853,14 +853,14 @@ export default class BaseStreamController
}
} else {
// Find a new start fragment when fragPrevious is null
const liveStart =
this.hls.liveSyncPosition ||
levelDetails.edge - levelDetails.totalduration / 2;
frag = this.getFragmentAtPosition(
liveStart,
levelDetails.edge,
levelDetails
);
const liveStart = this.hls.liveSyncPosition;
if (liveStart !== null) {
frag = this.getFragmentAtPosition(
liveStart,
levelDetails.edge,
levelDetails
);
}
}

return frag;
Expand Down
9 changes: 6 additions & 3 deletions src/controller/fragment-finders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,14 @@ export function pdtWithinToleranceTest(
return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
}

export function findFragWithCC(fragments, CC): Fragment | null {
export function findFragWithCC(
fragments: Fragment[],
cc: number
): Fragment | null {
return BinarySearch.search(fragments, (candidate) => {
if (candidate.cc < CC) {
if (candidate.cc < cc) {
return 1;
} else if (candidate.cc > CC) {
} else if (candidate.cc > cc) {
return -1;
} else {
return 0;
Expand Down
14 changes: 6 additions & 8 deletions src/controller/latency-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,13 @@ export default class LatencyController implements ComponentAPI {
return null;
}
const edge = levelDetails.edge;
return Math.min(
Math.max(
edge - levelDetails.totalduration,
liveEdge - targetLatency - this.edgeStalled
),
const syncPosition = liveEdge - targetLatency - this.edgeStalled;
const min = edge - levelDetails.totalduration;
const max =
edge -
((this.config.lowLatencyMode && levelDetails.partTarget) ||
levelDetails.targetduration)
);
((this.config.lowLatencyMode && levelDetails.partTarget) ||
levelDetails.targetduration);
return Math.min(Math.max(min, syncPosition), max);
}

get edgeStalled(): number {
Expand Down
139 changes: 125 additions & 14 deletions tests/unit/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,6 @@ describe('StreamController', function () {
fragPrevious.cc = 0;

const levelDetails = new LevelDetails('');
levelDetails.startSN = mockFragments[0].sn;
levelDetails.endSN = mockFragments[mockFragments.length - 1].sn;
levelDetails.fragments = mockFragments;

const bufferEnd = fragPrevious.start + fragPrevious.duration;
const end =
Expand All @@ -117,6 +114,14 @@ describe('StreamController', function () {

beforeEach(function () {
streamController['fragPrevious'] = fragPrevious;
levelDetails.startSN = mockFragments[0].sn;
levelDetails.endSN = mockFragments[mockFragments.length - 1].sn;
levelDetails.fragments = mockFragments;
levelDetails.targetduration = mockFragments[0].duration;
levelDetails.totalduration = mockFragments.reduce(
(sum, frag) => sum + frag.duration,
0
);
});

it('PTS search choosing wrong fragment (3 instead of 2) after level loaded', function () {
Expand Down Expand Up @@ -149,21 +154,127 @@ describe('StreamController', function () {
expect(actual).to.equal(mockFragments[mockFragments.length - 1]);
});

describe('PDT Searching during a live stream', function () {
it('PDT search choosing fragment after level loaded', function () {
describe('getInitialLiveFragment', function () {
let fragPrevious;

beforeEach(function () {
// onLevelUpdated updates latencyController.levelDetails used to get live sync position
hls['latencyController']['levelDetails'] = levelDetails;

fragPrevious = new Fragment(PlaylistLevelType.MAIN, '');
// Fragment with PDT 1505502681523 in level 1 does not have the same sn as in level 2 where cc is 1
fragPrevious.cc = 0;
fragPrevious.programDateTime = 1505502681523;
fragPrevious.duration = 5.0;
fragPrevious.level = 1;
fragPrevious.start = 15.0;
fragPrevious.sn = 3;
streamController['fragPrevious'] = fragPrevious;

levelDetails.PTSKnown = false;
levelDetails.alignedSliding = false;
levelDetails.live = true;
});

describe('with program-date-time', function () {
it('does PDT search, choosing fragment after level loaded', function () {
const foundFragment = streamController['getInitialLiveFragment'](
levelDetails,
mockFragments
);
expect(foundFragment).to.equal(
mockFragments[4],
`Expected sn 4, found sn segment ${
foundFragment ? foundFragment.sn : -1
}`
);
});
});

const foundFragment = streamController['getInitialLiveFragment'](
levelDetails,
mockFragments
);
const resultSN = foundFragment ? foundFragment.sn : -1;
expect(foundFragment).to.equal(
mockFragments[2],
'Expected sn 2, found sn segment ' + resultSN
);
describe('without program-date-time', function () {
const fragmentsWithoutPdt = mockFragments.map((frag) => {
const newFragment = new Fragment(PlaylistLevelType.MAIN, '');
return Object.assign(newFragment, frag, {
programDateTime: null,
});
});

beforeEach(function () {
// For sn lookup, cc much match
fragPrevious.cc = 1;

levelDetails.PTSKnown = false;
levelDetails.alignedSliding = false;
levelDetails.live = true;
levelDetails.startSN = fragmentsWithoutPdt[0].sn;
levelDetails.endSN =
fragmentsWithoutPdt[fragmentsWithoutPdt.length - 1].sn;
levelDetails.fragments = fragmentsWithoutPdt;
});

it('finds the next fragment to load based on the last fragment buffered', function () {
fragPrevious.sn = 0;
let foundFragment = streamController['getInitialLiveFragment'](
levelDetails,
fragmentsWithoutPdt
);
expect(foundFragment).to.equal(
fragmentsWithoutPdt[1],
`Expected sn 1, found sn segment ${
foundFragment ? foundFragment.sn : -1
}`
);

fragPrevious.sn = 3;
foundFragment = streamController['getInitialLiveFragment'](
levelDetails,
fragmentsWithoutPdt
);
expect(foundFragment).to.equal(
fragmentsWithoutPdt[4],
`Expected sn 4, found sn segment ${
foundFragment ? foundFragment.sn : -1
}`
);
});

it('finds the first fragment to load when starting or re-syncing with a live stream', function () {
streamController['fragPrevious'] = null;

const foundFragment = streamController['getInitialLiveFragment'](
levelDetails,
fragmentsWithoutPdt
);
expect(foundFragment).to.equal(
fragmentsWithoutPdt[2],
`Expected sn 2, found sn segment ${
foundFragment ? foundFragment.sn : -1
}`
);
});

it('finds the fragment with the same cc when there is no sn match', function () {
fragPrevious.cc = 0;
const foundFragment = streamController['getInitialLiveFragment'](
levelDetails,
fragmentsWithoutPdt
);
expect(foundFragment).to.equal(
fragmentsWithoutPdt[0],
`Expected sn 0, found sn segment ${
foundFragment ? foundFragment.sn : -1
}`
);
});

it('returns null when there is no cc match with the previous segment', function () {
fragPrevious.cc = 2;
const foundFragment = streamController['getInitialLiveFragment'](
levelDetails,
fragmentsWithoutPdt
);
expect(foundFragment).to.equal(null);
});
});
});
});
Expand Down

0 comments on commit 33571bb

Please sign in to comment.