Browse Source
Implement seekable
Implement seekable
Override the Flash tech's seekable method to take into account live playlists.pull/6/head

10 changed files with 276 additions and 35 deletions
-
1Gruntfile.js
-
3example.html
-
115src/playlist.js
-
48src/videojs-hls.js
-
1test/karma.conf.js
-
1test/localkarma.conf.js
-
1test/playlist-loader_test.js
-
133test/playlist_test.js
-
2test/videojs-hls.html
-
6test/videojs-hls_test.js
@ -0,0 +1,115 @@ |
|||
/** |
|||
* Playlist related utilities. |
|||
*/ |
|||
(function(window, videojs) { |
|||
'use strict'; |
|||
|
|||
var DEFAULT_TARGET_DURATION = 10; |
|||
var duration, seekable, segmentsDuration; |
|||
|
|||
/** |
|||
* Calculate the media duration from the segments associated with a |
|||
* playlist. The duration of a subinterval of the available segments |
|||
* may be calculated by specifying a start and end index. The |
|||
* minimum recommended live buffer is automatically subtracted for |
|||
* the last segments of live playlists. |
|||
* @param playlist {object} a media playlist object |
|||
* @param startIndex {number} (optional) an inclusive lower |
|||
* boundary for the playlist. Defaults to 0. |
|||
* @param endIndex {number} (optional) an exclusive upper boundary |
|||
* for the playlist. Defaults to playlist length. |
|||
* @return {number} the duration between the start index and end |
|||
* index. |
|||
*/ |
|||
segmentsDuration = function(playlist, startIndex, endIndex) { |
|||
var targetDuration, i, segment, result = 0; |
|||
|
|||
startIndex = startIndex || 0; |
|||
endIndex = endIndex !== undefined ? endIndex : (playlist.segments || []).length; |
|||
targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION; |
|||
|
|||
for (i = endIndex - 1; i >= startIndex; i--) { |
|||
segment = playlist.segments[i]; |
|||
result += segment.preciseDuration || |
|||
segment.duration || |
|||
targetDuration; |
|||
} |
|||
|
|||
// live playlists should not expose three segment durations worth
|
|||
// of content from the end of the playlist
|
|||
// https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3
|
|||
if (!playlist.endList) { |
|||
result -= targetDuration * (3 - (playlist.segments.length - endIndex)); |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
/** |
|||
* Calculates the duration of a playlist. If a start and end index |
|||
* are specified, the duration will be for the subset of the media |
|||
* timeline between those two indices. The total duration for live |
|||
* playlists is always Infinity. |
|||
* @param playlist {object} a media playlist object |
|||
* @param startIndex {number} (optional) an inclusive lower |
|||
* boundary for the playlist. Defaults to 0. |
|||
* @param endIndex {number} (optional) an exclusive upper boundary |
|||
* for the playlist. Defaults to playlist length. |
|||
* @return {number} the duration between the start index and end |
|||
* index. |
|||
*/ |
|||
duration = function(playlist, startIndex, endIndex) { |
|||
if (!playlist) { |
|||
return 0; |
|||
} |
|||
|
|||
// if a slice of the total duration is not requested, use
|
|||
// playlist-level duration indicators when they're present
|
|||
if (startIndex === undefined && endIndex === undefined) { |
|||
// if present, use the duration specified in the playlist
|
|||
if (playlist.totalDuration) { |
|||
return playlist.totalDuration; |
|||
} |
|||
|
|||
// duration should be Infinity for live playlists
|
|||
if (!playlist.endList) { |
|||
return window.Infinity; |
|||
} |
|||
} |
|||
|
|||
// calculate the total duration based on the segment durations
|
|||
return segmentsDuration(playlist, |
|||
startIndex, |
|||
endIndex); |
|||
}; |
|||
|
|||
/** |
|||
* Calculates the interval of time that is currently seekable in a |
|||
* playlist. |
|||
* @param playlist {object} a media playlist object |
|||
* @return {TimeRanges} the periods of time that are valid targets |
|||
* for seeking |
|||
*/ |
|||
seekable = function(playlist) { |
|||
var startOffset, targetDuration; |
|||
// without segments, there are no seekable ranges
|
|||
if (!playlist.segments) { |
|||
return videojs.createTimeRange(); |
|||
} |
|||
// when the playlist is complete, the entire duration is seekable
|
|||
if (playlist.endList) { |
|||
return videojs.createTimeRange(0, duration(playlist)); |
|||
} |
|||
|
|||
targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION; |
|||
startOffset = targetDuration * (playlist.mediaSequence || 0); |
|||
return videojs.createTimeRange(startOffset, |
|||
startOffset + segmentsDuration(playlist)); |
|||
}; |
|||
|
|||
// exports
|
|||
videojs.Hls.Playlist = { |
|||
duration: duration, |
|||
seekable: seekable |
|||
}; |
|||
})(window, window.videojs); |
@ -0,0 +1,133 @@ |
|||
/* Tests for the playlist utilities */ |
|||
(function(window, videojs) { |
|||
'use strict'; |
|||
var Playlist = videojs.Hls.Playlist; |
|||
|
|||
module('Playlist Utilities'); |
|||
|
|||
test('total duration for live playlists is Infinity', function() { |
|||
var duration = Playlist.duration({ |
|||
segments: [{ |
|||
duration: 4, |
|||
uri: '0.ts' |
|||
}] |
|||
}); |
|||
|
|||
equal(duration, Infinity, 'duration is infinity'); |
|||
}); |
|||
|
|||
test('interval duration does not include upcoming live segments', function() { |
|||
var duration = Playlist.duration({ |
|||
segments: [{ |
|||
duration: 4, |
|||
uri: '0.ts' |
|||
}, { |
|||
duration: 10, |
|||
uri: '1.ts' |
|||
}, { |
|||
duration: 10, |
|||
uri: '2.ts' |
|||
}, { |
|||
duration: 10, |
|||
uri: '3.ts' |
|||
}] |
|||
}, 0, 3); |
|||
|
|||
equal(duration, 4, 'does not include upcoming live segments'); |
|||
}); |
|||
|
|||
test('calculates seekable time ranges from the available segments', function() { |
|||
var playlist = { |
|||
segments: [{ |
|||
duration: 10, |
|||
uri: '0.ts' |
|||
}, { |
|||
duration: 10, |
|||
uri: '1.ts' |
|||
}], |
|||
endList: true |
|||
}, seekable = Playlist.seekable(playlist); |
|||
|
|||
equal(seekable.length, 1, 'there are seekable ranges'); |
|||
equal(seekable.start(0), 0, 'starts at zero'); |
|||
equal(seekable.end(0), Playlist.duration(playlist), 'ends at the duration'); |
|||
}); |
|||
|
|||
test('master playlists have empty seekable ranges', function() { |
|||
var seekable = Playlist.seekable({ |
|||
playlists: [{ |
|||
uri: 'low.m3u8' |
|||
}, { |
|||
uri: 'high.m3u8' |
|||
}] |
|||
}); |
|||
equal(seekable.length, 0, 'no seekable ranges from a master playlist'); |
|||
}); |
|||
|
|||
test('seekable end is three target durations from the actual end of live playlists', function() { |
|||
var seekable = Playlist.seekable({ |
|||
segments: [{ |
|||
duration: 7, |
|||
uri: '0.ts' |
|||
}, { |
|||
duration: 10, |
|||
uri: '1.ts' |
|||
}, { |
|||
duration: 10, |
|||
uri: '2.ts' |
|||
}, { |
|||
duration: 10, |
|||
uri: '3.ts' |
|||
}] |
|||
}); |
|||
equal(seekable.length, 1, 'there are seekable ranges'); |
|||
equal(seekable.start(0), 0, 'starts at zero'); |
|||
equal(seekable.end(0), 7, 'ends three target durations from the last segment'); |
|||
}); |
|||
|
|||
test('adjusts seekable to the live playlist window', function() { |
|||
var seekable = Playlist.seekable({ |
|||
targetDuration: 10, |
|||
mediaSequence: 7, |
|||
segments: [{ |
|||
uri: '8.ts' |
|||
}, { |
|||
uri: '9.ts' |
|||
}, { |
|||
uri: '10.ts' |
|||
}, { |
|||
uri: '11.ts' |
|||
}] |
|||
}); |
|||
equal(seekable.length, 1, 'there are seekable ranges'); |
|||
equal(seekable.start(0), 10 * 7, 'starts at the earliest available segment'); |
|||
equal(seekable.end(0), 10 * 8, 'ends three target durations from the last available segment'); |
|||
}); |
|||
|
|||
test('seekable end accounts for non-standard target durations', function() { |
|||
var seekable = Playlist.seekable({ |
|||
targetDuration: 2, |
|||
segments: [{ |
|||
duration: 2, |
|||
uri: '0.ts' |
|||
}, { |
|||
duration: 2, |
|||
uri: '1.ts' |
|||
}, { |
|||
duration: 1, |
|||
uri: '2.ts' |
|||
}, { |
|||
duration: 2, |
|||
uri: '3.ts' |
|||
}, { |
|||
duration: 2, |
|||
uri: '4.ts' |
|||
}] |
|||
}); |
|||
equal(seekable.start(0), 0, 'starts at the earliest available segment'); |
|||
equal(seekable.end(0), |
|||
9 - (2 * 3), |
|||
'allows seeking no further than three target durations from the end'); |
|||
}); |
|||
|
|||
})(window, window.videojs); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue