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: use serverControl and preloadSegment llhls features behind a flag #1078

Merged
merged 30 commits into from
Apr 5, 2021

Conversation

brandonocasey
Copy link
Contributor

@brandonocasey brandonocasey commented Feb 22, 2021

Description

llhls features are already behind a flag, from the previous pull request #1055.

This implements usage of:

  • preloadSegment when it has parts. Think of this as the segment that is about to appear in the manifest, but we don't have a uri for yet.
  • severControl but only PART-HOLD-BACK and HOLD-BACK which specify how far behind a live playlist we should start
  • partTargetDuration in any way that we current use targetDuration, such as how much of a delay we should have on a playlist refresh


if (this.masterPlaylistLoader_.master.hasOwnProperty('suggestedPresentationDelay')) {
delay = this.masterPlaylistLoader_.master.suggestedPresentationDelay;
} else if (media.serverControl && media.serverControl['PART-HOLD-BACK'] && media.partTargetDuration) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ll-hls has it's own version of when a playlist should be started called HOLD-BACK and PART-HOLD-BACK

} else if (media.partTargetDuration) {
delay = media.partTargetDuration * 3;
} else if (media.targetDuration) {
delay = media.targetDuration * 3;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are the default values for HOLD-BACK as per the spec, so even if a playlist doesn't have HOLD-BACK we can still set the default HOLD-BACK

// if the playlist is unchanged since the last reload or last segment duration
// cannot be determined, try again after half the target duration
delay = (media.targetDuration || 10) * 500;
const lastPart = lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts - 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.

use the last part duration if it exists or the last segment duration


// if the playlist is unchanged since the last reload or last segment duration
// cannot be determined, try again after half the target duration
return (media.partTargetDuration || media.targetDuration || 10) * 500;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

use partTargetDuration or targetDuration

@@ -304,7 +313,7 @@ export default class PlaylistLoader extends EventTarget {
// merge this playlist into the master
const update = updateMaster(this.master, playlist);

this.targetDuration = playlist.targetDuration;
this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We use a playlists targetDuration in a few places outside of these files. In the case where we have partTargetDuration that is always what we will use as out "true" targetDuration, I understand that this is ambiguous though and I think we might want to rename this.targetDuration to something else, but that might be a breaking change. Should we deprecate this name?

src/playlist.js Outdated
let segment;
const numSegments = playlist.segments.length;

const partSegments = playlist.segments.reduce((acc, segment, si) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need this function to know about parts now. So we break put all of our segments and or parts into one list.

timeline
} = segmentInfo;

const name = segmentInfo.segment.uri ? 'segment' : 'pre-segment';

return [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Better logging in general, but especially needed now that we have parts.

const mediaSourceInfo = Playlist.getMediaInfoForTime(
playlist,
currentTime,
this.fetchAtBuffer_ ? lastBufferedEnd : currentTime,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was silly to have 10 lines duplicated for a single argument change.

nextPartIndex = mediaSourceInfo.partIndex;
}

if (typeof nextPartIndex !== 'number' && playlist.segments[nextMediaIndex] && playlist.segments[nextMediaIndex].parts) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

if we don't have a nextPartIndex, check if we should.

@codecov
Copy link

codecov bot commented Mar 12, 2021

Codecov Report

Merging #1078 (01a6763) into main (1e94680) will increase coverage by 0.06%.
The diff coverage is 94.66%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1078      +/-   ##
==========================================
+ Coverage   86.06%   86.13%   +0.06%     
==========================================
  Files          38       38              
  Lines        8923     8967      +44     
  Branches     1998     2020      +22     
==========================================
+ Hits         7680     7724      +44     
  Misses       1243     1243              
Impacted Files Coverage Δ
src/playlist.js 93.20% <90.62%> (-0.27%) ⬇️
src/playlist-loader.js 91.40% <94.11%> (+0.24%) ⬆️
src/manifest.js 97.67% <100.00%> (+0.23%) ⬆️
src/master-playlist-controller.js 94.13% <100.00%> (+0.27%) ⬆️
src/segment-loader.js 95.32% <100.00%> (+0.02%) ⬆️

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 1e94680...01a6763. Read the comment docs.

src/playlist.js Outdated Show resolved Hide resolved
src/playlist-loader.js Outdated Show resolved Hide resolved
src/playlist-loader.js Outdated Show resolved Hide resolved
src/playlist-loader.js Outdated Show resolved Hide resolved
Comment on lines +57 to +59
// dash suggestedPresentationDelay trumps everything
if (master && master.suggestedPresentationDelay) {
return master.suggestedPresentationDelay;
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be worth pulling this up to short circuit the rest of the function.

@@ -1352,41 +1360,36 @@ export default class SegmentLoader extends videojs.EventTarget {
} else if (currentMediaIndex !== null) {
// Under normal playback conditions fetching is a simple walk forward
const segment = playlist.segments[currentMediaIndex];
const partIndex = typeof currentPartIndex === 'number' ? currentPartIndex : -1;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible for it to be a valid case where the currentMediaIndex is the segment we're supposed to be fetching will the partIndex is 0? Are the hinted segments available here to see?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

currentPartIndex will come in as null, in which case we need to check if the current media index has parts at index 0. it could also come in as 0 and in that case we know we appended part 0 and need to look for part 1, or increment media index if we don't have it.

Comment on lines 1412 to 1413
startOfSegment,
isSyncRequest
Copy link
Contributor

Choose a reason for hiding this comment

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

These can actually be valuable to contain, in particular startOfSegment, which has been a source of various bugs we've encountered.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are all on segmentInfo, which is my reasoning behind removing them. But we could keep them as they keep things a bit more relevant to the checkBuffer log.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think these get logged except for here. Maybe worth adding to segmentInfoString?

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 have the next pull request in the series (where I refactor our segment selection logic) that adds selection to segmentInfoString. I think we should wait until that pull request. I will revert this logging change here for now.

test/playlist.test.js Outdated Show resolved Hide resolved
test/playlist.test.js Outdated Show resolved Hide resolved
2,
'uses last three part durations'
);

Copy link
Contributor

Choose a reason for hiding this comment

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

May want another test here with fewer than 3 parts available.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in a real scenario the last 3 segments will always have >= 2 parts. as having only one part is the same as not using parts at all. I will update the test for that.

src/playlist.js Outdated Show resolved Hide resolved
test/playlist.test.js Outdated Show resolved Hide resolved
test/playlist.test.js Outdated Show resolved Hide resolved
src/playlist.js Outdated
Comment on lines 84 to 89
// we shouldn't ever end up using lastThreeDurations as targetDuration
// is usually required, but if we somehow get here, with a missing
// targetDuration we should handle it
} else if (lastThreeDurations) {
return lastThreeDurations;
}
Copy link
Contributor

@gesinger gesinger Mar 19, 2021

Choose a reason for hiding this comment

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

Since we shouldn't end up here, it's probably worth pulling all of that logic down into an else here and return either 0 or whatever we calculate as the last three. Might also be worth warning (if we don't warn elsewhere for missing target duration), or logging anyway to help us determine what's happening in the event of a missing target duration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We use lastThreeDurations in the case where we have parts, but no partTargetDuration so that we get a more accurate hold back for ll-hls. We warn about target duration via the manifest parser warnings that now go to the debug log.

Base automatically changed from poc-ll-hls to main March 19, 2021 16:01
src/playlist.js Outdated
Comment on lines 66 to 68
// by default we use the last three durations of segments if
// part target duration or target duration isn't found.
// see: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.3.8
Copy link
Contributor

Choose a reason for hiding this comment

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

I may be missing something, but I don't think that section of the spec, if referring to HOLD-BACK, matches the comment here, since that is saying to use part target duration or target duration when the parameter is not available, and doesn't refer to the use of segment durations. Do we just have this in case the required target durations are missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes we use three segment durations in case target durations are not available for whatever reason.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if it's worth it to add the logic for this here if those are required attributes. It adds logic to the code that we shouldn't really encounter, and if we were to, then it's something that should be resolved by the creator of the stream, since target duration is always required (https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.3.1) and part target duration is required when parts are used (https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.3.7).

I think if we're trying to account for missing target durations we should do that at a higher level, when we first parse the manifest. It would help to simplify the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok I will remove the last three durations logic.

Copy link
Member

Choose a reason for hiding this comment

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

What about just default to 10s target duration in case somehow it isn't set or whatever? While I agree that bad streams are bad, we should still try to play as much as possible and having a default, if bad value, is worth it. Plus, it should make things simpler than trying to figure out segment duration and using that for target duation.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think having a sort of default may be appropriate (along with a warning). We should probably add it at the point where we first parse the stream (i.e., in the loaders).

Copy link
Member

@gkatsev gkatsev left a comment

Choose a reason for hiding this comment

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

While I don't see any major issues, I think there are a couple of small changes that should be made in playlist.js based on Garrett's comments. Would make that function slightly simpler.

src/playlist.js Outdated Show resolved Hide resolved
src/playlist.js Outdated
Comment on lines 66 to 68
// by default we use the last three durations of segments if
// part target duration or target duration isn't found.
// see: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.3.8
Copy link
Member

Choose a reason for hiding this comment

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

What about just default to 10s target duration in case somehow it isn't set or whatever? While I agree that bad streams are bad, we should still try to play as much as possible and having a default, if bad value, is worth it. Plus, it should make things simpler than trying to figure out segment duration and using that for target duation.

@gkatsev
Copy link
Member

gkatsev commented Mar 29, 2021

Also, this PR is now out of date with main.

src/playlist.js Outdated Show resolved Hide resolved
src/playlist.js Outdated
Comment on lines 66 to 68
// by default we use the last three durations of segments if
// part target duration or target duration isn't found.
// see: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.3.8
Copy link
Contributor

Choose a reason for hiding this comment

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

I think having a sort of default may be appropriate (along with a warning). We should probably add it at the point where we first parse the stream (i.e., in the loaders).

src/manifest.js Outdated
const lastDuration = lastSegment && Math.round(lastSegment) || 10;

if (onwarn) {
onwarn(`manifest has no targetDuration defaulting to ${lastDuration}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we may be better off using the largest segment duration found (and 10 if no segments included), since the TARGETDURATION is supposed to be the highest duration that will be encountered in a stream (and we will run into issues if the target duration is shorter than some segment durations).

Would also be worth having a test for it.

gesinger
gesinger previously approved these changes Apr 5, 2021
test/manifest.test.js Outdated Show resolved Hide resolved
Co-authored-by: Garrett Singer <[email protected]>
const lastPartDuration = lastSegment && lastSegment.duration || media.targetDuration;

return lastPartDuration + media.targetDuration * 2;
return media.targetDuration * 3;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was changed in main via #1105

@brandonocasey brandonocasey merged commit fa1b6b5 into main Apr 5, 2021
@brandonocasey brandonocasey deleted the feat/llhls-2 branch April 5, 2021 16:17
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