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

DASH: Be more optimistic on the buffer depth of DASH contents #1483

Merged
merged 1 commit into from
Aug 2, 2024

Conversation

peaBerberian
Copy link
Collaborator

@peaBerberian peaBerberian commented Jul 16, 2024

Overview

We were debugging some ad insertion usage, where an ad would be dynamically inserted at a live content's start on the initial Manifest request.

An application reported to us that while playing such content with the RxPlayer, we were starting a few seconds into the ad instead of right at the beginning of it. When looking at segment requests we saw that the first media segment was never requested: we always started with the second one instead.

Issue

After investigation, I found out that the issue was that the MPD@timeShiftBufferDepth attribute of the produced stream was on the first Manifest request exactly equal to the duration of the video segments generated until now.

Yet the RxPlayer always interpreted that it could only play segments whose ending timestamp is inferior or equal to the "live edge" (the live position basically, which monotically increases as time increases) and, more importantly here, whose starting timestamp is superior or equal to that same live edge minus the timeShiftBufferDepth.

All this is not shocking, and other players have a similar interpretation here, but I noticed that this was why we were not requesting the first segment on the problematic content: as some milliseconds passed between the time the Manifest is parsed and the time at which we began choosing a segment to request, our "live edge" was further in the future (by the same amount of milliseconds) and thus the minimum timestamp we can request will also be further in the future - and ahead of the starting timestamp of the first segment by the same amount of milliseconds (so too late for it).

How to fix this

So the fix seemed to be to either raise the MPD@timeShiftBufferDepth server-side or to raise the tolerance of what can still be requested RxPlayer-side.

I dived into the DASH specification (5th edition) - which is a little hard to interpret here - but I found the following passage:

Assume the parameter availabilityTimeOffset is determined as the sum of all values of
@availabilityTimeOffset on all levels that are processed in determining the URL for the corresponding segment. If the attribute @availabilityTimeOffset is not present, the value is of availabilityTimeOffset is 0. Then for services with MPD@type='dynamic', the Segment availability start time Tavail[i] for a Segment i in a specific Period is determined as MPD@availabilityStartTime + PeriodStart + MediaSegment[i].startTime + MediaSegment[i].duration - availabilityTimeOffset and the Segment availability end time is determined as MPD@availabilityStartTime + PeriodStart + MediaSegment[i].startTime + @timeshiftBufferDepth + 2*MediaSegment[i].duration

At the very end, it seems that we have to re-apply a segment's duration, on top of the timeShiftBufferDepth to know when it is not available anymore (they indicate "2 times the duration").

So I guess we may have been too pessimistic with how we previously considered whether a segment could be requested or not.

How other players treat this

Because the people behind the ad-insertion tool told me they succeeded to rely on the shaka-player, I looked at what they did on this issue.

It turns out that they have roughly the same interpretation that we had for the minimum requestable position (live edge - timeshiftBufferDepth) but their "live edge" was basically offset by - MPD@maxSegmentDuration, so in the end they were removing the MPD's maximum segment duration from the minimum requestable position relatively to us.

If I had to guess why they do that, it may be that they clonflate the "live edge" concept (the timestamp pointing to the current last theoretical media data basically) and what in the RxPlayer we call the "maximum safe position" (the last timestamp for which we're sure that a segment is currently available) where the RxPlayer make those two very different concepts and properties.

@peaBerberian peaBerberian added bug This is an RxPlayer issue (unexpected result when comparing to the API) DASH Relative to the DASH streaming protocol labels Jul 16, 2024
@peaBerberian peaBerberian added this to the 4.2.0 milestone Jul 16, 2024
@peaBerberian peaBerberian force-pushed the fix/better-timeshift-depth-estimate branch 4 times, most recently from fc6e35e to fb9176d Compare July 16, 2024 17:41
Overview
--------

We were debugging some ad insertion usage, where an ad would be
dynamically inserted at a live content's start on the initial Manifest
request.

An application reported to us that while playing such content with the
RxPlayer, we were starting a few seconds into the ad instead of right
at the beginning of it. When looking at segment requests we saw that the
first media segment was never requested: we always started with the second
one instead.

Issue
-----

After investigation, I found out that one of the issue, was that the
`MPD@timeShiftBufferDepth` attribute of the produced stream was on the
first Manifest request exactly equal to the duration of the video segments
generated until now.

Yet the RxPlayer always interpreted that it could only play segments
whose ending timestamp is inferior or equal to the "live edge" (the live
position basically, which monotically increases as time increases) and,
more importantly here, whose starting timestamp is superior or equal to
that same live edge minus the `timeShiftBufferDepth`.

All this is not shocking, and other players have a similar interpretation
here, but I noticed that this was why we were not requesting the first
segment on the problematic content: as some milliseconds passed between
the time the Manifest is parsed and the time at which we began choosing a
segment to request, our "live edge" as further in the future (by the same
amount of milliseconds) and thus the minimum timestamp we can request will
also be further in the future - and ahead of the first segment position
by the same amount of milliseconds.

How to fix this
---------------

So the fix seemed to be to either raise the `MPD@timeShiftBufferDepth`
server-side or to raise the tolerance of what can still be requested
RxPlayer-side.

I dived into the DASH specification (5th edition) - which is a little
hard to interpret here - but I found the following passage:

> Assume the parameter availabilityTimeOffset is determined as the sum of all values of
@availabilityTimeOffset on all levels that are processed in determining the URL for the
corresponding segment. If the attribute @availabilityTimeOffset is not present, the value is of
availabilityTimeOffset is 0. Then for services with MPD@type='dynamic', the Segment
availability start time Tavail[i] for a Segment i in a specific Period is determined as
MPD@availabilityStartTime + PeriodStart + MediaSegment[i].startTime +
MediaSegment[i].duration - availabilityTimeOffset and the Segment availability end time is
determined as MPD@availabilityStartTime + PeriodStart + MediaSegment[i].startTime +
@timeshiftBufferDepth + 2*MediaSegment[i].duration

At the very end, it seems that we have to re-apply a segment's duration,
on top of the `timeShiftBufferDepth` to know when it is not available
anymore (they indicate "2 times the duration").

So I guess we may have been too pessimistic with how we previously
considered whether a segment could be requested or not.

How other players treat this
----------------------------

Because the people behind the ad-insertion tool told me they succeeded
to rely on the shaka-player, I looked at what they did on this issue.

It turns out that they have roughly the same interpretation that we had
for the minimum requestable position (`live edge -
timeshiftBufferDepth`) but their "live edge" was basically offset by `-
MPD@maxSegmentDuration`, so in the end they were removing the MPD's
maximum segment duration from the minimum requestable position
relatively to us.

If I had to guess why they do that, it may be that they clonflate the
"live edge" concept (the timestamp pointing to the current last media
data basically) and what in the RxPlayer we call the "maximum safe
position" (the last timestamp for which we're sure that a segment is
currently available) where the RxPlayer make those two very different
concepts and properties.
@peaBerberian peaBerberian force-pushed the fix/better-timeshift-depth-estimate branch from fb9176d to c453d11 Compare July 16, 2024 17:53
@Florent-Bouisset
Copy link
Collaborator

I was just thinking about a case (that were already possible) and I'm not sure how the player handle this:
Imagine a segment is not available anymore at T=100s, the player calculate the minimal position to be at T=100s.
If current time is T=99s and the player start a request to fetch the segment, but due to bad network condition the request is take 4 seconds to reach the server and fails at T=103s because the segment is not available anymore.
If the player was configured to retry on segment fetch error, does the player will retry to download the same segment ?

The segment is not available anymore so we should fall into error again, or does the player run a logic to determine if the segment is still available when the request was retried ?

@peaBerberian
Copy link
Collaborator Author

peaBerberian commented Jul 25, 2024

The segment is not available anymore so we should fall into error again, or does the player run a logic to determine if the segment is still available when the request was retried ?

We already though of that exact scenario hehehe! :P

From the IRepresentationIndex type:

  /**
   * Returns `true` if a Segment returned by this index is still considered
   * available.
   * Returns `false` if it is not available anymore.
   * Returns `undefined` if we cannot know whether it is still available or not.
   * @param {Object} segment
   * @returns {Boolean|undefined}
   */
  isSegmentStillAvailable(segment: ISegment): boolean | undefined;

It is used like this in the RepresentationStream:

  downloadingQueue.addEventListener("requestRetry", (payload) => {
    callbacks.warning(payload.error);
    if (segmentsLoadingCanceller.signal.isCancelled()) {
      return; // If the previous callback led to loading operations being stopped, skip
    }
    const retriedSegment = payload.segment;
    const { index } = representation;
    if (index.isSegmentStillAvailable(retriedSegment) === false) {
      checkStatus();
    } else if (index.canBeOutOfSyncError(payload.error, retriedSegment)) {
      callbacks.manifestMightBeOufOfSync();
    }
  });

NOTES:

checkStatus() just restarts the RepresentationStream's segment checks instead of blindly retrying. An IRepresentationIndex is supposed to be auto-updating so when you call getSegments from it a new time, you should not get that since-removed segment anymore.

canBeOutOfSyncError is another mechanism which can be necessary because we've MPD updating optimizations which could theoretically be destructive - so that's like: "
OK we're not supposed to 404 for that segment but we did, is the MPD invalid?"

@Florent-Bouisset
Copy link
Collaborator

Thanks for your explanation!

@peaBerberian peaBerberian added the Priority: 1 (High) This issue or PR has a high priority. label Jul 26, 2024
@peaBerberian peaBerberian merged commit bd3d49d into dev Aug 2, 2024
7 checks passed
@peaBerberian peaBerberian deleted the fix/better-timeshift-depth-estimate branch November 9, 2024 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This is an RxPlayer issue (unexpected result when comparing to the API) DASH Relative to the DASH streaming protocol Priority: 1 (High) This issue or PR has a high priority.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants