17dd1e68 by David LaPalomento

Create test harness and start porting segment parser

Create a qunit test to verify the flv header. Start work on parsing the m2ts packets. The test harness is using a hard-coded Uint8Array which is the first segment of the "bipbop" video. Currently the segment parser is consuming bytes and passing them off to the internal packet parsing function but that isn't yet implemented.
1 parent 7f0a59b2
...@@ -53,7 +53,7 @@ module.exports = function(grunt) { ...@@ -53,7 +53,7 @@ module.exports = function(grunt) {
53 options: { 53 options: {
54 jshintrc: 'test/.jshintrc' 54 jshintrc: 'test/.jshintrc'
55 }, 55 },
56 src: ['test/**/*.js'] 56 src: ['test/**/*.js', '!test/tsSegment.js']
57 }, 57 },
58 }, 58 },
59 watch: { 59 watch: {
......
1 { 1 {
2 "curly": true, 2 "curly": true,
3 "eqeqeq": true, 3 "eqeqeq": true,
4 "globals": {
5 "console": true
6 },
4 "immed": true, 7 "immed": true,
5 "latedef": true, 8 "latedef": true,
6 "newcap": true, 9 "newcap": true,
......
1 /*
2 * aac-stream
3 *
4 *
5 * Copyright (c) 2013 Brightcove
6 * All rights reserved.
7 */
8
9 (function(window) {
10
11 window.videojs.hls.AacStream = function(){
12 this.tags = [];
13 };
14
15 })(this);
1 (function(window) {
2
3 window.videojs.hls.FlvTag = function() {};
4
5 /*
6 package com.videojs.providers.hls.utils{
7
8 import flash.utils.ByteArray;
9 import flash.utils.Endian;
10
11 public class FlvTag extends ByteArray
12 {
13 public static const AUDIO_TAG:uint = 0x08;
14 public static const VIDEO_TAG:uint = 0x09;
15 public static const METADATA_TAG:uint = 0x12;
16
17 public var keyFrame:Boolean = false;
18 private var extraData:Boolean = false;
19 private var adHoc:uint = 0; // Counter if this is a metadata tag, nal start marker if this is a video tag. unused if this is an audio tag
20
21 public var pts:uint;
22 public var dts:uint;
23
24 public static function isAudioFrame(tag:ByteArray):Boolean
25 {
26 return AUDIO_TAG == tag[0];
27 }
28
29 public static function isVideoFrame(tag:ByteArray):Boolean
30 {
31 return VIDEO_TAG == tag[0];
32 }
33
34 public static function isMetaData(tag:ByteArray):Boolean
35 {
36 return METADATA_TAG == tag[0];
37 }
38
39 public static function isKeyFrame(tag:ByteArray):Boolean
40 {
41 if ( isVideoFrame(tag) )
42 return tag[11] == 0x17;
43
44 if( isAudioFrame(tag) )
45 return true;
46
47 if( isMetaData(tag) )
48 return true;
49
50 return false;
51 }
52
53 public static function frameTime(tag:ByteArray):uint
54 {
55 var pts:uint = tag[ 4] << 16;
56 pts |= tag[ 5] << 8;
57 pts |= tag[ 6] << 0;
58 pts |= tag[ 7] << 24;
59 return pts;
60 }
61
62
63 public function FlvTag(type:uint, ed:Boolean = false)
64 {
65 super();
66 extraData = ed;
67 this.endian = Endian.BIG_ENDIAN;
68 switch(type)
69 {
70 case VIDEO_TAG: this.length = 16; break;
71 case AUDIO_TAG: this.length = 13; keyFrame = true; break;
72 case METADATA_TAG: this.length = 29; keyFrame = true; break;
73 default: throw("Error Unknown TagType");
74 }
75
76 this[0] = type
77 this.position = this.length;
78 keyFrame = extraData; // Defaults to false
79 pts = dts = 0;
80 }
81
82 // Negative index into array
83 public function negIndex(pos:uint):int
84 {
85 return this[this.length - pos];
86 }
87
88 // The functions below ONLY work when this[0] == VIDEO_TAG.
89 // We are not going to check for that because we dont want the overhead
90 public function nalUnitSize(nal:ByteArray = null):int
91 {
92 if( 0 == adHoc )
93 return 0;
94
95 return this.length - ( adHoc + 4 );
96 }
97
98
99 public function startNalUnit():void
100 { // remember position and add 4 bytes
101 if ( 0 < adHoc )
102 {
103 throw new Error("Attempted to create new NAL wihout closing the old one");
104 }
105
106 // reserve 4 bytes for nal unit size
107 adHoc = this.length;
108 this.length += 4;
109 this.position = this.length;
110 }
111
112 public function endNalUnit(nal:ByteArray = null):void
113 { // Rewind to the marker and write the size
114 if ( this.length == adHoc + 4 )
115 {
116 this.length -= 4; // we started a nal unit, but didnt write one, so roll back the 4 byte size value
117 }
118 else
119 if ( 0 < adHoc )
120 {
121 var nalStart:uint = adHoc + 4;
122 var nalLength:uint = this.length - nalStart;
123
124 this.position = adHoc;
125 this.writeUnsignedInt( nalLength );
126 this.position = this.length;
127
128 if ( null != nal ) // If the user pass in a ByteArray, copy the NAL to it.
129 nal.writeBytes( this, nalStart, nalLength );
130 }
131
132 adHoc = 0;
133 }
134
135 public function writeMetaDataDouble(key:String, val:Number):void
136 {
137 writeShort ( key.length );
138 writeUTFBytes ( key );
139 writeByte ( 0x00 );
140 writeDouble ( val );
141 ++adHoc;
142 }
143
144 public function writeMetaDataBoolean(key:String, val:Boolean):void
145 {
146 writeShort ( key.length );
147 writeUTFBytes ( key );
148 writeByte ( 0x01 );
149 writeByte ( true == val ? 0x01 : 0x00 );
150 ++adHoc;
151 }
152
153 public function finalize():ByteArray
154 {
155 switch(this[0])
156 { // Video Data
157 case VIDEO_TAG:
158 this[11] = ( ( keyFrame || extraData ) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame)
159 this[12] = extraData ? 0x00 : 0x01;
160
161 var dtsDelta:int = pts - dts;
162 this[13] = ( dtsDelta & 0x00FF0000 ) >>> 16;
163 this[14] = ( dtsDelta & 0x0000FF00 ) >>> 8;
164 this[15] = ( dtsDelta & 0x000000FF ) >>> 0;
165 break;
166
167 case AUDIO_TAG:
168 this[11] = 0xAF;
169 this[12] = extraData ? 0x00 : 0x01;
170 break;
171
172 case METADATA_TAG:
173 this.position = 11;
174 writeByte(0x02); // String type
175 writeShort(0x0A); // 10 Bytes
176 writeUTFBytes("onMetaData");
177 writeByte(0x08); // Array type
178 writeUnsignedInt( adHoc );
179 this.position = this.length;
180 writeUnsignedInt( 0x09 ); // End Data Tag
181 break;
182 }
183
184 var len:int = this.length - 11;
185
186 this[ 1] = ( len & 0x00FF0000 ) >>> 16;
187 this[ 2] = ( len & 0x0000FF00 ) >>> 8;
188 this[ 3] = ( len & 0x000000FF ) >>> 0;
189 this[ 4] = ( pts & 0x00FF0000 ) >>> 16;
190 this[ 5] = ( pts & 0x0000FF00 ) >>> 8;
191 this[ 6] = ( pts & 0x000000FF ) >>> 0;
192 this[ 7] = ( pts & 0xFF000000 ) >>> 24;
193 this[ 8] = 0;
194 this[ 9] = 0;
195 this[10] = 0;
196
197 this.writeUnsignedInt( this.length );
198 return this;
199 }
200 }
201 }
202 */
203 })(this);
1 /*
2 * h264-stream
3 *
4 *
5 * Copyright (c) 2013 Brightcove
6 * All rights reserved.
7 */
8
9 (function(window) {
10
11 window.videojs.hls.H264Stream = function(){
12 this.tags = [];
13 };
14
15 })(this);
1 (function(window) {
2 var
3 FlvTag = window.videojs.hls.FlvTag,
4 H264Stream = window.videojs.hls.H264Stream,
5 AacStream = window.videojs.hls.AacStream,
6 m2tsPacketSize = 188;
7
8 console.assert(H264Stream);
9 console.assert(AacStream);
10
11 window.videojs.hls.SegmentParser = function() {
12 var
13 self = this,
14 parseTSPacket,
15 pmtPid,
16 streamBuffer = new Uint8Array(m2tsPacketSize),
17 streamBufferByteCount = 0,
18 videoPid,
19 h264Stream = new H264Stream(),
20 audioPid,
21 aacStream = new AacStream(),
22 seekToKeyFrame = false;
23
24 // For information on the FLV format, see
25 // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf.
26 // Technically, this function returns the header and a metadata FLV tag
27 // if duration is greater than zero
28 // duration in seconds
29 self.getFlvHeader = function(duration, audio, video) { // :ByteArray {
30 var
31 headBytes = new Uint8Array(3 + 1 + 1 + 4),
32 head = new DataView(headBytes.buffer),
33 metadata,
34 result;
35
36 // default arguments
37 duration = duration || 0;
38 audio = audio === undefined? true : audio;
39 video = video === undefined? true : video;
40
41 // signature
42 head.setUint8(0, 0x46); // 'F'
43 head.setUint8(1, 0x4c); // 'L'
44 head.setUint8(2, 0x56); // 'V'
45
46 // version
47 head.setUint8(3, 0x01);
48
49 // flags
50 head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00));
51
52 // data offset, should be 9 for FLV v1
53 head.setUint32(5, headBytes.byteLength);
54
55 // init the first FLV tag
56 if (duration <= 0) {
57 // no duration available so just write the first field of the first
58 // FLV tag
59 result = new Uint8Array(headBytes.byteLength + 4);
60 result.set(headBytes);
61 result.set([0, 0, 0, 0], headBytes.byteLength);
62 return result;
63 }
64
65 // write out the duration metadata tag
66 metadata = new FlvTag(FlvTag.METADATA_TAG);
67 metadata.pts = metadata.dts = 0;
68 metadata.writeMetaDataDouble("duration", duration);
69 result = new Uint8Array(headBytes.byteLength + metadata.byteLength);
70 result.set(head);
71 result.set(head.bytesLength, metadata.finalize());
72
73 return result;
74 };
75
76 self.flushTags = function() {
77 h264Stream.finishFrame();
78 };
79 self.doSeek = function() {
80 self.flushTags();
81 aacStream.tags.length = 0;
82 h264Stream.tags.length = 0;
83 seekToKeyFrame = true;
84 };
85
86 self.tagsAvailable = function() { // :int {
87 var i, pts; // :uint
88
89 if (seekToKeyFrame) {
90 for (i = 0 ; i < h264Stream.tags.length && seekToKeyFrame; ++i) {
91 if (h264Stream.tags[i].keyFrame) {
92 seekToKeyFrame = false; // We found, a keyframe, stop seeking
93 }
94 }
95
96 if (seekToKeyFrame) {
97 // we didnt find a keyframe. yet
98 h264Stream.tags.length = 0;
99 return 0;
100 }
101
102 // TODO we MAY need to use dts, not pts
103 h264Stream.tags = h264Stream.tags.slice(i);
104 pts = h264Stream.tags[0].pts;
105
106 // Remove any audio before the found keyframe
107 while( 0 < aacStream.tags.length && pts > aacStream.tags[0].pts ) {
108 aacStream.tags.shift();
109 }
110 }
111
112 return h264Stream.tags.length + aacStream.tags.length;
113 };
114
115 self.getNextTag = function() { // :ByteArray {
116 var tag; // :FlvTag; // return tags in approximate dts order
117
118 if (0 === self.tagsAvailable()) {
119 throw new Error("getNextTag() called when 0 == tagsAvailable()");
120 }
121
122 if (0 < h264Stream.tags.length) {
123 if (0 < aacStream.tags.length && aacStream.tags[0].dts < h264Stream.tags[0].dts) {
124 tag = aacStream.tags.shift();
125 } else {
126 tag = h264Stream.tags.shift();
127 }
128 } else if ( 0 < aacStream.tags.length ) {
129 tag = aacStream.tags.shift();
130 } else {
131 // We dont have any tags available to return
132 return new Uint8Array();
133 }
134
135 return tag.finalize();
136 };
137
138 self.parseSegmentBinaryData = function(data) { // :ByteArray) {
139 var
140 dataPosition = 0,
141 dataSlice;
142
143 // To avoid an extra copy, we will stash overflow data, and only
144 // reconstruct the first packet. The rest of the packets will be
145 // parsed directly from data
146 if (streamBufferByteCount > 0) {
147 if (data.byteLength + streamBufferByteCount < m2tsPacketSize) {
148 // the current data is less than a single m2ts packet, so stash it
149 // until we receive more
150
151 // ?? this seems to append streamBuffer onto data and then just give up. I'm not sure why that would be interesting.
152 console.log('data.length + streamBuffer.length < m2tsPacketSize ??');
153 streamBuffer.readBytes(data, data.length, streamBuffer.length);
154 return;
155 } else {
156 // we have enough data for an m2ts packet
157 // process it immediately
158 dataSlice = data.subarray(0, m2tsPacketSize - streamBufferByteCount);
159 streamBuffer.set(dataSlice, streamBufferByteCount);
160
161 parseTSPacket(streamBuffer);
162
163 // reset the buffer
164 streamBuffer = new Uint8Array(m2tsPacketSize);
165 streamBufferByteCount = 0;
166 }
167 }
168
169 while (true) {
170 // Make sure we are TS aligned
171 while(dataPosition < data.byteLength && data[dataPosition] !== 0x47) {
172 // If there is no sync byte skip forward until we find one
173 // TODO if we find a sync byte, look 188 bytes in the future (if
174 // possible). If there is not a sync byte there, keep looking
175 dataPosition++;
176 }
177
178 // base case: not enough data to parse a m2ts packet
179 if (data.byteLength - dataPosition < m2tsPacketSize) {
180 if (data.byteLength - dataPosition > 0) {
181 // there are bytes remaining, save them for next time
182 streamBuffer.set(data.subarray(dataPosition),
183 streamBufferByteCount);
184 streamBufferByteCount += data.byteLength - dataPosition;
185 }
186 return;
187 }
188
189 // attempt to parse a m2ts packet
190 if (parseTSPacket(data.subarray(dataPosition, m2tsPacketSize))) {
191 dataPosition += m2tsPacketSize;
192 } else {
193 // If there was an error parsing a TS packet. it could be
194 // because we are not TS packet aligned. Step one forward by
195 // one byte and allow the code above to find the next
196 console.log('error parsing m2ts packet, attempting to re-align');
197 dataPosition++;
198 }
199 }
200 };
201
202 // TODO add more testing to make sure we dont walk past the end of a TS
203 // packet!
204 parseTSPacket = function(data) { // :ByteArray):Boolean {
205 var
206 s = data.position, //:uint
207 o = s, // :uint
208 e = o + m2tsPacketSize, // :uint
209
210 // Don't look for a sync byte. We handle that in
211 // parseSegmentBinaryData()
212
213 // Payload Unit Start Indicator
214 pusi = !!(data[o+1] & 0x40),
215
216 // PacketId
217 pid = (data[o + 1] & 0x1F) << 8 | data[o+2],
218 afflag = (data[o + 3] & 0x30 ) >>> 4,
219
220 aflen, // :uint
221 patTableId, // :int
222 patCurrentNextIndicator, // Boolean
223 patSectionLength, // :uint
224
225 pesPacketSize, // :int,
226 dataAlignmentIndicator, // :Boolean,
227 ptsDtsIndicator, // :int
228 pesHeaderLength, // :int
229
230 pts, // :uint
231 dts, // :uint
232
233 pmtTableId, // :int
234 pmtCurrentNextIndicator, // :Boolean
235 pmtSectionLength, // :uint
236
237 streamType, // :int
238 elementaryPID, // :int
239 ESInfolength; // :int
240
241 // Continuity Counter we could use this for sanity check, and
242 // corrupt stream detection
243 // cc = (data[o + 3] & 0x0F);
244
245 // Done with TS header
246 o += 4;
247
248 if (afflag > 0x01) { // skip most of the adaption field
249 aflen = data[o];
250 o += aflen + 1;
251 }
252
253 if (0x0000 === pid) {
254 // always test for PMT first! (becuse other variables default to 0)
255
256 // if pusi is set we must skip X bytes (PSI pointer field)
257 o += pusi ? 1 + data[o] : 0;
258 patTableId = data[o];
259
260 console.assert(0x00 === patTableId, 'patTableId should be 0x00');
261
262 patCurrentNextIndicator = !!(data[o+5] & 0x01);
263 if (patCurrentNextIndicator) {
264 patSectionLength = (data[o + 1] & 0x0F) << 8 | data[o + 2];
265 o += 8; // skip past PSI header
266
267 // We currently only support streams with 1 program
268 patSectionLength = (patSectionLength - 9) / 4;
269 if (1 !== patSectionLength) {
270 throw new Error("TS has more that 1 program");
271 }
272
273 // if we ever support more that 1 program (unlikely) loop over them here
274 // var programNumber = data[o + 0] << 8 | data[o + 1];
275 // var programId = (data[o+2] & 0x1F) << 8 | data[o + 3];
276 pmtPid = (data[o + 2] & 0x1F) << 8 | data[o + 3];
277 }
278
279 // We could test the CRC here to detect corruption with extra CPU cost
280 } else if (videoPid === pid || audioPid === pid) {
281 if (pusi) {
282 // comment out for speed
283 // if( 0x00 != data[o+0] || 0x00 != data[o+1] || 0x01 != data[o+2] )
284 // {// look for PES start code
285 // throw new Error("PES did not begin with start code");
286 // }
287
288 // var sid:int = data[o+3]; // StreamID
289 pesPacketSize = (data[o + 4] << 8) | data[o + 5];
290 dataAlignmentIndicator = !!((data[o + 6] & 0x04) >>> 2);
291 ptsDtsIndicator = (data[o + 7] & 0xC0) >>> 6;
292 pesHeaderLength = data[o + 8]; // TODO sanity check header length
293 o += 9; // Skip past PES header
294
295 // PTS and DTS are normially stored as a 33 bit number.
296 // JavaScript does not have a integer type larger than 32 bit
297 // BUT, we need to convert from 90ns to 1ms time scale anyway.
298 // so what we are going to do instead, is drop the least
299 // significant bit (the same as dividing by two) then we can
300 // divide by 45 (45 * 2 = 90) to get ms.
301 if (ptsDtsIndicator & 0x03) {
302 pts = (data[o + 0] & 0x0E) << 28
303 | (data[o + 1] & 0xFF) << 21
304 | (data[o + 2] & 0xFE) << 13
305 | (data[o + 3] & 0xFF) << 6
306 | (data[o + 4] & 0xFE) >>> 2;
307 pts /= 45;
308 if (ptsDtsIndicator & 0x01) {// DTS
309 dts = (data[o + 5] & 0x0E ) << 28
310 | (data[o + 6] & 0xFF ) << 21
311 | (data[o + 7] & 0xFE ) << 13
312 | (data[o + 8] & 0xFF ) << 6
313 | (data[o + 9] & 0xFE ) >>> 2;
314 dts /= 45;
315 } else {
316 dts = pts;
317 }
318 }
319 // Skip past "optional" portion of PTS header
320 o += pesHeaderLength;
321
322 if (videoPid === pid) {
323 // Stash this frame for future use.
324 // console.assert(videoFrames.length < 3);
325
326 h264Stream.setNextTimeStamp(pts,
327 dts,
328 dataAlignmentIndicator);
329 } else if (audioPid === pid) {
330 aacStream.setNextTimeStamp(pts,
331 pesPacketSize,
332 dataAlignmentIndicator);
333 }
334 }
335
336 if (audioPid === pid) {
337 aacStream.writeBytes(data,o,e-o);
338 } else if (videoPid === pid) {
339 h264Stream.writeBytes(data,o,e-o);
340 }
341 } else if (pmtPid === pid) {
342 // TODO sanity check data[o]
343 // if pusi is set we must skip X bytes (PSI pointer field)
344 o += ( pusi ? 1 + data[o] : 0 );
345 pmtTableId = data[o];
346
347 console.assert(0x02 === pmtTableId);
348
349 pmtCurrentNextIndicator = !!(data[o + 5] & 0x01);
350 if (pmtCurrentNextIndicator) {
351 audioPid = videoPid = 0;
352 pmtSectionLength = (data[o + 1] & 0x0F ) << 8 | data[o + 2];
353 // skip CRC and PSI data we dont care about
354 pmtSectionLength -= 13;
355
356 o += 12; // skip past PSI header and some PMT data
357 while (0 < pmtSectionLength) {
358 streamType = data[o + 0];
359 elementaryPID = (data[o + 1] & 0x1F) << 8 | data[o + 2];
360 ESInfolength = (data[o + 3] & 0x0F ) << 8 | data[o + 4];
361 o += 5 + ESInfolength;
362 pmtSectionLength -= 5 + ESInfolength;
363
364 if (0x1B === streamType) {
365 if (0 !== videoPid) {
366 throw new Error("Program has more than 1 video stream");
367 }
368 videoPid = elementaryPID;
369 } else if (0x0F === streamType) {
370 if (0 !== audioPid) {
371 throw new Error("Program has more than 1 audio Stream");
372 }
373 audioPid = elementaryPID;
374 }
375 // TODO add support for MP3 audio
376 }
377 }
378 // We could test the CRC here to detect corruption with extra CPU cost
379 } else if (0x0011 === pid) {
380 // Service Description Table
381 } else if (0x1FFF === pid) {
382 // NULL packet
383 } else {
384 console.log("Unknown PID " + pid);
385 }
386
387 return true;
388 };
389 };
390 })(this);
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
6 * All rights reserved. 6 * All rights reserved.
7 */ 7 */
8 8
9 (function() { 9 (function(window) {
10 10
11 })(); 11 window.videojs.hls = {};
12
13 })(this);
......
This diff could not be displayed because it is too large.
...@@ -6,8 +6,46 @@ ...@@ -6,8 +6,46 @@
6 <!-- Load local QUnit. --> 6 <!-- Load local QUnit. -->
7 <link rel="stylesheet" href="../libs/qunit/qunit.css" media="screen"> 7 <link rel="stylesheet" href="../libs/qunit/qunit.css" media="screen">
8 <script src="../libs/qunit/qunit.js"></script> 8 <script src="../libs/qunit/qunit.js"></script>
9 <!-- Load local lib and tests. --> 9
10 <!-- video.js -->
11 <script src="../libs/video-js/src/js/core.js"></script>
12 <script src="../libs/video-js/src/js/core-object.js"></script>
13 <script src="../libs/video-js/src/js/events.js"></script>
14 <script src="../libs/video-js/src/js/lib.js"></script>
15 <script src="../libs/video-js/src/js/component.js"></script>
16 <script src="../libs/video-js/src/js/button.js"></script>
17 <script src="../libs/video-js/src/js/slider.js"></script>
18 <script src="../libs/video-js/src/js/menu.js"></script>
19 <script src="../libs/video-js/src/js/player.js"></script>
20 <script src="../libs/video-js/src/js/control-bar/control-bar.js"></script>
21 <script src="../libs/video-js/src/js/control-bar/play-toggle.js"></script>
22 <script src="../libs/video-js/src/js/control-bar/time-display.js"></script>
23 <script src="../libs/video-js/src/js/control-bar/fullscreen-toggle.js"></script>
24 <script src="../libs/video-js/src/js/control-bar/progress-control.js"></script>
25 <script src="../libs/video-js/src/js/control-bar/volume-control.js"></script>
26 <script src="../libs/video-js/src/js/control-bar/mute-toggle.js"></script>
27 <script src="../libs/video-js/src/js/control-bar/volume-menu-button.js"></script>
28 <script src="../libs/video-js/src/js/poster.js"></script>
29 <script src="../libs/video-js/src/js/loading-spinner.js"></script>
30 <script src="../libs/video-js/src/js/big-play-button.js"></script>
31 <script src="../libs/video-js/src/js/media/media.js"></script>
32 <script src="../libs/video-js/src/js/media/html5.js"></script>
33 <script src="../libs/video-js/src/js/media/flash.js"></script>
34 <script src="../libs/video-js/src/js/media/loader.js"></script>
35 <script src="../libs/video-js/src/js/tracks.js"></script>
36 <script src="../libs/video-js/src/js/json.js"></script>
37 <script src="../libs/video-js/src/js/setup.js"></script>
38 <script src="../libs/video-js/src/js/plugins.js"></script>
39
40 <!-- HLS plugin -->
10 <script src="../src/video-js-hls.js"></script> 41 <script src="../src/video-js-hls.js"></script>
42 <script src="../src/h264-stream.js"></script>
43 <script src="../src/aac-stream.js"></script>
44 <script src="../src/flv-tag.js"></script>
45 <script src="../src/segment-parser.js"></script>
46 <!-- an example MPEG2-TS segment -->
47 <script src="tsSegment.js"></script>
48
11 <script src="video-js-hls_test.js"></script> 49 <script src="video-js-hls_test.js"></script>
12 </head> 50 </head>
13 <body> 51 <body>
......
1 (function() { 1 (function(window) {
2 /* 2 /*
3 ======== A Handy Little QUnit Reference ======== 3 ======== A Handy Little QUnit Reference ========
4 http://api.qunitjs.com/ 4 http://api.qunitjs.com/
...@@ -19,15 +19,33 @@ ...@@ -19,15 +19,33 @@
19 notStrictEqual(actual, expected, [message]) 19 notStrictEqual(actual, expected, [message])
20 throws(block, [expected], [message]) 20 throws(block, [expected], [message])
21 */ 21 */
22 var parser;
22 23
23 module('environment', { 24 module('environment');
24 // This will run before each test in this module.
25 setup: function() {
26 }
27 });
28 25
29 test('is sane', function() { 26 test('is sane', function() {
30 expect(1); 27 expect(1);
31 ok(true); 28 ok(true);
32 }); 29 });
33 }()); 30
31 module('segment parser', {
32 setup: function() {
33 parser = new window.videojs.hls.SegmentParser();
34 }
35 });
36
37 test('creates an flv header', function() {
38 var header = parser.getFlvHeader();
39 ok(header, 'the header is truthy');
40 equal(9 + 4, header.byteLength, 'the header length is correct');
41 equal(header[0], 'F'.charCodeAt(0), 'the signature is correct');
42 equal(header[1], 'L'.charCodeAt(0), 'the signature is correct');
43 equal(header[2], 'V'.charCodeAt(0), 'the signature is correct');
44 });
45
46 test('parses the first bipbop segment', function() {
47 parser.parseSegmentBinaryData(window.testSegment);
48
49 ok(parser.tagsAvailable(), 'tags should be available');
50 });
51 })(this);
......