Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
brainfood
/
videojs-contrib-hls
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Graphs
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
dcce9f45
authored
2015-06-15 13:52:15 -0400
by
David LaPalomento
Browse Files
Options
Browse Files
Tag
Download
Plain Diff
Fix seeking in live streams. Closes #308.
2 parents
c6c973f6
6b05241f
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
168 additions
and
38 deletions
CHANGELOG.md
src/playlist-loader.js
src/videojs-hls.js
test/playlist-loader_test.js
test/videojs-hls_test.js
CHANGELOG.md
View file @
dcce9f4
...
...
@@ -3,6 +3,7 @@ CHANGELOG
## HEAD (Unreleased)
*
@dmlap fix seeking in live streams (
[
view
](
https://github.com/videojs/videojs-contrib-hls/pull/308
)
)
--------------------
...
...
src/playlist-loader.js
View file @
dcce9f4
...
...
@@ -362,5 +362,55 @@
this
.
media_
=
this
.
master
.
playlists
[
update
.
uri
];
};
/**
* Determine the index of the segment that contains a specified
* playback position in the current media playlist. Early versions
* of the HLS specification require segment durations to be rounded
* to the nearest integer which means it may not be possible to
* determine the correct segment for a playback position if that
* position is within .5 seconds of the segment duration. This
* function will always return the lower of the two possible indices
* in those cases.
*
* @param time {number} The number of seconds since the earliest
* possible position to determine the containing segment for
* @returns {number} The number of the media segment that contains
* that time position. If the specified playback position is outside
* the time range of the current set of media segments, the return
* value will be clamped to the index of the segment containing the
* closest playback position that is currently available.
*/
PlaylistLoader
.
prototype
.
getMediaIndexForTime_
=
function
(
time
)
{
var
i
;
if
(
!
this
.
media_
)
{
return
0
;
}
// when the requested position is earlier than the current set of
// segments, return the earliest segment index
time
-=
this
.
expiredPreDiscontinuity_
+
this
.
expiredPostDiscontinuity_
;
if
(
time
<
0
)
{
return
0
;
}
for
(
i
=
0
;
i
<
this
.
media_
.
segments
.
length
;
i
++
)
{
time
-=
Playlist
.
duration
(
this
.
media_
,
this
.
media_
.
mediaSequence
+
i
,
this
.
media_
.
mediaSequence
+
i
+
1
);
// HLS version 3 and lower round segment durations to the
// nearest decimal integer. When the correct media index is
// ambiguous, prefer the lower one.
if
(
time
<=
0
)
{
return
i
;
}
}
// the playback position is outside the range of available
// segments so return the last one
return
this
.
media_
.
segments
.
length
-
1
;
};
videojs
.
Hls
.
PlaylistLoader
=
PlaylistLoader
;
})(
window
,
window
.
videojs
);
...
...
src/videojs-hls.js
View file @
dcce9f4
...
...
@@ -99,6 +99,10 @@ videojs.Hls.prototype.src = function(src) {
this
.
playlists
.
dispose
();
}
// The index of the next segment to be downloaded in the current
// media playlist. When the current media playlist is live with
// expiring segments, it may be a different value from the media
// sequence number for a segment.
this
.
mediaIndex
=
0
;
this
.
playlists
=
new
videojs
.
Hls
.
PlaylistLoader
(
this
.
src_
,
settings
.
withCredentials
);
...
...
@@ -313,7 +317,6 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
* ended.
*/
videojs
.
Hls
.
prototype
.
play
=
function
()
{
var
media
;
if
(
this
.
ended
())
{
this
.
mediaIndex
=
0
;
}
...
...
@@ -323,9 +326,7 @@ videojs.Hls.prototype.play = function() {
if
(
this
.
duration
()
===
Infinity
&&
this
.
playlists
.
media
()
&&
!
this
.
player
().
hasClass
(
'vjs-has-started'
))
{
media
=
this
.
playlists
.
media
();
this
.
mediaIndex
=
videojs
.
Hls
.
getMediaIndexForLive_
(
media
);
this
.
setCurrentTime
(
videojs
.
Hls
.
Playlist
.
seekable
(
media
).
end
(
0
));
this
.
setCurrentTime
(
this
.
seekable
().
end
(
0
));
}
// delegate back to the Flash implementation
...
...
@@ -360,7 +361,7 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
this
.
lastSeekedTime_
=
currentTime
;
// determine the requested segment
this
.
mediaIndex
=
videojs
.
Hls
.
getMediaIndexByTime
(
this
.
playlists
.
media
(),
currentTime
);
this
.
mediaIndex
=
this
.
playlists
.
getMediaIndexForTime_
(
currentTime
);
// abort any segments still being decoded
this
.
sourceBuffer
.
abort
();
...
...
@@ -641,7 +642,8 @@ videojs.Hls.prototype.fillBuffer = function(offset) {
// being buffering so we don't preload data that will never be
// played
if
(
!
this
.
playlists
.
media
().
endList
&&
!
this
.
player
().
hasClass
(
'vjs-has-started'
))
{
!
this
.
player
().
hasClass
(
'vjs-has-started'
)
&&
offset
===
undefined
)
{
return
;
}
...
...
@@ -1103,33 +1105,14 @@ videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) {
};
/**
* Determine the media index in one playlist by a time in seconds. This
* function iterates through the segments of a playlist and creates TimeRange
* objects for each and then returns the most appropriate segment index by
* checking the time value versus each range.
* Deprecated.
*
* @param playlist {object} The playlist of the segments being searched.
* @param time {number} The time in seconds of what segment you want.
* @returns {number} The media index, or -1 if none appropriate.
* @deprecated use player.hls.playlists.getMediaIndexForTime_() instead
*/
videojs
.
Hls
.
getMediaIndexByTime
=
function
(
playlist
,
time
)
{
var
index
,
counter
,
timeRanges
,
currentSegmentRange
;
timeRanges
=
[];
for
(
index
=
0
;
index
<
playlist
.
segments
.
length
;
index
++
)
{
currentSegmentRange
=
{};
currentSegmentRange
.
start
=
(
index
===
0
)
?
0
:
timeRanges
[
index
-
1
].
end
;
currentSegmentRange
.
end
=
currentSegmentRange
.
start
+
playlist
.
segments
[
index
].
duration
;
timeRanges
.
push
(
currentSegmentRange
);
}
for
(
counter
=
0
;
counter
<
timeRanges
.
length
;
counter
++
)
{
if
(
time
>=
timeRanges
[
counter
].
start
&&
time
<
timeRanges
[
counter
].
end
)
{
return
counter
;
}
}
return
-
1
;
videojs
.
Hls
.
getMediaIndexByTime
=
function
()
{
videojs
.
log
.
warn
(
'getMediaIndexByTime is deprecated. '
+
'Use PlaylistLoader.getMediaIndexForTime_ instead.'
);
return
0
;
};
/**
...
...
test/playlist-loader_test.js
View file @
dcce9f4
...
...
@@ -700,6 +700,69 @@
strictEqual
(
mediaChanges
,
2
,
'ignored a no-op media change'
);
});
test
(
'can get media index by playback position for non-live videos'
,
function
()
{
var
loader
=
new
videojs
.
Hls
.
PlaylistLoader
(
'media.m3u8'
);
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:0\n'
+
'#EXTINF:4,\n'
+
'0.ts\n'
+
'#EXTINF:5,\n'
+
'1.ts\n'
+
'#EXTINF:6,\n'
+
'2.ts\n'
+
'#EXT-X-ENDLIST\n'
);
equal
(
loader
.
getMediaIndexForTime_
(
-
1
),
0
,
'the index is never less than zero'
);
equal
(
loader
.
getMediaIndexForTime_
(
0
),
0
,
'time zero is index zero'
);
equal
(
loader
.
getMediaIndexForTime_
(
3
),
0
,
'time three is index zero'
);
equal
(
loader
.
getMediaIndexForTime_
(
10
),
2
,
'time 10 is index 2'
);
equal
(
loader
.
getMediaIndexForTime_
(
22
),
2
,
'the index is never greater than the length'
);
});
test
(
'returns the lower index when calculating for a segment boundary'
,
function
()
{
var
loader
=
new
videojs
.
Hls
.
PlaylistLoader
(
'media.m3u8'
);
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:0\n'
+
'#EXTINF:4,\n'
+
'0.ts\n'
+
'#EXTINF:5,\n'
+
'1.ts\n'
+
'#EXT-X-ENDLIST\n'
);
equal
(
loader
.
getMediaIndexForTime_
(
4
),
0
,
'rounds down exact matches'
);
equal
(
loader
.
getMediaIndexForTime_
(
3.7
),
0
,
'rounds down'
);
// FIXME: the test below should pass for HLSv3
//equal(loader.getMediaIndexForTime_(4.2), 0, 'rounds down');
equal
(
loader
.
getMediaIndexForTime_
(
4.5
),
1
,
'rounds up at 0.5'
);
});
test
(
'accounts for expired time when calculating media index'
,
function
()
{
var
loader
=
new
videojs
.
Hls
.
PlaylistLoader
(
'media.m3u8'
);
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:1001\n'
+
'#EXTINF:4,\n'
+
'1001.ts\n'
+
'#EXTINF:5,\n'
+
'1002.ts\n'
);
loader
.
expiredPreDiscontinuity_
=
50
;
loader
.
expiredPostDiscontinuity_
=
100
;
equal
(
loader
.
getMediaIndexForTime_
(
0
),
0
,
'the lowest returned value is zero'
);
equal
(
loader
.
getMediaIndexForTime_
(
45
),
0
,
'expired content returns zero'
);
equal
(
loader
.
getMediaIndexForTime_
(
75
),
0
,
'expired content returns zero'
);
equal
(
loader
.
getMediaIndexForTime_
(
50
+
100
),
0
,
'calculates the earliest available position'
);
equal
(
loader
.
getMediaIndexForTime_
(
50
+
100
+
2
),
0
,
'calculates within the first segment'
);
equal
(
loader
.
getMediaIndexForTime_
(
50
+
100
+
2
),
0
,
'calculates within the first segment'
);
equal
(
loader
.
getMediaIndexForTime_
(
50
+
100
+
4.5
),
1
,
'calculates within the second segment'
);
equal
(
loader
.
getMediaIndexForTime_
(
50
+
100
+
6
),
1
,
'calculates within the second segment'
);
});
test
(
'does not misintrepret playlists missing newlines at the end'
,
function
()
{
var
loader
=
new
videojs
.
Hls
.
PlaylistLoader
(
'media.m3u8'
);
requests
.
shift
().
respond
(
200
,
null
,
...
...
test/videojs-hls_test.js
View file @
dcce9f4
...
...
@@ -1663,19 +1663,33 @@ test('updates the media index when a playlist reloads', function() {
test
(
'live playlist starts three target durations before live'
,
function
()
{
var
mediaPlaylist
;
player
.
src
({
src
:
'
http://example.com/manifest/liveStart30sBefor
e.m3u8'
,
src
:
'
liv
e.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
standardXHRResponse
(
requests
.
shift
());
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:101\n'
+
'#EXTINF:10,\n'
+
'0.ts\n'
+
'#EXTINF:10,\n'
+
'1.ts\n'
+
'#EXTINF:10,\n'
+
'2.ts\n'
+
'#EXTINF:10,\n'
+
'3.ts\n'
+
'#EXTINF:10,\n'
+
'4.ts\n'
);
equal
(
player
.
hls
.
mediaIndex
,
0
,
'waits for the first play to start buffering'
);
equal
(
requests
.
length
,
0
,
'no outstanding segment request'
);
player
.
play
();
mediaPlaylist
=
player
.
hls
.
playlists
.
media
();
equal
(
player
.
hls
.
mediaIndex
,
6
,
'mediaIndex is updated at play'
);
equal
(
player
.
currentTime
(),
videojs
.
Hls
.
Playlist
.
seekable
(
mediaPlaylist
).
end
(
0
));
equal
(
player
.
hls
.
mediaIndex
,
1
,
'mediaIndex is updated at play'
);
equal
(
player
.
currentTime
(),
player
.
seekable
().
end
(
0
));
equal
(
requests
.
length
,
1
,
'begins buffering'
);
});
test
(
'does not reset live currentTime if mediaIndex is one beyond the last available segment'
,
function
()
{
...
...
@@ -1728,6 +1742,24 @@ test('mediaIndex is zero before the first segment loads', function() {
strictEqual
(
player
.
hls
.
mediaIndex
,
0
,
'mediaIndex is zero'
);
});
test
(
'mediaIndex returns correctly at playlist boundaries'
,
function
()
{
player
.
src
({
src
:
'http://example.com/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
standardXHRResponse
(
requests
.
shift
());
// master
standardXHRResponse
(
requests
.
shift
());
// media
strictEqual
(
player
.
hls
.
mediaIndex
,
0
,
'mediaIndex is zero at first segment'
);
// seek to end
player
.
currentTime
(
40
);
strictEqual
(
player
.
hls
.
mediaIndex
,
3
,
'mediaIndex is 3 at last segment'
);
});
test
(
'reloads out-of-date live playlists when switching variants'
,
function
()
{
player
.
src
({
src
:
'http://example.com/master.m3u8'
,
...
...
@@ -1919,18 +1951,19 @@ test('continues playing after seek to discontinuity', function() {
'#EXTINF:10,0\n'
+
'2.ts\n'
+
'#EXT-X-ENDLIST\n'
);
standardXHRResponse
(
requests
.
pop
());
standardXHRResponse
(
requests
.
pop
());
// 1.ts
currentTime
=
1
;
bufferEnd
=
10
;
player
.
hls
.
checkBuffer_
();
standardXHRResponse
(
requests
.
pop
());
standardXHRResponse
(
requests
.
pop
());
// 2.ts
// seek to the discontinuity
player
.
currentTime
(
10
);
tags
.
push
({
pts
:
0
,
bytes
:
new
Uint8Array
(
1
)
});
standardXHRResponse
(
requests
.
pop
());
tags
.
push
({
pts
:
11
*
1000
,
bytes
:
new
Uint8Array
(
1
)
});
standardXHRResponse
(
requests
.
pop
());
// 1.ts, again
strictEqual
(
aborts
,
1
,
'aborted once for the seek'
);
// the source buffer empties. is 2.ts still in the segment buffer?
...
...
Please
register
or
sign in
to post a comment