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
4624510d
authored
2015-04-16 16:05:10 -0400
by
David LaPalomento
Browse Files
Options
Browse Files
Tag
Download
Plain Diff
Merge pull request #246 from videojs/live-hlse-fixes
Fix discontinuities
2 parents
021896e3
4222a1f8
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
179 additions
and
285 deletions
example.html
package.json
src/videojs-hls.js
test/videojs-hls_test.js
example.html
View file @
4624510
...
...
@@ -70,7 +70,7 @@
type=
"application/x-mpegURL"
>
</video>
<script>
videojs
.
options
.
flash
.
swf
=
'node_modules/video
.js/dist/video-js
/video-js.swf'
;
videojs
.
options
.
flash
.
swf
=
'node_modules/video
js-swf/dist
/video-js.swf'
;
// initialize the player
var
player
=
videojs
(
'video'
);
</script>
...
...
package.json
View file @
4624510
...
...
@@ -43,6 +43,7 @@
},
"dependencies"
:
{
"pkcs7"
:
"^0.2.2"
,
"videojs-contrib-media-sources"
:
"^0.3.0"
"videojs-contrib-media-sources"
:
"^0.3.0"
,
"videojs-swf"
:
"^4.6.0"
}
}
...
...
src/videojs-hls.js
View file @
4624510
...
...
@@ -221,14 +221,13 @@ videojs.Hls.prototype.src = function(src) {
this
.
mediaIndex
=
videojs
.
Hls
.
translateMediaIndex
(
this
.
mediaIndex
,
oldMediaPlaylist
,
updatedPlaylist
);
oldMediaPlaylist
=
updatedPlaylist
;
this
.
fetchKeys
(
updatedPlaylist
,
this
.
mediaIndex
);
this
.
fetchKeys
_
(
);
}));
this
.
playlists
.
on
(
'mediachange'
,
videojs
.
bind
(
this
,
function
()
{
// abort outstanding key requests and check if new keys need to be retrieved
if
(
keyXhr
)
{
this
.
cancelKeyXhr
();
this
.
fetchKeys
(
this
.
playlists
.
media
(),
this
.
mediaIndex
);
}
player
.
trigger
(
'mediachange'
);
...
...
@@ -330,11 +329,10 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
// cancel outstanding requests and buffer appends
this
.
cancelSegmentXhr
();
//
fetch new encryption key
s, if necessary
//
abort outstanding key request
s, if necessary
if
(
keyXhr
)
{
keyXhr
.
aborted
=
true
;
this
.
cancelKeyXhr
();
this
.
fetchKeys
(
this
.
playlists
.
media
(),
this
.
mediaIndex
);
}
// clear out any buffered segments
...
...
@@ -659,6 +657,7 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) {
offset
:
offset
,
bytes
:
new
Uint8Array
(
this
.
response
)
});
player
.
trigger
(
'progress'
);
tech
.
drainBuffer
();
tech
.
mediaIndex
++
;
...
...
@@ -700,7 +699,8 @@ videojs.Hls.prototype.drainBuffer = function(event) {
if
(
keyFailed
(
segment
.
key
))
{
return
segmentBuffer
.
shift
();
}
else
if
(
!
segment
.
key
.
bytes
)
{
return
;
// trigger a key request if one is not already in-flight
return
this
.
fetchKeys_
();
}
else
{
// if the media sequence is greater than 2^32, the IV will be incorrect
// assuming 10s segments, that would be about 1300 years
...
...
@@ -714,23 +714,6 @@ videojs.Hls.prototype.drainBuffer = function(event) {
event
=
event
||
{};
segmentOffset
=
videojs
.
Hls
.
getPlaylistDuration
(
playlist
,
0
,
mediaIndex
)
*
1000
;
// abort() clears any data queued in the source buffer so wait
// until it empties before calling it when a discontinuity is
// next in the buffer
if
(
segment
.
discontinuity
)
{
if
(
event
.
type
===
'waiting'
)
{
this
.
sourceBuffer
.
abort
();
// tell the SWF where playback is continuing in the stitched timeline
this
.
el
().
vjs_setProperty
(
'currentTime'
,
segmentOffset
*
0.001
);
}
else
if
(
event
.
type
===
'timeupdate'
)
{
return
;
}
else
if
(
typeof
offset
!==
'number'
)
{
//if the discontinuity is reached under normal conditions, ie not a seek,
//the buffer already contains data and does not need to be refilled,
return
;
}
}
// transmux the segment data from MP2T to FLV
this
.
segmentParser_
.
parseSegmentBinaryData
(
bytes
);
this
.
segmentParser_
.
flushTags
();
...
...
@@ -758,6 +741,12 @@ videojs.Hls.prototype.drainBuffer = function(event) {
this
.
lastSeekedTime_
=
null
;
}
// when we're crossing a discontinuity, inject metadata to indicate
// that the decoder should be reset appropriately
if
(
segment
.
discontinuity
&&
tags
.
length
)
{
this
.
el
().
vjs_discontinuity
();
}
for
(
i
=
0
;
i
<
tags
.
length
;
i
++
)
{
// queue up the bytes to be appended to the SourceBuffer
// the queue gives control back to the browser between tags
...
...
@@ -776,11 +765,19 @@ videojs.Hls.prototype.drainBuffer = function(event) {
}
};
videojs
.
Hls
.
prototype
.
fetchKeys
=
function
(
playlist
,
index
)
{
var
i
,
key
,
tech
,
player
,
settings
,
view
;
/**
* Attempt to retrieve keys starting at a particular media
* segment. This method has no effect if segments are not yet
* available or a key request is already in progress.
*
* @param playlist {object} the media playlist to fetch keys for
* @param index {number} the media segment index to start from
*/
videojs
.
Hls
.
prototype
.
fetchKeys_
=
function
()
{
var
i
,
key
,
tech
,
player
,
settings
,
segment
,
view
,
receiveKey
;
// if there is a pending XHR or no segments, don't do anything
if
(
keyXhr
||
!
playlist
.
segments
)
{
if
(
keyXhr
||
!
this
.
segmentBuffer_
.
length
)
{
return
;
}
...
...
@@ -788,22 +785,19 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) {
player
=
this
.
player
();
settings
=
player
.
options
().
hls
||
{};
// jshint -W083
for
(
i
=
index
;
i
<
playlist
.
segments
.
length
;
i
++
)
{
key
=
playlist
.
segments
[
i
].
key
;
if
(
key
&&
!
key
.
bytes
&&
!
keyFailed
(
key
))
{
keyXhr
=
videojs
.
Hls
.
xhr
({
url
:
this
.
playlistUriToUrl
(
key
.
uri
),
responseType
:
'arraybuffer'
,
withCredentials
:
settings
.
withCredentials
},
function
(
err
,
url
)
{
/**
* Handle a key XHR response. This function needs to lookup the
*/
receiveKey
=
function
(
key
)
{
return
function
(
error
)
{
keyXhr
=
null
;
if
(
er
r
||
!
this
.
response
||
this
.
response
.
byteLength
!==
16
)
{
if
(
erro
r
||
!
this
.
response
||
this
.
response
.
byteLength
!==
16
)
{
key
.
retries
=
key
.
retries
||
0
;
key
.
retries
++
;
if
(
!
this
.
aborted
)
{
tech
.
fetchKeys
(
playlist
,
i
);
// try fetching again
tech
.
fetchKeys_
();
}
return
;
}
...
...
@@ -815,12 +809,31 @@ videojs.Hls.prototype.fetchKeys = function(playlist, index) {
view
.
getUint32
(
8
),
view
.
getUint32
(
12
)
]);
tech
.
fetchKeys
(
playlist
,
i
++
,
url
);
});
// check to see if this allows us to make progress buffering now
tech
.
checkBuffer_
();
};
};
for
(
i
=
0
;
i
<
tech
.
segmentBuffer_
.
length
;
i
++
)
{
segment
=
tech
.
segmentBuffer_
[
i
].
playlist
.
segments
[
tech
.
segmentBuffer_
[
i
].
mediaIndex
];
key
=
segment
.
key
;
// continue looking if this segment is unencrypted
if
(
!
key
)
{
continue
;
}
// request the key if the retry limit hasn't been reached
if
(
!
key
.
bytes
&&
!
keyFailed
(
key
))
{
keyXhr
=
videojs
.
Hls
.
xhr
({
url
:
this
.
playlistUriToUrl
(
key
.
uri
),
responseType
:
'arraybuffer'
,
withCredentials
:
settings
.
withCredentials
},
receiveKey
(
key
));
break
;
}
}
// jshint +W083
};
/**
...
...
@@ -925,9 +938,7 @@ videojs.Hls.getPlaylistTotalDuration = function(playlist) {
* playlist
*/
videojs
.
Hls
.
translateMediaIndex
=
function
(
mediaIndex
,
original
,
update
)
{
var
i
,
originalSegment
,
translatedMediaIndex
;
var
translatedMediaIndex
;
// no segments have been loaded from the original playlist
if
(
mediaIndex
===
0
)
{
...
...
@@ -939,15 +950,8 @@ videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) {
return
0
;
}
// try to sync based on URI
i
=
update
.
segments
.
length
;
originalSegment
=
original
.
segments
[
mediaIndex
-
1
];
while
(
i
--
)
{
if
(
originalSegment
.
uri
===
update
.
segments
[
i
].
uri
)
{
return
i
+
1
;
}
}
// translate based on media sequence numbers. syncing up across
// bitrate switches should be happening here.
translatedMediaIndex
=
(
mediaIndex
+
(
original
.
mediaSequence
-
update
.
mediaSequence
));
if
(
translatedMediaIndex
>=
update
.
segments
.
length
||
translatedMediaIndex
<
0
)
{
...
...
@@ -955,7 +959,6 @@ videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) {
return
videojs
.
Hls
.
getMediaIndexForLive_
(
update
)
+
1
;
}
// sync on media sequence
return
translatedMediaIndex
;
};
...
...
test/videojs-hls_test.js
View file @
4624510
...
...
@@ -54,6 +54,7 @@ var
tech
.
vjs_setProperty
=
function
()
{};
tech
.
vjs_src
=
function
()
{};
tech
.
vjs_play
=
function
()
{};
tech
.
vjs_discontinuity
=
function
()
{};
videojs
.
Flash
.
onReady
(
tech
.
id
);
return
player
;
...
...
@@ -86,7 +87,7 @@ var
contentType
=
'video/MP2T'
;
}
request
.
response
=
new
Uint8Array
(
[
1
]
).
buffer
;
request
.
response
=
new
Uint8Array
(
16
).
buffer
;
request
.
respond
(
200
,
{
'Content-Type'
:
contentType
},
window
.
manifests
[
manifestName
]);
...
...
@@ -571,6 +572,23 @@ test('calculates the bandwidth after downloading a segment', function() {
'saves segment request time: '
+
player
.
hls
.
segmentXhrTime
+
's'
);
});
test
(
'fires a progress event after downloading a segment'
,
function
()
{
var
progressCount
=
0
;
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
standardXHRResponse
(
requests
.
shift
());
player
.
on
(
'progress'
,
function
()
{
progressCount
++
;
});
standardXHRResponse
(
requests
.
shift
());
equal
(
progressCount
,
1
,
'fired a progress event'
);
});
test
(
'selects a playlist after segment downloads'
,
function
()
{
var
calls
=
0
;
player
.
src
({
...
...
@@ -1221,6 +1239,7 @@ test('updates the media index when a playlist reloads', function() {
// reload the updated playlist
player
.
hls
.
playlists
.
media
=
function
()
{
return
{
mediaSequence
:
1
,
segments
:
[{
uri
:
'1.ts'
},
{
...
...
@@ -1348,9 +1367,10 @@ test('does not break if the playlist has no segments', function() {
strictEqual
(
requests
.
length
,
1
,
'no requests for non-existent segments were queued'
);
});
test
(
'
waits until the buffer is empty
before appending bytes at a discontinuity'
,
function
()
{
var
aborts
=
0
,
setTime
,
currentTime
,
bufferEnd
;
test
(
'
calls vjs_discontinuity()
before appending bytes at a discontinuity'
,
function
()
{
var
discontinuities
=
0
,
tags
=
[]
,
currentTime
,
bufferEnd
;
videojs
.
Hls
.
SegmentParser
=
mockSegmentParser
(
tags
);
player
.
src
({
src
:
'discontinuity.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
...
...
@@ -1360,13 +1380,8 @@ test('waits until the buffer is empty before appending bytes at a discontinuity'
player
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
bufferEnd
);
};
player
.
hls
.
sourceBuffer
.
abort
=
function
()
{
aborts
++
;
};
player
.
hls
.
el
().
vjs_setProperty
=
function
(
name
,
value
)
{
if
(
name
===
'currentTime'
)
{
return
setTime
=
value
;
}
player
.
el
().
querySelector
(
'.vjs-tech'
).
vjs_discontinuity
=
function
()
{
discontinuities
++
;
};
requests
.
pop
().
respond
(
200
,
null
,
...
...
@@ -1382,15 +1397,11 @@ test('waits until the buffer is empty before appending bytes at a discontinuity'
currentTime
=
6
;
bufferEnd
=
10
;
player
.
hls
.
checkBuffer_
();
strictEqual
(
aborts
,
0
,
'no aborts before the buffer empties
'
);
strictEqual
(
discontinuities
,
0
,
'no discontinuities before the segment is received
'
);
tags
.
push
({});
standardXHRResponse
(
requests
.
pop
());
strictEqual
(
aborts
,
0
,
'no aborts before the buffer empties'
);
// pretend the buffer has emptied
player
.
trigger
(
'waiting'
);
strictEqual
(
aborts
,
1
,
'aborted before appending the new segment'
);
strictEqual
(
setTime
,
10
,
'updated the time after crossing the discontinuity'
);
strictEqual
(
discontinuities
,
1
,
'signals a discontinuity'
);
});
test
(
'clears the segment buffer on seek'
,
function
()
{
...
...
@@ -1781,40 +1792,23 @@ test('drainBuffer will not proceed with empty source buffer', function() {
player
.
hls
.
playlists
.
media
=
oldMedia
;
});
test
(
'
calling fetchKeys() when a new playlist is loaded will create an XHR
'
,
function
()
{
test
(
'
keys are requested when an encrypted segment is loaded
'
,
function
()
{
player
.
src
({
src
:
'https://example.com/encrypted
-media
.m3u8'
,
src
:
'https://example.com/encrypted.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
var
oldMedia
=
player
.
hls
.
playlists
.
media
;
player
.
hls
.
playlists
.
media
=
function
()
{
return
{
segments
:
[{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=52'
},
uri
:
'http://media.example.com/fileSequence52-A.ts'
},
{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=53'
},
uri
:
'http://media.example.com/fileSequence53-B.ts'
}]
};
};
player
.
hls
.
playlists
.
trigger
(
'loadedplaylist'
);
strictEqual
(
requests
.
length
,
2
,
'a key XHR is created'
);
strictEqual
(
requests
[
1
].
url
,
player
.
hls
.
playlists
.
media
().
segments
[
0
].
key
.
uri
,
'a key XHR is created with correct uri'
);
standardXHRResponse
(
requests
.
shift
());
// playlist
standardXHRResponse
(
requests
.
shift
());
// first segment
player
.
hls
.
playlists
.
media
=
oldMedia
;
strictEqual
(
requests
.
length
,
1
,
'a key XHR is created'
);
strictEqual
(
requests
[
0
].
url
,
player
.
hls
.
playlists
.
media
().
segments
[
0
].
key
.
uri
,
'a key XHR is created with correct uri'
);
});
test
(
'
fetchKeys() resolves URLs
relative to the master playlist'
,
function
()
{
test
(
'
keys are resolved
relative to the master playlist'
,
function
()
{
player
.
src
({
src
:
'video/master-encrypted.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
...
...
@@ -1833,12 +1827,13 @@ test('fetchKeys() resolves URLs relative to the master playlist', function() {
'http://media.example.com/fileSequence1.ts\n'
+
'#EXT-X-ENDLIST\n'
);
equal
(
requests
.
length
,
2
,
'requested two URLs'
);
standardXHRResponse
(
requests
.
shift
());
equal
(
requests
.
length
,
1
,
'requested the key'
);
ok
((
/video
\/
playlist
\/
keys
\/
key
\.
php$/
).
test
(
requests
[
0
].
url
),
'resolves multiple relative paths'
);
});
test
(
'
fetchKeys() resolves URLs
relative to their containing playlist'
,
function
()
{
test
(
'
keys are resolved
relative to their containing playlist'
,
function
()
{
player
.
src
({
src
:
'video/media-encrypted.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
...
...
@@ -1851,197 +1846,100 @@ test('fetchKeys() resolves URLs relative to their containing playlist', function
'#EXTINF:2.833,\n'
+
'http://media.example.com/fileSequence1.ts\n'
+
'#EXT-X-ENDLIST\n'
);
equal
(
requests
.
length
,
2
,
'requested two URLs'
);
standardXHRResponse
(
requests
.
shift
());
equal
(
requests
.
length
,
1
,
'requested a key'
);
ok
((
/video
\/
keys
\/
key
\.
php$/
).
test
(
requests
[
0
].
url
),
'resolves multiple relative paths'
);
});
test
(
'a new key
s XHR is created when a previous key XHR finishes
'
,
function
()
{
test
(
'a new key
XHR is created when a the segment is received
'
,
function
()
{
player
.
src
({
src
:
'https://example.com/encrypted-media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
var
oldMedia
=
player
.
hls
.
playlists
.
media
;
player
.
hls
.
playlists
.
media
=
function
()
{
return
{
segments
:
[{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=52'
},
uri
:
'http://media.example.com/fileSequence52-A.ts'
},
{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=53'
},
uri
:
'http://media.example.com/fileSequence53-B.ts'
}]
};
};
// we're inject the media playlist, so drop the request
requests
.
shift
();
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-TARGETDURATION:15\n'
+
'#EXT-X-KEY:METHOD=AES-128,URI="keys/key.php"\n'
+
'#EXTINF:2.833,\n'
+
'http://media.example.com/fileSequence1.ts\n'
+
'#EXT-X-KEY:METHOD=AES-128,URI="keys/key2.php"\n'
+
'#EXTINF:2.833,\n'
+
'http://media.example.com/fileSequence2.ts\n'
+
'#EXT-X-ENDLIST\n'
);
standardXHRResponse
(
requests
.
shift
());
// segment 1
standardXHRResponse
(
requests
.
shift
());
// key 1
standardXHRResponse
(
requests
.
shift
());
// segment 2
player
.
hls
.
playlists
.
trigger
(
'loadedplaylist'
);
// key response
requests
[
0
].
response
=
new
Uint32Array
([
0
,
0
,
0
,
0
]).
buffer
;
requests
.
shift
().
respond
(
200
,
null
,
''
);
strictEqual
(
requests
.
length
,
1
,
'a key XHR is created'
);
strictEqual
(
requests
[
0
].
url
,
player
.
hls
.
playlists
.
media
().
segments
[
1
].
key
.
uri
,
'a key XHR is created with the correct uri'
);
player
.
hls
.
playlists
.
media
=
oldMedia
;
strictEqual
(
requests
[
0
].
url
,
'https://example.com/'
+
player
.
hls
.
playlists
.
media
().
segments
[
1
].
key
.
uri
,
'a key XHR is created with the correct uri'
);
});
test
(
'
calling fetchKeys() when a seek happens will create an XHR
'
,
function
()
{
test
(
'
seeking should abort an outstanding key request and create a new one
'
,
function
()
{
player
.
src
({
src
:
'https://example.com/encrypted
-media
.m3u8'
,
src
:
'https://example.com/encrypted.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
var
oldMedia
=
player
.
hls
.
playlists
.
media
;
player
.
hls
.
playlists
.
media
=
function
()
{
return
{
segments
:
[{
duration
:
10
,
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=52'
},
uri
:
'http://media.example.com/fileSequence52-A.ts'
},
{
duration
:
10
,
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=53'
},
uri
:
'http://media.example.com/fileSequence53-B.ts'
}]
};
};
player
.
hls
.
fetchKeys
(
player
.
hls
.
playlists
.
media
(),
0
);
player
.
currentTime
(
11
);
ok
(
requests
[
1
].
aborted
,
'the key XHR should be aborted'
);
equal
(
requests
.
length
,
3
,
'we should get a new key XHR'
);
equal
(
requests
[
2
].
url
,
player
.
hls
.
playlists
.
media
().
segments
[
1
].
key
.
uri
,
'urls should match'
);
player
.
hls
.
playlists
.
media
=
oldMedia
;
});
test
(
'calling fetchKeys() when a key XHR is in progress will *not* create an XHR'
,
function
()
{
player
.
src
({
src
:
'https://example.com/encrypted-media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
var
oldMedia
=
player
.
hls
.
playlists
.
media
;
player
.
hls
.
playlists
.
media
=
function
()
{
return
{
segments
:
[{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=52'
},
uri
:
'http://media.example.com/fileSequence52-A.ts'
},
{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=53'
},
uri
:
'http://media.example.com/fileSequence53-B.ts'
}]
};
};
strictEqual
(
requests
.
length
,
1
,
'no key XHR created for the player'
);
player
.
hls
.
playlists
.
trigger
(
'loadedplaylist'
);
player
.
hls
.
fetchKeys
(
player
.
hls
.
playlists
.
media
(),
0
);
strictEqual
(
requests
.
length
,
2
,
'only the original XHR is available'
);
player
.
hls
.
playlists
.
media
=
oldMedia
;
});
test
(
'calling fetchKeys() when all keys are fetched, will *not* create an XHR'
,
function
()
{
player
.
src
({
src
:
'https://example.com/encrypted-media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
var
oldMedia
=
player
.
hls
.
playlists
.
media
;
player
.
hls
.
playlists
.
media
=
function
()
{
return
{
segments
:
[{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=52'
,
bytes
:
new
Uint8Array
([
1
])
},
uri
:
'http://media.example.com/fileSequence52-A.ts'
},
{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=53'
,
bytes
:
new
Uint8Array
([
1
])
},
uri
:
'http://media.example.com/fileSequence53-B.ts'
}]
};
};
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-TARGETDURATION:15\n'
+
'#EXT-X-KEY:METHOD=AES-128,URI="keys/key.php"\n'
+
'#EXTINF:9,\n'
+
'http://media.example.com/fileSequence1.ts\n'
+
'#EXT-X-KEY:METHOD=AES-128,URI="keys/key2.php"\n'
+
'#EXTINF:9,\n'
+
'http://media.example.com/fileSequence2.ts\n'
+
'#EXT-X-ENDLIST\n'
);
standardXHRResponse
(
requests
.
shift
());
// segment 1
player
.
hls
.
fetchKeys
(
player
.
hls
.
playlists
.
media
(),
0
);
strictEqual
(
requests
.
length
,
1
,
'no XHR for keys created since they were all downloaded'
);
player
.
currentTime
(
11
);
ok
(
requests
[
0
].
aborted
,
'the key XHR should be aborted'
);
requests
.
shift
();
// aborted key 1
player
.
hls
.
playlists
.
media
=
oldMedia
;
equal
(
requests
.
length
,
1
,
'requested the new segment'
);
standardXHRResponse
(
requests
.
shift
());
// segment 2
equal
(
requests
.
length
,
1
,
'requested the new key'
);
equal
(
requests
[
0
].
url
,
'https://example.com/'
+
player
.
hls
.
playlists
.
media
().
segments
[
1
].
key
.
uri
,
'urls should match'
);
});
test
(
'retries key requests once upon failure'
,
function
()
{
player
.
src
({
src
:
'https://example.com/encrypted
-media
.m3u8'
,
src
:
'https://example.com/encrypted.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
openMediaSource
(
player
);
var
oldMedia
=
player
.
hls
.
playlists
.
media
;
player
.
hls
.
playlists
.
media
=
function
()
{
return
{
segments
:
[{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=52'
},
uri
:
'http://media.example.com/fileSequence52-A.ts'
},
{
key
:
{
'method'
:
'AES-128'
,
'uri'
:
'https://priv.example.com/key.php?r=53'
},
uri
:
'http://media.example.com/fileSequence53-B.ts'
}]
};
};
player
.
hls
.
fetchKeys
(
player
.
hls
.
playlists
.
media
(),
0
);
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-KEY:METHOD=AES-128,URI="htts://priv.example.com/key.php?r=52"\n'
+
'#EXTINF:2.833,\n'
+
'http://media.example.com/fileSequence52-A.ts\n'
+
'#EXT-X-KEY:METHOD=AES-128,URI="htts://priv.example.com/key.php?r=53"\n'
+
'#EXTINF:15.0,\n'
+
'http://media.example.com/fileSequence53-A.ts\n'
);
standardXHRResponse
(
requests
.
shift
());
// segment
requests
[
0
].
respond
(
404
);
equal
(
requests
.
length
,
2
,
'create a new XHR for the same key'
);
equal
(
requests
[
1
].
url
,
requests
[
0
].
url
,
'should be the same key'
);
requests
[
1
].
respond
(
404
);
equal
(
requests
.
length
,
3
,
'create a new XHR for the same key'
);
equal
(
requests
[
2
].
url
,
requests
[
1
].
url
,
'should be the same key'
);
requests
[
2
].
respond
(
404
);
equal
(
requests
.
length
,
4
,
'create a new XHR for the same key'
);
notEqual
(
requests
[
3
].
url
,
requests
[
2
].
url
,
'should be the same key'
);
equal
(
requests
[
3
].
url
,
player
.
hls
.
playlists
.
media
().
segments
[
1
].
key
.
uri
);
player
.
hls
.
playlists
.
media
=
oldMedia
;
equal
(
requests
.
length
,
2
,
'gives up after one retry'
);
});
test
(
'skip segments if key requests fail more than once'
,
function
()
{
var
bytes
=
[],
tags
=
[{
p
a
ts
:
0
,
bytes
:
0
}];
tags
=
[{
pts
:
0
,
bytes
:
0
}];
videojs
.
Hls
.
SegmentParser
=
mockSegmentParser
(
tags
);
window
.
videojs
.
SourceBuffer
=
function
()
{
...
...
@@ -2057,7 +1955,7 @@ test('skip segments if key requests fail more than once', function() {
});
openMediaSource
(
player
);
requests
.
pop
().
respond
(
200
,
null
,
requests
.
shift
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-KEY:METHOD=AES-128,URI="htts://priv.example.com/key.php?r=52"\n'
+
'#EXTINF:2.833,\n'
+
...
...
@@ -2065,32 +1963,19 @@ test('skip segments if key requests fail more than once', function() {
'#EXT-X-KEY:METHOD=AES-128,URI="htts://priv.example.com/key.php?r=53"\n'
+
'#EXTINF:15.0,\n'
+
'http://media.example.com/fileSequence53-A.ts\n'
);
standardXHRResponse
(
requests
.
shift
());
// segment 1
requests
.
shift
().
respond
(
404
);
// fail key
requests
.
shift
().
respond
(
404
);
// fail key, again
player
.
hls
.
playlists
.
trigger
(
'loadedplaylist'
)
;
tags
.
length
=
0
;
tags
.
push
({
pts
:
0
,
bytes
:
1
});
player
.
hls
.
checkBuffer_
();
// respond to ts segment
standardXHRResponse
(
requests
.
pop
());
// fail key
requests
.
pop
().
respond
(
404
);
// fail key, again
requests
.
pop
().
respond
(
404
);
standardXHRResponse
(
requests
.
shift
());
// segment 2
equal
(
bytes
.
length
,
1
,
'bytes from the ts segments should not be added'
);
// key for second segment
requests
[
0
].
response
=
new
Uint32Array
([
0
,
0
,
0
,
0
]).
buffer
;
requests
[
0
].
respond
(
200
,
null
,
''
);
requests
.
shift
();
equal
(
bytes
.
length
,
1
,
'bytes from the ts segments should not be added'
);
player
.
hls
.
checkBuffer_
();
tags
.
length
=
0
;
tags
.
push
({
pts
:
0
,
bytes
:
1
});
// second segment
standardXHRResponse
(
requests
.
pop
());
requests
.
shift
().
respond
(
200
,
null
,
''
);
equal
(
bytes
.
length
,
2
,
'bytes from the second ts segment should be added'
);
equal
(
bytes
[
1
],
1
,
'the bytes from the second segment are added and not the first'
);
...
...
@@ -2120,10 +2005,10 @@ test('the key is supplied to the decrypter in the correct format', function() {
return
new
Uint8Array
([
0
]);
};
standardXHRResponse
(
requests
.
shift
());
// segment
requests
[
0
].
response
=
new
Uint32Array
([
0
,
1
,
2
,
3
]).
buffer
;
requests
[
0
].
respond
(
200
,
null
,
''
);
requests
.
shift
();
standardXHRResponse
(
requests
.
pop
());
requests
.
shift
();
// key
equal
(
keys
.
length
,
1
,
'only one call to decrypt was made'
);
deepEqual
(
keys
[
0
],
...
...
@@ -2188,23 +2073,26 @@ test('switching playlists with an outstanding key request does not stall playbac
player
.
hls
.
playlists
.
media
=
function
()
{
return
player
.
hls
.
playlists
.
master
.
playlists
[
0
];
};
// don't respond to the initial key request
requests
.
shift
();
// first segment of the original media playlist
standardXHRResponse
(
requests
.
shift
());
// don't respond to the initial key request
requests
.
shift
();
// "switch" media
player
.
hls
.
playlists
.
trigger
(
'mediachange'
);
player
.
trigger
(
'timeupdate'
);
player
.
hls
.
checkBuffer_
(
);
ok
(
requests
.
length
,
'made a request'
);
equal
(
requests
[
0
].
url
,
'http://media.example.com/fileSequence52-B.ts'
,
'requested the segment'
);
equal
(
requests
[
1
].
url
,
'https://priv.example.com/key.php?r=52'
,
'requested the
segment and
key'
);
'requested the key'
);
});
test
(
'reso
vl
es relative key URLs against the playlist'
,
function
()
{
test
(
'reso
lv
es relative key URLs against the playlist'
,
function
()
{
player
.
src
({
src
:
'https://example.com/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
...
...
@@ -2217,6 +2105,8 @@ test('resovles relative key URLs against the playlist', function() {
'#EXT-X-KEY:METHOD=AES-128,URI="key.php?r=52"\n'
+
'#EXTINF:2.833,\n'
+
'http://media.example.com/fileSequence52-A.ts\n'
);
standardXHRResponse
(
requests
.
shift
());
// segment
equal
(
requests
[
0
].
url
,
'https://example.com/key.php?r=52'
,
'resolves the key URL'
);
});
...
...
@@ -2243,11 +2133,11 @@ test('treats invalid keys as a key request failure', function() {
'#EXT-X-KEY:METHOD=NONE\n'
+
'#EXTINF:15.0,\n'
+
'http://media.example.com/fileSequence52-B.ts\n'
);
// segment request
standardXHRResponse
(
requests
.
shift
());
// keys should be 16 bytes long
requests
[
0
].
response
=
new
Uint8Array
(
1
).
buffer
;
requests
.
shift
().
respond
(
200
,
null
,
''
);
// segment request
standardXHRResponse
(
requests
.
shift
());
equal
(
requests
[
0
].
url
,
'https://priv.example.com/key.php?r=52'
,
'retries the key'
);
...
...
Please
register
or
sign in
to post a comment