-
Notifications
You must be signed in to change notification settings - Fork 428
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/more llhls fixes #1201
Fix/more llhls fixes #1201
Conversation
@@ -1431,7 +1431,7 @@ export class MasterPlaylistController extends videojs.EventTarget { | |||
onSyncInfoUpdate_() { | |||
let audioSeekable; | |||
|
|||
if (!this.masterPlaylistLoader_) { | |||
if (!this.masterPlaylistLoader_ || this.sourceUpdater_.hasCreatedSourceBuffers()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was causing us to:
- As soon as audio/video segment loader fires this we come in here
- Since only one of the loaders is actually ready at this point, we get seekable from just that one
- We seek to the end of this invalid seekable range
- We come back in here again and this time we get an intersection between the two and a valid seekable
- We seek to the valid seekable end via playback watcher
- This causes content to be removes and things to be downloaded/appended again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be worth adding a small comment above stating that. Something like: // if only one source buffer has been created, the seekable range will be invalid
src/playback-watcher.js
Outdated
@@ -350,7 +350,9 @@ export default class PlaybackWatcher { | |||
|
|||
// verify that at least two segment durations have been | |||
// appended before checking for a gap. | |||
const twoSegmentDurations = (this.media().targetDuration - Ranges.TIME_FUDGE_FACTOR) * 2; | |||
const targetDuration = this.media().partTargetDuration || this.media().targetDuration; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helps skip gaps faster in llhls, mostly this is helping with our bad segment choice at the start of a playlist. It is likely something we want to do anyway though, as we don't want to wait very long to skip gaps in llhls.
897da8c
to
ff350fa
Compare
src/playlist-loader.js
Outdated
@@ -257,7 +257,8 @@ const getAllSegments = function(media) { | |||
export const isPlaylistUnchanged = (a, b) => a === b || | |||
(a.segments && b.segments && a.segments.length === b.segments.length && | |||
a.endList === b.endList && | |||
a.mediaSequence === b.mediaSequence); | |||
a.mediaSequence === b.mediaSequence && | |||
(a.preloadSegment && b.preloadSegment && a.preloadSegment === b.preloadSegment)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this line we can trigger playlistunchaned
for llhls playlists that have changed.
src/playlist.js
Outdated
@@ -117,7 +117,19 @@ const backwardDuration = function(playlist, endSequence) { | |||
return { result: result + segment.end, precise: true }; | |||
} | |||
|
|||
result += segment.duration; | |||
if (segment.preload) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This prevents us from considering the full segment duration for preload segments that do not have all of their parts yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we have a helper like segmentDurationWithParts(segment)
which does this piece and below?
@@ -321,19 +333,19 @@ export const playlistEnd = function(playlist, expired, useSafeLiveEnd, liveEdgeP | |||
|
|||
expired = expired || 0; | |||
|
|||
let lastSegmentTime = intervalDuration( | |||
let lastSegmentEndTime = intervalDuration( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A more descriptive name as it isn't the start time, but the end time we are getting here.
@@ -1984,7 +1984,7 @@ export default class SegmentLoader extends videojs.EventTarget { | |||
this.setTimeMapping_(segmentInfo.timeline); | |||
|
|||
// for tracking overall stats | |||
this.updateMediaSecondsLoaded_(segmentInfo.segment); | |||
this.updateMediaSecondsLoaded_(segmentInfo.part || segmentInfo.segment); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stats fix
@@ -662,6 +665,8 @@ export default class PlaylistLoader extends EventTarget { | |||
this.trigger('mediachanging'); | |||
} | |||
|
|||
this.pendingMedia_ = playlist; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this to prevent switching to the same playlist twice at the start of playback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does it get used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
masterPlaylistLoader_.shouldSwitchToMedia_()
src/master-playlist-controller.js
Outdated
buffered.end(buffered.length - 1) - this.tech_.currentTime() : 0; | ||
|
||
const currentTime = this.tech_.currentTime(); | ||
const forwardBuffer = Ranges.timeAheadOf(buffered, currentTime); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use timeAheadOf
so that we consider gaps in our forward buffer and don't try to switch when we have enough buffer but have gaps.
src/master-playlist-controller.js
Outdated
|
||
return shouldSwitchToMedia({ | ||
isBuffered, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really want to switch at the start of playback for live streams before we have actually started. It almost doubles are time to first playback.
src/playback-watcher.js
Outdated
// appended before checking for a gap. | ||
const twoSegmentDurations = (this.media().targetDuration - Ranges.TIME_FUDGE_FACTOR) * 2; | ||
const durations = media.partTargetDuration ? media.partTargetDuration : media.targetDuration * 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I lowered this to one part target duration. parts are so tiny that its almost impossible not be be off by a few ms.
@@ -46,35 +46,33 @@ export const syncPointStrategies = [ | |||
const datetimeMapping = | |||
syncController.timelineToDatetimeMappings[segment.timeline]; | |||
|
|||
if (!datetimeMapping) { | |||
if (!datetimeMapping || !segment.dateTimeObject) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
short circuit earlier
Codecov Report
@@ Coverage Diff @@
## main #1201 +/- ##
==========================================
+ Coverage 86.33% 86.42% +0.08%
==========================================
Files 39 39
Lines 9701 9743 +42
Branches 2243 2263 +20
==========================================
+ Hits 8375 8420 +45
+ Misses 1326 1323 -3
Continue to review full report at Codecov.
|
src/playlist.js
Outdated
@@ -117,7 +117,19 @@ const backwardDuration = function(playlist, endSequence) { | |||
return { result: result + segment.end, precise: true }; | |||
} | |||
|
|||
result += segment.duration; | |||
if (segment.preload) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we have a helper like segmentDurationWithParts(segment)
which does this piece and below?
src/segment-loader.js
Outdated
// we must "resync" the segment loader when we switch renditions and | ||
// the segment loader is already synced to the previous rendition | ||
this.resyncLoader(); | ||
this.resetLoader(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come we're switching from resync to reset here? Also, the comment would need to be changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only extra thing that reset does that resync does not is fetchAtBuffer_ = false
. I think that we cannot use the end of the buffer for live stream segment selection reliably. This was especially true when changing renditions near the start of playback, as it would put us behind. Non-independent segments also don't change the buffer by themselves so if we switch while appending them we would be parts behind. We could change this so that it only resets during a live stream, otherwise it resyncs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be nice to do this only for live streams, though, if @gesinger thinks it's fine to always do it, I won't have any objections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed it to only reset during live streams.
Added tests for segmentInfoString and added segmentDurationWithParts + tests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM and improves start-up time a lot.
src/master-playlist-controller.js
Outdated
@@ -47,6 +47,7 @@ const sumLoaderStat = function(stat) { | |||
}; | |||
const shouldSwitchToMedia = function({ | |||
currentPlaylist, | |||
isBuffered, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took me a little to figure out what this reflected. It might make sense for shouldSwitchToMedia
to take in the overall buffer and do the calculations on its own internally.
@@ -78,6 +79,10 @@ const shouldSwitchToMedia = function({ | |||
// playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble | |||
// in those segments, a viewer will never experience a rendition upswitch. | |||
if (!currentPlaylist.endList) { | |||
if (!isBuffered && typeof currentPlaylist.partTargetDuration === 'number') { | |||
log(`not ${sharedLogLine} as current playlist is live llhls, but currentTime isn't in buffered.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add the comment you made in github below above here.
@@ -1431,7 +1431,7 @@ export class MasterPlaylistController extends videojs.EventTarget { | |||
onSyncInfoUpdate_() { | |||
let audioSeekable; | |||
|
|||
if (!this.masterPlaylistLoader_) { | |||
if (!this.masterPlaylistLoader_ || this.sourceUpdater_.hasCreatedSourceBuffers()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be worth adding a small comment above stating that. Something like: // if only one source buffer has been created, the seekable range will be invalid
src/playback-watcher.js
Outdated
|
||
// verify that at least two segment durations or one part duration have been | ||
// appended before checking for a gap. | ||
const durations = media.partTargetDuration ? media.partTargetDuration : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can rename this to something like minAppendedDuration
.
@@ -662,6 +665,8 @@ export default class PlaylistLoader extends EventTarget { | |||
this.trigger('mediachanging'); | |||
} | |||
|
|||
this.pendingMedia_ = playlist; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does it get used?
src/segment-loader.js
Outdated
// the segment loader is already synced to the previous rendition | ||
this.resyncLoader(); | ||
|
||
// for live streams reset, for vod resync |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be worth adding why we reset for live vs resync for VOD.
|
||
loader.playlist(newPlaylist); | ||
|
||
assert.true(resyncCalled, 'resync was called'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since reset
also calls resync
, we'll want to check reset
as well.
test/loader-common.js
Outdated
playlist.segments[4].parts[1].independent = true; | ||
const segmentInfo = loader.chooseNextRequest_(); | ||
|
||
assert.equal(segmentInfo.partIndex, 1, 'previous part'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this match the partIndex above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it does, but we also don't have anything buffered so it tries to choose sepmentIndex 4, partIndex 2 but that isn't independent so we choose the current partIndex. Let me see if I can find something better than this though.
mpc.masterPlaylistLoader_.media = () => ({id: 'foo', endList: false, partTargetDuration: 5}); | ||
const nextPlaylist = {id: 'bar', endList: false, partTargetDuration: 5}; | ||
|
||
assert.notOk(mpc.shouldSwitchToMedia_(nextPlaylist), 'should switch without currentPlaylist'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be "shouldn't switch when video not buffered"?
mpc.masterPlaylistLoader_.media = () => ({id: 'foo', endList: false, partTargetDuration: 5}); | ||
const nextPlaylist = {id: 'bar', endList: false, partTargetDuration: 5}; | ||
|
||
assert.ok(mpc.shouldSwitchToMedia_(nextPlaylist), 'should switch without currentPlaylist'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a currentPlaylists here I imagine?
Co-authored-by: Garrett Singer <[email protected]>
Co-authored-by: Garrett Singer <[email protected]>
Description
Based on #1195 for now, but we can pull it out if we want as only one of the fixes requires it.