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
eda08662
authored
2013-10-24 16:28:47 -0400
by
Tom Johnson
Committed by
David LaPalomento
2013-10-24 14:17:58 -0700
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
toms working example base
1 parent
3b271282
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
485 additions
and
374 deletions
example.html
src/hls-playback-controller.js
src/m3u8/m3u8-parser.js
src/m3u8/m3u8.js
src/manifest-controller.js
src/segment-controller.js
test/video-js-hls_test.js
example.html
View file @
eda0866
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"utf-8"
>
<title>
video.js HLS Plugin Example
</title>
<meta
charset=
"utf-8"
>
<title>
video.js HLS Plugin Example
</title>
<link
href=
"node_modules/video.js/video-js.css"
rel=
"stylesheet"
>
...
...
@@ -25,9 +25,11 @@
<!-- m3u8 handling -->
<script
src=
"src/m3u8/m3u8.js"
></script>
<script
src=
"src/m3u8/m3u8-parser.js"
></script>
<script
src=
"src/m3u8/m3u8-tag-types.js"
></script>
<script
src=
"src/m3u8/m3u8-parser.js"
></script>
<script
src=
"src/manifest-controller.js"
></script>
<script
src=
"src/segment-controller.js"
></script>
<script
src=
"src/hls-playback-controller.js"
></script>
<!-- example MPEG2-TS segments -->
<!-- bipbop -->
...
...
@@ -48,51 +50,12 @@
// initialize the player
videojs
.
options
.
flash
.
swf
=
'node_modules/videojs-media-sources/video-js-with-mse.swf'
;
video
=
videojs
(
'video'
);
// create a media source
mediaSource
=
new
videojs
.
MediaSource
();
mediaSource
.
addEventListener
(
'sourceopen'
,
function
(
event
){
var
parser
=
new
videojs
.
hls
.
SegmentParser
(),
sourceBuffer
=
mediaSource
.
addSourceBuffer
(
'video/flv; codecs="vp6,aac"'
);
everything
=
[];
// feed parsed bytes into the player
everything
.
push
(
parser
.
getFlvHeader
());
sourceBuffer
.
appendBuffer
(
everything
[
everything
.
length
-
1
],
video
);
parser
.
parseSegmentBinaryData
(
window
.
bcSegment
);
while
(
parser
.
tagsAvailable
())
{
everything
.
push
(
parser
.
getNextTag
().
bytes
);
sourceBuffer
.
appendBuffer
(
everything
[
everything
.
length
-
1
],
video
);
}
parser
.
flushTags
();
while
(
parser
.
tagsAvailable
())
{
everything
.
push
(
parser
.
getNextTag
().
bytes
);
sourceBuffer
.
appendBuffer
(
everything
[
everything
.
length
-
1
],
video
);
}
var
iframe
=
document
.
createElement
(
'iframe'
);
iframe
.
src
=
'data:video/x-flv;base64,'
+
window
.
btoa
((
Array
.
prototype
.
map
.
call
(
everything
.
reduce
(
function
(
result
,
next
)
{
var
array
=
new
Uint8Array
(
result
.
byteLength
+
next
.
byteLength
);
array
.
set
(
result
);
array
.
set
(
next
,
result
.
length
);
return
array
;
}),
function
(
byte
)
{
return
String
.
fromCharCode
(
byte
);
})).
join
(
''
));
//console.log(iframe);
// document.body.appendChild(iframe);
},
false
);
url
=
videojs
.
URL
.
createObjectURL
(
mediaSource
);
video
.
src
({
src
:
url
,
type
:
"video/flv"
video
=
videojs
(
'video'
,{},
function
(){
this
.
playbackController
=
new
window
.
videojs
.
hls
.
HLSPlaybackController
(
this
);
this
.
playbackController
.
loadManifest
(
"http://localhost:7070/test/basic-playback/zencoder/gogo/manifest.m3u8"
,
function
(
data
){
console
.
log
(
data
)});
});
</script>
</body>
</html>
...
...
src/hls-playback-controller.js
0 → 100644
View file @
eda0866
(
function
(
window
)
{
window
.
videojs
.
hls
.
HLSPlaybackController
=
function
(
vjsPlayerReference
)
{
var
ManifestController
=
window
.
videojs
.
hls
.
ManifestController
;
var
SegmentController
=
window
.
videojs
.
hls
.
SegmentController
;
var
MediaSource
=
window
.
videojs
.
MediaSource
;
var
SegmentParser
=
window
.
videojs
.
hls
.
SegmentParser
;
var
M3U8
=
window
.
videojs
.
hls
.
M3U8
;
var
self
=
this
;
self
.
player
=
vjsPlayerReference
;
self
.
mediaSource
=
new
MediaSource
();
self
.
parser
=
new
SegmentParser
();
self
.
manifestController
=
null
;
self
.
segmentController
=
null
;
self
.
manifestLoaded
=
false
;
self
.
currentSegment
=
0
;
self
.
currentManifest
=
null
;
self
.
currentPlaylist
=
null
;
self
.
currentRendition
=
null
;
// Register Externall Callbacks
self
.
manifestLoadCompleteCallback
;
self
.
player
.
on
(
'timeupdate'
,
function
()
{
console
.
log
(
self
.
player
.
currentTime
());
});
self
.
player
.
on
(
'onsrcchange'
,
function
()
{
console
.
log
(
'src change'
,
self
.
player
.
currentSrc
());
//if src.url.m3u8 -- loadManifest.url
});
self
.
rendition
=
function
(
rendition
)
{
self
.
currentRendition
=
rendition
;
self
.
loadManifest
(
self
.
currentRendition
.
url
,
self
.
onM3U8LoadComplete
,
self
.
onM3U8LoadError
,
self
.
onM3U8Update
);
};
self
.
loadManifest
=
function
(
manifestUrl
,
onDataCallback
,
onErrorCallback
,
onUpdateCallback
)
{
self
.
mediaSource
.
addEventListener
(
'sourceopen'
,
function
(
event
)
{
console
.
log
(
'source open here'
);
// feed parsed bytes into the player
self
.
sourceBuffer
=
self
.
mediaSource
.
addSourceBuffer
(
'video/flv; codecs="vp6,aac"'
);
self
.
parser
=
new
SegmentParser
();
self
.
sourceBuffer
.
appendBuffer
(
self
.
parser
.
getFlvHeader
(),
video
);
if
(
onDataCallback
)
{
self
.
manifestLoadCompleteCallback
=
onDataCallback
;
}
self
.
manifestController
=
new
ManifestController
();
self
.
manifestController
.
loadManifest
(
manifestUrl
,
self
.
onM3U8LoadComplete
,
self
.
onM3U8LoadError
,
self
.
onM3U8Update
);
},
false
);
self
.
player
.
src
({
src
:
videojs
.
URL
.
createObjectURL
(
self
.
mediaSource
),
type
:
"video/flv"
});
};
self
.
onM3U8LoadComplete
=
function
(
m3u8
)
{
if
(
m3u8
.
invalidReasons
.
length
==
0
)
{
if
(
m3u8
.
isPlaylist
)
{
self
.
currentPlaylist
=
m3u8
;
self
.
rendition
(
self
.
currentPlaylist
.
playlistItems
[
0
]);
}
else
{
self
.
currentManifest
=
m3u8
;
self
.
manifestLoaded
=
true
;
self
.
loadSegment
(
self
.
currentManifest
.
mediaItems
[
0
]);
if
(
self
.
manifestLoadCompleteCallback
)
{
self
.
manifestLoadCompleteCallback
(
m3u8
);
}
}
}
};
self
.
onM3U8LoadError
=
function
(
error
)
{
};
self
.
onM3U8Update
=
function
(
m3u8
)
{
};
self
.
loadSegment
=
function
(
segment
)
{
self
.
segmentController
=
new
SegmentController
();
self
.
segmentController
.
loadSegment
(
segment
.
url
,
self
.
onSegmentLoadComplete
,
self
.
onSegmentLoadError
);
};
self
.
onSegmentLoadComplete
=
function
(
segment
)
{
self
.
parser
.
parseSegmentBinaryData
(
segment
.
binaryData
);
while
(
self
.
parser
.
tagsAvailable
())
{
self
.
sourceBuffer
.
appendBuffer
(
self
.
parser
.
getNextTag
().
bytes
,
self
.
player
);
};
console
.
log
(
'load another'
,
self
.
currentSegment
,
self
.
currentManifest
.
mediaItems
.
length
);
if
(
self
.
currentSegment
<
self
.
currentManifest
.
mediaItems
.
length
-
1
)
{
console
.
log
(
'load another'
);
self
.
loadNextSegment
();
}
};
self
.
loadNextSegment
=
function
()
{
self
.
currentSegment
++
;
self
.
loadSegment
(
self
.
currentManifest
.
mediaItems
[
self
.
currentSegment
]);
}
self
.
onSegmentLoadError
=
function
(
error
)
{
};
};
})(
this
);
\ No newline at end of file
src/m3u8/m3u8-parser.js
View file @
eda0866
...
...
@@ -4,6 +4,8 @@
window
.
videojs
.
hls
.
M3U8Parser
=
function
()
{
var
self
=
this
;
self
.
directory
;
var
tagTypes
=
window
.
videojs
.
hls
.
m3u8TagType
;
var
lines
=
[];
var
data
;
...
...
@@ -31,6 +33,11 @@
self
.
parse
=
function
(
rawDataString
)
{
data
=
new
M3U8
();
if
(
self
.
directory
)
{
data
.
directory
=
self
.
directory
;
}
if
(
rawDataString
!=
undefined
&&
rawDataString
.
toString
().
length
>
0
)
{
lines
=
rawDataString
.
split
(
'\n'
);
...
...
@@ -72,6 +79,15 @@
segment
.
url
=
lines
[
index
+
1
];
}
if
(
segment
.
url
.
indexOf
(
"http"
)
===-
1
&&
self
.
directory
)
{
if
(
data
.
directory
[
data
.
directory
.
length
-
1
]
===
segment
.
url
[
0
]
&&
segment
.
url
[
0
]
===
"/"
)
{
segment
.
url
=
segment
.
url
.
substr
(
1
);
}
segment
.
url
=
self
.
directory
+
segment
.
url
;
}
data
.
mediaItems
.
push
(
segment
);
break
;
...
...
@@ -116,7 +132,7 @@
break
;
case
tagTypes
.
ZEN_TOTAL_DURATION
:
data
.
totalDuration
=
self
.
getTagValue
(
value
);
data
.
totalDuration
=
Number
(
self
.
getTagValue
(
value
)
);
break
;
case
tagTypes
.
VERSION
:
...
...
src/m3u8/m3u8.js
View file @
eda0866
(
function
(
window
)
{
window
.
videojs
.
hls
.
M3U8
=
function
()
{
this
.
directory
=
""
;
this
.
allowCache
=
"NO"
;
this
.
playlistItems
=
[];
this
.
mediaItems
=
[];
...
...
src/manifest-controller.js
View file @
eda0866
(
function
(
window
)
{
(
function
(
window
)
{
var
M3U8
=
window
.
videojs
.
hls
.
M3U8
;
var
M3U8Parser
=
window
.
videojs
.
hls
.
M3U8Parser
;
window
.
videojs
.
hls
.
ManifestController
=
function
(){
window
.
videojs
.
hls
.
ManifestController
=
function
()
{
var
self
=
this
;
var
parser
;
var
data
;
var
onDataCallback
;
var
onErrorCallback
;
var
onUpdateCallback
;
self
.
parser
;
self
.
data
;
self
.
url
;
self
.
onDataCallback
;
self
.
onErrorCallback
;
self
.
onUpdateCallback
;
self
.
loadManifest
=
function
(
manifestUrl
,
onDataCallback
,
onErrorCallback
,
onUpdateCallback
)
{
self
.
url
=
manifestUrl
;
self
.
loadManifest
=
function
(
manifestUrl
,
onDataCallback
,
onErrorCallback
,
onUpdateCallback
)
{
self
.
onDataCallback
=
onDataCallback
;
self
.
onErrorCallback
=
onErrorCallback
;
self
.
onUpdateCallback
=
onUpdateCallback
;
if
(
onDataCallback
)
{
self
.
onDataCallback
=
onDataCallback
;
}
if
(
onErrorCallback
)
{
self
.
onErrorCallback
=
onErrorCallback
;
}
if
(
onUpdateCallback
)
{
self
.
onUpdateCallback
=
onUpdateCallback
;
}
vjs
.
get
(
manifestUrl
,
self
.
onManifestLoadComplete
,
self
.
onManifestLoadError
);
};
self
.
parseManifest
=
function
(
dataAsString
)
{
self
.
parseManifest
=
function
(
dataAsString
)
{
self
.
parser
=
new
M3U8Parser
();
self
.
data
=
self
.
parser
.
parse
(
dataAsString
);
self
.
parser
.
directory
=
/^
(\/?
|
)([\s\S]
*
?)((?:\.{1,2}
|
[^\/]
+
?
|
)(\.[^
.
\/]
*|
))(?:[\/]
*
)
$/
.
exec
(
self
.
url
).
slice
(
1
)[
1
];
self
.
data
=
self
.
parser
.
parse
(
dataAsString
);
return
self
.
data
;
};
self
.
onManifestLoadComplete
=
function
(
response
)
{
self
.
onManifestLoadComplete
=
function
(
response
)
{
var
output
=
self
.
parseManifest
(
response
);
if
(
self
.
onDataCallback
!=
undefined
)
{
if
(
self
.
onDataCallback
!=
undefined
)
{
self
.
onDataCallback
(
output
);
}
};
self
.
onManifestLoadError
=
function
(
err
)
{
if
(
err
)
{
console
.
log
(
err
.
message
);
}
if
(
self
.
onErrorCallback
!=
undefined
)
{
onErrorCallback
((
err
!=
undefined
)
?
err
:
null
);
self
.
onManifestLoadError
=
function
(
err
)
{
if
(
self
.
onErrorCallback
!=
undefined
)
{
self
.
onErrorCallback
((
err
!=
undefined
)
?
err
:
null
);
}
};
}
...
...
src/segment-controller.js
View file @
eda0866
(
function
(
window
)
{
var
SegmentParser
=
window
.
videojs
.
hls
.
SegmentParser
;
window
.
videojs
.
hls
.
SegmentController
=
function
(){
var
self
=
this
;
var
url
;
var
parser
;
var
requestTimestamp
;
var
responseTimestamp
;
var
data
;
var
onDataCallback
;
var
onErrorCallback
;
var
onUpdateCallback
;
self
.
url
;
self
.
requestTimestamp
;
self
.
responseTimestamp
;
self
.
data
;
self
.
onDataCallback
;
self
.
onErrorCallback
;
self
.
onUpdateCallback
;
self
.
loadSegment
=
function
(
segmentUrl
,
onDataCallback
,
onErrorCallback
,
onUpdateCallback
)
{
self
.
url
=
segmentUrl
;
...
...
@@ -22,29 +21,31 @@
self
.
onUpdateCallback
=
onUpdateCallback
;
self
.
requestTimestamp
=
new
Date
().
getTime
();
var
req
=
new
XMLHttpRequest
();
req
.
open
(
'GET'
,
segmentUrl
,
true
);
req
.
responseType
=
'arraybuffer'
;
req
.
onload
=
function
(
response
)
{
self
.
onSegmentLoadComplete
(
new
Uint8Array
(
req
.
response
));
};
req
.
send
(
null
);
var
req
=
new
XMLHttpRequest
();
req
.
open
(
'GET'
,
segmentUrl
,
true
);
req
.
responseType
=
'arraybuffer'
;
req
.
onload
=
function
(
response
)
{
self
.
onSegmentLoadComplete
(
new
Uint8Array
(
req
.
response
));
};
req
.
send
(
null
);
//vjs.get(segmentUrl, self.onSegmentLoadComplete, self.onSegmentLoadError);
};
self
.
parseSegment
=
function
(
incomingData
)
{
// Add David's code later //
console
.
log
(
'got segment data'
,
incomingData
.
byteLength
);
self
.
data
=
{
whatever
:
incomingData
};
self
.
data
=
{};
self
.
data
.
binaryData
=
incomingData
;
self
.
data
.
url
=
self
.
url
;
self
.
data
.
isCached
=
false
;
self
.
data
.
requestTimestamp
=
self
.
requestTimestamp
;
self
.
data
.
responseTimestamp
=
self
.
responseTimestamp
;
self
.
data
.
byteLength
=
incomingData
.
byteLength
;
self
.
data
.
isCached
=
(
parseInt
(
self
.
responseTimestamp
-
self
.
requestTimestamp
)
<
75
);
self
.
data
.
throughput
=
self
.
calculateThroughput
(
self
.
data
.
byteLength
,
self
.
requestTimestamp
,
self
.
responseTimestamp
)
self
.
data
.
throughput
=
self
.
calculateThroughput
(
self
.
data
.
byteLength
,
self
.
requestTimestamp
,
self
.
responseTimestamp
)
;
return
self
.
data
;
};
...
...
test/video-js-hls_test.js
View file @
eda0866
(
function
(
window
)
{
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
manifestController
,
segmentController
,
m3u8parser
,
parser
,
expectedHeader
=
[
0x46
,
0x4c
,
0x56
,
0x01
,
0x05
,
0x00
,
0x00
,
0x00
,
0x09
,
0x00
,
0x00
,
0x00
,
0x00
],
testAudioTag
,
testVideoTag
,
testScriptTag
,
asciiFromBytes
,
testScriptString
,
testScriptEcmaArray
;
module
(
'environment'
);
test
(
'is sane'
,
function
()
{
expect
(
1
);
ok
(
true
);
});
module
(
'segment parser'
,
{
setup
:
function
()
{
parser
=
new
window
.
videojs
.
hls
.
SegmentParser
();
}
});
test
(
'creates an flv header'
,
function
()
{
var
header
=
Array
.
prototype
.
slice
.
call
(
parser
.
getFlvHeader
());
ok
(
header
,
'the header is truthy'
);
equal
(
9
+
4
,
header
.
length
,
'the header length is correct'
);
equal
(
header
[
0
],
'F'
.
charCodeAt
(
0
),
'the first character is "F"'
);
equal
(
header
[
1
],
'L'
.
charCodeAt
(
0
),
'the second character is "L"'
);
equal
(
header
[
2
],
'V'
.
charCodeAt
(
0
),
'the third character is "V"'
);
deepEqual
(
expectedHeader
,
header
,
'the rest of the header is correct'
);
});
test
(
'parses the first bipbop segment'
,
function
()
{
var
tag
,
bytes
,
i
;
parser
.
parseSegmentBinaryData
(
window
.
bcSegment
);
ok
(
parser
.
tagsAvailable
(),
'tags are available'
);
console
.
log
(
'h264 tags:'
,
parser
.
stats
.
h264Tags
(),
'aac tags:'
,
parser
.
stats
.
aacTags
());
});
testAudioTag
=
function
(
tag
)
{
var
byte
=
tag
.
bytes
[
11
],
format
=
(
byte
&
0xF0
)
>>>
4
,
soundRate
=
byte
&
0x03
,
soundSize
=
(
byte
&
0x2
)
>>>
1
,
soundType
=
byte
&
0x1
,
aacPacketType
=
tag
.
bytes
[
12
];
equal
(
10
,
format
,
'the audio format is aac'
);
equal
(
3
,
soundRate
,
'the sound rate is 44kHhz'
);
equal
(
1
,
soundSize
,
'the sound size is 16-bit samples'
);
equal
(
1
,
soundType
,
'the sound type is stereo'
);
ok
(
aacPacketType
===
0
||
aacPacketType
===
1
,
'aac packets should have a valid type'
);
};
testVideoTag
=
function
(
tag
)
{
var
byte
=
tag
.
bytes
[
11
],
frameType
=
(
byte
&
0xF0
)
>>>
4
,
codecId
=
byte
&
0x0F
,
packetType
=
tag
.
bytes
[
12
],
compositionTime
=
(
tag
.
view
.
getInt32
(
13
)
&
0xFFFFFF00
)
>>
8
,
nalHeader
;
// payload starts at tag.bytes[16]
// XXX: I'm not sure that frame types 3-5 are invalid
ok
(
frameType
===
1
||
frameType
===
2
,
'the frame type should be valid'
);
equal
(
7
,
codecId
,
'the codec ID is AVC for h264'
);
ok
(
packetType
<=
2
&&
packetType
>=
0
,
'the packet type is within [0, 2]'
);
if
(
packetType
!==
1
)
{
equal
(
0
,
compositionTime
,
'the composition time is zero for non-NALU packets'
);
}
// TODO: the rest of the bytes are an NLU unit
if
(
packetType
===
0
)
{
// AVC decoder configuration record
}
else
{
// NAL units
testNalUnit
(
tag
.
bytes
.
subarray
(
16
));
}
};
testNalUnit
=
function
(
bytes
)
{
var
nalHeader
=
bytes
[
0
],
unitType
=
nalHeader
&
0x1F
;
equal
(
0
,
(
nalHeader
&
0x80
)
>>>
7
,
'the first bit is always 0'
);
// equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something');
// ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0');
// ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22');
};
asciiFromBytes
=
function
(
bytes
)
{
var
string
=
[],
i
=
bytes
.
byteLength
;
while
(
i
--
)
{
string
[
i
]
=
String
.
fromCharCode
(
bytes
[
i
]);
}
return
string
.
join
(
''
);
};
testScriptString
=
function
(
tag
,
offset
,
expected
)
{
var
type
=
tag
.
bytes
[
offset
],
stringLength
=
tag
.
view
.
getUint16
(
offset
+
1
),
string
,
i
=
expected
.
length
;
equal
(
2
,
type
,
'the script element is of string type'
);
equal
(
stringLength
,
expected
.
length
,
'the script string length is correct'
);
string
=
asciiFromBytes
(
tag
.
bytes
.
subarray
(
offset
+
3
,
offset
+
3
+
stringLength
));
equal
(
expected
,
string
,
'the string value is "'
+
expected
+
'"'
);
};
testScriptEcmaArray
=
function
(
tag
,
start
)
{
var
numItems
=
tag
.
view
.
getUint32
(
start
),
i
=
numItems
,
offset
=
start
+
4
,
length
,
type
;
while
(
i
--
)
{
length
=
tag
.
view
.
getUint16
(
offset
);
// advance offset to the property value
offset
+=
2
+
length
;
type
=
tag
.
bytes
[
offset
];
ok
(
type
===
1
||
type
===
0
,
'the ecma array property value type is number or boolean'
);
offset
++
;
if
(
type
)
{
// boolean
ok
(
tag
.
bytes
[
offset
]
===
0
||
tag
.
bytes
[
offset
]
===
1
,
'the script boolean value is 0 or 1'
);
offset
++
;
}
else
{
// number
ok
(
!
isNaN
(
tag
.
view
.
getFloat64
(
offset
)),
'the value is not NaN'
);
offset
+=
8
;
}
}
equal
(
tag
.
bytes
[
offset
],
0
,
'the property array terminator is valid'
);
equal
(
tag
.
bytes
[
offset
+
1
],
0
,
'the property array terminator is valid'
);
equal
(
tag
.
bytes
[
offset
+
2
],
9
,
'the property array terminator is valid'
);
};
testScriptTag
=
function
(
tag
)
{
testScriptString
(
tag
,
11
,
'onMetaData'
);
// the onMetaData object is stored as an 'ecma array', an array with non-
// integer indices (i.e. a dictionary or hash-map).
equal
(
8
,
tag
.
bytes
[
24
],
'onMetaData is of ecma array type'
);
testScriptEcmaArray
(
tag
,
25
);
};
test
(
'the flv tags are well-formed'
,
function
()
{
var
tag
,
byte
,
type
,
lastTime
=
0
;
parser
.
parseSegmentBinaryData
(
window
.
bcSegment
);
while
(
parser
.
tagsAvailable
())
{
tag
=
parser
.
getNextTag
();
type
=
tag
.
bytes
[
0
];
// generic flv headers
ok
(
type
===
8
||
type
===
9
||
type
===
18
,
'the type field specifies audio, video or script'
);
byte
=
(
tag
.
view
.
getUint32
(
1
)
&
0xFFFFFF00
)
>>>
8
;
equal
(
tag
.
bytes
.
byteLength
-
11
-
4
,
byte
,
'the size field is correct'
);
byte
=
tag
.
view
.
getUint32
(
5
)
&
0xFFFFFF00
;
ok
(
byte
>=
lastTime
,
'the timestamp for the tag is greater than zero'
);
lastTime
=
byte
;
// tag type-specific headers
({
8
:
testAudioTag
,
9
:
testVideoTag
,
18
:
testScriptTag
})[
type
](
tag
);
// previous tag size
equal
(
tag
.
bytes
.
byteLength
-
4
,
tag
.
view
.
getUint32
(
tag
.
bytes
.
byteLength
-
4
),
'the size of the previous tag is correct'
);
}
});
(
function
(
window
)
{
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
manifestController
,
segmentController
,
m3u8parser
,
parser
,
expectedHeader
=
[
0x46
,
0x4c
,
0x56
,
0x01
,
0x05
,
0x00
,
0x00
,
0x00
,
0x09
,
0x00
,
0x00
,
0x00
,
0x00
],
testAudioTag
,
testVideoTag
,
testScriptTag
,
asciiFromBytes
,
testScriptString
,
testScriptEcmaArray
;
module
(
'environment'
);
test
(
'is sane'
,
function
()
{
expect
(
1
);
ok
(
true
);
});
module
(
'segment parser'
,
{
setup
:
function
()
{
parser
=
new
window
.
videojs
.
hls
.
SegmentParser
();
}
});
test
(
'creates an flv header'
,
function
()
{
var
header
=
Array
.
prototype
.
slice
.
call
(
parser
.
getFlvHeader
());
ok
(
header
,
'the header is truthy'
);
equal
(
9
+
4
,
header
.
length
,
'the header length is correct'
);
equal
(
header
[
0
],
'F'
.
charCodeAt
(
0
),
'the first character is "F"'
);
equal
(
header
[
1
],
'L'
.
charCodeAt
(
0
),
'the second character is "L"'
);
equal
(
header
[
2
],
'V'
.
charCodeAt
(
0
),
'the third character is "V"'
);
deepEqual
(
expectedHeader
,
header
,
'the rest of the header is correct'
);
});
test
(
'parses the first bipbop segment'
,
function
()
{
var
tag
,
bytes
,
i
;
parser
.
parseSegmentBinaryData
(
window
.
bcSegment
);
ok
(
parser
.
tagsAvailable
(),
'tags are available'
);
console
.
log
(
'h264 tags:'
,
parser
.
stats
.
h264Tags
(),
'aac tags:'
,
parser
.
stats
.
aacTags
());
});
testAudioTag
=
function
(
tag
)
{
var
byte
=
tag
.
bytes
[
11
],
format
=
(
byte
&
0xF0
)
>>>
4
,
soundRate
=
byte
&
0x03
,
soundSize
=
(
byte
&
0x2
)
>>>
1
,
soundType
=
byte
&
0x1
,
aacPacketType
=
tag
.
bytes
[
12
];
equal
(
10
,
format
,
'the audio format is aac'
);
equal
(
3
,
soundRate
,
'the sound rate is 44kHhz'
);
equal
(
1
,
soundSize
,
'the sound size is 16-bit samples'
);
equal
(
1
,
soundType
,
'the sound type is stereo'
);
ok
(
aacPacketType
===
0
||
aacPacketType
===
1
,
'aac packets should have a valid type'
);
};
testVideoTag
=
function
(
tag
)
{
var
byte
=
tag
.
bytes
[
11
],
frameType
=
(
byte
&
0xF0
)
>>>
4
,
codecId
=
byte
&
0x0F
,
packetType
=
tag
.
bytes
[
12
],
compositionTime
=
(
tag
.
view
.
getInt32
(
13
)
&
0xFFFFFF00
)
>>
8
,
nalHeader
;
// payload starts at tag.bytes[16]
// XXX: I'm not sure that frame types 3-5 are invalid
ok
(
frameType
===
1
||
frameType
===
2
,
'the frame type should be valid'
);
equal
(
7
,
codecId
,
'the codec ID is AVC for h264'
);
ok
(
packetType
<=
2
&&
packetType
>=
0
,
'the packet type is within [0, 2]'
);
if
(
packetType
!==
1
)
{
equal
(
0
,
compositionTime
,
'the composition time is zero for non-NALU packets'
);
}
// TODO: the rest of the bytes are an NLU unit
if
(
packetType
===
0
)
{
// AVC decoder configuration record
}
else
{
// NAL units
testNalUnit
(
tag
.
bytes
.
subarray
(
16
));
}
};
testNalUnit
=
function
(
bytes
)
{
var
nalHeader
=
bytes
[
0
],
unitType
=
nalHeader
&
0x1F
;
equal
(
0
,
(
nalHeader
&
0x80
)
>>>
7
,
'the first bit is always 0'
);
// equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something');
// ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0');
// ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22');
};
asciiFromBytes
=
function
(
bytes
)
{
var
string
=
[],
i
=
bytes
.
byteLength
;
while
(
i
--
)
{
string
[
i
]
=
String
.
fromCharCode
(
bytes
[
i
]);
}
return
string
.
join
(
''
);
};
testScriptString
=
function
(
tag
,
offset
,
expected
)
{
var
type
=
tag
.
bytes
[
offset
],
stringLength
=
tag
.
view
.
getUint16
(
offset
+
1
),
string
,
i
=
expected
.
length
;
equal
(
2
,
type
,
'the script element is of string type'
);
equal
(
stringLength
,
expected
.
length
,
'the script string length is correct'
);
string
=
asciiFromBytes
(
tag
.
bytes
.
subarray
(
offset
+
3
,
offset
+
3
+
stringLength
));
equal
(
expected
,
string
,
'the string value is "'
+
expected
+
'"'
);
};
testScriptEcmaArray
=
function
(
tag
,
start
)
{
var
numItems
=
tag
.
view
.
getUint32
(
start
),
i
=
numItems
,
offset
=
start
+
4
,
length
,
type
;
while
(
i
--
)
{
length
=
tag
.
view
.
getUint16
(
offset
);
// advance offset to the property value
offset
+=
2
+
length
;
type
=
tag
.
bytes
[
offset
];
ok
(
type
===
1
||
type
===
0
,
'the ecma array property value type is number or boolean'
);
offset
++
;
if
(
type
)
{
// boolean
ok
(
tag
.
bytes
[
offset
]
===
0
||
tag
.
bytes
[
offset
]
===
1
,
'the script boolean value is 0 or 1'
);
offset
++
;
}
else
{
// number
ok
(
!
isNaN
(
tag
.
view
.
getFloat64
(
offset
)),
'the value is not NaN'
);
offset
+=
8
;
}
}
equal
(
tag
.
bytes
[
offset
],
0
,
'the property array terminator is valid'
);
equal
(
tag
.
bytes
[
offset
+
1
],
0
,
'the property array terminator is valid'
);
equal
(
tag
.
bytes
[
offset
+
2
],
9
,
'the property array terminator is valid'
);
};
testScriptTag
=
function
(
tag
)
{
testScriptString
(
tag
,
11
,
'onMetaData'
);
// the onMetaData object is stored as an 'ecma array', an array with non-
// integer indices (i.e. a dictionary or hash-map).
equal
(
8
,
tag
.
bytes
[
24
],
'onMetaData is of ecma array type'
);
testScriptEcmaArray
(
tag
,
25
);
};
test
(
'the flv tags are well-formed'
,
function
()
{
var
tag
,
byte
,
type
,
lastTime
=
0
;
parser
.
parseSegmentBinaryData
(
window
.
bcSegment
);
while
(
parser
.
tagsAvailable
())
{
tag
=
parser
.
getNextTag
();
type
=
tag
.
bytes
[
0
];
// generic flv headers
ok
(
type
===
8
||
type
===
9
||
type
===
18
,
'the type field specifies audio, video or script'
);
byte
=
(
tag
.
view
.
getUint32
(
1
)
&
0xFFFFFF00
)
>>>
8
;
equal
(
tag
.
bytes
.
byteLength
-
11
-
4
,
byte
,
'the size field is correct'
);
byte
=
tag
.
view
.
getUint32
(
5
)
&
0xFFFFFF00
;
ok
(
byte
>=
lastTime
,
'the timestamp for the tag is greater than zero'
);
lastTime
=
byte
;
// tag type-specific headers
({
8
:
testAudioTag
,
9
:
testVideoTag
,
18
:
testScriptTag
})[
type
](
tag
);
// previous tag size
equal
(
tag
.
bytes
.
byteLength
-
4
,
tag
.
view
.
getUint32
(
tag
.
bytes
.
byteLength
-
4
),
'the size of the previous tag is correct'
);
}
});
/*
M3U8 Test Suite
M3U8 Test Suite
*/
module
(
'm3u8 parser'
,
{
setup
:
function
()
{
m3u8parser
=
new
window
.
videojs
.
hls
.
M3U8Parser
();
}
});
module
(
'm3u8 parser'
,
{
setup
:
function
()
{
m3u8parser
=
new
window
.
videojs
.
hls
.
M3U8Parser
();
}
});
test
(
'should create my parser'
,
function
()
{
ok
(
m3u8parser
!=
undefined
);
test
(
'should create my parser'
,
function
()
{
ok
(
m3u8parser
!=
undefined
);
}
);
test
(
'should successfully parse manifest data'
,
function
()
{
test
(
'should successfully parse manifest data'
,
function
()
{
var
parsedData
=
m3u8parser
.
parse
(
window
.
playlistData
);
ok
(
parsedData
);
}
);
test
(
'test for expected results'
,
function
()
{
test
(
'test for expected results'
,
function
()
{
var
data
=
m3u8parser
.
parse
(
window
.
playlistData
);
notEqual
(
data
,
null
,
'data is not NULL'
);
equal
(
data
.
invalidReasons
.
length
,
0
,
'data has 0 invalid reasons'
);
equal
(
data
.
invalidReasons
.
length
,
0
,
'data has 0 invalid reasons'
);
equal
(
data
.
hasValidM3UTag
,
true
,
'data has valid EXTM3U'
);
equal
(
data
.
targetDuration
,
10
,
'data has correct TARGET DURATION'
);
equal
(
data
.
allowCache
,
"NO"
,
'acceptable ALLOW CACHE'
);
...
...
@@ -275,109 +275,109 @@
);
module
(
'brightcove playlist'
,
{
setup
:
function
()
{
setup
:
function
()
{
m3u8parser
=
new
window
.
videojs
.
hls
.
M3U8Parser
();
}
});
test
(
'should parse a brightcove manifest data'
,
function
()
{
var
data
=
m3u8parser
.
parse
(
window
.
brightcove_playlist_data
);
test
(
'should parse a brightcove manifest data'
,
function
()
{
var
data
=
m3u8parser
.
parse
(
window
.
brightcove_playlist_data
);
ok
(
data
);
equal
(
data
.
playlistItems
.
length
,
4
,
'Has correct rendition count'
);
equal
(
data
.
playlistItems
[
0
].
bandwidth
,
240000
,
'First rendition index bandwidth is correct'
);
equal
(
data
.
playlistItems
[
0
][
"program-id"
],
1
,
'First rendition index program-id is correct'
);
equal
(
data
.
playlistItems
[
0
].
resolution
.
width
,
396
,
'First rendition index resolution width is correct'
);
equal
(
data
.
playlistItems
[
0
].
resolution
.
height
,
224
,
'First rendition index resolution height is correct'
);
equal
(
data
.
playlistItems
[
0
].
bandwidth
,
240000
,
'First rendition index bandwidth is correct'
);
equal
(
data
.
playlistItems
[
0
][
"program-id"
],
1
,
'First rendition index program-id is correct'
);
equal
(
data
.
playlistItems
[
0
].
resolution
.
width
,
396
,
'First rendition index resolution width is correct'
);
equal
(
data
.
playlistItems
[
0
].
resolution
.
height
,
224
,
'First rendition index resolution height is correct'
);
}
);
module
(
'manifest controller'
,
{
setup
:
function
()
{
setup
:
function
()
{
manifestController
=
new
window
.
videojs
.
hls
.
ManifestController
();
this
.
vjsget
=
vjs
.
get
;
vjs
.
get
=
function
(
url
,
success
,
error
)
{
vjs
.
get
=
function
(
url
,
success
,
error
)
{
console
.
log
(
url
);
success
(
window
.
brightcove_playlist_data
);
};
},
teardown
:
function
()
{
teardown
:
function
()
{
vjs
.
get
=
this
.
vjsget
;
}
});
test
(
'should create'
,
function
()
{
test
(
'should create'
,
function
()
{
ok
(
manifestController
);
});
test
(
'should return a parsed object'
,
function
()
{
test
(
'should return a parsed object'
,
function
()
{
var
data
=
manifestController
.
parseManifest
(
window
.
brightcove_playlist_data
);
ok
(
data
);
equal
(
data
.
playlistItems
.
length
,
4
,
'Has correct rendition count'
);
equal
(
data
.
playlistItems
[
0
].
bandwidth
,
240000
,
'First rendition index bandwidth is correct'
);
equal
(
data
.
playlistItems
[
0
][
"program-id"
],
1
,
'First rendition index program-id is correct'
);
equal
(
data
.
playlistItems
[
0
].
resolution
.
width
,
396
,
'First rendition index resolution width is correct'
);
equal
(
data
.
playlistItems
[
0
].
resolution
.
height
,
224
,
'First rendition index resolution height is correct'
);
equal
(
data
.
playlistItems
[
0
].
bandwidth
,
240000
,
'First rendition index bandwidth is correct'
);
equal
(
data
.
playlistItems
[
0
][
"program-id"
],
1
,
'First rendition index program-id is correct'
);
equal
(
data
.
playlistItems
[
0
].
resolution
.
width
,
396
,
'First rendition index resolution width is correct'
);
equal
(
data
.
playlistItems
[
0
].
resolution
.
height
,
224
,
'First rendition index resolution height is correct'
);
})
test
(
'should get a manifest from hermes'
,
function
()
{
test
(
'should get a manifest from hermes'
,
function
()
{
var
hermesUrl
=
"http://localhost:7070/test/basic-playback/brightcove/16x9-master.m3u8"
;
manifestController
.
loadManifest
(
hermesUrl
,
function
(
responseData
)
{
function
(
responseData
)
{
ok
(
true
);
},
function
(
errorData
)
{
function
(
errorData
)
{
console
.
log
(
'got error data'
);
},
function
(
updateData
)
{
function
(
updateData
)
{
console
.
log
(
'got update data'
);
}
)
});
module
(
'segment controller'
,
{
setup
:
function
()
{
setup
:
function
()
{
segmentController
=
new
window
.
videojs
.
hls
.
SegmentController
();
this
.
vjsget
=
vjs
.
get
;
vjs
.
get
=
function
(
url
,
success
,
error
)
{
vjs
.
get
=
function
(
url
,
success
,
error
)
{
console
.
log
(
'load segment url'
,
url
);
success
(
window
.
bcSegment
);
};
},
teardown
:
function
()
{
teardown
:
function
()
{
vjs
.
get
=
this
.
vjsget
;
}
});
test
(
'should get a segment data'
,
function
()
{
test
(
'should get a segment data'
,
function
()
{
ok
(
true
);
var
hermesUrl
=
"http://localhost:7070/test/ts-files/brightcove/s-1.ts"
;
segmentController
.
loadSegment
(
hermesUrl
,
function
(
responseData
)
{
function
(
responseData
)
{
console
.
log
(
'got response from segment controller'
);
ok
(
true
);
},
function
(
errorData
)
{
function
(
errorData
)
{
console
.
log
(
'got error data'
);
},
function
(
updateData
)
{
function
(
updateData
)
{
console
.
log
(
'got update data'
);
}
)
}
)
test
(
'bandwidth calulation test'
,
function
()
{
var
multiSecondData
=
segmentController
.
calculateThroughput
(
10000
,
1000
,
2000
);
var
subSecondData
=
segmentController
.
calculateThroughput
(
10000
,
1000
,
1500
);
test
(
'bandwidth calulation test'
,
function
()
{
var
multiSecondData
=
segmentController
.
calculateThroughput
(
10000
,
1000
,
2000
);
var
subSecondData
=
segmentController
.
calculateThroughput
(
10000
,
1000
,
1500
);
equal
(
multiSecondData
,
80000
,
'MULTI-Second bits per second calculation'
);
equal
(
subSecondData
,
160000
,
'SUB-Second bits per second calculation'
);
...
...
Please
register
or
sign in
to post a comment