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
09fa9fb5
authored
2016-02-10 18:41:39 -0500
by
Jon-Carlos Rivera
Browse Files
Options
Browse Files
Tag
Download
Plain Diff
Merge pull request #547 from videojs/duration-fixes
Duration and endOfStream fixes
2 parents
264b9516
aac49993
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
217 additions
and
19 deletions
src/videojs-hls.js
test/videojs-hls_test.js
src/videojs-hls.js
View file @
09fa9fb
...
...
@@ -297,6 +297,72 @@ videojs.Hls.findSoleUncommonTimeRangesEnd_ = function(original, update) {
return
result
[
0
];
};
/**
* Updates segment with information about its end-point in time and, optionally,
* the segment duration if we have enough information to determine a segment duration
* accurately.
* @param playlist {object} a media playlist object
* @param segmentIndex {number} the index of segment we last appended
* @param segmentEnd {number} the known of the segment referenced by segmentIndex
*/
videojs
.
HlsHandler
.
prototype
.
updateSegmentMetadata_
=
function
(
playlist
,
segmentIndex
,
segmentEnd
)
{
var
segment
,
previousSegment
;
if
(
!
playlist
)
{
return
;
}
segment
=
playlist
.
segments
[
segmentIndex
];
previousSegment
=
playlist
.
segments
[
segmentIndex
-
1
];
if
(
segmentEnd
&&
segment
)
{
segment
.
end
=
segmentEnd
;
// fix up segment durations based on segment end data
if
(
!
previousSegment
)
{
// first segment is always has a start time of 0 making its duration
// equal to the segment end
segment
.
duration
=
segment
.
end
;
}
else
if
(
previousSegment
.
end
)
{
segment
.
duration
=
segment
.
end
-
previousSegment
.
end
;
}
}
};
/**
* Determines if we should call endOfStream on the media source based on the state
* of the buffer or if appened segment was the final segment in the playlist.
* @param playlist {object} a media playlist object
* @param segmentIndex {number} the index of segment we last appended
* @param currentBuffered {object} the buffered region that currentTime resides in
* @return {boolean} whether the calling function should call endOfStream on the MediaSource
*/
videojs
.
HlsHandler
.
prototype
.
isEndOfStream_
=
function
(
playlist
,
segmentIndex
,
currentBuffered
)
{
var
segments
=
playlist
.
segments
,
appendedLastSegment
,
bufferedToEnd
;
if
(
!
playlist
)
{
return
false
;
}
// determine a few boolean values to help make the branch below easier
// to read
appendedLastSegment
=
(
segmentIndex
===
segments
.
length
-
1
);
bufferedToEnd
=
(
currentBuffered
.
length
&&
segments
[
segments
.
length
-
1
].
end
<=
currentBuffered
.
end
(
0
));
// if we've buffered to the end of the video, we need to call endOfStream
// so that MediaSources can trigger the `ended` event when it runs out of
// buffered data instead of waiting for me
return
playlist
.
endList
&&
this
.
mediaSource
.
readyState
===
'open'
&&
(
appendedLastSegment
||
bufferedToEnd
);
};
var
parseCodecs
=
function
(
codecs
)
{
var
result
=
{
codecCount
:
0
,
...
...
@@ -506,11 +572,18 @@ videojs.HlsHandler.prototype.setCurrentTime = function(currentTime) {
};
videojs
.
HlsHandler
.
prototype
.
duration
=
function
()
{
var
playlists
=
this
.
playlists
;
if
(
playlists
)
{
return
videojs
.
Hls
.
Playlist
.
duration
(
playlists
.
media
());
var
playlists
=
this
.
playlists
;
if
(
!
playlists
)
{
return
0
;
}
if
(
this
.
mediaSource
)
{
return
this
.
mediaSource
.
duration
;
}
return
0
;
return
videojs
.
Hls
.
Playlist
.
duration
(
playlists
.
media
());
};
videojs
.
HlsHandler
.
prototype
.
seekable
=
function
()
{
...
...
@@ -551,6 +624,7 @@ videojs.HlsHandler.prototype.seekable = function() {
videojs
.
HlsHandler
.
prototype
.
updateDuration
=
function
(
playlist
)
{
var
oldDuration
=
this
.
mediaSource
.
duration
,
newDuration
=
videojs
.
Hls
.
Playlist
.
duration
(
playlist
),
buffered
=
this
.
tech_
.
buffered
(),
setDuration
=
function
()
{
this
.
mediaSource
.
duration
=
newDuration
;
this
.
tech_
.
trigger
(
'durationchange'
);
...
...
@@ -558,6 +632,10 @@ videojs.HlsHandler.prototype.updateDuration = function(playlist) {
this
.
mediaSource
.
removeEventListener
(
'sourceopen'
,
setDuration
);
}.
bind
(
this
);
if
(
buffered
.
length
>
0
)
{
newDuration
=
Math
.
max
(
newDuration
,
buffered
.
end
(
buffered
.
length
-
1
));
}
// if the duration has changed, invalidate the cached value
if
(
oldDuration
!==
newDuration
)
{
// update the duration
...
...
@@ -1227,7 +1305,8 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
currentMediaIndex
,
currentBuffered
,
seekable
,
timelineUpdate
;
timelineUpdate
,
isEndOfStream
;
// stop here if the update errored or was aborted
if
(
!
segmentInfo
)
{
...
...
@@ -1243,14 +1322,18 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
this
.
pendingSegment_
=
null
;
playlist
=
this
.
playlists
.
media
()
;
playlist
=
segmentInfo
.
playlist
;
segments
=
playlist
.
segments
;
currentMediaIndex
=
segmentInfo
.
mediaIndex
+
(
segmentInfo
.
mediaSequence
-
playlist
.
mediaSequence
);
currentBuffered
=
this
.
findBufferedRange_
();
isEndOfStream
=
this
.
isEndOfStream_
(
playlist
,
currentMediaIndex
,
currentBuffered
);
// if we switched renditions don't try to add segment timeline
// information to the playlist
if
(
segmentInfo
.
playlist
.
uri
!==
this
.
playlists
.
media
().
uri
)
{
if
(
isEndOfStream
)
{
return
this
.
mediaSource
.
endOfStream
();
}
return
this
.
fillBuffer
();
}
...
...
@@ -1275,21 +1358,16 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
}
}
timelineUpdate
=
videojs
.
Hls
.
findSoleUncommonTimeRangesEnd_
(
segmentInfo
.
buffered
,
this
.
tech_
.
buffered
());
if
(
timelineUpdate
&&
segment
)
{
segment
.
end
=
timelineUpdate
;
}
// Update segment meta-data (duration and end-point) based on timeline
this
.
updateSegmentMetadata_
(
playlist
,
currentMediaIndex
,
timelineUpdate
);
// if we've buffered to the end of the video, let the MediaSource know
if
(
this
.
playlists
.
media
().
endList
&&
currentBuffered
.
length
&&
segments
[
segments
.
length
-
1
].
end
<=
currentBuffered
.
end
(
0
)
&&
this
.
mediaSource
.
readyState
===
'open'
)
{
this
.
mediaSource
.
endOfStream
();
return
;
// If we decide to signal the end of stream, then we can return instead
// of trying to fetch more segments
if
(
isEndOfStream
)
{
return
this
.
mediaSource
.
endOfStream
();
}
if
(
timelineUpdate
!==
null
||
...
...
test/videojs-hls_test.js
View file @
09fa9fb
...
...
@@ -2144,6 +2144,79 @@ test('tracks segment end times as they are buffered', function() {
equal
(
player
.
tech_
.
hls
.
mediaSource
.
duration
,
10
+
9.5
,
'updated duration'
);
});
test
(
'updates first segment duration as it is buffered'
,
function
()
{
var
bufferEnd
=
0
;
player
.
src
({
src
:
'media.m3u8'
,
type
:
'application/x-mpegURL'
});
openMediaSource
(
player
);
// as new segments are downloaded, the buffer end is updated
player
.
tech_
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
bufferEnd
);
};
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXTINF:10,\n'
+
'0.ts\n'
+
'#EXTINF:10,\n'
+
'1.ts\n'
+
'#EXT-X-ENDLIST\n'
);
// 0.ts is shorter than advertised
standardXHRResponse
(
requests
.
shift
());
equal
(
player
.
tech_
.
hls
.
mediaSource
.
duration
,
20
,
'original duration is from the m3u8'
);
equal
(
player
.
tech_
.
hls
.
playlists
.
media
().
segments
[
0
].
duration
,
10
,
'segment duration initially based on playlist'
);
bufferEnd
=
9.5
;
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'update'
);
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'updateend'
);
equal
(
player
.
tech_
.
hls
.
playlists
.
media
().
segments
[
0
].
duration
,
9.5
,
'updated segment duration'
);
});
test
(
'updates segment durations as they are buffered'
,
function
()
{
var
bufferEnd
=
0
;
player
.
src
({
src
:
'media.m3u8'
,
type
:
'application/x-mpegURL'
});
openMediaSource
(
player
);
// as new segments are downloaded, the buffer end is updated
player
.
tech_
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
bufferEnd
);
};
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXTINF:10,\n'
+
'0.ts\n'
+
'#EXTINF:10,\n'
+
'1.ts\n'
+
'#EXT-X-ENDLIST\n'
);
// 0.ts is shorter than advertised
standardXHRResponse
(
requests
.
shift
());
equal
(
player
.
tech_
.
hls
.
mediaSource
.
duration
,
20
,
'original duration is from the m3u8'
);
equal
(
player
.
tech_
.
hls
.
playlists
.
media
().
segments
[
1
].
duration
,
10
,
'segment duration initially based on playlist'
);
bufferEnd
=
9.5
;
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'update'
);
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'updateend'
);
clock
.
tick
(
1
);
standardXHRResponse
(
requests
.
shift
());
bufferEnd
=
19
;
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'update'
);
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'updateend'
);
equal
(
player
.
tech_
.
hls
.
playlists
.
media
().
segments
[
1
].
duration
,
9.5
,
'updated segment duration'
);
});
QUnit
.
skip
(
'seeking does not fail when targeted between segments'
,
function
()
{
var
currentTime
,
segmentUrl
;
player
.
src
({
...
...
@@ -2428,7 +2501,7 @@ test('can be disposed before finishing initialization', function() {
}
});
test
(
'calls end
ed() on the media source at the end of a playlis
t'
,
function
()
{
test
(
'calls end
OfStream on the media source after appending the last segmen
t'
,
function
()
{
var
endOfStreams
=
0
,
buffered
=
[[]];
player
.
src
({
src
:
'http://example.com/media.m3u8'
,
...
...
@@ -2441,11 +2514,15 @@ test('calls ended() on the media source at the end of a playlist', function() {
player
.
tech_
.
hls
.
mediaSource
.
endOfStream
=
function
()
{
endOfStreams
++
;
};
player
.
currentTime
(
20
);
clock
.
tick
(
1
);
// playlist response
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXTINF:10,\n'
+
'0.ts\n'
+
'#EXTINF:10,\n'
+
'1.ts\n'
+
'#EXT-X-ENDLIST\n'
);
// segment response
requests
[
0
].
response
=
new
ArrayBuffer
(
17
);
...
...
@@ -2454,7 +2531,50 @@ test('calls ended() on the media source at the end of a playlist', function() {
buffered
=
[[
0
,
10
]];
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'updateend'
);
strictEqual
(
endOfStreams
,
1
,
'ended media source'
);
strictEqual
(
endOfStreams
,
1
,
'called endOfStream on the media source'
);
});
test
(
'calls endOfStream on the media source when the current buffer ends at duration'
,
function
()
{
var
endOfStreams
=
0
,
buffered
=
[[]];
player
.
src
({
src
:
'http://example.com/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
player
.
tech_
.
buffered
=
function
()
{
return
videojs
.
createTimeRanges
(
buffered
);
};
player
.
tech_
.
hls
.
mediaSource
.
endOfStream
=
function
()
{
endOfStreams
++
;
};
player
.
currentTime
(
19
);
clock
.
tick
(
1
);
// playlist response
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXTINF:10,\n'
+
'0.ts\n'
+
'#EXTINF:10,\n'
+
'1.ts\n'
+
'#EXT-X-ENDLIST\n'
);
// segment response
requests
[
0
].
response
=
new
ArrayBuffer
(
17
);
requests
.
shift
().
respond
(
200
,
null
,
''
);
strictEqual
(
endOfStreams
,
0
,
'waits for the buffer update to finish'
);
buffered
=
[[
10
,
20
]];
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'updateend'
);
player
.
currentTime
(
5
);
clock
.
tick
(
1
);
// segment response
requests
[
0
].
response
=
new
ArrayBuffer
(
17
);
requests
.
shift
().
respond
(
200
,
null
,
''
);
buffered
=
[[
0
,
20
]];
player
.
tech_
.
hls
.
sourceBuffer
.
trigger
(
'updateend'
);
strictEqual
(
endOfStreams
,
2
,
'called endOfStream on the media source twice'
);
});
test
(
'calling play() at the end of a video replays'
,
function
()
{
...
...
Please
register
or
sign in
to post a comment