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
dd4862c9
authored
2013-10-24 19:28:40 -0400
by
Tom Johnson
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
toms intro to jshint
1 parent
0b06638f
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
397 additions
and
390 deletions
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
src/hls-playback-controller.js
View file @
dd4862c
...
...
@@ -3,8 +3,8 @@
ManifestController
=
window
.
videojs
.
hls
.
ManifestController
,
SegmentController
=
window
.
videojs
.
hls
.
SegmentController
,
MediaSource
=
window
.
videojs
.
MediaSource
,
SegmentParser
=
window
.
videojs
.
hls
.
SegmentParser
,
M3U8
=
window
.
videojs
.
hls
.
M3U8
;
SegmentParser
=
window
.
videojs
.
hls
.
SegmentParser
;
window
.
videojs
.
hls
.
HLSPlaybackController
=
function
(
player
)
{
...
...
@@ -23,14 +23,14 @@
self
.
loadManifest
(
self
.
currentRendition
.
url
,
self
.
onM3U8LoadComplete
,
self
.
onM3U8LoadError
,
self
.
onM3U8Update
);
};
self
.
loadManifest
=
function
(
manifestUrl
,
onDataCallback
,
onErrorCallback
,
onUpdateCallback
)
{
self
.
mediaSource
.
addEventListener
(
'sourceopen'
,
function
(
event
)
{
self
.
loadManifest
=
function
(
manifestUrl
,
onDataCallback
)
{
self
.
mediaSource
.
addEventListener
(
'sourceopen'
,
function
()
{
// 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
);
self
.
sourceBuffer
.
appendBuffer
(
self
.
parser
.
getFlvHeader
(),
self
.
player
);
if
(
onDataCallback
)
{
self
.
manifestLoadCompleteCallback
=
onDataCallback
;
...
...
@@ -42,7 +42,7 @@
},
false
);
self
.
player
.
src
({
src
:
videojs
.
URL
.
createObjectURL
(
self
.
mediaSource
),
src
:
window
.
videojs
.
URL
.
createObjectURL
(
self
.
mediaSource
),
type
:
"video/flv"
});
};
...
...
@@ -65,8 +65,18 @@
}
};
self
.
onM3U8LoadError
=
function
(
error
)
{};
self
.
onM3U8Update
=
function
(
m3u8
)
{};
self
.
onM3U8LoadError
=
function
(
error
)
{
if
(
error
)
{
console
.
log
(
error
);
}
};
self
.
onM3U8Update
=
function
(
m3u8
)
{
if
(
m3u8
)
{
console
.
log
(
m3u8
);
}
};
self
.
loadSegment
=
function
(
segment
)
{
self
.
segmentController
=
new
SegmentController
();
...
...
@@ -88,9 +98,14 @@
self
.
loadNextSegment
=
function
()
{
self
.
currentSegment
++
;
self
.
loadSegment
(
self
.
currentManifest
.
mediaItems
[
self
.
currentSegment
]);
}
}
;
self
.
onSegmentLoadError
=
function
(
error
)
{};
self
.
onSegmentLoadError
=
function
(
error
)
{
if
(
error
)
{
console
.
log
(
error
);
}
};
};
})(
this
);
...
...
src/m3u8/m3u8-parser.js
View file @
dd4862c
...
...
@@ -38,11 +38,11 @@
lines
=
rawDataString
.
split
(
'\n'
);
lines
.
forEach
(
function
(
value
,
index
)
{
var
segment
,
rendition
,
attribute
;
var
segment
,
rendition
,
attribute
s
;
switch
(
self
.
getTagType
(
value
))
{
case
tagTypes
.
EXTM3U
:
data
.
hasValidM3UTag
=
(
index
==
0
);
data
.
hasValidM3UTag
=
(
index
==
=
0
);
if
(
!
data
.
hasValidM3UTag
)
{
data
.
invalidReasons
.
push
(
"Invalid EXTM3U Tag"
);
}
...
...
@@ -95,12 +95,12 @@
if
(
rendition
[
attrValue
.
split
(
'='
)[
0
].
toLowerCase
()].
split
(
'x'
).
length
===
2
)
{
rendition
.
resolution
=
{
width
:
parseInt
(
rendition
[
attrValue
.
split
(
'='
)[
0
].
toLowerCase
()].
split
(
'x'
)[
0
]),
height
:
parseInt
(
rendition
[
attrValue
.
split
(
'='
)[
0
].
toLowerCase
()].
split
(
'x'
)[
1
])
}
width
:
parseInt
(
rendition
[
attrValue
.
split
(
'='
)[
0
].
toLowerCase
()].
split
(
'x'
)[
0
]
,
10
),
height
:
parseInt
(
rendition
[
attrValue
.
split
(
'='
)[
0
].
toLowerCase
()].
split
(
'x'
)[
1
]
,
10
)
}
;
}
}
else
{
rendition
[
attrValue
.
split
(
'='
)[
0
].
toLowerCase
()]
=
parseInt
(
attrValue
.
split
(
'='
)[
1
]);
rendition
[
attrValue
.
split
(
'='
)[
0
].
toLowerCase
()]
=
parseInt
(
attrValue
.
split
(
'='
)[
1
]
,
10
);
}
});
...
...
@@ -128,7 +128,7 @@
break
;
case
tagTypes
.
MEDIA_SEQUENCE
:
data
.
mediaSequence
=
parseInt
(
self
.
getTagValue
(
value
));
data
.
mediaSequence
=
parseInt
(
self
.
getTagValue
(
value
)
,
10
);
break
;
case
tagTypes
.
ALLOW_CACHE
:
...
...
src/m3u8/m3u8.js
View file @
dd4862c
(
function
(
window
)
{
window
.
videojs
.
hls
.
M3U8
=
function
()
{
(
function
(
window
)
{
window
.
videojs
.
hls
.
M3U8
=
function
()
{
this
.
directory
=
""
;
this
.
allowCache
=
"NO"
;
this
.
playlistItems
=
[];
...
...
src/manifest-controller.js
View file @
dd4862c
(
function
(
window
)
{
var
M3U8
=
window
.
videojs
.
hls
.
M3U8
,
M3U8Parser
=
window
.
videojs
.
hls
.
M3U8Parser
;
window
.
videojs
.
hls
.
ManifestController
=
function
()
{
var
self
=
this
;
self
.
parser
;
self
.
data
;
self
.
url
;
self
.
onDataCallback
;
self
.
onErrorCallback
;
self
.
onUpdateCallback
;
self
.
loadManifest
=
function
(
manifestUrl
,
onDataCallback
,
onErrorCallback
,
onUpdateCallback
)
{
self
.
url
=
manifestUrl
;
...
...
@@ -28,7 +19,7 @@
self
.
onUpdateCallback
=
onUpdateCallback
;
}
vjs
.
get
(
manifestUrl
,
self
.
onManifestLoadComplete
,
self
.
onManifestLoadError
);
window
.
vjs
.
get
(
manifestUrl
,
self
.
onManifestLoadComplete
,
self
.
onManifestLoadError
);
};
self
.
parseManifest
=
function
(
dataAsString
)
{
...
...
@@ -42,15 +33,15 @@
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
(
self
.
onErrorCallback
!=
undefined
)
{
self
.
onErrorCallback
((
err
!=
undefined
)
?
err
:
null
);
if
(
self
.
onErrorCallback
!=
=
undefined
)
{
self
.
onErrorCallback
((
err
!=
=
undefined
)
?
err
:
null
);
}
};
}
}
;
})(
this
);
...
...
src/segment-controller.js
View file @
dd4862c
...
...
@@ -14,7 +14,7 @@
request
.
open
(
'GET'
,
segmentUrl
,
true
);
request
.
responseType
=
'arraybuffer'
;
request
.
onload
=
function
(
response
)
{
request
.
onload
=
function
()
{
self
.
onSegmentLoadComplete
(
new
Uint8Array
(
request
.
response
));
};
...
...
@@ -29,7 +29,7 @@
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
.
isCached
=
parseInt
(
self
.
responseTimestamp
-
self
.
requestTimestamp
,
10
)
<
75
;
self
.
data
.
throughput
=
self
.
calculateThroughput
(
self
.
data
.
byteLength
,
self
.
requestTimestamp
,
self
.
responseTimestamp
);
return
self
.
data
;
...
...
@@ -37,7 +37,7 @@
self
.
calculateThroughput
=
function
(
dataAmount
,
startTime
,
endTime
)
{
return
Math
.
round
(
dataAmount
/
(
endTime
-
startTime
)
*
1000
)
*
8
;
}
}
;
self
.
onSegmentLoadComplete
=
function
(
response
)
{
var
output
;
...
...
@@ -57,8 +57,8 @@
}
if
(
self
.
onErrorCallback
!==
undefined
)
{
onErrorCallback
(
error
);
self
.
onErrorCallback
(
error
);
}
};
}
}
;
})(
this
);
...
...
test/video-js-hls_test.js
View file @
dd4862c
(
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
*/
module
(
'm3u8 parser'
,
{
setup
:
function
()
{
m3u8parser
=
new
window
.
videojs
.
hls
.
M3U8Parser
();
}
});
test
(
'should create my parser'
,
function
()
{
ok
(
m3u8parser
!=
undefined
);
});
test
(
'should successfully parse manifest data'
,
function
()
{
var
parsedData
=
m3u8parser
.
parse
(
window
.
playlistData
);
ok
(
parsedData
);
});
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
.
hasValidM3UTag
,
true
,
'data has valid EXTM3U'
);
equal
(
data
.
targetDuration
,
10
,
'data has correct TARGET DURATION'
);
equal
(
data
.
allowCache
,
"NO"
,
'acceptable ALLOW CACHE'
);
equal
(
data
.
isPlaylist
,
true
,
'data is parsed as a PLAYLIST as expected'
);
equal
(
data
.
playlistType
,
"VOD"
,
'acceptable PLAYLIST TYPE'
);
equal
(
data
.
mediaItems
.
length
,
16
,
'acceptable mediaItem count'
);
equal
(
data
.
mediaSequence
,
0
,
'MEDIA SEQUENCE is correct'
);
equal
(
data
.
totalDuration
,
-
1
,
"ZEN TOTAL DURATION is unknown as expected"
);
equal
(
data
.
hasEndTag
,
true
,
'should have ENDLIST tag'
);
});
module
(
'brightcove playlist'
,
{
setup
:
function
()
{
m3u8parser
=
new
window
.
videojs
.
hls
.
M3U8Parser
();
}
});
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'
);
}
);
module
(
'manifest controller'
,
{
setup
:
function
()
{
manifestController
=
new
window
.
videojs
.
hls
.
ManifestController
();
this
.
vjsget
=
vjs
.
get
;
vjs
.
get
=
function
(
url
,
success
,
error
)
{
success
(
window
.
brightcove_playlist_data
);
};
},
teardown
:
function
()
{
vjs
.
get
=
this
.
vjsget
;
}
});
test
(
'should create'
,
function
()
{
ok
(
manifestController
);
});
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'
);
});
test
(
'should get a manifest from hermes'
,
function
()
{
manifestController
.
loadManifest
(
'http://example.com/16x9-master.m3u8'
,
function
(
responseData
)
{
ok
(
responseData
);
},
function
(
errorData
)
{
ok
(
false
,
'does not error'
);
},
function
(
updateData
)
{});
});
module
(
'segment controller'
,
{
setup
:
function
()
{
segmentController
=
new
window
.
videojs
.
hls
.
SegmentController
();
this
.
vjsget
=
vjs
.
get
;
vjs
.
get
=
function
(
url
,
success
,
error
)
{
console
.
log
(
'load segment url'
,
url
);
success
(
window
.
bcSegment
);
};
},
teardown
:
function
()
{
vjs
.
get
=
this
.
vjsget
;
}
});
test
(
'bandwidth calulation test'
,
function
()
{
var
multiSecondData
=
segmentController
.
calculateThroughput
(
10000
,
1000
,
2000
),
subSecondData
=
segmentController
.
calculateThroughput
(
10000
,
1000
,
1500
);
equal
(
multiSecondData
,
80000
,
'MULTI-Second bits per second calculation'
);
equal
(
subSecondData
,
160000
,
'SUB-Second bits per second calculation'
);
});
/*
======== 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
*/
module
(
'm3u8 parser'
,
{
setup
:
function
()
{
m3u8parser
=
new
window
.
videojs
.
hls
.
M3U8Parser
();
}
});
test
(
'should create my parser'
,
function
()
{
ok
(
m3u8parser
!=
undefined
);
});
test
(
'should successfully parse manifest data'
,
function
()
{
var
parsedData
=
m3u8parser
.
parse
(
window
.
playlistData
);
ok
(
parsedData
);
});
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
.
hasValidM3UTag
,
true
,
'data has valid EXTM3U'
);
equal
(
data
.
targetDuration
,
10
,
'data has correct TARGET DURATION'
);
equal
(
data
.
allowCache
,
"NO"
,
'acceptable ALLOW CACHE'
);
equal
(
data
.
isPlaylist
,
true
,
'data is parsed as a PLAYLIST as expected'
);
equal
(
data
.
playlistType
,
"VOD"
,
'acceptable PLAYLIST TYPE'
);
equal
(
data
.
mediaItems
.
length
,
16
,
'acceptable mediaItem count'
);
equal
(
data
.
mediaSequence
,
0
,
'MEDIA SEQUENCE is correct'
);
equal
(
data
.
totalDuration
,
-
1
,
"ZEN TOTAL DURATION is unknown as expected"
);
equal
(
data
.
hasEndTag
,
true
,
'should have ENDLIST tag'
);
});
module
(
'brightcove playlist'
,
{
setup
:
function
()
{
m3u8parser
=
new
window
.
videojs
.
hls
.
M3U8Parser
();
}
});
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'
);
}
);
module
(
'manifest controller'
,
{
setup
:
function
()
{
manifestController
=
new
window
.
videojs
.
hls
.
ManifestController
();
this
.
vjsget
=
vjs
.
get
;
vjs
.
get
=
function
(
url
,
success
,
error
)
{
success
(
window
.
brightcove_playlist_data
);
};
},
teardown
:
function
()
{
vjs
.
get
=
this
.
vjsget
;
}
});
test
(
'should create'
,
function
()
{
ok
(
manifestController
);
});
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'
);
});
test
(
'should get a manifest from hermes'
,
function
()
{
manifestController
.
loadManifest
(
'http://example.com/16x9-master.m3u8'
,
function
(
responseData
)
{
ok
(
responseData
);
},
function
(
errorData
)
{
ok
(
false
,
'does not error'
);
},
function
(
updateData
)
{
});
});
module
(
'segment controller'
,
{
setup
:
function
()
{
segmentController
=
new
window
.
videojs
.
hls
.
SegmentController
();
this
.
vjsget
=
vjs
.
get
;
vjs
.
get
=
function
(
url
,
success
,
error
)
{
console
.
log
(
'load segment url'
,
url
);
success
(
window
.
bcSegment
);
};
},
teardown
:
function
()
{
vjs
.
get
=
this
.
vjsget
;
}
});
test
(
'bandwidth calulation test'
,
function
()
{
var
multiSecondData
=
segmentController
.
calculateThroughput
(
10000
,
1000
,
2000
),
subSecondData
=
segmentController
.
calculateThroughput
(
10000
,
1000
,
1500
);
equal
(
multiSecondData
,
80000
,
'MULTI-Second bits per second calculation'
);
equal
(
subSecondData
,
160000
,
'SUB-Second bits per second calculation'
);
});
})(
this
);
...
...
Please
register
or
sign in
to post a comment