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
50a78066
authored
2014-05-21 17:55:29 -0400
by
David LaPalomento
Browse Files
Options
Browse Files
Tag
Download
Plain Diff
Merge pull request #62 from videojs/tech2
HLS Tech
2 parents
fafd0563
d30df95f
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
539 additions
and
470 deletions
Gruntfile.js
example.html
package.json
src/aac-stream.js
src/bin-utils.js
src/exp-golomb.js
src/flv-tag.js
src/h264-stream.js
src/m3u8/m3u8-parser.js
src/playlist-loader.js
src/segment-parser.js
src/stream.js
src/videojs-hls.js
test/exp-golomb_test.js
test/flv-tag_test.js
test/h264-stream_test.js
test/muxer/index.html
test/playlist-loader_test.js
test/segment-parser.js
test/videojs-hls_test.js
Gruntfile.js
View file @
50a7806
...
...
@@ -45,9 +45,6 @@ module.exports = function(grunt) {
dest
:
'dist/videojs.hls.min.js'
}
},
qunit
:
{
files
:
[
'test/**/*.html'
,
'!test/perf.html'
,
'!test/muxer/**'
]
},
jshint
:
{
gruntfile
:
{
options
:
{
...
...
@@ -93,11 +90,11 @@ module.exports = function(grunt) {
},
src
:
{
files
:
'<%= jshint.src.src %>'
,
tasks
:
[
'jshint:src'
,
'
quni
t'
]
tasks
:
[
'jshint:src'
,
'
tes
t'
]
},
test
:
{
files
:
'<%= jshint.test.src %>'
,
tasks
:
[
'jshint:test'
,
'
quni
t'
]
tasks
:
[
'jshint:test'
,
'
tes
t'
]
}
},
concurrent
:
{
...
...
@@ -194,7 +191,6 @@ module.exports = function(grunt) {
grunt
.
loadNpmTasks
(
'grunt-contrib-clean'
);
grunt
.
loadNpmTasks
(
'grunt-contrib-concat'
);
grunt
.
loadNpmTasks
(
'grunt-contrib-uglify'
);
grunt
.
loadNpmTasks
(
'grunt-contrib-qunit'
);
grunt
.
loadNpmTasks
(
'grunt-contrib-jshint'
);
grunt
.
loadNpmTasks
(
'grunt-contrib-watch'
);
grunt
.
loadNpmTasks
(
'grunt-contrib-connect'
);
...
...
@@ -255,7 +251,7 @@ module.exports = function(grunt) {
[
'clean'
,
'jshint'
,
'manifests-to-js'
,
'
quni
t'
,
'
tes
t'
,
'concat'
,
'uglify'
]);
...
...
example.html
View file @
50a7806
...
...
@@ -31,7 +31,7 @@
<!-- bipbop -->
<!-- <script src="test/tsSegment.js"></script> -->
<!-- bunnies -->
<
script
src=
"test/tsSegment-bc.js"
></script
>
<
!--<script src="test/tsSegment-bc.js"></script>--
>
<style>
body
{
...
...
@@ -63,10 +63,12 @@
<script>
videojs
.
options
.
flash
.
swf
=
'node_modules/video.js/dist/video-js/video-js.swf'
;
// initialize the player
var
player
=
videojs
(
'video'
);
var
player
=
videojs
(
'video'
,
{
techOrder
:
[
'hls'
]
});
// initialize the plugin
player
.
hls
();
//player.hls()
</script>
</body>
</html>
...
...
package.json
View file @
50a7806
...
...
@@ -19,7 +19,6 @@
"grunt-contrib-concat"
:
"~0.3.0"
,
"grunt-contrib-connect"
:
"~0.6.0"
,
"grunt-contrib-jshint"
:
"~0.6.0"
,
"grunt-contrib-qunit"
:
"~0.2.0"
,
"grunt-contrib-uglify"
:
"~0.2.0"
,
"grunt-contrib-watch"
:
"~0.4.0"
,
"grunt-karma"
:
"~0.6.2"
,
...
...
src/aac-stream.js
View file @
50a7806
...
...
@@ -8,7 +8,7 @@
(
function
(
window
)
{
var
FlvTag
=
window
.
videojs
.
h
ls
.
FlvTag
,
FlvTag
=
window
.
videojs
.
H
ls
.
FlvTag
,
adtsSampleingRates
=
[
96000
,
88200
,
64000
,
48000
,
...
...
@@ -17,7 +17,7 @@ var
16000
,
12000
];
window
.
videojs
.
h
ls
.
AacStream
=
function
()
{
window
.
videojs
.
H
ls
.
AacStream
=
function
()
{
var
next_pts
,
// :uint
pts_offset
,
// :int
...
...
src/bin-utils.js
View file @
50a7806
...
...
@@ -29,5 +29,5 @@
}
};
window
.
videojs
.
h
ls
.
utils
=
module
;
window
.
videojs
.
H
ls
.
utils
=
module
;
})(
this
);
...
...
src/exp-golomb.js
View file @
50a7806
...
...
@@ -4,7 +4,7 @@
* Parser for exponential Golomb codes, a variable-bitwidth number encoding
* scheme used by h264.
*/
window
.
videojs
.
h
ls
.
ExpGolomb
=
function
(
workingData
)
{
window
.
videojs
.
H
ls
.
ExpGolomb
=
function
(
workingData
)
{
var
// the number of bytes left to examine in workingData
workingBytesAvailable
=
workingData
.
byteLength
,
...
...
src/flv-tag.js
View file @
50a7806
(
function
(
window
)
{
window
.
videojs
=
window
.
videojs
||
{};
window
.
videojs
.
hls
=
window
.
videojs
.
h
ls
||
{};
window
.
videojs
.
Hls
=
window
.
videojs
.
H
ls
||
{};
var
hls
=
window
.
videojs
.
h
ls
;
var
hls
=
window
.
videojs
.
H
ls
;
// (type:uint, extraData:Boolean = false) extends ByteArray
hls
.
FlvTag
=
function
(
type
,
extraData
)
{
...
...
src/h264-stream.js
View file @
50a7806
...
...
@@ -8,8 +8,8 @@
(
function
(
window
)
{
var
ExpGolomb
=
window
.
videojs
.
h
ls
.
ExpGolomb
,
FlvTag
=
window
.
videojs
.
h
ls
.
FlvTag
,
ExpGolomb
=
window
.
videojs
.
H
ls
.
ExpGolomb
,
FlvTag
=
window
.
videojs
.
H
ls
.
FlvTag
,
H264ExtraData
=
function
()
{
this
.
sps
=
[];
// :Array
...
...
@@ -234,7 +234,7 @@
* an h264 stream. Exactly one byte.
*/
// incomplete, see Table 7.1 of ITU-T H.264 for 12-32
window
.
videojs
.
h
ls
.
NALUnitType
=
NALUnitType
=
{
window
.
videojs
.
H
ls
.
NALUnitType
=
NALUnitType
=
{
unspecified
:
0
,
slice_layer_without_partitioning_rbsp_non_idr
:
1
,
slice_data_partition_a_layer_rbsp
:
2
,
...
...
@@ -249,7 +249,7 @@
end_of_stream_rbsp
:
11
};
window
.
videojs
.
h
ls
.
H264Stream
=
function
()
{
window
.
videojs
.
H
ls
.
H264Stream
=
function
()
{
var
next_pts
,
// :uint;
next_dts
,
// :uint;
...
...
src/m3u8/m3u8-parser.js
View file @
50a7806
...
...
@@ -32,7 +32,7 @@
}
return
result
;
},
Stream
=
videojs
.
h
ls
.
Stream
,
Stream
=
videojs
.
H
ls
.
Stream
,
LineStream
,
ParseStream
,
Parser
;
...
...
src/playlist-loader.js
View file @
50a7806
...
...
@@ -5,8 +5,8 @@
(
function
(
window
,
videojs
)
{
'use strict'
;
var
resolveUrl
=
videojs
.
h
ls
.
resolveUrl
,
xhr
=
videojs
.
h
ls
.
xhr
,
resolveUrl
=
videojs
.
H
ls
.
resolveUrl
,
xhr
=
videojs
.
H
ls
.
xhr
,
/**
* Returns a new master playlist that is the result of merging an
...
...
@@ -51,6 +51,7 @@
var
loader
=
this
,
media
,
mediaUpdateTimeout
,
request
,
haveMetadata
=
function
(
error
,
xhr
,
url
)
{
...
...
@@ -88,7 +89,7 @@
// refresh live playlists after a target duration passes
if
(
!
loader
.
media
().
endList
)
{
window
.
setTimeout
(
function
()
{
mediaUpdateTimeout
=
window
.
setTimeout
(
function
()
{
loader
.
trigger
(
'mediaupdatetimeout'
);
},
refreshDelay
);
}
...
...
@@ -104,6 +105,13 @@
loader
.
state
=
'HAVE_NOTHING'
;
loader
.
dispose
=
function
()
{
if
(
request
)
{
request
.
abort
();
}
window
.
clearTimeout
(
mediaUpdateTimeout
);
};
/**
* When called without any arguments, returns the currently
* active media playlist. When called with a single argument,
...
...
@@ -213,7 +221,9 @@
haveMetadata
(
error
,
this
,
parser
.
manifest
.
playlists
[
0
].
uri
);
if
(
!
error
)
{
loader
.
trigger
(
'loadedmetadata'
);
}
});
return
loader
.
trigger
(
'loadedplaylist'
);
}
...
...
@@ -231,7 +241,7 @@
return
loader
.
trigger
(
'loadedmetadata'
);
});
};
PlaylistLoader
.
prototype
=
new
videojs
.
h
ls
.
Stream
();
PlaylistLoader
.
prototype
=
new
videojs
.
H
ls
.
Stream
();
videojs
.
h
ls
.
PlaylistLoader
=
PlaylistLoader
;
videojs
.
H
ls
.
PlaylistLoader
=
PlaylistLoader
;
})(
window
,
window
.
videojs
);
...
...
src/segment-parser.js
View file @
50a7806
(
function
(
window
)
{
var
videojs
=
window
.
videojs
,
FlvTag
=
videojs
.
h
ls
.
FlvTag
,
H264Stream
=
videojs
.
h
ls
.
H264Stream
,
AacStream
=
videojs
.
h
ls
.
AacStream
,
FlvTag
=
videojs
.
H
ls
.
FlvTag
,
H264Stream
=
videojs
.
H
ls
.
H264Stream
,
AacStream
=
videojs
.
H
ls
.
AacStream
,
MP2T_PACKET_LENGTH
,
STREAM_TYPES
;
...
...
@@ -11,7 +11,7 @@
* An object that incrementally transmuxes MPEG2 Trasport Stream
* chunks into an FLV.
*/
videojs
.
h
ls
.
SegmentParser
=
function
()
{
videojs
.
H
ls
.
SegmentParser
=
function
()
{
var
self
=
this
,
parseTSPacket
,
...
...
@@ -432,8 +432,8 @@
};
// MPEG2-TS constants
videojs
.
h
ls
.
SegmentParser
.
MP2T_PACKET_LENGTH
=
MP2T_PACKET_LENGTH
=
188
;
videojs
.
h
ls
.
SegmentParser
.
STREAM_TYPES
=
STREAM_TYPES
=
{
videojs
.
H
ls
.
SegmentParser
.
MP2T_PACKET_LENGTH
=
MP2T_PACKET_LENGTH
=
188
;
videojs
.
H
ls
.
SegmentParser
.
STREAM_TYPES
=
STREAM_TYPES
=
{
h264
:
0x1b
,
adts
:
0x0f
};
...
...
src/stream.js
View file @
50a7806
...
...
@@ -65,5 +65,5 @@
});
};
videojs
.
h
ls
.
Stream
=
Stream
;
videojs
.
H
ls
.
Stream
=
Stream
;
})(
window
.
videojs
);
...
...
src/videojs-hls.js
View file @
50a7806
...
...
@@ -8,28 +8,6 @@
(
function
(
window
,
videojs
,
document
,
undefined
)
{
videojs
.
hls
=
{
/**
* Whether the browser has built-in HLS support.
*/
supportsNativeHls
:
(
function
()
{
var
video
=
document
.
createElement
(
'video'
),
xMpegUrl
,
vndMpeg
;
// native HLS is definitely not supported if HTML5 video isn't
if
(
!
videojs
.
Html5
.
isSupported
())
{
return
false
;
}
xMpegUrl
=
video
.
canPlayType
(
'application/x-mpegURL'
);
vndMpeg
=
video
.
canPlayType
(
'application/vnd.apple.mpegURL'
);
return
(
/probably|maybe/
).
test
(
xMpegUrl
)
||
(
/probably|maybe/
).
test
(
vndMpeg
);
})()
};
var
// the desired length of video to maintain in the buffer, in seconds
...
...
@@ -95,60 +73,7 @@ var
}
},
/**
* Creates and sends an XMLHttpRequest.
* @param options {string | object} if this argument is a string, it
* is intrepreted as a URL and a simple GET request is
* inititated. If it is an object, it should contain a `url`
* property that indicates the URL to request and optionally a
* `method` which is the type of HTTP request to send.
* @param callback (optional) {function} a function to call when the
* request completes. If the request was not successful, the first
* argument will be falsey.
* @return {object} the XMLHttpRequest that was initiated.
*/
xhr
=
videojs
.
hls
.
xhr
=
function
(
url
,
callback
)
{
var
options
=
{
method
:
'GET'
},
request
;
if
(
typeof
callback
!==
'function'
)
{
callback
=
function
()
{};
}
if
(
typeof
url
===
'object'
)
{
options
=
videojs
.
util
.
mergeOptions
(
options
,
url
);
url
=
options
.
url
;
}
request
=
new
window
.
XMLHttpRequest
();
request
.
open
(
options
.
method
,
url
);
if
(
options
.
responseType
)
{
request
.
responseType
=
options
.
responseType
;
}
if
(
options
.
withCredentials
)
{
request
.
withCredentials
=
true
;
}
request
.
onreadystatechange
=
function
()
{
// wait until the request completes
if
(
this
.
readyState
!==
4
)
{
return
;
}
// request error
if
(
this
.
status
>=
400
||
this
.
status
===
0
)
{
return
callback
.
call
(
this
,
true
,
url
);
}
return
callback
.
call
(
this
,
false
,
url
);
};
request
.
send
(
null
);
return
request
;
},
xhr
,
/**
* TODO - Document this great feature.
...
...
@@ -228,6 +153,12 @@ var
var
duration
=
0
,
segment
,
i
;
if
(
!
playlist
)
{
return
0
;
}
i
=
(
playlist
.
segments
||
[]).
length
;
// if present, use the duration specified in the playlist
...
...
@@ -247,134 +178,17 @@ var
return
duration
;
},
/**
* Constructs a new URI by interpreting a path relative to another
* URI.
* @param basePath {string} a relative or absolute URI
* @param path {string} a path part to combine with the base
* @return {string} a URI that is equivalent to composing `base`
* with `path`
* @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
*/
resolveUrl
=
videojs
.
hls
.
resolveUrl
=
function
(
basePath
,
path
)
{
// use the base element to get the browser to handle URI resolution
var
oldBase
=
document
.
querySelector
(
'base'
),
docHead
=
document
.
querySelector
(
'head'
),
a
=
document
.
createElement
(
'a'
),
base
=
oldBase
,
oldHref
,
result
;
resolveUrl
,
// prep the document
if
(
oldBase
)
{
oldHref
=
oldBase
.
href
;
}
else
{
base
=
docHead
.
appendChild
(
document
.
createElement
(
'base'
));
}
base
.
href
=
basePath
;
a
.
href
=
path
;
result
=
a
.
href
;
// clean up
if
(
oldBase
)
{
oldBase
.
href
=
oldHref
;
}
else
{
docHead
.
removeChild
(
base
);
}
return
result
;
},
/**
* Initializes the HLS plugin.
* @param options {mixed} the URL to an HLS playlist
*/
init
=
function
(
options
)
{
initSource
=
function
(
player
,
mediaSource
,
srcUrl
)
{
var
mediaSource
=
new
videojs
.
MediaSource
(),
segmentParser
=
new
videojs
.
hls
.
SegmentParser
(),
player
=
this
,
srcUrl
,
segmentParser
=
new
videojs
.
Hls
.
SegmentParser
(),
segmentXhr
,
settings
,
settings
=
videojs
.
util
.
mergeOptions
({},
player
.
options
().
hls
)
,
fillBuffer
,
updateDuration
;
// if the video element supports HLS natively, do nothing
if
(
videojs
.
hls
.
supportsNativeHls
)
{
return
;
}
settings
=
videojs
.
util
.
mergeOptions
({},
options
);
srcUrl
=
(
function
()
{
var
extname
,
i
=
0
,
j
=
0
,
src
=
player
.
el
().
querySelector
(
'.vjs-tech'
).
src
,
sources
=
player
.
options
().
sources
,
techName
,
length
=
sources
.
length
;
// use the URL specified in options if one was provided
if
(
typeof
options
===
'string'
)
{
return
options
;
}
else
if
(
options
&&
options
.
url
)
{
return
options
.
url
;
}
// src attributes take precedence over source children
if
(
src
)
{
// assume files with the m3u8 extension are HLS
extname
=
(
/
[^
#?
]
*
(?:\/[^
#?
]
*
\.([^
#?
]
*
))
/
).
exec
(
src
);
if
(
extname
&&
extname
[
1
]
===
'm3u8'
)
{
return
src
;
}
return
;
}
// find the first playable source
for
(;
i
<
length
;
i
++
)
{
// ignore sources without a specified type
if
(
!
sources
[
i
].
type
)
{
continue
;
}
// do nothing if the source is handled by one of the standard techs
for
(
j
in
player
.
options
().
techOrder
)
{
techName
=
player
.
options
().
techOrder
[
j
];
techName
=
techName
[
0
].
toUpperCase
()
+
techName
.
substring
(
1
);
if
(
videojs
[
techName
].
canPlaySource
({
type
:
sources
[
i
].
type
}))
{
return
;
}
}
// use the plugin if the MIME type specifies HLS
if
((
/application
\/
x-mpegURL/
).
test
(
sources
[
i
].
type
)
||
(
/application
\/
vnd
\.
apple
\.
mpegURL/
).
test
(
sources
[
i
].
type
))
{
return
sources
[
i
].
src
;
}
}
})();
if
(
!
srcUrl
)
{
// do nothing until the plugin is initialized with a valid URL
videojs
.
log
(
'hls: no valid playlist URL specified'
);
return
;
}
// expose the HLS plugin state
player
.
hls
.
readyState
=
function
()
{
if
(
!
player
.
hls
.
media
)
{
return
0
;
// HAVE_NOTHING
}
return
1
;
// HAVE_METADATA
};
player
.
on
(
'seeking'
,
function
()
{
var
currentTime
=
player
.
currentTime
();
...
...
@@ -397,16 +211,8 @@ var
* Update the player duration
*/
updateDuration
=
function
(
playlist
)
{
var
tech
;
// update the duration
player
.
duration
(
totalDuration
(
playlist
));
// tell the flash tech of the new duration
tech
=
player
.
el
().
querySelector
(
'.vjs-tech'
);
if
(
tech
.
vjs_setProperty
)
{
tech
.
vjs_setProperty
(
'duration'
,
player
.
duration
());
}
// manually fire the duration change
player
.
trigger
(
'durationchange'
);
};
/**
...
...
@@ -502,7 +308,8 @@ var
}
// if no segments are available, do nothing
if
(
!
player
.
hls
.
playlists
.
media
().
segments
)
{
if
(
player
.
hls
.
playlists
.
state
===
"HAVE_NOTHING"
||
!
player
.
hls
.
playlists
.
media
().
segments
)
{
return
;
}
...
...
@@ -609,7 +416,7 @@ var
player
.
hls
.
mediaIndex
=
0
;
player
.
hls
.
playlists
=
new
videojs
.
h
ls
.
PlaylistLoader
(
srcUrl
,
settings
.
withCredentials
);
new
videojs
.
H
ls
.
PlaylistLoader
(
srcUrl
,
settings
.
withCredentials
);
player
.
hls
.
playlists
.
on
(
'loadedmetadata'
,
function
()
{
oldMediaPlaylist
=
player
.
hls
.
playlists
.
media
();
...
...
@@ -638,28 +445,160 @@ var
oldMediaPlaylist
=
updatedPlaylist
;
});
});
player
.
src
([{
};
var
mpegurlRE
=
/^application
\/(?:
x-|vnd
\.
apple
\.)
mpegurl/i
;
videojs
.
Hls
=
videojs
.
Flash
.
extend
({
init
:
function
(
player
,
options
,
ready
)
{
var
source
=
options
.
source
,
settings
=
player
.
options
();
player
.
hls
=
this
;
delete
options
.
source
;
options
.
swf
=
settings
.
flash
.
swf
;
videojs
.
Flash
.
call
(
this
,
player
,
options
,
ready
);
options
.
source
=
source
;
videojs
.
Hls
.
prototype
.
src
.
call
(
this
,
options
.
source
&&
options
.
source
.
src
);
}
});
videojs
.
Hls
.
prototype
.
src
=
function
(
src
)
{
var
player
=
this
.
player
(),
mediaSource
,
source
;
if
(
src
)
{
mediaSource
=
new
videojs
.
MediaSource
();
source
=
{
src
:
videojs
.
URL
.
createObjectURL
(
mediaSource
),
type
:
"video/flv"
}]);
};
this
.
mediaSource
=
mediaSource
;
initSource
(
player
,
mediaSource
,
src
);
this
.
ready
(
function
()
{
this
.
el
().
vjs_src
(
source
.
src
);
});
}
};
if
(
player
.
options
().
autoplay
)
{
player
.
play
();
videojs
.
Hls
.
prototype
.
duration
=
function
()
{
var
playlists
=
this
.
playlists
;
if
(
playlists
)
{
return
totalDuration
(
playlists
.
media
());
}
return
0
;
};
videojs
.
Hls
.
prototype
.
dispose
=
function
()
{
if
(
this
.
playlists
)
{
this
.
playlists
.
dispose
();
}
videojs
.
Flash
.
prototype
.
dispose
.
call
(
this
);
};
videojs
.
Hls
.
isSupported
=
function
()
{
return
videojs
.
Flash
.
isSupported
()
&&
videojs
.
MediaSource
;
};
videojs
.
Hls
.
canPlaySource
=
function
(
srcObj
)
{
return
mpegurlRE
.
test
(
srcObj
.
type
)
||
videojs
.
Flash
.
canPlaySource
.
call
(
this
,
srcObj
);
};
/**
* Creates and sends an XMLHttpRequest.
* @param options {string | object} if this argument is a string, it
* is intrepreted as a URL and a simple GET request is
* inititated. If it is an object, it should contain a `url`
* property that indicates the URL to request and optionally a
* `method` which is the type of HTTP request to send.
* @param callback (optional) {function} a function to call when the
* request completes. If the request was not successful, the first
* argument will be falsey.
* @return {object} the XMLHttpRequest that was initiated.
*/
xhr
=
videojs
.
Hls
.
xhr
=
function
(
url
,
callback
)
{
var
options
=
{
method
:
'GET'
},
request
;
if
(
typeof
callback
!==
'function'
)
{
callback
=
function
()
{};
}
if
(
typeof
url
===
'object'
)
{
options
=
videojs
.
util
.
mergeOptions
(
options
,
url
);
url
=
options
.
url
;
}
};
videojs
.
plugin
(
'hls'
,
function
()
{
if
(
typeof
Uint8Array
===
'undefined'
)
{
request
=
new
window
.
XMLHttpRequest
();
request
.
open
(
options
.
method
,
url
);
if
(
options
.
responseType
)
{
request
.
responseType
=
options
.
responseType
;
}
if
(
options
.
withCredentials
)
{
request
.
withCredentials
=
true
;
}
request
.
onreadystatechange
=
function
()
{
// wait until the request completes
if
(
this
.
readyState
!==
4
)
{
return
;
}
var
initialize
=
function
()
{
return
function
()
{
this
.
hls
=
initialize
();
init
.
apply
(
this
,
arguments
);
};
// request error
if
(
this
.
status
>=
400
||
this
.
status
===
0
)
{
return
callback
.
call
(
this
,
true
,
url
);
}
return
callback
.
call
(
this
,
false
,
url
);
};
initialize
().
apply
(
this
,
arguments
);
});
request
.
send
(
null
);
return
request
;
};
/**
* Constructs a new URI by interpreting a path relative to another
* URI.
* @param basePath {string} a relative or absolute URI
* @param path {string} a path part to combine with the base
* @return {string} a URI that is equivalent to composing `base`
* with `path`
* @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
*/
resolveUrl
=
videojs
.
Hls
.
resolveUrl
=
function
(
basePath
,
path
)
{
// use the base element to get the browser to handle URI resolution
var
oldBase
=
document
.
querySelector
(
'base'
),
docHead
=
document
.
querySelector
(
'head'
),
a
=
document
.
createElement
(
'a'
),
base
=
oldBase
,
oldHref
,
result
;
// prep the document
if
(
oldBase
)
{
oldHref
=
oldBase
.
href
;
}
else
{
base
=
docHead
.
appendChild
(
document
.
createElement
(
'base'
));
}
base
.
href
=
basePath
;
a
.
href
=
path
;
result
=
a
.
href
;
// clean up
if
(
oldBase
)
{
oldBase
.
href
=
oldHref
;
}
else
{
docHead
.
removeChild
(
base
);
}
return
result
;
};
})(
window
,
window
.
videojs
,
document
);
...
...
test/exp-golomb_test.js
View file @
50a7806
...
...
@@ -21,7 +21,7 @@
*/
var
buffer
,
ExpGolomb
=
window
.
videojs
.
h
ls
.
ExpGolomb
,
ExpGolomb
=
window
.
videojs
.
H
ls
.
ExpGolomb
,
expGolomb
;
module
(
'Exponential Golomb coding'
);
...
...
test/flv-tag_test.js
View file @
50a7806
...
...
@@ -19,7 +19,7 @@
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
FlvTag
=
window
.
videojs
.
h
ls
.
FlvTag
;
var
FlvTag
=
window
.
videojs
.
H
ls
.
FlvTag
;
module
(
'FLV tag'
);
...
...
test/h264-stream_test.js
View file @
50a7806
...
...
@@ -2,12 +2,12 @@
module
(
'H264 Stream'
);
var
nalUnitTypes
=
window
.
videojs
.
h
ls
.
NALUnitType
,
FlvTag
=
window
.
videojs
.
h
ls
.
FlvTag
;
nalUnitTypes
=
window
.
videojs
.
H
ls
.
NALUnitType
,
FlvTag
=
window
.
videojs
.
H
ls
.
FlvTag
;
test
(
'metadata is generated for IDRs after a full NAL unit is written'
,
function
()
{
var
h264Stream
=
new
videojs
.
h
ls
.
H264Stream
(),
h264Stream
=
new
videojs
.
H
ls
.
H264Stream
(),
accessUnitDelimiter
=
new
Uint8Array
([
0x00
,
0x00
,
...
...
@@ -62,7 +62,7 @@ test('metadata is generated for IDRs after a full NAL unit is written', function
test
(
'starting PTS values can be negative'
,
function
()
{
var
h264Stream
=
new
videojs
.
h
ls
.
H264Stream
(),
h264Stream
=
new
videojs
.
H
ls
.
H264Stream
(),
accessUnitDelimiter
=
new
Uint8Array
([
0x00
,
0x00
,
...
...
test/muxer/index.html
View file @
50a7806
...
...
@@ -127,7 +127,7 @@
original
.
addEventListener
(
'change'
,
function
()
{
var
reader
=
new
FileReader
();
reader
.
addEventListener
(
'loadend'
,
function
()
{
var
parser
=
new
videojs
.
h
ls
.
SegmentParser
(),
var
parser
=
new
videojs
.
H
ls
.
SegmentParser
(),
tags
=
[
parser
.
getFlvHeader
()],
tag
,
hex
,
...
...
@@ -164,7 +164,7 @@
}
hex
=
'<pre>'
hex
+=
videojs
.
h
ls
.
utils
.
hexDump
(
data
);
hex
+=
videojs
.
H
ls
.
utils
.
hexDump
(
data
);
hex
+=
'</pre>'
vjsOutput
.
innerHTML
=
hex
;
...
...
@@ -201,7 +201,7 @@
}
// output the hex dump
hex
+=
videojs
.
h
ls
.
utils
.
hexDump
(
bytes
);
hex
+=
videojs
.
H
ls
.
utils
.
hexDump
(
bytes
);
hex
+=
'</pre>'
;
workingOutput
.
innerHTML
=
hex
;
});
...
...
test/playlist-loader_test.js
View file @
50a7806
...
...
@@ -36,26 +36,26 @@
test
(
'throws if the playlist url is empty or undefined'
,
function
()
{
throws
(
function
()
{
videojs
.
h
ls
.
PlaylistLoader
();
videojs
.
H
ls
.
PlaylistLoader
();
},
'requires an argument'
);
throws
(
function
()
{
videojs
.
h
ls
.
PlaylistLoader
(
''
);
videojs
.
H
ls
.
PlaylistLoader
(
''
);
},
'does not accept the empty string'
);
});
test
(
'starts without any metadata'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
strictEqual
(
loader
.
state
,
'HAVE_NOTHING'
,
'no metadata has loaded yet'
);
});
test
(
'requests the initial playlist immediately'
,
function
()
{
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
strictEqual
(
requests
.
length
,
1
,
'made a request'
);
strictEqual
(
requests
[
0
].
url
,
'master.m3u8'
,
'requested the initial playlist'
);
});
test
(
'moves to HAVE_MASTER after loading a master playlist'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-STREAM-INF:\n'
+
...
...
@@ -67,7 +67,7 @@
test
(
'jumps to HAVE_METADATA when initialized with a media playlist'
,
function
()
{
var
loadedmetadatas
=
0
,
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'media.m3u8'
);
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'media.m3u8'
);
loader
.
on
(
'loadedmetadata'
,
function
()
{
loadedmetadatas
++
;
});
...
...
@@ -85,7 +85,7 @@
});
test
(
'jumps to HAVE_METADATA when initialized with a live media playlist'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'media.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'media.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXTINF:10,\n'
+
...
...
@@ -99,7 +99,7 @@
var
loadedPlaylist
=
0
,
loadedMetadata
=
0
,
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
loader
.
on
(
'loadedplaylist'
,
function
()
{
loadedPlaylist
++
;
});
...
...
@@ -131,7 +131,7 @@
});
test
(
'moves to HAVE_CURRENT_METADATA when refreshing the playlist'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'live.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'live.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXTINF:10,\n'
+
...
...
@@ -145,7 +145,7 @@
});
test
(
'returns to HAVE_METADATA after refreshing the playlist'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'live.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'live.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXTINF:10,\n'
+
...
...
@@ -161,7 +161,7 @@
test
(
'emits an error when an initial playlist request fails'
,
function
()
{
var
errors
=
[],
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
loader
.
on
(
'error'
,
function
()
{
errors
.
push
(
loader
.
error
);
...
...
@@ -175,7 +175,7 @@
test
(
'errors when an initial media playlist request fails'
,
function
()
{
var
errors
=
[],
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
loader
.
on
(
'error'
,
function
()
{
errors
.
push
(
loader
.
error
);
...
...
@@ -197,7 +197,7 @@
// http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4
test
(
'halves the refresh timeout if a playlist is unchanged'
+
'since the last reload'
,
function
()
{
new
videojs
.
h
ls
.
PlaylistLoader
(
'live.m3u8'
);
new
videojs
.
H
ls
.
PlaylistLoader
(
'live.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:0\n'
+
...
...
@@ -218,7 +218,7 @@
});
test
(
'media-sequence updates are considered a playlist change'
,
function
()
{
new
videojs
.
h
ls
.
PlaylistLoader
(
'live.m3u8'
);
new
videojs
.
H
ls
.
PlaylistLoader
(
'live.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:0\n'
+
...
...
@@ -238,7 +238,7 @@
test
(
'emits an error if a media refresh fails'
,
function
()
{
var
errors
=
0
,
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'live.m3u8'
);
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'live.m3u8'
);
loader
.
on
(
'error'
,
function
()
{
errors
++
;
...
...
@@ -256,7 +256,7 @@
});
test
(
'switches media playlists when requested'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-STREAM-INF:BANDWIDTH=1\n'
+
...
...
@@ -284,7 +284,7 @@
});
test
(
'can switch media playlists based on URI'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-STREAM-INF:BANDWIDTH=1\n'
+
...
...
@@ -312,7 +312,7 @@
});
test
(
'aborts in-flight playlist refreshes when switching'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-STREAM-INF:BANDWIDTH=1\n'
+
...
...
@@ -331,7 +331,7 @@
});
test
(
'switching to the active playlist is a no-op'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-STREAM-INF:BANDWIDTH=1\n'
+
...
...
@@ -350,7 +350,7 @@
});
test
(
'throws an error if a media switch is initiated too early'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
throws
(
function
()
{
loader
.
media
(
'high.m3u8'
);
...
...
@@ -368,7 +368,7 @@
});
test
(
'throws an error if a switch to an unrecognized playlist is requested'
,
function
()
{
var
loader
=
new
videojs
.
h
ls
.
PlaylistLoader
(
'master.m3u8'
);
var
loader
=
new
videojs
.
H
ls
.
PlaylistLoader
(
'master.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-STREAM-INF:BANDWIDTH=1\n'
+
...
...
@@ -378,4 +378,31 @@
loader
.
media
(
'unrecognized.m3u8'
);
},
'throws an error'
);
});
test
(
'dispose cancels the refresh timeout'
,
function
()
{
var
loader
=
new
videojs
.
Hls
.
PlaylistLoader
(
'live.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:0\n'
+
'#EXTINF:10,\n'
+
'0.ts\n'
);
loader
.
dispose
();
// a lot of time passes...
clock
.
tick
(
15
*
1000
);
strictEqual
(
requests
.
length
,
0
,
'no refresh request was made'
);
});
test
(
'dispose aborts pending refresh requests'
,
function
()
{
var
loader
=
new
videojs
.
Hls
.
PlaylistLoader
(
'live.m3u8'
);
requests
.
pop
().
respond
(
200
,
null
,
'#EXTM3U\n'
+
'#EXT-X-MEDIA-SEQUENCE:0\n'
+
'#EXTINF:10,\n'
+
'0.ts\n'
);
clock
.
tick
(
10
*
1000
);
loader
.
dispose
();
ok
(
requests
[
0
].
aborted
,
'refresh request aborted'
);
});
})(
window
);
...
...
test/segment-parser.js
View file @
50a7806
...
...
@@ -39,7 +39,7 @@
module
(
'segment parser'
,
{
setup
:
function
()
{
parser
=
new
window
.
videojs
.
h
ls
.
SegmentParser
();
parser
=
new
window
.
videojs
.
H
ls
.
SegmentParser
();
}
});
...
...
@@ -168,11 +168,11 @@
result
=
result
.
concat
(
makePsi
(
settings
));
// ensure the resulting packet is the correct size
result
.
length
=
window
.
videojs
.
h
ls
.
SegmentParser
.
MP2T_PACKET_LENGTH
;
result
.
length
=
window
.
videojs
.
H
ls
.
SegmentParser
.
MP2T_PACKET_LENGTH
;
return
result
;
},
h264Type
=
window
.
videojs
.
h
ls
.
SegmentParser
.
STREAM_TYPES
.
h264
,
adtsType
=
window
.
videojs
.
h
ls
.
SegmentParser
.
STREAM_TYPES
.
adts
;
h264Type
=
window
.
videojs
.
H
ls
.
SegmentParser
.
STREAM_TYPES
.
h264
,
adtsType
=
window
.
videojs
.
H
ls
.
SegmentParser
.
STREAM_TYPES
.
adts
;
parser
.
parseSegmentBinaryData
(
new
Uint8Array
(
makePacket
({
programs
:
{
...
...
test/videojs-hls_test.js
View file @
50a7806
(
function
(
window
,
videojs
,
undefined
)
{
'use strict'
;
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
...
...
@@ -22,15 +23,37 @@
var
player
,
old
FlashSupported
,
old
MediaSourceOpen
,
oldSegmentParser
,
oldSetTimeout
,
oldSourceBuffer
,
oldSupportsNativeHls
,
xhrUrls
,
oldFlashSupported
,
requests
,
xhr
,
createPlayer
=
function
(
options
)
{
var
tech
,
video
,
player
;
video
=
document
.
createElement
(
'video'
);
document
.
querySelector
(
'#qunit-fixture'
).
appendChild
(
video
);
player
=
videojs
(
video
,
{
flash
:
{
swf
:
''
},
techOrder
:
[
'hls'
],
hls
:
options
||
{}
});
player
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
0
);
};
tech
=
player
.
el
().
querySelector
(
'.vjs-tech'
);
tech
.
vjs_getProperty
=
function
()
{};
tech
.
vjs_src
=
function
()
{};
videojs
.
Flash
.
onReady
(
tech
.
id
);
return
player
;
},
standardXHRResponse
=
function
(
request
)
{
if
(
!
request
.
url
)
{
return
;
...
...
@@ -82,37 +105,23 @@ var
module
(
'HLS'
,
{
setup
:
function
()
{
oldMediaSourceOpen
=
videojs
.
MediaSource
.
open
;
videojs
.
MediaSource
.
open
=
function
()
{};
// mock out Flash features for phantomjs
oldFlashSupported
=
videojs
.
Flash
.
isSupported
;
videojs
.
Flash
.
isSupported
=
function
()
{
return
true
;
};
oldSourceBuffer
=
window
.
videojs
.
SourceBuffer
;
window
.
videojs
.
SourceBuffer
=
function
()
{
this
.
appendBuffer
=
function
()
{};
this
.
abort
=
function
()
{};
};
// force native HLS to be ignored
oldSupportsNativeHls
=
videojs
.
hls
.
supportsNativeHls
;
videojs
.
hls
.
supportsNativeHls
=
false
;
// create the test player
var
video
=
document
.
createElement
(
'video'
);
document
.
querySelector
(
'#qunit-fixture'
).
appendChild
(
video
);
player
=
videojs
(
video
,
{
flash
:
{
swf
:
'../node_modules/video.js/dist/video-js/video-js.swf'
},
techOrder
:
[
'flash'
]
});
player
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
0
);
};
// store functionality that some tests need to mock
oldSegmentParser
=
videojs
.
h
ls
.
SegmentParser
;
oldSegmentParser
=
videojs
.
H
ls
.
SegmentParser
;
oldSetTimeout
=
window
.
setTimeout
;
// fake XHRs
...
...
@@ -121,13 +130,16 @@ module('HLS', {
xhr
.
onCreate
=
function
(
xhr
)
{
requests
.
push
(
xhr
);
};
xhrUrls
=
[];
// create the test player
player
=
createPlayer
();
},
teardown
:
function
()
{
player
.
dispose
();
videojs
.
Flash
.
isSupported
=
oldFlashSupported
;
videojs
.
hls
.
supportsNativeHls
=
oldSupportsNativeHls
;
videojs
.
h
ls
.
SegmentParser
=
oldSegmentParser
;
videojs
.
MediaSource
.
open
=
oldMediaSourceOpen
;
videojs
.
H
ls
.
SegmentParser
=
oldSegmentParser
;
videojs
.
SourceBuffer
=
oldSourceBuffer
;
window
.
setTimeout
=
oldSetTimeout
;
xhr
.
restore
();
...
...
@@ -140,8 +152,11 @@ test('starts playing if autoplay is specified', function() {
plays
++
;
};
player
.
options
().
autoplay
=
true
;
player
.
hls
(
'manifest/playlist.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/playlist.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -155,9 +170,12 @@ test('creates a PlaylistLoader on init', function() {
loadedmetadata
=
true
;
});
player
.
hls
(
'manifest/playlist.m3u8'
);
ok
(
!
player
.
hls
.
playlists
,
'waits for sourceopen to create the loader'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
ok
(
!
player
.
hls
.
playlists
,
'waits for set src to create the loader'
);
player
.
src
({
src
:
'manifest/playlist.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
standardXHRResponse
(
requests
[
0
]);
...
...
@@ -178,8 +196,11 @@ test('sets the duration if one is available on the playlist', function() {
}
calls
++
;
};
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -197,8 +218,11 @@ test('calculates the duration if needed', function() {
}
durations
.
push
(
duration
);
};
player
.
hls
(
'http://example.com/manifest/missingExtinf.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'http://example.com/manifest/missingExtinf.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -210,11 +234,14 @@ test('calculates the duration if needed', function() {
});
test
(
'starts downloading a segment on loadedmetadata'
,
function
()
{
player
.
hls
(
'manifest/media.m3u8'
);
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
0
);
};
videojs
.
mediaSources
[
player
.
currentSrc
()]
.
trigger
({
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -228,8 +255,11 @@ test('starts downloading a segment on loadedmetadata', function() {
});
test
(
'recognizes absolute URIs and requests them unmodified'
,
function
()
{
player
.
hls
(
'manifest/absoluteUris.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/absoluteUris.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -241,8 +271,11 @@ test('recognizes absolute URIs and requests them unmodified', function() {
});
test
(
'recognizes domain-relative URLs'
,
function
()
{
player
.
hls
(
'manifest/domainUris.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/domainUris.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -253,14 +286,31 @@ test('recognizes domain-relative URLs', function() {
'the first segment is requested'
);
});
test
(
're-initializes the plugin for each source'
,
function
()
{
var
firstInit
,
secondInit
;
player
.
hls
(
'manifest/master.m3u8'
);
firstInit
=
player
.
hls
;
player
.
hls
(
'manifest/master.m3u8'
);
secondInit
=
player
.
hls
;
test
(
're-initializes the tech for each source'
,
function
()
{
var
firstPlaylists
,
secondPlaylists
,
firstMSE
,
secondMSE
;
notStrictEqual
(
firstInit
,
secondInit
,
'the plugin object is replaced'
);
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
firstPlaylists
=
player
.
hls
.
playlists
;
firstMSE
=
player
.
hls
.
mediaSource
;
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
secondPlaylists
=
player
.
hls
.
playlists
;
secondMSE
=
player
.
hls
.
mediaSource
;
notStrictEqual
(
firstPlaylists
,
secondPlaylists
,
'the playlist object is not reused'
);
notStrictEqual
(
firstMSE
,
secondMSE
,
'the media source object is not reused'
);
});
test
(
'triggers an error when a master playlist request errors'
,
function
()
{
...
...
@@ -268,8 +318,11 @@ test('triggers an error when a master playlist request errors', function() {
player
.
on
(
'error'
,
function
()
{
error
=
player
.
hls
.
error
;
});
player
.
hls
(
'manifest/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
requests
.
pop
().
respond
(
500
);
...
...
@@ -279,8 +332,11 @@ test('triggers an error when a master playlist request errors', function() {
});
test
(
'downloads media playlists after loading the master'
,
function
()
{
player
.
hls
(
'manifest/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -309,8 +365,11 @@ test('timeupdates do not check to fill the buffer until a media playlist is read
};
this
.
send
=
function
()
{};
};
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
player
.
trigger
(
'timeupdate'
);
...
...
@@ -320,8 +379,11 @@ test('timeupdates do not check to fill the buffer until a media playlist is read
});
test
(
'calculates the bandwidth after downloading a segment'
,
function
()
{
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -337,12 +399,15 @@ test('calculates the bandwidth after downloading a segment', function() {
test
(
'selects a playlist after segment downloads'
,
function
()
{
var
calls
=
0
;
player
.
hls
(
'manifest/master.m3u8'
);
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
selectPlaylist
=
function
()
{
calls
++
;
return
player
.
hls
.
playlists
.
master
.
playlists
[
0
];
};
videojs
.
mediaSources
[
player
.
currentSrc
()]
.
trigger
({
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -367,8 +432,11 @@ test('selects a playlist after segment downloads', function() {
test
(
'moves to the next segment if there is a network error'
,
function
()
{
var
mediaIndex
;
player
.
hls
(
'manifest/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -386,7 +454,10 @@ test('updates the duration after switching playlists', function() {
var
calls
=
0
,
selectedPlaylist
=
false
;
player
.
hls
(
'manifest/master.m3u8'
);
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
selectPlaylist
=
function
()
{
selectedPlaylist
=
true
;
return
player
.
hls
.
playlists
.
master
.
playlists
[
1
];
...
...
@@ -400,7 +471,7 @@ test('updates the duration after switching playlists', function() {
calls
++
;
}
};
videojs
.
mediaSources
[
player
.
currentSrc
()]
.
trigger
({
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -418,8 +489,11 @@ test('downloads additional playlists if required', function() {
playlist
=
{
uri
:
'media3.m3u8'
};
player
.
hls
(
'manifest/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -456,8 +530,11 @@ test('downloads additional playlists if required', function() {
test
(
'selects a playlist below the current bandwidth'
,
function
()
{
var
playlist
;
player
.
hls
(
'manifest/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -478,8 +555,11 @@ test('selects a playlist below the current bandwidth', function() {
test
(
'raises the minimum bitrate for a stream proportionially'
,
function
()
{
var
playlist
;
player
.
hls
(
'manifest/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -500,8 +580,11 @@ test('raises the minimum bitrate for a stream proportionially', function() {
test
(
'uses the lowest bitrate if no other is suitable'
,
function
()
{
var
playlist
;
player
.
hls
(
'manifest/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -520,9 +603,12 @@ test('uses the lowest bitrate if no other is suitable', function() {
test
(
'selects the correct rendition by player dimensions'
,
function
()
{
var
playlist
;
player
.
hls
(
'manifest/master.m3u8'
);
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
videojs
.
mediaSources
[
player
.
currentSrc
()]
.
trigger
({
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -550,14 +636,17 @@ test('selects the correct rendition by player dimensions', function() {
test
(
'does not download the next segment if the buffer is full'
,
function
()
{
player
.
hls
(
'manifest/media.m3u8'
);
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
currentTime
=
function
()
{
return
15
;
};
player
.
buffered
=
function
()
{
return
videojs
.
createTimeRange
(
0
,
20
);
};
videojs
.
mediaSources
[
player
.
currentSrc
()]
.
trigger
({
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -569,8 +658,11 @@ test('does not download the next segment if the buffer is full', function() {
});
test
(
'downloads the next segment if the buffer is getting low'
,
function
()
{
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -597,8 +689,11 @@ test('downloads the next segment if the buffer is getting low', function() {
});
test
(
'stops downloading segments at the end of the playlist'
,
function
()
{
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
standardXHRResponse
(
requests
[
0
]);
...
...
@@ -606,13 +701,16 @@ test('stops downloading segments at the end of the playlist', function() {
player
.
hls
.
mediaIndex
=
4
;
player
.
trigger
(
'timeupdate'
);
strictEqual
(
xhrUrl
s
.
length
,
0
,
'no request is made'
);
strictEqual
(
request
s
.
length
,
0
,
'no request is made'
);
});
test
(
'only makes one segment request at a time'
,
function
()
{
var
openedXhrs
=
0
;
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
xhr
.
restore
();
...
...
@@ -634,68 +732,12 @@ test('only makes one segment request at a time', function() {
xhr
=
sinon
.
useFakeXMLHttpRequest
();
});
test
(
'uses the src attribute if no options are provided and it ends in ".m3u8"'
,
function
()
{
var
url
=
'http://example.com/services/mobile/streaming/index/master.m3u8?videoId=1824650741001'
;
player
.
el
().
querySelector
(
'.vjs-tech'
).
src
=
url
;
player
.
hls
();
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
type
:
'sourceopen'
});
strictEqual
(
requests
[
0
].
url
,
url
,
'currentSrc is used'
);
});
test
(
'ignores src attribute if it doesn\'t have the "m3u8" extension'
,
function
()
{
var
tech
=
player
.
el
().
querySelector
(
'.vjs-tech'
);
tech
.
src
=
'basdfasdfasdfliel//.m3u9'
;
player
.
hls
();
ok
(
!
(
player
.
currentSrc
()
in
videojs
.
mediaSources
),
'no media source is created'
);
strictEqual
(
requests
.
length
,
0
,
'no request is made'
);
tech
.
src
=
''
;
player
.
hls
();
ok
(
!
(
player
.
currentSrc
()
in
videojs
.
mediaSources
),
'no media source is created'
);
strictEqual
(
requests
.
length
,
0
,
'no request is made'
);
tech
.
src
=
'http://example.com/movie.mp4?q=why.m3u8'
;
player
.
hls
();
ok
(
!
(
player
.
currentSrc
()
in
videojs
.
mediaSources
),
'no media source is created'
);
strictEqual
(
requests
.
length
,
0
,
'no request is made'
);
tech
.
src
=
'http://example.m3u8/movie.mp4'
;
player
.
hls
();
ok
(
!
(
player
.
currentSrc
()
in
videojs
.
mediaSources
),
'no media source is created'
);
strictEqual
(
requests
.
length
,
0
,
'no request is made'
);
tech
.
src
=
'//example.com/movie.mp4#http://tricky.com/master.m3u8'
;
player
.
hls
();
ok
(
!
(
player
.
currentSrc
()
in
videojs
.
mediaSources
),
'no media source is created'
);
strictEqual
(
requests
.
length
,
0
,
'no request is made'
);
});
test
(
'activates if the first playable source is HLS'
,
function
()
{
var
video
;
document
.
querySelector
(
'#qunit-fixture'
).
innerHTML
=
'<video controls>'
+
'<source type="slartibartfast$%" src="movie.slarti">'
+
'<source type="application/x-mpegURL" src="movie.m3u8">'
+
'<source type="video/mp4" src="movie.mp4">'
+
'</video>'
;
video
=
document
.
querySelector
(
'#qunit-fixture video'
);
player
=
videojs
(
video
,
{
flash
:
{
swf
:
'../node_modules/video.js/dist/video-js/video-js.swf'
},
techOrder
:
[
'flash'
]
});
player
.
hls
();
ok
(
player
.
currentSrc
()
in
videojs
.
mediaSources
,
'media source created'
);
});
test
(
'cancels outstanding XHRs when seeking'
,
function
()
{
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
standardXHRResponse
(
requests
[
0
]);
...
...
@@ -721,7 +763,7 @@ test('cancels outstanding XHRs when seeking', function() {
test
(
'flushes the parser after each segment'
,
function
()
{
var
flushes
=
0
;
// mock out the segment parser
videojs
.
h
ls
.
SegmentParser
=
function
()
{
videojs
.
H
ls
.
SegmentParser
=
function
()
{
this
.
getFlvHeader
=
function
()
{
return
[];
};
...
...
@@ -732,8 +774,11 @@ test('flushes the parser after each segment', function() {
this
.
tagsAvailable
=
function
()
{};
};
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -749,7 +794,7 @@ test('drops tags before the target timestamp when seeking', function() {
bytes
=
[];
// mock out the parser and source buffer
videojs
.
h
ls
.
SegmentParser
=
mockSegmentParser
(
tags
);
videojs
.
H
ls
.
SegmentParser
=
mockSegmentParser
(
tags
);
window
.
videojs
.
SourceBuffer
=
function
()
{
this
.
appendBuffer
=
function
(
chunk
)
{
bytes
.
push
(
chunk
);
...
...
@@ -764,8 +809,11 @@ test('drops tags before the target timestamp when seeking', function() {
// push a tag into the buffer
tags
.
push
({
pts
:
0
,
bytes
:
0
});
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
standardXHRResponse
(
requests
[
0
]);
...
...
@@ -803,7 +851,7 @@ test('clears pending buffer updates when seeking', function() {
tags
=
[{
pts
:
0
,
bytes
:
0
}];
// mock out the parser and source buffer
videojs
.
h
ls
.
SegmentParser
=
mockSegmentParser
(
tags
);
videojs
.
H
ls
.
SegmentParser
=
mockSegmentParser
(
tags
);
window
.
videojs
.
SourceBuffer
=
function
()
{
this
.
appendBuffer
=
function
(
chunk
)
{
bytes
.
push
(
chunk
);
...
...
@@ -818,8 +866,11 @@ test('clears pending buffer updates when seeking', function() {
};
// queue up a tag to be pushed into the buffer (but don't push it yet!)
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -846,8 +897,11 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() {
player
.
on
(
'error'
,
function
()
{
errorTriggered
=
true
;
});
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
requests
.
pop
().
respond
(
404
);
...
...
@@ -862,9 +916,12 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() {
});
test
(
'segment 404 should trigger MEDIA_ERR_NETWORK'
,
function
()
{
player
.
hls
(
'manifest/media.m3u8'
);
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
videojs
.
mediaSources
[
player
.
currentSrc
()]
.
trigger
({
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -875,9 +932,12 @@ test('segment 404 should trigger MEDIA_ERR_NETWORK', function () {
});
test
(
'segment 500 should trigger MEDIA_ERR_ABORTED'
,
function
()
{
player
.
hls
(
'manifest/media.m3u8'
);
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
videojs
.
mediaSources
[
player
.
currentSrc
()]
.
trigger
({
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -887,17 +947,12 @@ test('segment 500 should trigger MEDIA_ERR_ABORTED', function () {
equal
(
4
,
player
.
hls
.
error
.
code
,
'Player error code should be set to MediaError.MEDIA_ERR_ABORTED'
);
});
test
(
'has no effect if native HLS is available'
,
function
()
{
videojs
.
hls
.
supportsNativeHls
=
true
;
player
.
hls
(
'http://example.com/manifest/master.m3u8'
);
ok
(
!
(
player
.
currentSrc
()
in
videojs
.
mediaSources
),
'no media source was opened'
);
});
test
(
'duration is Infinity for live playlists'
,
function
()
{
player
.
hls
(
'http://example.com/manifest/missingEndlist.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'http://example.com/manifest/missingEndlist.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -912,8 +967,11 @@ test('does not reload playlists with an endlist tag', function() {
window
.
setTimeout
=
function
(
callback
,
timeout
)
{
callbacks
.
push
({
callback
:
callback
,
timeout
:
timeout
});
};
player
.
hls
(
'manifest/media.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'manifest/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -921,8 +979,11 @@ test('does not reload playlists with an endlist tag', function() {
});
test
(
'updates the media index when a playlist reloads'
,
function
()
{
player
.
hls
(
'http://example.com/live-updating.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'http://example.com/live-updating.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -964,8 +1025,11 @@ test('mediaIndex is zero before the first segment loads', function() {
this
.
open
=
function
()
{};
this
.
send
=
function
()
{};
};
player
.
hls
(
'http://example.com/first-seg-load.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'http://example.com/first-seg-load.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -973,8 +1037,11 @@ test('mediaIndex is zero before the first segment loads', function() {
});
test
(
'reloads out-of-date live playlists when switching variants'
,
function
()
{
player
.
hls
(
'http://example.com/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'http://example.com/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
...
...
@@ -1014,29 +1081,40 @@ test('does not reload master playlists', function() {
callbacks
.
push
(
callback
);
};
player
.
hls
(
'http://example.com/master.m3u8'
);
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'http://example.com/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
strictEqual
(
callbacks
.
length
,
0
,
'no reload scheduled'
);
strictEqual
(
callbacks
.
length
,
0
,
'no reload scheduled'
);
});
test
(
'if withCredentials option is used, withCredentials is set on the XHR object'
,
function
()
{
player
.
hls
({
url
:
'http://example.com/media.m3u8'
,
player
.
dispose
();
player
=
createPlayer
({
withCredentials
:
true
});
videojs
.
mediaSources
[
player
.
currentSrc
()].
trigger
({
player
.
src
({
src
:
'http://example.com/media.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
ok
(
requests
[
0
].
withCredentials
,
"with credentials should be set to true if that option is passed in"
);
});
test
(
'does not break if the playlist has no segments'
,
function
()
{
player
.
hls
(
'manifest/master.m3u8'
);
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
try
{
videojs
.
mediaSources
[
player
.
currentSrc
()]
.
trigger
({
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
requests
[
0
].
respond
(
200
,
null
,
...
...
@@ -1051,4 +1129,22 @@ test('does not break if the playlist has no segments', function() {
strictEqual
(
requests
.
length
,
1
,
'no requests for non-existent segments were queued'
);
});
test
(
'disposes the playlist loader'
,
function
()
{
var
disposes
=
0
,
player
;
player
=
createPlayer
();
player
.
src
({
src
:
'manifest/master.m3u8'
,
type
:
'application/vnd.apple.mpegurl'
});
player
.
hls
.
mediaSource
.
trigger
({
type
:
'sourceopen'
});
player
.
hls
.
playlists
.
dispose
=
function
()
{
disposes
++
;
};
player
.
dispose
();
strictEqual
(
disposes
,
1
,
'disposed playlist loader'
);
});
})(
window
,
window
.
videojs
);
...
...
Please
register
or
sign in
to post a comment