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

fix: better time to first frame for live playlists #1105

Merged
merged 9 commits into from
Apr 5, 2021

Conversation

brandonocasey
Copy link
Contributor

@brandonocasey brandonocasey commented Mar 25, 2021

Does the following:

  • Fixes some off by 1 errors
  • Loops backwards over segments, rather than forwards when looking for a sync-point in live videos
  • Actually uses live delay timing values as live delays, rather than doing it in a round-about fashion.

@brandonocasey brandonocasey force-pushed the fix/better-ttff branch 2 times, most recently from 7477bd8 to e0d34d4 Compare March 25, 2021 20:02
src/playlist.js Outdated Show resolved Hide resolved
@@ -293,13 +293,18 @@ export const playlistEnd = function(playlist, expired, useSafeLiveEnd, liveEdgeP

expired = expired || 0;

const endSequence = useSafeLiveEnd ? safeLiveIndex(playlist, liveEdgePadding) : playlist.segments.length;
Copy link
Contributor Author

@brandonocasey brandonocasey Mar 25, 2021

Choose a reason for hiding this comment

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

It doesn't really make sense to get a safe live index, based on how many seconds we want to start behind the playback head. Then get how many seconds that index starts at from behind the end of the playlist. We should instead get the timing information for the end of the playlist. Then subtract the live edge padding from that.

This was causing us to download 3/4 (but it could be any number really, especially for a fast connection.) segments before a frame of video would even be shown, because we were setting seekableEnd to an exact segment end point. We would then download a segment that should start -0.001s before the start of seekable end but when we append it the buffered region is actually +0.001s ahead of seekable end. Eventually playback-watcher would seek us into the buffered range.

This fix is better because:

  1. The spec stats that we should start 3 target durations behind live
  2. We will almost always start in the middle of a segment as targetDuration is almost never exact, preventing any rounding errors from stalling playback.

const getSegmentIndex = (i, playlist, currentTime = 0) => {
const segments = playlist.segments;

return (playlist.endList || currentTime === 0) ? i : segments.length - (i + 1);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Loop from the back of the playlist when looking for a live sync point, as we are much more likely to be closer to the end.

src/playlist.js Outdated Show resolved Hide resolved
src/playlist.js Show resolved Hide resolved
@@ -38,7 +44,8 @@ export const syncPointStrategies = [
currentTime = currentTime || 0;

for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
const segmentIndex = getSegmentIndex(i, playlist, currentTime);
Copy link
Contributor

Choose a reason for hiding this comment

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

Since ProgramDateTime is rare to see in VOD, the VOD strategy should be used more often for VOD, and, in theory, this code should not need to run very often for VOD (unless there's heavy rendition switching going on), we may be better off just always starting from the end, and keeping the code simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it's that much code and we share this bit between the two loops. I think that we will end up with more code if we change this to looping backwards.

@codecov
Copy link

codecov bot commented Mar 26, 2021

Codecov Report

Merging #1105 (ff70d0b) into main (1b990f1) will decrease coverage by 0.01%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1105      +/-   ##
==========================================
- Coverage   86.08%   86.06%   -0.02%     
==========================================
  Files          38       38              
  Lines        8931     8923       -8     
  Branches     2001     1998       -3     
==========================================
- Hits         7688     7680       -8     
  Misses       1243     1243              
Impacted Files Coverage Δ
src/playlist.js 93.47% <100.00%> (-0.57%) ⬇️
src/sync-controller.js 96.72% <100.00%> (+0.09%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1b990f1...ff70d0b. Read the comment docs.

@gkatsev
Copy link
Member

gkatsev commented Mar 29, 2021

I have to say I'm a bit leery of removing the safeLiveIndex, though, it's probably fine. Would like to hear back from @gesinger if he has more thoughts on the comment he brought up.

@brandonocasey
Copy link
Contributor Author

So the reason I removed safeLiveIndex is because we only used it to get the index of a segment based on a period of time that we want to be behind. Then we use that index to calculate the duration at which that segment would start, which will put seekableEnd at an estimated segment boundary. If that estimated segment boundary is wrong we will create a gap.
Example:

  • playlist is 3000s
  • targetDuration is 6
  • last segment durations from the manifest are -1 = 5.597, -2 = 5.5, -3 = 5.9, -4 = 5.5`.
  1. safeLiveDelay will be 17.9s behind live (targetDuration * 2, plus last segment duration)
  2. safeLiveIndex will return segment at index -4
  3. intervalDuration will return 2977.503
  4. this will be a nearly exact segment boundary for the start to segment at index -4

With this code just start at the specific distance away from the current end of the playlist that we want to be.
Example:

  • playlist is 3000s
  • targetDuration is 6
  • last segment durations from the manifest are -1 = 5.597, -2 = 5.5, -3 = 5.9, -4 = 5.5`.
  1. We get the duration of the entire playlist, 3000s
  2. We subtract our safeLiveDistance of 18s (targetDuration * 3) to get 2982s
  3. This will be most of the way through the segment at index -4 but not on a boundary.

assert.equal(playlistEnd, 148.5, 'playlist end at the last segment end');
}
);
QUnit.test('playlistEnd uses default live edge padding with useSafeLiveEnd true', function(assert) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The new tests are here

@brandonocasey
Copy link
Contributor Author

The test code should be reviewed with whitespace differences turned off as I moved things around so that running all the playlist tests isn't so difficult.


QUnit.module('Playlist estimateSegmentRequestTime');
QUnit.test('playlistEnd uses given live edge padding with useSafeLiveEnd true', function(assert) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this test is included twice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants