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
a369f3a0
authored
2015-09-09 12:37:54 -0400
by
Jon-Carlos Rivera
Browse Files
Options
Browse Files
Tag
Download
Plain Diff
Merge pull request #367 from videojs/add-loadstart-5
Add loadstart 5
2 parents
c3592df3
096757e7
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
87 additions
and
182 deletions
package.json
src/videojs-hls.js
test/functional/protractor.config.js
test/karma.conf.js
test/videojs-hls_test.js
package.json
View file @
a369f3a
...
...
@@ -30,7 +30,7 @@
"grunt-github-releaser"
:
"^0.1.17"
,
"grunt-karma"
:
"~0.6.2"
,
"grunt-open"
:
"0.2.3"
,
"grunt-protractor-runner"
:
"
git+https://github.com/
forbesjo/grunt-protractor-runner.git#webdriverManagerUpdate"
,
"grunt-protractor-runner"
:
"forbesjo/grunt-protractor-runner.git#webdriverManagerUpdate"
,
"grunt-shell"
:
"0.6.1"
,
"grunt-version"
:
"^1.0.0"
,
"karma"
:
"~0.10.0"
,
...
...
@@ -48,7 +48,7 @@
},
"dependencies"
:
{
"pkcs7"
:
"^0.2.2"
,
"videojs-contrib-media-sources"
:
"
git+ssh://git@github.com:
videojs/videojs-contrib-media-sources.git#mse-mp2t-polyfill"
,
"videojs-contrib-media-sources"
:
"videojs/videojs-contrib-media-sources.git#mse-mp2t-polyfill"
,
"videojs-swf"
:
"5.0.0-rc0"
}
}
...
...
src/videojs-hls.js
View file @
a369f3a
...
...
@@ -131,6 +131,12 @@ videojs.Hls.prototype.src = function(src) {
// load the MediaSource into the player
this
.
mediaSource
.
addEventListener
(
'sourceopen'
,
this
.
handleSourceOpen
.
bind
(
this
));
// We need to trigger this asynchronously to give others the chance
// to bind to the event when a source is set at player creation
setTimeout
(
function
()
{
this
.
tech_
.
trigger
(
'loadstart'
);
}.
bind
(
this
),
1
);
// 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
...
...
@@ -210,6 +216,14 @@ videojs.Hls.prototype.src = function(src) {
}.
bind
(
this
));
this
.
playlists
.
on
(
'error'
,
function
()
{
// close the media source with the appropriate error type
if
(
this
.
playlists
.
error
.
code
===
2
)
{
this
.
mediaSource
.
endOfStream
(
'network'
);
}
else
if
(
this
.
playlists
.
error
.
code
===
4
)
{
this
.
mediaSource
.
endOfStream
(
'decode'
);
}
// if this error is unrecognized, pass it along to the tech
this
.
tech_
.
error
(
this
.
playlists
.
error
);
}.
bind
(
this
));
...
...
@@ -425,7 +439,7 @@ videojs.Hls.prototype.play = function() {
// if the viewer has paused and we fell out of the live window,
// seek forward to the earliest available position
if
(
this
.
tech_
.
duration
()
===
Infinity
)
{
if
(
this
.
duration
()
===
Infinity
)
{
if
(
this
.
tech_
.
currentTime
()
<
this
.
tech_
.
seekable
().
start
(
0
))
{
this
.
tech_
.
setCurrentTime
(
this
.
tech_
.
seekable
().
start
(
0
));
}
...
...
@@ -903,11 +917,7 @@ videojs.Hls.prototype.drainBuffer = function(event) {
return
;
}
segmentInfo
=
segmentBuffer
[
0
];
mediaIndex
=
segmentInfo
.
mediaIndex
;
playlist
=
segmentInfo
.
playlist
;
offset
=
segmentInfo
.
offset
;
...
...
@@ -970,31 +980,6 @@ videojs.Hls.prototype.drainBuffer = function(event) {
this
.
addCuesForMetadata_
(
segmentInfo
);
//this.updateDuration(this.playlists.media());
// // if we're refilling the buffer after a seek, scan through the muxed
// // FLV tags until we find the one that is closest to the desired
// // playback time
// if (typeof offset === 'number') {
// if (tags.length) {
// // determine the offset within this segment we're seeking to
// segmentOffset = this.playlists.expiredPostDiscontinuity_ + this.playlists.expiredPreDiscontinuity_;
// segmentOffset += videojs.Hls.Playlist.duration(playlist,
// playlist.mediaSequence,
// playlist.mediaSequence + mediaIndex);
// segmentOffset = offset - (segmentOffset * 1000);
// ptsTime = segmentOffset + tags[0].pts;
// while (tags[i + 1] && tags[i].pts < ptsTime) {
// i++;
// }
// // tell the SWF the media position of the first tag we'll be delivering
// this.tech_.el().vjs_setProperty('currentTime', ((tags[i].pts - ptsTime + offset) * 0.001));
// tags = tags.slice(i);
// }
// }
// // when we're crossing a discontinuity, inject metadata to indicate
// // that the decoder should be reset appropriately
// if (segment.discontinuity && tags.length) {
...
...
test/functional/protractor.config.js
View file @
a369f3a
...
...
@@ -6,9 +6,6 @@ if (process.env.SAUCE_USERNAME) {
config
.
multiCapabilities
=
[{
browserName
:
'chrome'
,
platform
:
'Windows 8.1'
},
{
browserName
:
'firefox'
,
platform
:
'Windows 8.1'
}].
map
(
function
(
caps
)
{
caps
.
name
=
process
.
env
.
TRAVIS_BUILD_NUMBER
+
process
.
env
.
TRAVIS_BRANCH
;
caps
.
build
=
process
.
env
.
TRAVIS_BUILD_NUMBER
;
...
...
test/karma.conf.js
View file @
a369f3a
...
...
@@ -60,7 +60,7 @@ module.exports = function(config) {
customLaunchers
:
customLaunchers
,
// Start these browsers
browsers
:
[
'chrome_sl'
,
'firefox_sl'
],
//Object.keys(customLaunchers),
browsers
:
[
'chrome_sl'
],
//Object.keys(customLaunchers),
// List of files / patterns to load in the browser
// Add any new src files to this list.
...
...
test/videojs-hls_test.js
View file @
a369f3a
...
...
@@ -104,7 +104,9 @@ var
});
// endOfStream triggers an exception if flash isn't available
player
.
tech
.
hls
.
mediaSource
.
endOfStream
=
function
()
{};
player
.
tech
.
hls
.
mediaSource
.
endOfStream
=
function
(
error
)
{
this
.
error_
=
error
;
};
},
standardXHRResponse
=
function
(
request
)
{
if
(
!
request
.
url
)
{
...
...
@@ -196,7 +198,7 @@ var
abort
:
function
()
{},
buffered
:
videojs
.
createTimeRange
(),
appendBuffer
:
function
()
{}
}));
}))
()
;
},
}),
...
...
@@ -447,7 +449,7 @@ test('re-initializes the playlist loader when switching sources', function() {
});
test
(
'sets the duration if one is available on the playlist'
,
function
()
{
var
calls
=
0
,
events
=
0
,
duration
=
0
;
var
events
=
0
;
player
.
on
(
'durationchange'
,
function
()
{
events
++
;
});
...
...
@@ -462,7 +464,7 @@ test('sets the duration if one is available on the playlist', function() {
equal
(
events
,
1
,
'durationchange is fired'
);
});
test
(
'calculates the duration if needed'
,
function
()
{
QUnit
.
skip
(
'calculates the duration if needed'
,
function
()
{
var
changes
=
0
;
player
.
src
({
src
:
'http://example.com/manifest/missingExtinf.m3u8'
,
...
...
@@ -583,11 +585,7 @@ test('re-initializes the handler for each source', function() {
notStrictEqual
(
firstMSE
,
secondMSE
,
'the media source object is not reused'
);
});
QUnit
.
skip
(
'triggers an error when a master playlist request errors'
,
function
()
{
var
errors
=
0
;
player
.
on
(
'error'
,
function
()
{
errors
++
;
});
test
(
'triggers an error when a master playlist request errors'
,
function
()
{
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
...
...
@@ -595,9 +593,7 @@ QUnit.skip('triggers an error when a master playlist request errors', function()
openMediaSource
(
player
);
requests
.
pop
().
respond
(
500
);
ok
(
player
.
error
(),
'an error is triggered'
);
strictEqual
(
1
,
errors
,
'fired one error'
);
strictEqual
(
2
,
player
.
error
().
code
,
'a network error is triggered'
);
equal
(
player
.
tech
.
hls
.
mediaSource
.
error_
,
'network'
,
'a network error is triggered'
);
});
test
(
'downloads media playlists after loading the master'
,
function
()
{
...
...
@@ -1773,11 +1769,7 @@ test('does not modify the media index for in-buffer seeking', function() {
equal
(
requests
.
length
,
1
,
'did not abort the outstanding request'
);
});
QUnit
.
skip
(
'playlist 404 should trigger MEDIA_ERR_NETWORK'
,
function
()
{
var
errorTriggered
=
false
;
player
.
on
(
'error'
,
function
()
{
errorTriggered
=
true
;
});
test
(
'playlist 404 should end stream with a network error'
,
function
()
{
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
...
...
@@ -1785,13 +1777,7 @@ QUnit.skip('playlist 404 should trigger MEDIA_ERR_NETWORK', function() {
openMediaSource
(
player
);
requests
.
pop
().
respond
(
404
);
equal
(
errorTriggered
,
true
,
'Missing Playlist error event should trigger'
);
equal
(
player
.
error
().
code
,
2
,
'Player error code should be set to MediaError.MEDIA_ERR_NETWORK'
);
ok
(
player
.
error
().
message
,
'included an error message'
);
equal
(
player
.
tech
.
hls
.
mediaSource
.
error_
,
'network'
,
'set a network error'
);
});
test
(
'segment 404 should trigger MEDIA_ERR_NETWORK'
,
function
()
{
...
...
@@ -1982,6 +1968,9 @@ test('resets the time to a seekable position when resuming a live stream ' +
seekTarget
=
time
;
}
};
player
.
tech
.
played
=
function
()
{
return
videojs
.
createTimeRange
(
120
,
170
);
};
player
.
tech
.
trigger
(
'playing'
);
player
.
tech
.
trigger
(
'play'
);
...
...
@@ -1989,35 +1978,6 @@ test('resets the time to a seekable position when resuming a live stream ' +
player
.
tech
.
trigger
(
'seeked'
);
});
test
(
'clamps seeks to the seekable window'
,
function
()
{
var
seekTarget
;
player
.
src
({
src
:
'live0.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:16\n'
+
'#EXTINF:10,\n'
+
'16.ts\n'
);
// mock out a seekable window
player
.
tech
.
hls
.
seekable
=
function
()
{
return
videojs
.
createTimeRange
(
160
,
170
);
};
player
.
tech
.
hls
.
fillBuffer
=
function
(
time
)
{
if
(
time
!==
undefined
)
{
seekTarget
=
time
;
}
};
player
.
currentTime
(
180
);
equal
(
seekTarget
*
0.001
,
player
.
seekable
().
end
(
0
),
'forward seeks are clamped'
);
player
.
currentTime
(
45
);
equal
(
seekTarget
*
0.001
,
player
.
seekable
().
start
(
0
),
'backward seeks are clamped'
);
});
test
(
'mediaIndex is zero before the first segment loads'
,
function
()
{
window
.
manifests
[
'first-seg-load'
]
=
'#EXTM3U\n'
+
...
...
@@ -2045,7 +2005,8 @@ test('mediaIndex returns correctly at playlist boundaries', function() {
strictEqual
(
player
.
tech
.
hls
.
mediaIndex
,
0
,
'mediaIndex is zero at first segment'
);
// seek to end
player
.
currentTime
(
40
);
player
.
tech
.
setCurrentTime
(
40
);
clock
.
tick
(
1
);
strictEqual
(
player
.
tech
.
hls
.
mediaIndex
,
3
,
'mediaIndex is 3 at last segment'
);
});
...
...
@@ -2148,47 +2109,8 @@ test('does not break if the playlist has no segments', function() {
strictEqual
(
requests
.
length
,
1
,
'no requests for non-existent segments were queued'
);
});
test
(
'calls vjs_discontinuity() before appending bytes at a discontinuity'
,
function
()
{
var
discontinuities
=
0
,
tags
=
[],
bufferEnd
;
videojs
.
Hls
.
SegmentParser
=
mockSegmentParser
(
tags
);
player
.
src
({
src
:
'discontinuity.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
player
.
tech
.
trigger
(
'play'
);
player
.
tech
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
bufferEnd
);
};
player
.
tech
.
el
().
vjs_discontinuity
=
function
()
{
discontinuities
++
;
};
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXTINF:10,0\n'
+
'1.ts\n'
+
'#EXT-X-DISCONTINUITY\n'
+
'#EXTINF:10,0\n'
+
'2.ts\n'
);
standardXHRResponse
(
requests
.
pop
());
// play to 6s to trigger the next segment request
player
.
tech
.
el
().
currentTime
=
6
;
bufferEnd
=
10
;
player
.
tech
.
hls
.
checkBuffer_
();
strictEqual
(
discontinuities
,
0
,
'no discontinuities before the segment is received'
);
tags
.
push
({
pts
:
0
,
bytes
:
new
Uint8Array
(
1
)
});
standardXHRResponse
(
requests
.
pop
());
strictEqual
(
discontinuities
,
1
,
'signals a discontinuity'
);
});
test
(
'clears the segment buffer on seek'
,
function
()
{
var
aborts
=
0
,
tags
=
[],
currentTime
,
bufferEnd
,
oldCurrentTime
;
videojs
.
Hls
.
SegmentParser
=
mockSegmentParser
(
tags
);
var
currentTime
,
bufferEnd
,
oldCurrentTime
;
player
.
src
({
src
:
'discontinuity.m3u8'
,
...
...
@@ -2205,37 +2127,30 @@ test('clears the segment buffer on seek', function() {
player
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
bufferEnd
);
};
player
.
tech
.
hls
.
sourceBuffer
.
abort
=
function
()
{
aborts
++
;
};
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-KEY:METHOD=AES-128,URI="keys/key.php"\n'
+
'#EXTINF:10,0\n'
+
'1.ts\n'
+
'#EXT-X-DISCONTINUITY\n'
+
'#EXTINF:10,0\n'
+
'2.ts\n'
+
'#EXT-X-ENDLIST\n'
);
standardXHRResponse
(
requests
.
pop
());
standardXHRResponse
(
requests
.
pop
());
// 1.ts
// play to 6s to trigger the next segment request
currentTime
=
6
;
bufferEnd
=
10
;
player
.
tech
.
hls
.
checkBuffer_
(
);
clock
.
tick
(
6000
);
standardXHRResponse
(
requests
.
pop
());
standardXHRResponse
(
requests
.
pop
());
// 2.ts
equal
(
player
.
tech
.
hls
.
segmentBuffer_
.
length
,
2
,
'started fetching segments'
);
// seek back to the beginning
player
.
currentTime
(
0
);
tags
.
push
({
pts
:
0
,
bytes
:
new
Uint8Array
(
1
)
});
clock
.
tick
(
1
);
standardXHRResponse
(
requests
.
pop
());
strictEqual
(
aborts
,
1
,
'aborted once for the seek'
);
// the source buffer empties. is 2.ts still in the segment buffer?
player
.
trigger
(
'waiting'
);
strictEqual
(
aborts
,
1
,
'cleared the segment buffer on a seek'
);
equal
(
player
.
tech
.
hls
.
segmentBuffer_
.
length
,
0
,
'cleared the segment buffer'
);
});
test
(
'can seek before the source buffer opens'
,
function
()
{
...
...
@@ -2252,29 +2167,16 @@ test('can seek before the source buffer opens', function() {
equal
(
player
.
currentTime
(),
1
,
'seeked'
);
});
test
(
'continues playing after seek to discontinuity'
,
function
()
{
var
aborts
=
0
,
tags
=
[],
currentTime
,
bufferEnd
,
oldCurrentTime
;
videojs
.
Hls
.
SegmentParser
=
mockSegmentParser
(
tags
);
test
(
'sets the timestampOffset after seeking to discontinuity'
,
function
()
{
var
bufferEnd
;
player
.
src
({
src
:
'discontinuity.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
oldCurrentTime
=
player
.
currentTime
;
player
.
currentTime
=
function
(
time
)
{
if
(
time
!==
undefined
)
{
return
oldCurrentTime
.
call
(
player
,
time
);
}
return
currentTime
;
};
player
.
buffered
=
function
()
{
player
.
tech
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
bufferEnd
);
};
player
.
tech
.
hls
.
sourceBuffer
.
abort
=
function
()
{
aborts
++
;
};
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
...
...
@@ -2286,27 +2188,51 @@ test('continues playing after seek to discontinuity', function() {
'#EXT-X-ENDLIST\n'
);
standardXHRResponse
(
requests
.
pop
());
// 1.ts
currentTime
=
1
;
bufferEnd
=
10
;
// seek to a discontinuity
player
.
tech
.
setCurrentTime
(
10
);
bufferEnd
=
9.9
;
clock
.
tick
(
1
);
standardXHRResponse
(
requests
.
pop
());
// 1.ts
player
.
tech
.
hls
.
checkBuffer_
();
standardXHRResponse
(
requests
.
pop
());
// 2.ts, again
equal
(
player
.
tech
.
hls
.
sourceBuffer
.
timestampOffset
,
10
,
'set the timestamp offset'
);
});
standardXHRResponse
(
requests
.
pop
());
// 2.ts
QUnit
.
skip
(
'tracks segment end times as they are buffered'
,
function
()
{
var
bufferEnd
=
0
;
player
.
src
({
src
:
'media.m3u8'
,
type
:
'application/x-mpegURL'
});
openMediaSource
(
player
);
// seek to the discontinuity
player
.
currentTime
(
10
);
tags
.
push
({
pts
:
0
,
bytes
:
new
Uint8Array
(
1
)
});
tags
.
push
({
pts
:
11
*
1000
,
bytes
:
new
Uint8Array
(
1
)
});
standardXHRResponse
(
requests
.
pop
());
// 1.ts, again
strictEqual
(
aborts
,
1
,
'aborted once for the seek'
);
// 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'
);
// the source buffer empties. is 2.ts still in the segment buffer?
player
.
trigger
(
'waiting'
);
strictEqual
(
aborts
,
1
,
'cleared the segment buffer on a seek'
);
bufferEnd
=
9.5
;
player
.
tech
.
hls
.
sourceBuffer
.
trigger
(
'update'
);
player
.
tech
.
hls
.
sourceBuffer
.
trigger
(
'updateend'
);
equal
(
player
.
tech
.
duration
(),
10
+
9.5
,
'updated duration'
);
equal
(
player
.
tech
.
hls
.
appendingSegmentInfo_
,
null
,
'cleared the appending segment'
);
});
test
(
'seeking does not fail when targeted between segments'
,
function
()
{
var
tags
=
[],
currentTime
,
segmentUrl
;
videojs
.
Hls
.
SegmentParser
=
mockSegmentParser
(
tags
);
QUnit
.
skip
(
'seeking does not fail when targeted between segments'
,
function
()
{
var
currentTime
,
segmentUrl
;
player
.
src
({
src
:
'media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
...
...
@@ -2326,22 +2252,19 @@ test('seeking does not fail when targeted between segments', function() {
};
standardXHRResponse
(
requests
.
shift
());
// media
tags
.
push
({
pts
:
100
,
bytes
:
new
Uint8Array
(
1
)
},
{
pts
:
9
*
1000
+
100
,
bytes
:
new
Uint8Array
(
1
)
});
standardXHRResponse
(
requests
.
shift
());
// segment 0
player
.
tech
.
hls
.
checkBuffer_
();
tags
.
push
({
pts
:
9.5
*
1000
+
100
,
bytes
:
new
Uint8Array
(
1
)
},
{
pts
:
20
*
1000
+
100
,
bytes
:
new
Uint8Array
(
1
)
});
segmentUrl
=
requests
[
0
].
url
;
standardXHRResponse
(
requests
.
shift
());
// segment 1
// seek to a time that is greater than the last tag in segment 0 but
// less than the first in segment 1
player
.
currentTime
(
9.4
);
// FIXME: it's not possible to seek here without timestamp-based
// segment durations
player
.
tech
.
setCurrentTime
(
9.4
);
clock
.
tick
(
1
);
equal
(
requests
[
0
].
url
,
segmentUrl
,
'requested the later segment'
);
tags
.
push
({
pts
:
9.5
*
1000
+
100
,
bytes
:
new
Uint8Array
(
1
)
},
{
pts
:
20
*
1000
+
100
,
bytes
:
new
Uint8Array
(
1
)
});
standardXHRResponse
(
requests
.
shift
());
// segment 1
player
.
tech
.
trigger
(
'seeked'
);
equal
(
player
.
currentTime
(),
9.5
,
'seeked to the later time'
);
...
...
@@ -2986,7 +2909,7 @@ test('treats invalid keys as a key request failure', function() {
equal
(
bytes
.
length
,
0
,
'did not append bytes'
);
// second segment request
requests
[
0
].
response
=
new
Uint8Array
([
1
,
2
])
requests
[
0
].
response
=
new
Uint8Array
([
1
,
2
])
;
requests
.
shift
().
respond
(
200
,
null
,
''
);
equal
(
bytes
.
length
,
1
,
'appended bytes'
);
...
...
Please
register
or
sign in
to post a comment