Skip to content

Commit

Permalink
feat: Start at offset from EXT-X-START (#1389)
Browse files Browse the repository at this point in the history
Co-authored-by: Pat O'Neill <pgoneill@gmail.com>
Co-authored-by: Adam Waldron <awaldron@brightcove.com>
  • Loading branch information
3 people authored May 3, 2023
1 parent 2356c34 commit b3a508d
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 6 deletions.
1 change: 0 additions & 1 deletion docs/supported-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ not yet been implemented. VHS currently supports everything in the
* [EXT-X-SESSION-DATA]
* [EXT-X-SESSION-KEY]
* [EXT-X-INDEPENDENT-SEGMENTS]
* Use of [EXT-X-START] (value parsed but not used)
* Alternate video via [EXT-X-MEDIA] of type video
* ASSOC-LANGUAGE in [EXT-X-MEDIA]
* CHANNELS in [EXT-X-MEDIA]
Expand Down
23 changes: 18 additions & 5 deletions src/playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -989,20 +989,33 @@ export class PlaylistController extends videojs.EventTarget {
return false;
}

// when the video is a live stream
if (!media.endList) {
// when the video is a live stream and/or has a start time
if (!media.endList || media.start) {
const seekable = this.seekable();

if (!seekable.length) {
// without a seekable range, the player cannot seek to begin buffering at the live
// point
// without a seekable range, the player cannot seek to begin buffering at the
// live or start point
return false;
}

const seekableEnd = seekable.end(0);
let startPoint = seekableEnd;

if (media.start) {
const offset = media.start.timeOffset;

if (offset < 0) {
startPoint = Math.max(seekableEnd + offset, seekable.start(0));
} else {
startPoint = Math.min(seekableEnd, offset);
}
}

// trigger firstplay to inform the source handler to ignore the next seek event
this.trigger('firstplay');
// seek to the live point
this.tech_.setCurrentTime(seekable.end(0));
this.tech_.setCurrentTime(startPoint);
}

this.hasPlayed_ = true;
Expand Down
15 changes: 15 additions & 0 deletions test/manifests/startLive.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#EXTM3U
#EXT-X-START:TIME-OFFSET=0
#EXT-X-TARGETDURATION:10
#EXTINF:10,
media-00001.ts
#EXTINF:10,
media-00002.ts
#EXTINF:10,
media-00003.ts
#EXTINF:10,
media-00004.ts
#EXTINF:10,
media-00005.ts
#EXTINF:10,
media-00006.ts
12 changes: 12 additions & 0 deletions test/manifests/startNegative.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#EXTM3U
#EXT-X-START:TIME-OFFSET=-5.0
#EXT-X-TARGETDURATION:10
#EXTINF:10,
media-00001.ts
#EXTINF:10,
media-00002.ts
#EXTINF:10,
media-00003.ts
#EXTINF:10,
media-00004.ts
#EXT-X-ENDLIST
12 changes: 12 additions & 0 deletions test/manifests/startVod.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#EXTM3U
#EXT-X-START:TIME-OFFSET=10.3
#EXT-X-TARGETDURATION:10
#EXTINF:10,
media-00001.ts
#EXTINF:10,
media-00002.ts
#EXTINF:10,
media-00003.ts
#EXTINF:10,
media-00004.ts
#EXT-X-ENDLIST
66 changes: 66 additions & 0 deletions test/videojs-http-streaming.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,72 @@ QUnit.test('autoplay seeks to the live point after media source open', function(
assert.notEqual(currentTime, 0, 'seeked on autoplay');
});

QUnit.test('seeks to the start offset point', function(assert) {
let currentTime = 0;

this.player.autoplay(true);
this.player.on('seeking', () => {
currentTime = this.player.currentTime();
});
this.player.src({
src: 'startVod.m3u8',
type: 'application/vnd.apple.mpegurl'
});

this.clock.tick(1);

openMediaSource(this.player, this.clock);
this.player.tech_.trigger('play');
this.standardXHRResponse(this.requests.shift());
this.clock.tick(1);

assert.strictEqual(currentTime, 10.3, 'seeked to positive offset');
});

QUnit.test('seeks to non-negative offet for a live stream', function(assert) {
let currentTime = 0;

this.player.autoplay(true);
this.player.on('seeking', () => {
currentTime = this.player.currentTime();
});
this.player.src({
src: 'startLive.m3u8',
type: 'application/vnd.apple.mpegurl'
});

this.clock.tick(1);

openMediaSource(this.player, this.clock);
this.player.tech_.trigger('play');
this.standardXHRResponse(this.requests.shift());
this.clock.tick(1);

assert.strictEqual(currentTime, 0, 'seeked to offset on live stream');
});

QUnit.test('seeks to negative offset point', function(assert) {
let currentTime = 0;

this.player.autoplay(true);
this.player.on('seeking', () => {
currentTime = this.player.currentTime();
});
this.player.src({
src: 'startNegative.m3u8',
type: 'application/vnd.apple.mpegurl'
});

this.clock.tick(1);

openMediaSource(this.player, this.clock);
this.player.tech_.trigger('play');
this.standardXHRResponse(this.requests.shift());
this.clock.tick(1);

assert.strictEqual(currentTime, 35, 'seeked to negative offset');
});

QUnit.test(
'duration is set when the source opens after the playlist is loaded',
function(assert) {
Expand Down

0 comments on commit b3a508d

Please sign in to comment.