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: llhls syncing fixes #1125

Merged
merged 36 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3d39612
feat: Use ll-hls query directives and support skipping segments
brandonocasey Feb 11, 2021
5e46a1e
Merge branch 'main' into feat/llhls-3
brandonocasey Apr 7, 2021
df957d4
Merge branch 'main' into feat/llhls-3
brandonocasey Apr 7, 2021
ff40332
unified manifest parse function
brandonocasey Apr 8, 2021
53ec980
tests for llhls and query directives in playlist-loader
brandonocasey Apr 8, 2021
addf841
Merge branch 'main' into feat/llhls-3
brandonocasey Apr 8, 2021
285a4ae
Merge branch 'main' into feat/llhls-3
brandonocasey Apr 26, 2021
a46f0a7
remove unused option
brandonocasey Apr 26, 2021
9d1fc06
add duration to preload segment
brandonocasey Apr 27, 2021
00a8e25
always add a part target duration when we have parts
brandonocasey Apr 28, 2021
1ff48b0
query directive fixes
brandonocasey Apr 28, 2021
3ff694a
small logging fix, bring map to skipped segments
brandonocasey Apr 28, 2021
d3fb477
pare down changes
brandonocasey Apr 28, 2021
2854967
cover all scenarios
brandonocasey Apr 29, 2021
a842b84
fix logging and merging issues
brandonocasey Apr 29, 2021
4f842e6
fix: use partIndex and segmentIndex for syncPoints/getMediaInfoForTime
brandonocasey Apr 30, 2021
0205771
fix tests
brandonocasey May 7, 2021
402de98
tests
brandonocasey May 7, 2021
2c5080c
tests
brandonocasey May 7, 2021
6899546
Update src/playlist.js
brandonocasey May 25, 2021
00fd02c
code review
brandonocasey May 25, 2021
842650c
add question mark
brandonocasey May 25, 2021
4c19976
Update src/playlist.js
brandonocasey May 26, 2021
e079124
Update src/playlist.js
brandonocasey May 26, 2021
112d029
Update src/sync-controller.js
brandonocasey May 26, 2021
d0679f6
Update src/sync-controller.js
brandonocasey May 26, 2021
b3141c1
Update test/playlist.test.js
brandonocasey May 26, 2021
99dd646
Update test/playlist.test.js
brandonocasey May 26, 2021
ac9de31
code review
brandonocasey May 26, 2021
6c32f9b
Merge branch 'main' into fix/llhls-fixes
brandonocasey May 26, 2021
941bec9
Merge remote-tracking branch 'origin/main' into fix/llhls-fixes
brandonocasey May 26, 2021
99ee6e6
revert main merge changes
brandonocasey May 26, 2021
680d740
add comment for segment.start timing info
brandonocasey May 26, 2021
65da7f2
Merge remote-tracking branch 'origin/main' into fix/llhls-fixes
brandonocasey May 26, 2021
944df45
Update src/sync-controller.js
brandonocasey May 26, 2021
18d06c5
Update src/sync-controller.js
brandonocasey May 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import videojs from 'video.js';
import window from 'global/window';
import { Parser as M3u8Parser } from 'm3u8-parser';
import { resolveUrl } from './resolve-url';
import { getLastParts } from './playlist.js';

const { log } = videojs;

Expand Down Expand Up @@ -92,6 +93,21 @@ export const parseManifest = ({
manifest.targetDuration = targetDuration;
}

const parts = getLastParts(manifest);

if (parts.length && !manifest.partTargetDuration) {
let partTargetDuration = (manifest.targetDuration / parts.length);

if (manifest.segments && manifest.segments.length) {
partTargetDuration = parts.reduce((acc, p) => Math.max(acc, p.duration), 0);
}

if (onwarn) {
onwarn(`manifest has no partTargetDuration defaulting to ${partTargetDuration}`);
}
manifest.partTargetDuration = partTargetDuration;
}

return manifest;
};

Expand Down
180 changes: 148 additions & 32 deletions src/playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
masterForMedia,
setupMediaPlaylist
} from './manifest';
import {getKnownPartCount} from './playlist.js';

const { mergeOptions, EventTarget } = videojs;

Expand All @@ -34,6 +35,12 @@ export const updateSegment = (a, b) => {

const result = mergeOptions(a, b);

// if only the old segment has preload hints
// and the new one does not, remove preload hints.
if (a.preloadHints && !b.preloadHints) {
delete result.preloadHints;
}

// if only the old segment has parts
// then the parts are no longer valid
if (a.parts && !b.parts) {
Expand All @@ -49,6 +56,18 @@ export const updateSegment = (a, b) => {
}
}

// set skipped to false for segments that have
// have had information merged from the old segment.
if (!a.skipped && b.skipped) {
result.skipped = false;
}

// set preload to false for segments that have
// had information added in the new segment.
if (a.preload && !b.preload) {
result.preload = false;
}

return result;
};

Expand All @@ -69,15 +88,30 @@ export const updateSegment = (a, b) => {
*/
export const updateSegments = (original, update, offset) => {
const oldSegments = original.slice();
const result = update.slice();
const newSegments = update.slice();

offset = offset || 0;
const length = Math.min(original.length, update.length + offset);
const result = [];

let currentMap;

for (let newIndex = 0; newIndex < newSegments.length; newIndex++) {
const oldSegment = oldSegments[newIndex + offset];
const newSegment = newSegments[newIndex];

for (let i = offset; i < length; i++) {
const newIndex = i - offset;
if (oldSegment) {
currentMap = oldSegment.map || currentMap;

result[newIndex] = updateSegment(oldSegments[i], result[newIndex]);
result.push(updateSegment(oldSegment, newSegment));
} else {
// carry over map to new segment if it is missing
if (currentMap && !newSegment.map) {
newSegment.map = currentMap;
}

result.push(newSegment);

}
}
return result;
};
Expand Down Expand Up @@ -115,12 +149,27 @@ export const resolveSegmentUris = (segment, baseUri) => {

const getAllSegments = function(media) {
const segments = media.segments || [];
const preloadSegment = media.preloadSegment;

// a preloadSegment with only preloadHints is not currently
// a usable segment, only include a preloadSegment that has
// parts.
if (media.preloadSegment && media.preloadSegment.parts) {
segments.push(media.preloadSegment);
if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) {
// if preloadHints has a MAP that means that the
// init segment is going to change. We cannot use any of the parts
// from this preload segment.
if (preloadSegment.preloadHints) {
for (let i = 0; i < preloadSegment.preloadHints.length; i++) {
if (preloadSegment.preloadHints[i].type === 'MAP') {
return segments;
}
}
}
// set the duration for our preload segment to target duration.
preloadSegment.duration = media.targetDuration;
preloadSegment.preload = true;

segments.push(preloadSegment);
}

return segments;
Expand All @@ -146,28 +195,41 @@ export const isPlaylistUnchanged = (a, b) => a === b ||
* master playlist with the updated media playlist merged in, or
* null if the merge produced no change.
*/
export const updateMaster = (master, media, unchangedCheck = isPlaylistUnchanged) => {
export const updateMaster = (master, newMedia, unchangedCheck = isPlaylistUnchanged) => {
const result = mergeOptions(master, {});
const playlist = result.playlists[media.id];
const oldMedia = result.playlists[newMedia.id];

if (!playlist) {
if (!oldMedia) {
return null;
}

if (unchangedCheck(playlist, media)) {
if (unchangedCheck(oldMedia, newMedia)) {
return null;
}

const mergedPlaylist = mergeOptions(playlist, media);
newMedia.segments = getAllSegments(newMedia);

media.segments = getAllSegments(media);
const mergedPlaylist = mergeOptions(oldMedia, newMedia);

// always use the new medias preload segment.
if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
delete mergedPlaylist.preloadSegment;
}

// if the update could overlap existing segment information, merge the two segment lists
if (playlist.segments) {
if (oldMedia.segments) {
if (newMedia.skip) {
newMedia.segments = newMedia.segments || [];
// add back in objects for skipped segments, so that we merge
// old properties into this new segment
for (let i = 0; i < newMedia.skip.skippedSegments; i++) {
newMedia.segments.unshift({skipped: true});
}
}
mergedPlaylist.segments = updateSegments(
playlist.segments,
media.segments,
media.mediaSequence - playlist.mediaSequence
oldMedia.segments,
newMedia.segments,
newMedia.mediaSequence - oldMedia.mediaSequence
);
}

Expand All @@ -180,13 +242,13 @@ export const updateMaster = (master, media, unchangedCheck = isPlaylistUnchanged
// that is referenced by index, and one by URI. The index reference may no longer be
// necessary.
for (let i = 0; i < result.playlists.length; i++) {
if (result.playlists[i].id === media.id) {
if (result.playlists[i].id === newMedia.id) {
result.playlists[i] = mergedPlaylist;
}
}
result.playlists[media.id] = mergedPlaylist;
result.playlists[newMedia.id] = mergedPlaylist;
// URI reference added for backwards compatibility
result.playlists[media.uri] = mergedPlaylist;
result.playlists[newMedia.uri] = mergedPlaylist;

return result;
};
Expand Down Expand Up @@ -255,11 +317,60 @@ export default class PlaylistLoader extends EventTarget {
// only refresh the media playlist if no other activity is going on
return;
}
const media = this.media();

let uri = resolveUrl(this.master.uri, media.uri);

if (this.experimentalLLHLS) {
const query = [];

if (media.serverControl && media.serverControl.canBlockReload) {
const {preloadSegment} = media;
// next msn is a zero based value, length is not.
let nextMSN = media.mediaSequence + media.segments.length;

if (preloadSegment) {
const parts = preloadSegment.parts || [];
// _HLS_part is a zero based index
const nextPart = getKnownPartCount(media) - 1;

// if nextPart is > -1 and not equal to just the
// length of parts, then we know we had part preload hints
// and we need to add the _HLS_part= query
if (nextPart > -1 && nextPart !== (parts.length - 1)) {
// add existing parts to our preload hints
query.push(`_HLS_part=${nextPart}`);
}

// if we are requesting a nextPart or preload segment was added
// to our segment list. We are requesting a part of the preload segment
// or the full preload segment. Either way we need to go down by 1
// in nextMSN
if (nextPart > -1 || parts.length) {
nextMSN--;
}
}

// add _HLS_msn= infront of any _HLS_part query
query.unshift(`_HLS_msn=${nextMSN}`);
}

if (media.serverControl && media.serverControl.canSkipUntil) {
// add _HLS_skip= infront of all other queries.
query.unshift('_HLS_skip=' + (media.serverControl.canSkipDateranges ? 'v2' : 'YES'));
}

query.forEach(function(str, i) {
const symbol = i === 0 ? '?' : '&';

uri += `${symbol}${str}`;
});

}
this.state = 'HAVE_CURRENT_METADATA';

this.request = this.vhs_.xhr({
uri: resolveUrl(this.master.uri, this.media().uri),
uri,
withCredentials: this.withCredentials
}, (error, req) => {
// disposed
Expand Down Expand Up @@ -304,6 +415,17 @@ export default class PlaylistLoader extends EventTarget {
this.trigger('error');
}

parseManifest_({url, manifestString}) {
return parseManifest({
onwarn: ({message}) => this.logger_(`m3u8-parser warn for ${url}: ${message}`),
oninfo: ({message}) => this.logger_(`m3u8-parser info for ${url}: ${message}`),
manifestString,
customTagParsers: this.customTagParsers,
customTagMappers: this.customTagMappers,
experimentalLLHLS: this.experimentalLLHLS
});
}

/**
* Update the playlist loader's state in response to a new or updated playlist.
*
Expand All @@ -321,13 +443,9 @@ export default class PlaylistLoader extends EventTarget {
this.request = null;
this.state = 'HAVE_METADATA';

const playlist = playlistObject || parseManifest({
onwarn: ({message}) => this.logger_(`m3u8-parser warn for ${id}: ${message}`),
oninfo: ({message}) => this.logger_(`m3u8-parser info for ${id}: ${message}`),
manifestString: playlistString,
customTagParsers: this.customTagParsers,
customTagMappers: this.customTagMappers,
experimentalLLHLS: this.experimentalLLHLS
const playlist = playlistObject || this.parseManifest_({
url,
manifestString: playlistString
});

playlist.lastRequest = Date.now();
Expand Down Expand Up @@ -632,11 +750,9 @@ export default class PlaylistLoader extends EventTarget {

this.src = resolveManifestRedirect(this.handleManifestRedirects, this.src, req);

const manifest = parseManifest({
const manifest = this.parseManifest_({
manifestString: req.responseText,
customTagParsers: this.customTagParsers,
customTagMappers: this.customTagMappers,
experimentalLLHLS: this.experimentalLLHLS
url: this.src
});

this.setupInitialPlaylist(manifest);
Expand Down
Loading