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

feat: Add liveSync configuration to catch up on live streams #5304

Merged
merged 7 commits into from
Jun 22, 2023

Conversation

avelad
Copy link
Member

@avelad avelad commented Jun 14, 2023

No description provided.

@avelad avelad added type: enhancement New feature or request priority: P3 Useful but not urgent labels Jun 14, 2023
@avelad avelad added this to the v4.4 milestone Jun 14, 2023
@github-actions
Copy link
Contributor

Incremental code coverage: 28.57%

externs/shaka/player.js Outdated Show resolved Hide resolved
lib/player.js Outdated Show resolved Hide resolved
const buffered = this.video_.buffered;
if (buffered.length > 0) {
const bufferedEnd = buffered.end(buffered.length - 1);
offset = Math.max(liveSyncPlaybackRate, bufferedEnd - seekRange.end);
Copy link
Member

Choose a reason for hiding this comment

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

These are two different units fed into Math.max: a playback rate and a duration in seconds. What is the intent here?

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is that the timeupdate event is not fired every frame, instead it takes a bit of time, sometimes even one second. The idea here is to avoid reaching the live edge in less than a second. If the playbackrate is 3 for example, and we have 3 seconds left for the live point, it would take us a second to reach it, so we want to avoid this case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Another option is put the min in 0, but I'd prefer the current option

Copy link
Member

Choose a reason for hiding this comment

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

Your explanation makes sense, but I don't find the code clear enough on its own without this conversation. And I will definitely forget.

Here's a comment that I think might help, to replace the one above:

// In src= mode, the seek range isn't updated frequently enough, so we need to fudge
// the latency number with an offset.  The playback rate is used as an offset, since that
// is the amount we catch up 1 second of accelerated playback.

I still don't understand the other duration, though. Why do you subtract bufferedEnd - seekRange.end? That should be negative if we've fallen behind, right?

-------|---------------|-------------|--------------|------
seekRange.start   currentTime   bufferedEnd   seekRange.end

Copy link
Member Author

Choose a reason for hiding this comment

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

bufferedEnd can greater than seekRange.end when the current time is near to live edge.

I’ll add your comment tomorrow. Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

This Math.max involving the bufferedEnd - seekRange.end parameter still doesn't make sense to me.

Here's a concrete scenario. Let's say your latency goal is 3s. You want to be within 3s of the end of the seek range.

currentTime = 10
seekRange.end = 15
bufferedEnd = 18
liveSyncPlaybackRate = 1.1

latency = seekRange.end - currentTime = 5

offset = Math.max(liveSyncPlaybackRate, bufferedEnd - seekRange.end)
offset = Math.max(1.1, 18 - 15)
offset = 3

latency - offset = 2, which is less than our target of 3. So even though we're 5s behind the end of the seek range, we don't accelerate playback. And because we've buffered past the end of the seek range, we could accelerate playback very safely.

So it seems to me that in the cases where bufferedEnd > seekRange.end, the offset value has the opposite effect of what it should do.

Am I crazy?

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, but if we take into account Theodore's comment, the event should trigger at most every 250ms, so it would still be fine. (https://developer.mozilla.org/en-US/docs/Web/API/HTMMLediaElement/timeupdate_event)

Note: this is for valid, because the src= for live should only be used in Safari.

Copy link
Contributor

Choose a reason for hiding this comment

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

4Hz is the minimum frequency, not the maximum. The maximum is 66Hz, which would be one call every 15ms.

Copy link
Member Author

Choose a reason for hiding this comment

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

In the case of the maximum, we have no problem :)

@joeyparrish joeyparrish changed the title feat: Add liveSync configuration feat: Add liveSync configuration to catch up on live streams Jun 14, 2023
@avelad avelad requested review from joeyparrish and theodab June 14, 2023 15:37
@avelad
Copy link
Member Author

avelad commented Jun 15, 2023

@joeyparrish can you review it again? Thanks!

Copy link
Member

@joeyparrish joeyparrish left a comment

Choose a reason for hiding this comment

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

Looks okay otherwise

const buffered = this.video_.buffered;
if (buffered.length > 0) {
const bufferedEnd = buffered.end(buffered.length - 1);
offset = Math.max(liveSyncPlaybackRate, bufferedEnd - seekRange.end);
Copy link
Member

Choose a reason for hiding this comment

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

Your explanation makes sense, but I don't find the code clear enough on its own without this conversation. And I will definitely forget.

Here's a comment that I think might help, to replace the one above:

// In src= mode, the seek range isn't updated frequently enough, so we need to fudge
// the latency number with an offset.  The playback rate is used as an offset, since that
// is the amount we catch up 1 second of accelerated playback.

I still don't understand the other duration, though. Why do you subtract bufferedEnd - seekRange.end? That should be negative if we've fallen behind, right?

-------|---------------|-------------|--------------|------
seekRange.start   currentTime   bufferedEnd   seekRange.end

const buffered = this.video_.buffered;
if (buffered.length > 0) {
const bufferedEnd = buffered.end(buffered.length - 1);
offset = Math.max(liveSyncPlaybackRate, bufferedEnd - seekRange.end);
Copy link
Contributor

Choose a reason for hiding this comment

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

According to MDN, the lowest frequency you can expect for the timeupdate event is around 4 hz.
So taking an entire second's worth of fast-playback as an offset might be overkill, if the expectation is that this is being called at least 4 times a second. Even if you want to be safe and assume that system load is high, liveSyncPlaybackRate / 2 might still work.

Copy link
Member Author

Choose a reason for hiding this comment

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

In my tests this does not always work on SmartTV, I prefer to be more conservative in this case.

@avelad avelad requested review from joeyparrish and theodab June 17, 2023 16:18
const buffered = this.video_.buffered;
if (buffered.length > 0) {
const bufferedEnd = buffered.end(buffered.length - 1);
offset = Math.max(liveSyncPlaybackRate, bufferedEnd - seekRange.end);
Copy link
Member

Choose a reason for hiding this comment

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

This Math.max involving the bufferedEnd - seekRange.end parameter still doesn't make sense to me.

Here's a concrete scenario. Let's say your latency goal is 3s. You want to be within 3s of the end of the seek range.

currentTime = 10
seekRange.end = 15
bufferedEnd = 18
liveSyncPlaybackRate = 1.1

latency = seekRange.end - currentTime = 5

offset = Math.max(liveSyncPlaybackRate, bufferedEnd - seekRange.end)
offset = Math.max(1.1, 18 - 15)
offset = 3

latency - offset = 2, which is less than our target of 3. So even though we're 5s behind the end of the seek range, we don't accelerate playback. And because we've buffered past the end of the seek range, we could accelerate playback very safely.

So it seems to me that in the cases where bufferedEnd > seekRange.end, the offset value has the opposite effect of what it should do.

Am I crazy?

@avelad avelad requested a review from joeyparrish June 20, 2023 20:49
@avelad avelad dismissed joeyparrish’s stale review June 22, 2023 20:11

Reviewed by Theodore

@avelad avelad merged commit db44dc8 into shaka-project:main Jun 22, 2023
@avelad avelad deleted the live-sync branch June 23, 2023 06:47
@github-actions github-actions bot added the status: archived Archived and locked; will not be updated label Aug 21, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
priority: P3 Useful but not urgent status: archived Archived and locked; will not be updated type: enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants