f8527bfc by David LaPalomento

Remove old transmuxing infrastructure

All the transmuxing code has moved into mux.js so we can get rid of the remnants in this project.
1 parent a63f0154
1 (function(window) {
2 var
3 FlvTag = window.videojs.Hls.FlvTag,
4 adtsSampleingRates = [
5 96000, 88200,
6 64000, 48000,
7 44100, 32000,
8 24000, 22050,
9 16000, 12000
10 ];
11
12 window.videojs.Hls.AacStream = function() {
13 var
14 next_pts, // :uint
15 state, // :uint
16 pes_length, // :int
17 lastMetaPts,
18
19 adtsProtectionAbsent, // :Boolean
20 adtsObjectType, // :int
21 adtsSampleingIndex, // :int
22 adtsChanelConfig, // :int
23 adtsFrameSize, // :int
24 adtsSampleCount, // :int
25 adtsDuration, // :int
26
27 aacFrame, // :FlvTag = null;
28 extraData; // :uint;
29
30 this.tags = [];
31
32 // (pts:uint):void
33 this.setTimeStampOffset = function(pts) {
34
35 // keep track of the last time a metadata tag was written out
36 // set the initial value so metadata will be generated before any
37 // payload data
38 lastMetaPts = pts - 1000;
39 };
40
41 // (pts:uint, pes_size:int, dataAligned:Boolean):void
42 this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
43 next_pts = pts;
44 pes_length = pes_size;
45
46 // If data is aligned, flush all internal buffers
47 if (dataAligned) {
48 state = 0;
49 }
50 };
51
52 // (data:ByteArray, o:int = 0, l:int = 0):void
53 this.writeBytes = function(data, offset, length) {
54 var
55 end, // :int
56 newExtraData, // :uint
57 bytesToCopy; // :int
58
59 // default arguments
60 offset = offset || 0;
61 length = length || 0;
62
63 // Do not allow more than 'pes_length' bytes to be written
64 length = (pes_length < length ? pes_length : length);
65 pes_length -= length;
66 end = offset + length;
67 while (offset < end) {
68 switch (state) {
69 default:
70 state = 0;
71 break;
72 case 0:
73 if (offset >= end) {
74 return;
75 }
76 if (0xFF !== data[offset]) {
77 console.assert(false, 'Error no ATDS header found');
78 offset += 1;
79 state = 0;
80 return;
81 }
82
83 offset += 1;
84 state = 1;
85 break;
86 case 1:
87 if (offset >= end) {
88 return;
89 }
90 if (0xF0 !== (data[offset] & 0xF0)) {
91 console.assert(false, 'Error no ATDS header found');
92 offset +=1;
93 state = 0;
94 return;
95 }
96
97 adtsProtectionAbsent = !!(data[offset] & 0x01);
98
99 offset += 1;
100 state = 2;
101 break;
102 case 2:
103 if (offset >= end) {
104 return;
105 }
106 adtsObjectType = ((data[offset] & 0xC0) >>> 6) + 1;
107 adtsSampleingIndex = ((data[offset] & 0x3C) >>> 2);
108 adtsChanelConfig = ((data[offset] & 0x01) << 2);
109
110 offset += 1;
111 state = 3;
112 break;
113 case 3:
114 if (offset >= end) {
115 return;
116 }
117 adtsChanelConfig |= ((data[offset] & 0xC0) >>> 6);
118 adtsFrameSize = ((data[offset] & 0x03) << 11);
119
120 offset += 1;
121 state = 4;
122 break;
123 case 4:
124 if (offset >= end) {
125 return;
126 }
127 adtsFrameSize |= (data[offset] << 3);
128
129 offset += 1;
130 state = 5;
131 break;
132 case 5:
133 if(offset >= end) {
134 return;
135 }
136 adtsFrameSize |= ((data[offset] & 0xE0) >>> 5);
137 adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9);
138
139 offset += 1;
140 state = 6;
141 break;
142 case 6:
143 if (offset >= end) {
144 return;
145 }
146 adtsSampleCount = ((data[offset] & 0x03) + 1) * 1024;
147 adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex];
148
149 newExtraData = (adtsObjectType << 11) |
150 (adtsSampleingIndex << 7) |
151 (adtsChanelConfig << 3);
152
153 // write out metadata tags every 1 second so that the decoder
154 // is re-initialized quickly after seeking into a different
155 // audio configuration
156 if (newExtraData !== extraData || next_pts - lastMetaPts >= 1000) {
157 aacFrame = new FlvTag(FlvTag.METADATA_TAG);
158 aacFrame.pts = next_pts;
159 aacFrame.dts = next_pts;
160
161 // AAC is always 10
162 aacFrame.writeMetaDataDouble("audiocodecid", 10);
163 aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig);
164 aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]);
165 // Is AAC always 16 bit?
166 aacFrame.writeMetaDataDouble ("audiosamplesize", 16);
167
168 this.tags.push(aacFrame);
169
170 extraData = newExtraData;
171 aacFrame = new FlvTag(FlvTag.AUDIO_TAG, true);
172 // For audio, DTS is always the same as PTS. We want to set the DTS
173 // however so we can compare with video DTS to determine approximate
174 // packet order
175 aacFrame.pts = next_pts;
176 aacFrame.dts = aacFrame.pts;
177
178 aacFrame.view.setUint16(aacFrame.position, newExtraData);
179 aacFrame.position += 2;
180 aacFrame.length = Math.max(aacFrame.length, aacFrame.position);
181
182 this.tags.push(aacFrame);
183
184 lastMetaPts = next_pts;
185 }
186
187 // Skip the checksum if there is one
188 offset += 1;
189 state = 7;
190 break;
191 case 7:
192 if (!adtsProtectionAbsent) {
193 if (2 > (end - offset)) {
194 return;
195 } else {
196 offset += 2;
197 }
198 }
199
200 aacFrame = new FlvTag(FlvTag.AUDIO_TAG);
201 aacFrame.pts = next_pts;
202 aacFrame.dts = next_pts;
203 state = 8;
204 break;
205 case 8:
206 while (adtsFrameSize) {
207 if (offset >= end) {
208 return;
209 }
210 bytesToCopy = (end - offset) < adtsFrameSize ? (end - offset) : adtsFrameSize;
211 aacFrame.writeBytes(data, offset, bytesToCopy);
212 offset += bytesToCopy;
213 adtsFrameSize -= bytesToCopy;
214 }
215
216 this.tags.push(aacFrame);
217
218 // finished with this frame
219 state = 0;
220 next_pts += adtsDuration;
221 }
222 }
223 };
224 };
225
226 })(this);
1 (function(window) {
2
3 /**
4 * Parser for exponential Golomb codes, a variable-bitwidth number encoding
5 * scheme used by h264.
6 */
7 window.videojs.Hls.ExpGolomb = function(workingData) {
8 var
9 // the number of bytes left to examine in workingData
10 workingBytesAvailable = workingData.byteLength,
11
12 // the current word being examined
13 workingWord = 0, // :uint
14
15 // the number of bits left to examine in the current word
16 workingBitsAvailable = 0; // :uint;
17
18 // ():uint
19 this.length = function() {
20 return (8 * workingBytesAvailable);
21 };
22
23 // ():uint
24 this.bitsAvailable = function() {
25 return (8 * workingBytesAvailable) + workingBitsAvailable;
26 };
27
28 // ():void
29 this.loadWord = function() {
30 var
31 position = workingData.byteLength - workingBytesAvailable,
32 workingBytes = new Uint8Array(4),
33 availableBytes = Math.min(4, workingBytesAvailable);
34
35 if (availableBytes === 0) {
36 throw new Error('no bytes available');
37 }
38
39 workingBytes.set(workingData.subarray(position,
40 position + availableBytes));
41 workingWord = new DataView(workingBytes.buffer).getUint32(0);
42
43 // track the amount of workingData that has been processed
44 workingBitsAvailable = availableBytes * 8;
45 workingBytesAvailable -= availableBytes;
46 };
47
48 // (count:int):void
49 this.skipBits = function(count) {
50 var skipBytes; // :int
51 if (workingBitsAvailable > count) {
52 workingWord <<= count;
53 workingBitsAvailable -= count;
54 } else {
55 count -= workingBitsAvailable;
56 skipBytes = count / 8;
57
58 count -= (skipBytes * 8);
59 workingBytesAvailable -= skipBytes;
60
61 this.loadWord();
62
63 workingWord <<= count;
64 workingBitsAvailable -= count;
65 }
66 };
67
68 // (size:int):uint
69 this.readBits = function(size) {
70 var
71 bits = Math.min(workingBitsAvailable, size), // :uint
72 valu = workingWord >>> (32 - bits); // :uint
73
74 console.assert(size < 32, 'Cannot read more than 32 bits at a time');
75
76 workingBitsAvailable -= bits;
77 if (workingBitsAvailable > 0) {
78 workingWord <<= bits;
79 } else if (workingBytesAvailable > 0) {
80 this.loadWord();
81 }
82
83 bits = size - bits;
84 if (bits > 0) {
85 return valu << bits | this.readBits(bits);
86 } else {
87 return valu;
88 }
89 };
90
91 // ():uint
92 this.skipLeadingZeros = function() {
93 var leadingZeroCount; // :uint
94 for (leadingZeroCount = 0 ; leadingZeroCount < workingBitsAvailable ; ++leadingZeroCount) {
95 if (0 !== (workingWord & (0x80000000 >>> leadingZeroCount))) {
96 // the first bit of working word is 1
97 workingWord <<= leadingZeroCount;
98 workingBitsAvailable -= leadingZeroCount;
99 return leadingZeroCount;
100 }
101 }
102
103 // we exhausted workingWord and still have not found a 1
104 this.loadWord();
105 return leadingZeroCount + this.skipLeadingZeros();
106 };
107
108 // ():void
109 this.skipUnsignedExpGolomb = function() {
110 this.skipBits(1 + this.skipLeadingZeros());
111 };
112
113 // ():void
114 this.skipExpGolomb = function() {
115 this.skipBits(1 + this.skipLeadingZeros());
116 };
117
118 // ():uint
119 this.readUnsignedExpGolomb = function() {
120 var clz = this.skipLeadingZeros(); // :uint
121 return this.readBits(clz + 1) - 1;
122 };
123
124 // ():int
125 this.readExpGolomb = function() {
126 var valu = this.readUnsignedExpGolomb(); // :int
127 if (0x01 & valu) {
128 // the number is odd if the low order bit is set
129 return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
130 } else {
131 return -1 * (valu >>> 1); // divide by two then make it negative
132 }
133 };
134
135 // Some convenience functions
136 // :Boolean
137 this.readBoolean = function() {
138 return 1 === this.readBits(1);
139 };
140
141 // ():int
142 this.readUnsignedByte = function() {
143 return this.readBits(8);
144 };
145
146 this.loadWord();
147
148 };
149 })(this);
1 (function() {
2 var
3 H264ExtraData,
4 ExpGolomb = window.videojs.Hls.ExpGolomb,
5 FlvTag = window.videojs.Hls.FlvTag;
6
7 window.videojs.Hls.H264ExtraData = H264ExtraData = function() {
8 this.sps = []; // :Array
9 this.pps = []; // :Array
10 };
11
12 H264ExtraData.prototype.extraDataExists = function() { // :Boolean
13 return this.sps.length > 0;
14 };
15
16 // (sizeOfScalingList:int, expGolomb:ExpGolomb):void
17 H264ExtraData.prototype.scaling_list = function(sizeOfScalingList, expGolomb) {
18 var
19 lastScale = 8, // :int
20 nextScale = 8, // :int
21 j,
22 delta_scale; // :int
23
24 for (j = 0; j < sizeOfScalingList; ++j) {
25 if (0 !== nextScale) {
26 delta_scale = expGolomb.readExpGolomb();
27 nextScale = (lastScale + delta_scale + 256) % 256;
28 //useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 )
29 }
30
31 lastScale = (nextScale === 0) ? lastScale : nextScale;
32 // scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
33 // lastScale = scalingList[ j ]
34 }
35 };
36
37 /**
38 * RBSP: raw bit-stream payload. The actual encoded video data.
39 *
40 * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
41 * to a complete video sequence, like width and height.
42 */
43 H264ExtraData.prototype.getSps0Rbsp = function() { // :ByteArray
44 var
45 sps = this.sps[0],
46 offset = 1,
47 start = 1,
48 written = 0,
49 end = sps.byteLength - 2,
50 result = new Uint8Array(sps.byteLength);
51
52 // In order to prevent 0x0000 01 from being interpreted as a
53 // NAL start code, occurences of that byte sequence in the
54 // RBSP are escaped with an "emulation byte". That turns
55 // sequences of 0x0000 01 into 0x0000 0301. When interpreting
56 // a NAL payload, they must be filtered back out.
57 while (offset < end) {
58 if (sps[offset] === 0x00 &&
59 sps[offset + 1] === 0x00 &&
60 sps[offset + 2] === 0x03) {
61 result.set(sps.subarray(start, offset + 1), written);
62 written += offset + 1 - start;
63 start = offset + 3;
64 }
65 offset++;
66 }
67 result.set(sps.subarray(start), written);
68 return result.subarray(0, written + (sps.byteLength - start));
69 };
70
71 // (pts:uint):FlvTag
72 H264ExtraData.prototype.metaDataTag = function(pts) {
73 var
74 tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
75 expGolomb, // :ExpGolomb
76 profile_idc, // :int
77 chroma_format_idc, // :int
78 imax, // :int
79 i, // :int
80
81 pic_order_cnt_type, // :int
82 num_ref_frames_in_pic_order_cnt_cycle, // :uint
83
84 pic_width_in_mbs_minus1, // :int
85 pic_height_in_map_units_minus1, // :int
86
87 frame_mbs_only_flag, // :int
88 frame_cropping_flag, // :Boolean
89
90 frame_crop_left_offset = 0, // :int
91 frame_crop_right_offset = 0, // :int
92 frame_crop_top_offset = 0, // :int
93 frame_crop_bottom_offset = 0, // :int
94
95 width,
96 height;
97
98 tag.dts = pts;
99 tag.pts = pts;
100 expGolomb = new ExpGolomb(this.getSps0Rbsp());
101
102 // :int = expGolomb.readUnsignedByte(); // profile_idc u(8)
103 profile_idc = expGolomb.readUnsignedByte();
104
105 // constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
106 expGolomb.skipBits(16);
107
108 // seq_parameter_set_id
109 expGolomb.skipUnsignedExpGolomb();
110
111 if (profile_idc === 100 ||
112 profile_idc === 110 ||
113 profile_idc === 122 ||
114 profile_idc === 244 ||
115 profile_idc === 44 ||
116 profile_idc === 83 ||
117 profile_idc === 86 ||
118 profile_idc === 118 ||
119 profile_idc === 128) {
120 chroma_format_idc = expGolomb.readUnsignedExpGolomb();
121 if (3 === chroma_format_idc) {
122 expGolomb.skipBits(1); // separate_colour_plane_flag
123 }
124 expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
125 expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
126 expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag
127 if (expGolomb.readBoolean()) { // seq_scaling_matrix_present_flag
128 imax = (chroma_format_idc !== 3) ? 8 : 12;
129 for (i = 0 ; i < imax ; ++i) {
130 if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
131 if (i < 6) {
132 this.scaling_list(16, expGolomb);
133 } else {
134 this.scaling_list(64, expGolomb);
135 }
136 }
137 }
138 }
139 }
140
141 expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
142 pic_order_cnt_type = expGolomb.readUnsignedExpGolomb();
143
144 if ( 0 === pic_order_cnt_type ) {
145 expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
146 } else if ( 1 === pic_order_cnt_type ) {
147 expGolomb.skipBits(1); // delta_pic_order_always_zero_flag
148 expGolomb.skipExpGolomb(); // offset_for_non_ref_pic
149 expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field
150 num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb();
151 for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) {
152 expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ]
153 }
154 }
155
156 expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames
157 expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag
158 pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb();
159 pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb();
160
161 frame_mbs_only_flag = expGolomb.readBits(1);
162 if (0 === frame_mbs_only_flag) {
163 expGolomb.skipBits(1); // mb_adaptive_frame_field_flag
164 }
165
166 expGolomb.skipBits(1); // direct_8x8_inference_flag
167 frame_cropping_flag = expGolomb.readBoolean();
168 if (frame_cropping_flag) {
169 frame_crop_left_offset = expGolomb.readUnsignedExpGolomb();
170 frame_crop_right_offset = expGolomb.readUnsignedExpGolomb();
171 frame_crop_top_offset = expGolomb.readUnsignedExpGolomb();
172 frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb();
173 }
174
175 width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2;
176 height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2);
177
178 tag.writeMetaDataDouble("videocodecid", 7);
179 tag.writeMetaDataDouble("width", width);
180 tag.writeMetaDataDouble("height", height);
181 // tag.writeMetaDataDouble("videodatarate", 0 );
182 // tag.writeMetaDataDouble("framerate", 0);
183
184 return tag;
185 };
186
187 // (pts:uint):FlvTag
188 H264ExtraData.prototype.extraDataTag = function(pts) {
189 var
190 i,
191 tag = new FlvTag(FlvTag.VIDEO_TAG, true);
192
193 tag.dts = pts;
194 tag.pts = pts;
195
196 tag.writeByte(0x01);// version
197 tag.writeByte(this.sps[0][1]);// profile
198 tag.writeByte(this.sps[0][2]);// compatibility
199 tag.writeByte(this.sps[0][3]);// level
200 tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
201 tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
202 tag.writeShort( this.sps[0].length ); // data of SPS
203 tag.writeBytes( this.sps[0] ); // SPS
204
205 tag.writeByte( this.pps.length ); // num of PPS (will there ever be more that 1 PPS?)
206 for (i = 0 ; i < this.pps.length ; ++i) {
207 tag.writeShort(this.pps[i].length); // 2 bytes for length of PPS
208 tag.writeBytes(this.pps[i]); // data of PPS
209 }
210
211 return tag;
212 };
213 })();
1 (function(window) {
2 var
3 FlvTag = window.videojs.Hls.FlvTag,
4 H264ExtraData = window.videojs.Hls.H264ExtraData,
5 H264Stream,
6 NALUnitType;
7
8 /**
9 * Network Abstraction Layer (NAL) units are the packets of an H264
10 * stream. NAL units are divided into types based on their payload
11 * data. Each type has a unique numeric identifier.
12 *
13 * NAL unit
14 * |- NAL header -|------ RBSP ------|
15 *
16 * NAL unit: Network abstraction layer unit. The combination of a NAL
17 * header and an RBSP.
18 * NAL header: the encapsulation unit for transport-specific metadata in
19 * an h264 stream. Exactly one byte.
20 */
21 // incomplete, see Table 7.1 of ITU-T H.264 for 12-32
22 window.videojs.Hls.NALUnitType = NALUnitType = {
23 unspecified: 0,
24 slice_layer_without_partitioning_rbsp_non_idr: 1,
25 slice_data_partition_a_layer_rbsp: 2,
26 slice_data_partition_b_layer_rbsp: 3,
27 slice_data_partition_c_layer_rbsp: 4,
28 slice_layer_without_partitioning_rbsp_idr: 5,
29 sei_rbsp: 6,
30 seq_parameter_set_rbsp: 7,
31 pic_parameter_set_rbsp: 8,
32 access_unit_delimiter_rbsp: 9,
33 end_of_seq_rbsp: 10,
34 end_of_stream_rbsp: 11
35 };
36
37 window.videojs.Hls.H264Stream = H264Stream = function() {
38 this._next_pts = 0; // :uint;
39 this._next_dts = 0; // :uint;
40
41 this._h264Frame = null; // :FlvTag
42
43 this._oldExtraData = new H264ExtraData(); // :H264ExtraData
44 this._newExtraData = new H264ExtraData(); // :H264ExtraData
45
46 this._nalUnitType = -1; // :int
47
48 this._state = 0; // :uint;
49
50 this.tags = [];
51 };
52
53 //(pts:uint):void
54 H264Stream.prototype.setTimeStampOffset = function() {};
55
56 //(pts:uint, dts:uint, dataAligned:Boolean):void
57 H264Stream.prototype.setNextTimeStamp = function(pts, dts, dataAligned) {
58 // We could end up with a DTS less than 0 here. We need to deal with that!
59 this._next_pts = pts;
60 this._next_dts = dts;
61
62 // If data is aligned, flush all internal buffers
63 if (dataAligned) {
64 this.finishFrame();
65 }
66 };
67
68 H264Stream.prototype.finishFrame = function() {
69 if (this._h264Frame) {
70 // Push SPS before EVERY IDR frame for seeking
71 if (this._newExtraData.extraDataExists()) {
72 this._oldExtraData = this._newExtraData;
73 this._newExtraData = new H264ExtraData();
74 }
75
76 // Check if keyframe and the length of tags.
77 // This makes sure we write metadata on the first frame of a segment.
78 if (this._oldExtraData.extraDataExists() &&
79 (this._h264Frame.keyFrame || this.tags.length === 0)) {
80 // Push extra data on every IDR frame in case we did a stream change + seek
81 this.tags.push(this._oldExtraData.metaDataTag(this._h264Frame.pts));
82 this.tags.push(this._oldExtraData.extraDataTag(this._h264Frame.pts));
83 }
84
85 this._h264Frame.endNalUnit();
86 this.tags.push(this._h264Frame);
87
88 }
89
90 this._h264Frame = null;
91 this._nalUnitType = -1;
92 this._state = 0;
93 };
94
95 // (data:ByteArray, o:int, l:int):void
96 H264Stream.prototype.writeBytes = function(data, offset, length) {
97 var
98 nalUnitSize, // :uint
99 start, // :uint
100 end, // :uint
101 t; // :int
102
103 // default argument values
104 offset = offset || 0;
105 length = length || 0;
106
107 if (length <= 0) {
108 // data is empty so there's nothing to write
109 return;
110 }
111
112 // scan through the bytes until we find the start code (0x000001) for a
113 // NAL unit and then begin writing it out
114 // strip NAL start codes as we go
115 switch (this._state) {
116 default:
117 /* falls through */
118 case 0:
119 this._state = 1;
120 /* falls through */
121 case 1:
122 // A NAL unit may be split across two TS packets. Look back a bit to
123 // make sure the prefix of the start code wasn't already written out.
124 if (data[offset] <= 1) {
125 nalUnitSize = this._h264Frame ? this._h264Frame.nalUnitSize() : 0;
126 if (nalUnitSize >= 1 && this._h264Frame.negIndex(1) === 0) {
127 // ?? ?? 00 | O[01] ?? ??
128 if (data[offset] === 1 &&
129 nalUnitSize >= 2 &&
130 this._h264Frame.negIndex(2) === 0) {
131 // ?? 00 00 : 01
132 if (3 <= nalUnitSize && 0 === this._h264Frame.negIndex(3)) {
133 this._h264Frame.length -= 3; // 00 00 00 : 01
134 } else {
135 this._h264Frame.length -= 2; // 00 00 : 01
136 }
137
138 this._state = 3;
139 return this.writeBytes(data, offset + 1, length - 1);
140 }
141
142 if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) {
143 // ?? 00 | 00 01
144 if (nalUnitSize >= 2 && this._h264Frame.negIndex(2) === 0) {
145 this._h264Frame.length -= 2; // 00 00 : 00 01
146 } else {
147 this._h264Frame.length -= 1; // 00 : 00 01
148 }
149
150 this._state = 3;
151 return this.writeBytes(data, offset + 2, length - 2);
152 }
153
154 if (length > 2 &&
155 data[offset] === 0 &&
156 data[offset + 1] === 0 &&
157 data[offset + 2] === 1) {
158 // 00 : 00 00 01
159 // this._h264Frame.length -= 1;
160 this._state = 3;
161 return this.writeBytes(data, offset + 3, length - 3);
162 }
163 }
164 }
165 // allow fall through if the above fails, we may end up checking a few
166 // bytes a second time. But that case will be VERY rare
167 this._state = 2;
168 /* falls through */
169 case 2:
170 // Look for start codes in the data from the current offset forward
171 start = offset;
172 end = start + length;
173 for (t = end - 3; offset < t;) {
174 if (data[offset + 2] > 1) {
175 // if data[offset + 2] is greater than 1, there is no way a start
176 // code can begin before offset + 3
177 offset += 3;
178 } else if (data[offset + 1] !== 0) {
179 offset += 2;
180 } else if (data[offset] !== 0) {
181 offset += 1;
182 } else {
183 // If we get here we have 00 00 00 or 00 00 01
184 if (data[offset + 2] === 1) {
185 if (offset > start) {
186 this._h264Frame.writeBytes(data, start, offset - start);
187 }
188 this._state = 3;
189 offset += 3;
190 return this.writeBytes(data, offset, end - offset);
191 }
192
193 if (end - offset >= 4 &&
194 data[offset + 2] === 0 &&
195 data[offset + 3] === 1) {
196 if (offset > start) {
197 this._h264Frame.writeBytes(data, start, offset - start);
198 }
199 this._state = 3;
200 offset += 4;
201 return this.writeBytes(data, offset, end - offset);
202 }
203
204 // We are at the end of the buffer, or we have 3 NULLS followed by
205 // something that is not a 1, either way we can step forward by at
206 // least 3
207 offset += 3;
208 }
209 }
210
211 // We did not find any start codes. Try again next packet
212 this._state = 1;
213 if (this._h264Frame) {
214 this._h264Frame.writeBytes(data, start, length);
215 }
216 return;
217 case 3:
218 // The next byte is the first byte of a NAL Unit
219
220 if (this._h264Frame) {
221 // we've come to a new NAL unit so finish up the one we've been
222 // working on
223
224 switch (this._nalUnitType) {
225 case NALUnitType.seq_parameter_set_rbsp:
226 this._h264Frame.endNalUnit(this._newExtraData.sps);
227 break;
228 case NALUnitType.pic_parameter_set_rbsp:
229 this._h264Frame.endNalUnit(this._newExtraData.pps);
230 break;
231 case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
232 this._h264Frame.endNalUnit();
233 break;
234 default:
235 this._h264Frame.endNalUnit();
236 break;
237 }
238 }
239
240 // setup to begin processing the new NAL unit
241 this._nalUnitType = data[offset] & 0x1F;
242 if (this._h264Frame) {
243 if (this._nalUnitType === NALUnitType.access_unit_delimiter_rbsp) {
244 // starting a new access unit, flush the previous one
245 this.finishFrame();
246 } else if (this._nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
247 this._h264Frame.keyFrame = true;
248 }
249 }
250
251 // finishFrame may render this._h264Frame null, so we must test again
252 if (!this._h264Frame) {
253 this._h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
254 this._h264Frame.pts = this._next_pts;
255 this._h264Frame.dts = this._next_dts;
256 }
257
258 this._h264Frame.startNalUnit();
259 // We know there will not be an overlapping start code, so we can skip
260 // that test
261 this._state = 2;
262 return this.writeBytes(data, offset, length);
263 } // switch
264 };
265 })(this);
1 /**
2 * Accepts program elementary stream (PES) data events and parses out
3 * ID3 metadata from them, if present.
4 * @see http://id3.org/id3v2.3.0
5 */
6 (function(window, videojs, undefined) {
7 'use strict';
8 var
9 // return a percent-encoded representation of the specified byte range
10 // @see http://en.wikipedia.org/wiki/Percent-encoding
11 percentEncode = function(bytes, start, end) {
12 var i, result = '';
13 for (i = start; i < end; i++) {
14 result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
15 }
16 return result;
17 },
18 // return the string representation of the specified byte range,
19 // interpreted as UTf-8.
20 parseUtf8 = function(bytes, start, end) {
21 return window.decodeURIComponent(percentEncode(bytes, start, end));
22 },
23 // return the string representation of the specified byte range,
24 // interpreted as ISO-8859-1.
25 parseIso88591 = function(bytes, start, end) {
26 return window.unescape(percentEncode(bytes, start, end));
27 },
28 tagParsers = {
29 'TXXX': function(tag) {
30 var i;
31 if (tag.data[0] !== 3) {
32 // ignore frames with unrecognized character encodings
33 return;
34 }
35
36 for (i = 1; i < tag.data.length; i++) {
37 if (tag.data[i] === 0) {
38 // parse the text fields
39 tag.description = parseUtf8(tag.data, 1, i);
40 // do not include the null terminator in the tag value
41 tag.value = parseUtf8(tag.data, i + 1, tag.data.length - 1);
42 break;
43 }
44 }
45 },
46 'WXXX': function(tag) {
47 var i;
48 if (tag.data[0] !== 3) {
49 // ignore frames with unrecognized character encodings
50 return;
51 }
52
53 for (i = 1; i < tag.data.length; i++) {
54 if (tag.data[i] === 0) {
55 // parse the description and URL fields
56 tag.description = parseUtf8(tag.data, 1, i);
57 tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
58 break;
59 }
60 }
61 },
62 'PRIV': function(tag) {
63 var i;
64
65 for (i = 0; i < tag.data.length; i++) {
66 if (tag.data[i] === 0) {
67 // parse the description and URL fields
68 tag.owner = parseIso88591(tag.data, 0, i);
69 break;
70 }
71 }
72 tag.privateData = tag.data.subarray(i + 1);
73 }
74 },
75 MetadataStream;
76
77 MetadataStream = function(options) {
78 var
79 settings = {
80 debug: !!(options && options.debug),
81
82 // the bytes of the program-level descriptor field in MP2T
83 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
84 // program element descriptors"
85 descriptor: options && options.descriptor
86 },
87 // the total size in bytes of the ID3 tag being parsed
88 tagSize = 0,
89 // tag data that is not complete enough to be parsed
90 buffer = [],
91 // the total number of bytes currently in the buffer
92 bufferSize = 0,
93 i;
94
95 MetadataStream.prototype.init.call(this);
96
97 // calculate the text track in-band metadata track dispatch type
98 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
99 this.dispatchType = videojs.Hls.SegmentParser.STREAM_TYPES.metadata.toString(16);
100 if (settings.descriptor) {
101 for (i = 0; i < settings.descriptor.length; i++) {
102 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
103 }
104 }
105
106 this.push = function(chunk) {
107 var tag, frameStart, frameSize, frame, i;
108
109 // ignore events that don't look like ID3 data
110 if (buffer.length === 0 &&
111 (chunk.data.length < 10 ||
112 chunk.data[0] !== 'I'.charCodeAt(0) ||
113 chunk.data[1] !== 'D'.charCodeAt(0) ||
114 chunk.data[2] !== '3'.charCodeAt(0))) {
115 if (settings.debug) {
116 videojs.log('Skipping unrecognized metadata packet');
117 }
118 return;
119 }
120
121 // add this chunk to the data we've collected so far
122 buffer.push(chunk);
123 bufferSize += chunk.data.byteLength;
124
125 // grab the size of the entire frame from the ID3 header
126 if (buffer.length === 1) {
127 // the frame size is transmitted as a 28-bit integer in the
128 // last four bytes of the ID3 header.
129 // The most significant bit of each byte is dropped and the
130 // results concatenated to recover the actual value.
131 tagSize = (chunk.data[6] << 21) |
132 (chunk.data[7] << 14) |
133 (chunk.data[8] << 7) |
134 (chunk.data[9]);
135
136 // ID3 reports the tag size excluding the header but it's more
137 // convenient for our comparisons to include it
138 tagSize += 10;
139 }
140
141 // if the entire frame has not arrived, wait for more data
142 if (bufferSize < tagSize) {
143 return;
144 }
145
146 // collect the entire frame so it can be parsed
147 tag = {
148 data: new Uint8Array(tagSize),
149 frames: [],
150 pts: buffer[0].pts,
151 dts: buffer[0].dts
152 };
153 for (i = 0; i < tagSize;) {
154 tag.data.set(buffer[0].data, i);
155 i += buffer[0].data.byteLength;
156 bufferSize -= buffer[0].data.byteLength;
157 buffer.shift();
158 }
159
160 // find the start of the first frame and the end of the tag
161 frameStart = 10;
162 if (tag.data[5] & 0x40) {
163 // advance the frame start past the extended header
164 frameStart += 4; // header size field
165 frameStart += (tag.data[10] << 24) |
166 (tag.data[11] << 16) |
167 (tag.data[12] << 8) |
168 (tag.data[13]);
169
170 // clip any padding off the end
171 tagSize -= (tag.data[16] << 24) |
172 (tag.data[17] << 16) |
173 (tag.data[18] << 8) |
174 (tag.data[19]);
175 }
176
177 // parse one or more ID3 frames
178 // http://id3.org/id3v2.3.0#ID3v2_frame_overview
179 do {
180 // determine the number of bytes in this frame
181 frameSize = (tag.data[frameStart + 4] << 24) |
182 (tag.data[frameStart + 5] << 16) |
183 (tag.data[frameStart + 6] << 8) |
184 (tag.data[frameStart + 7]);
185 if (frameSize < 1) {
186 return videojs.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
187 }
188
189 frame = {
190 id: String.fromCharCode(tag.data[frameStart],
191 tag.data[frameStart + 1],
192 tag.data[frameStart + 2],
193 tag.data[frameStart + 3]),
194 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
195 };
196 if (tagParsers[frame.id]) {
197 tagParsers[frame.id](frame);
198 }
199 tag.frames.push(frame);
200
201 frameStart += 10; // advance past the frame header
202 frameStart += frameSize; // advance past the frame body
203 } while (frameStart < tagSize);
204 this.trigger('data', tag);
205 };
206 };
207 MetadataStream.prototype = new videojs.Hls.Stream();
208
209 videojs.Hls.MetadataStream = MetadataStream;
210 })(window, window.videojs);
1 (function(window) {
2 /*
3 ======== A Handy Little QUnit Reference ========
4 http://api.qunitjs.com/
5
6 Test methods:
7 module(name, {[setup][ ,teardown]})
8 test(name, callback)
9 expect(numberOfAssertions)
10 stop(increment)
11 start(decrement)
12 Test assertions:
13 ok(value, [message])
14 equal(actual, expected, [message])
15 notEqual(actual, expected, [message])
16 deepEqual(actual, expected, [message])
17 notDeepEqual(actual, expected, [message])
18 strictEqual(actual, expected, [message])
19 notStrictEqual(actual, expected, [message])
20 throws(block, [expected], [message])
21 */
22 var
23 buffer,
24 ExpGolomb = window.videojs.Hls.ExpGolomb,
25 expGolomb;
26
27 module('Exponential Golomb coding');
28
29 test('small numbers are coded correctly', function() {
30 var
31 expected = [
32 [0xF8, 0],
33 [0x5F, 1],
34 [0x7F, 2],
35 [0x27, 3],
36 [0x2F, 4],
37 [0x37, 5],
38 [0x3F, 6],
39 [0x11, 7],
40 [0x13, 8],
41 [0x15, 9]
42 ],
43 i = expected.length,
44 result;
45
46 while (i--) {
47 buffer = new Uint8Array([expected[i][0]]);
48 expGolomb = new ExpGolomb(buffer);
49 result = expGolomb.readUnsignedExpGolomb();
50 equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]);
51 }
52 });
53
54 test('drops working data as it is parsed', function() {
55 var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0xFF]));
56 expGolomb.skipBits(8);
57 equal(8, expGolomb.bitsAvailable(), '8 bits remain');
58 equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
59 });
60
61 test('drops working data when skipping leading zeros', function() {
62 var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF]));
63 equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped');
64 equal(8, expGolomb.bitsAvailable(), '8 bits remain');
65 equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
66 });
67
68 test('drops working data when skipping leading zeros', function() {
69 var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF]));
70 equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped');
71 equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain');
72 expGolomb.skipBits(1);
73 equal(0x5a, expGolomb.readBits(8), 'the next bits are read');
74 });
75
76 test('parses a sequence parameter set', function() {
77 var
78 sps = new Uint8Array([
79 0x27, 0x42, 0xe0, 0x0b,
80 0xa9, 0x18, 0x60, 0x9d,
81 0x80, 0x35, 0x06, 0x01,
82 0x06, 0xb6, 0xc2, 0xb5,
83 0xef, 0x7c, 0x04
84 ]),
85 expGolomb = new ExpGolomb(sps);
86
87 strictEqual(expGolomb.readBits(8), 0x27, 'the NAL type specifies an SPS');
88 strictEqual(expGolomb.readBits(8), 66, 'profile_idc is 66');
89 strictEqual(expGolomb.readBits(4), 0x0E, 'constraints 0-3 are correct');
90
91 expGolomb.skipBits(4);
92 strictEqual(expGolomb.readBits(8), 11, 'level_idc is 11');
93 strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'seq_parameter_set_id is 0');
94 strictEqual(expGolomb.readUnsignedExpGolomb(), 1, 'log2_max_frame_num_minus4 is 1');
95 strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'pic_order_cnt_type is 0');
96 strictEqual(expGolomb.readUnsignedExpGolomb(), 3, 'log2_max_pic_order_cnt_lsb_minus4 is 3');
97 strictEqual(expGolomb.readUnsignedExpGolomb(), 2, 'max_num_ref_frames is 2');
98 strictEqual(expGolomb.readBits(1), 0, 'gaps_in_frame_num_value_allowed_flag is false');
99 strictEqual(expGolomb.readUnsignedExpGolomb(), 11, 'pic_width_in_mbs_minus1 is 11');
100 strictEqual(expGolomb.readUnsignedExpGolomb(), 8, 'pic_height_in_map_units_minus1 is 8');
101 strictEqual(expGolomb.readBits(1), 1, 'frame_mbs_only_flag is true');
102 strictEqual(expGolomb.readBits(1), 1, 'direct_8x8_inference_flag is true');
103 strictEqual(expGolomb.readBits(1), 0, 'frame_cropping_flag is false');
104 });
105
106 })(this);
1 (function(window) {
2 /*
3 ======== A Handy Little QUnit Reference ========
4 http://api.qunitjs.com/
5
6 Test methods:
7 module(name, {[setup][ ,teardown]})
8 test(name, callback)
9 expect(numberOfAssertions)
10 stop(increment)
11 start(decrement)
12 Test assertions:
13 ok(value, [message])
14 equal(actual, expected, [message])
15 notEqual(actual, expected, [message])
16 deepEqual(actual, expected, [message])
17 notDeepEqual(actual, expected, [message])
18 strictEqual(actual, expected, [message])
19 notStrictEqual(actual, expected, [message])
20 throws(block, [expected], [message])
21 */
22 var FlvTag = window.videojs.Hls.FlvTag;
23
24 module('FLV tag');
25
26 test('writeBytes with zero length writes the entire array', function() {
27 var
28 tag = new FlvTag(FlvTag.VIDEO_TAG),
29 headerLength = tag.length;
30 tag.writeBytes(new Uint8Array([0x1, 0x2, 0x3]));
31
32 equal(3 + headerLength, tag.length, '3 payload bytes are written');
33 });
34
35 test('writeShort writes a two byte sequence', function() {
36 var
37 tag = new FlvTag(FlvTag.VIDEO_TAG),
38 headerLength = tag.length;
39 tag.writeShort(0x0102);
40
41 equal(2 + headerLength, tag.length, '2 bytes are written');
42 equal(0x0102,
43 new DataView(tag.bytes.buffer).getUint16(tag.length - 2),
44 'the value is written');
45 });
46
47 test('writeBytes grows the internal byte array dynamically', function() {
48 var
49 tag = new FlvTag(FlvTag.VIDEO_TAG),
50 tooManyBytes = new Uint8Array(tag.bytes.byteLength + 1);
51
52 try {
53 tag.writeBytes(tooManyBytes);
54 ok(true, 'the buffer grew to fit the data');
55 } catch(e) {
56 ok(!e, 'the buffer should grow');
57 }
58 });
59
60 })(this);
1 (function(videojs) {
2 module('H264 Stream');
3
4 var
5 nalUnitTypes = window.videojs.Hls.NALUnitType,
6 FlvTag = window.videojs.Hls.FlvTag,
7
8 accessUnitDelimiter = new Uint8Array([
9 0x00,
10 0x00,
11 0x01,
12 nalUnitTypes.access_unit_delimiter_rbsp
13 ]),
14 seqParamSet = new Uint8Array([
15 0x00,
16 0x00,
17 0x01,
18 0x60 | nalUnitTypes.seq_parameter_set_rbsp,
19 0x00, // profile_idc
20 0x00, // constraint_set flags
21 0x00, // level_idc
22 // seq_parameter_set_id ue(v) 0 => 1
23 // log2_max_frame_num_minus4 ue(v) 1 => 010
24 // pic_order_cnt_type ue(v) 0 => 1
25 // log2_max_pic_order_cnt_lsb_minus4 ue(v) 1 => 010
26 // max_num_ref_frames ue(v) 1 => 010
27 // gaps_in_frame_num_value_allowed u(1) 0
28 // pic_width_in_mbs_minus1 ue(v) 0 => 1
29 // pic_height_in_map_units_minus1 ue(v) 0 => 1
30 // frame_mbs_only_flag u(1) 1
31 // direct_8x8_inference_flag u(1) 0
32 // frame_cropping_flag u(1) 0
33 // vui_parameters_present_flag u(1) 0
34 // 1010 1010 0100 1110 00(00 0000)
35 0xAA,
36 0x4E,
37 0x00
38 ]);
39
40 test('metadata is generated for IDRs after a full NAL unit is written', function() {
41 var
42 h264Stream = new videojs.Hls.H264Stream(),
43 idr = new Uint8Array([
44 0x00,
45 0x00,
46 0x01,
47 nalUnitTypes.slice_layer_without_partitioning_rbsp_idr
48 ]);
49
50 h264Stream.setNextTimeStamp(0, 0, true);
51 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
52 h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength);
53 h264Stream.writeBytes(idr, 0, idr.byteLength);
54 h264Stream.setNextTimeStamp(1, 1, true);
55
56 strictEqual(h264Stream.tags.length, 3, 'three tags are written');
57 ok(FlvTag.isMetaData(h264Stream.tags[0].bytes),
58 'metadata is written');
59 ok(FlvTag.isVideoFrame(h264Stream.tags[1].bytes),
60 'picture parameter set is written');
61 ok(h264Stream.tags[2].keyFrame, 'key frame is written');
62 });
63
64 test('make sure we add metadata and extra data at the beginning of a stream', function() {
65 var
66 H264ExtraData = videojs.Hls.H264ExtraData,
67 oldExtraData = H264ExtraData.prototype.extraDataTag,
68 oldMetadata = H264ExtraData.prototype.metaDataTag,
69 h264Stream;
70
71 H264ExtraData.prototype.extraDataTag = function() {
72 return 'extraDataTag';
73 };
74 H264ExtraData.prototype.metaDataTag = function() {
75 return 'metaDataTag';
76 };
77
78 h264Stream = new videojs.Hls.H264Stream();
79
80 h264Stream.setTimeStampOffset(0);
81 h264Stream.setNextTimeStamp(0, 0, true);
82 // the sps provides the metadata for the stream
83 h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength);
84 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
85
86 // make sure that keyFrame is set to false but that we don't have any tags currently written out
87 h264Stream._h264Frame.keyFrame = false;
88 h264Stream.tags = [];
89
90 h264Stream.setNextTimeStamp(5, 5, true);
91 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
92 // flush out the last tag
93 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
94
95 strictEqual(h264Stream.tags.length, 4, 'three tags are ready');
96 strictEqual(h264Stream.tags[0], 'metaDataTag', 'the first tag is the metaDataTag');
97 strictEqual(h264Stream.tags[1], 'extraDataTag', 'the second tag is the extraDataTag');
98
99 H264ExtraData.prototype.extraDataTag = oldExtraData;
100 H264ExtraData.prototype.metaDataTag = oldMetadata;
101 });
102
103 })(window.videojs);
...@@ -77,25 +77,15 @@ module.exports = function(config) { ...@@ -77,25 +77,15 @@ module.exports = function(config) {
77 '../test/karma-qunit-shim.js', 77 '../test/karma-qunit-shim.js',
78 '../src/videojs-hls.js', 78 '../src/videojs-hls.js',
79 '../src/stream.js', 79 '../src/stream.js',
80 '../src/flv-tag.js',
81 '../src/exp-golomb.js',
82 '../src/h264-extradata.js',
83 '../src/h264-stream.js',
84 '../src/aac-stream.js',
85 '../src/metadata-stream.js',
86 '../src/segment-parser.js',
87 '../src/m3u8/m3u8-parser.js', 80 '../src/m3u8/m3u8-parser.js',
88 '../src/xhr.js', 81 '../src/xhr.js',
89 '../src/playlist.js', 82 '../src/playlist.js',
90 '../src/playlist-loader.js', 83 '../src/playlist-loader.js',
91 '../src/decrypter.js', 84 '../src/decrypter.js',
92 '../src/mp4-generator.js',
93 '../src/transmuxer.js',
94 '../tmp/manifests.js', 85 '../tmp/manifests.js',
95 '../tmp/expected.js', 86 '../tmp/expected.js',
96 'tsSegment-bc.js', 87 'tsSegment-bc.js',
97 '../src/bin-utils.js', 88 '../src/bin-utils.js',
98 '../test/muxer/js/mp4-inspector.js',
99 '../test/*.js', 89 '../test/*.js',
100 ], 90 ],
101 91
......
...@@ -42,25 +42,15 @@ module.exports = function(config) { ...@@ -42,25 +42,15 @@ module.exports = function(config) {
42 '../test/karma-qunit-shim.js', 42 '../test/karma-qunit-shim.js',
43 '../src/videojs-hls.js', 43 '../src/videojs-hls.js',
44 '../src/stream.js', 44 '../src/stream.js',
45 '../src/flv-tag.js',
46 '../src/exp-golomb.js',
47 '../src/h264-extradata.js',
48 '../src/h264-stream.js',
49 '../src/aac-stream.js',
50 '../src/metadata-stream.js',
51 '../src/segment-parser.js',
52 '../src/m3u8/m3u8-parser.js', 45 '../src/m3u8/m3u8-parser.js',
53 '../src/xhr.js', 46 '../src/xhr.js',
54 '../src/playlist.js', 47 '../src/playlist.js',
55 '../src/playlist-loader.js', 48 '../src/playlist-loader.js',
56 '../src/decrypter.js', 49 '../src/decrypter.js',
57 '../src/mp4-generator.js',
58 '../src/transmuxer.js',
59 '../tmp/manifests.js', 50 '../tmp/manifests.js',
60 '../tmp/expected.js', 51 '../tmp/expected.js',
61 'tsSegment-bc.js', 52 'tsSegment-bc.js',
62 '../src/bin-utils.js', 53 '../src/bin-utils.js',
63 '../test/muxer/js/mp4-inspector.js',
64 '../test/*.js', 54 '../test/*.js',
65 ], 55 ],
66 56
......
1 /* ==========================================================================
2 HTML5 Boilerplate styles - h5bp.com (generated via initializr.com)
3 ========================================================================== */
4
5 html,
6 button,
7 input,
8 select,
9 textarea {
10 color: #222;
11 }
12
13 body {
14 font-size: 1em;
15 line-height: 1.4;
16 }
17
18 ::-moz-selection {
19 background: #b3d4fc;
20 text-shadow: none;
21 }
22
23 ::selection {
24 background: #b3d4fc;
25 text-shadow: none;
26 }
27
28 hr {
29 display: block;
30 height: 1px;
31 border: 0;
32 border-top: 1px solid #ccc;
33 margin: 1em 0;
34 padding: 0;
35 }
36
37 img {
38 vertical-align: middle;
39 }
40
41 fieldset {
42 border: 0;
43 margin: 0;
44 padding: 0;
45 }
46
47 textarea {
48 resize: vertical;
49 }
50
51 .chromeframe {
52 margin: 0.2em 0;
53 background: #ccc;
54 color: #000;
55 padding: 0.2em 0;
56 }
57
58
59 /* ===== Initializr Styles ==================================================
60 Author: Jonathan Verrecchia - verekia.com/initializr/responsive-template
61 ========================================================================== */
62
63 body {
64 font: 16px/26px Helvetica, Helvetica Neue, Arial;
65 }
66
67 .wrapper {
68 width: 90%;
69 margin: 0 5%;
70 }
71
72 /* ===================
73 ALL: Orange Theme
74 =================== */
75
76 .header-container {
77 border-bottom: 20px solid #e44d26;
78 }
79
80 .footer-container,
81 .main aside {
82 border-top: 20px solid #e44d26;
83 }
84
85 .header-container,
86 .footer-container,
87 .main aside {
88 background: #f16529;
89 }
90
91 .title {
92 color: white;
93 }
94
95 /* ==============
96 MOBILE: Menu
97 ============== */
98
99 nav ul {
100 margin: 0;
101 padding: 0;
102 }
103
104 nav a {
105 display: block;
106 margin-bottom: 10px;
107 padding: 15px 0;
108
109 text-align: center;
110 text-decoration: none;
111 font-weight: bold;
112
113 color: white;
114 background: #e44d26;
115 }
116
117 nav a:hover,
118 nav a:visited {
119 color: white;
120 }
121
122 nav a:hover {
123 text-decoration: underline;
124 }
125
126 /* ==============
127 MOBILE: Main
128 ============== */
129
130 .main {
131 padding: 30px 0;
132 }
133
134 .main article h1 {
135 font-size: 2em;
136 }
137
138 .main aside {
139 color: white;
140 padding: 0px 5% 10px;
141 }
142
143 .footer-container footer {
144 color: white;
145 padding: 20px 0;
146 }
147
148 /* ===============
149 ALL: IE Fixes
150 =============== */
151
152 .ie7 .title {
153 padding-top: 20px;
154 }
155
156 /* ==========================================================================
157 Author's custom styles
158 ========================================================================== */
159
160 section {
161 clear: both;
162 }
163
164 form label {
165 display: block;
166 }
167
168 .result-wrapper {
169 float: left;
170 margin: 10px 0;
171 min-width: 422px;
172 width: 50%;
173 }
174
175 .result {
176 border: thin solid #aaa;
177 border-radius: 5px;
178 font-size: 10px;
179 line-height: 15px;
180 margin: 0 5px;
181 padding: 0 10px;
182 }
183
184 /* ==========================================================================
185 Media Queries
186 ========================================================================== */
187
188 @media only screen and (min-width: 480px) {
189
190 /* ====================
191 INTERMEDIATE: Menu
192 ==================== */
193
194 nav a {
195 float: left;
196 width: 27%;
197 margin: 0 1.7%;
198 padding: 25px 2%;
199 margin-bottom: 0;
200 }
201
202 nav li:first-child a {
203 margin-left: 0;
204 }
205
206 nav li:last-child a {
207 margin-right: 0;
208 }
209
210 /* ========================
211 INTERMEDIATE: IE Fixes
212 ======================== */
213
214 nav ul li {
215 display: inline;
216 }
217
218 .oldie nav a {
219 margin: 0 0.7%;
220 }
221 }
222
223 @media only screen and (min-width: 768px) {
224
225 /* ====================
226 WIDE: CSS3 Effects
227 ==================== */
228
229 .header-container,
230 .main aside {
231 -webkit-box-shadow: 0 5px 10px #aaa;
232 -moz-box-shadow: 0 5px 10px #aaa;
233 box-shadow: 0 5px 10px #aaa;
234 }
235
236 /* ============
237 WIDE: Menu
238 ============ */
239
240 .title {
241 float: left;
242 }
243
244 nav {
245 float: right;
246 width: 38%;
247 }
248
249 /* ============
250 WIDE: Main
251 ============ */
252
253 .main article {
254 float: left;
255 width: 100%;
256 }
257 }
258
259 @media only screen and (min-width: 1140px) {
260
261 /* ===============
262 Maximal Width
263 =============== */
264
265 .wrapper {
266 width: 1026px; /* 1140px - 10% for margins */
267 margin: 0 auto;
268 }
269 }
270
271 /* ==========================================================================
272 Helper classes
273 ========================================================================== */
274
275 .ir {
276 background-color: transparent;
277 border: 0;
278 overflow: hidden;
279 *text-indent: -9999px;
280 }
281
282 .ir:before {
283 content: "";
284 display: block;
285 width: 0;
286 height: 150%;
287 }
288
289 .hidden {
290 display: none !important;
291 visibility: hidden;
292 }
293
294 .visuallyhidden {
295 border: 0;
296 clip: rect(0 0 0 0);
297 height: 1px;
298 margin: -1px;
299 overflow: hidden;
300 padding: 0;
301 position: absolute;
302 width: 1px;
303 }
304
305 .visuallyhidden.focusable:active,
306 .visuallyhidden.focusable:focus {
307 clip: auto;
308 height: auto;
309 margin: 0;
310 overflow: visible;
311 position: static;
312 width: auto;
313 }
314
315 .invisible {
316 visibility: hidden;
317 }
318
319 .clearfix:before,
320 .clearfix:after {
321 content: " ";
322 display: table;
323 }
324
325 .clearfix:after {
326 clear: both;
327 }
328
329 .clearfix {
330 *zoom: 1;
331 }
332
333 /* ==========================================================================
334 Print styles
335 ========================================================================== */
336
337 @media print {
338 * {
339 background: transparent !important;
340 color: #000 !important; /* Black prints faster: h5bp.com/s */
341 box-shadow: none !important;
342 text-shadow: none !important;
343 }
344
345 a,
346 a:visited {
347 text-decoration: underline;
348 }
349
350 a[href]:after {
351 content: " (" attr(href) ")";
352 }
353
354 abbr[title]:after {
355 content: " (" attr(title) ")";
356 }
357
358 /*
359 * Don't show links for images, or javascript/internal links
360 */
361
362 .ir a:after,
363 a[href^="javascript:"]:after,
364 a[href^="#"]:after {
365 content: "";
366 }
367
368 pre,
369 blockquote {
370 border: 1px solid #999;
371 page-break-inside: avoid;
372 }
373
374 thead {
375 display: table-header-group; /* h5bp.com/t */
376 }
377
378 tr,
379 img {
380 page-break-inside: avoid;
381 }
382
383 img {
384 max-width: 100% !important;
385 }
386
387 @page {
388 margin: 0.5cm;
389 }
390
391 p,
392 h2,
393 h3 {
394 orphans: 3;
395 widows: 3;
396 }
397
398 h2,
399 h3 {
400 page-break-after: avoid;
401 }
402 }
403
404 /**
405 * Diff styling
406 */
407 #comparison p {
408 white-space: normal;
409 }
410 #comparison pre {
411 font-size: 90%;
412 }
413 ins, del {
414 text-decoration: none;
415 }
416 ins {
417 background-color: #C6E746;
418 }
419
420 del {
421 background-color: #EE5757
422 }
...\ No newline at end of file ...\ No newline at end of file
1 /*! normalize.css v1.1.2 | MIT License | git.io/normalize */
2
3 /* ==========================================================================
4 HTML5 display definitions
5 ========================================================================== */
6
7 /**
8 * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
9 */
10
11 article,
12 aside,
13 details,
14 figcaption,
15 figure,
16 footer,
17 header,
18 hgroup,
19 main,
20 nav,
21 section,
22 summary {
23 display: block;
24 }
25
26 /**
27 * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
28 */
29
30 audio,
31 canvas,
32 video {
33 display: inline-block;
34 *display: inline;
35 *zoom: 1;
36 }
37
38 /**
39 * Prevent modern browsers from displaying `audio` without controls.
40 * Remove excess height in iOS 5 devices.
41 */
42
43 audio:not([controls]) {
44 display: none;
45 height: 0;
46 }
47
48 /**
49 * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
50 * Known issue: no IE 6 support.
51 */
52
53 [hidden] {
54 display: none;
55 }
56
57 /* ==========================================================================
58 Base
59 ========================================================================== */
60
61 /**
62 * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
63 * `em` units.
64 * 2. Prevent iOS text size adjust after orientation change, without disabling
65 * user zoom.
66 */
67
68 html {
69 font-size: 100%; /* 1 */
70 -ms-text-size-adjust: 100%; /* 2 */
71 -webkit-text-size-adjust: 100%; /* 2 */
72 }
73
74 /**
75 * Address `font-family` inconsistency between `textarea` and other form
76 * elements.
77 */
78
79 html,
80 button,
81 input,
82 select,
83 textarea {
84 font-family: sans-serif;
85 }
86
87 /**
88 * Address margins handled incorrectly in IE 6/7.
89 */
90
91 body {
92 margin: 0;
93 }
94
95 /* ==========================================================================
96 Links
97 ========================================================================== */
98
99 /**
100 * Address `outline` inconsistency between Chrome and other browsers.
101 */
102
103 a:focus {
104 outline: thin dotted;
105 }
106
107 /**
108 * Improve readability when focused and also mouse hovered in all browsers.
109 */
110
111 a:active,
112 a:hover {
113 outline: 0;
114 }
115
116 /* ==========================================================================
117 Typography
118 ========================================================================== */
119
120 /**
121 * Address font sizes and margins set differently in IE 6/7.
122 * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
123 * and Chrome.
124 */
125
126 h1 {
127 font-size: 2em;
128 margin: 0.67em 0;
129 }
130
131 h2 {
132 font-size: 1.5em;
133 margin: 0.83em 0;
134 }
135
136 h3 {
137 font-size: 1.17em;
138 margin: 1em 0;
139 }
140
141 h4 {
142 font-size: 1em;
143 margin: 1.33em 0;
144 }
145
146 h5 {
147 font-size: 0.83em;
148 margin: 1.67em 0;
149 }
150
151 h6 {
152 font-size: 0.67em;
153 margin: 2.33em 0;
154 }
155
156 /**
157 * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
158 */
159
160 abbr[title] {
161 border-bottom: 1px dotted;
162 }
163
164 /**
165 * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
166 */
167
168 b,
169 strong {
170 font-weight: bold;
171 }
172
173 blockquote {
174 margin: 1em 40px;
175 }
176
177 /**
178 * Address styling not present in Safari 5 and Chrome.
179 */
180
181 dfn {
182 font-style: italic;
183 }
184
185 /**
186 * Address differences between Firefox and other browsers.
187 * Known issue: no IE 6/7 normalization.
188 */
189
190 hr {
191 -moz-box-sizing: content-box;
192 box-sizing: content-box;
193 height: 0;
194 }
195
196 /**
197 * Address styling not present in IE 6/7/8/9.
198 */
199
200 mark {
201 background: #ff0;
202 color: #000;
203 }
204
205 /**
206 * Address margins set differently in IE 6/7.
207 */
208
209 p,
210 pre {
211 margin: 1em 0;
212 }
213
214 /**
215 * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
216 */
217
218 code,
219 kbd,
220 pre,
221 samp {
222 font-family: monospace, serif;
223 _font-family: 'courier new', monospace;
224 font-size: 1em;
225 }
226
227 /**
228 * Improve readability of pre-formatted text in all browsers.
229 */
230
231 pre {
232 white-space: pre;
233 white-space: pre-wrap;
234 word-wrap: break-word;
235 }
236
237 /**
238 * Address CSS quotes not supported in IE 6/7.
239 */
240
241 q {
242 quotes: none;
243 }
244
245 /**
246 * Address `quotes` property not supported in Safari 4.
247 */
248
249 q:before,
250 q:after {
251 content: '';
252 content: none;
253 }
254
255 /**
256 * Address inconsistent and variable font size in all browsers.
257 */
258
259 small {
260 font-size: 80%;
261 }
262
263 /**
264 * Prevent `sub` and `sup` affecting `line-height` in all browsers.
265 */
266
267 sub,
268 sup {
269 font-size: 75%;
270 line-height: 0;
271 position: relative;
272 vertical-align: baseline;
273 }
274
275 sup {
276 top: -0.5em;
277 }
278
279 sub {
280 bottom: -0.25em;
281 }
282
283 /* ==========================================================================
284 Lists
285 ========================================================================== */
286
287 /**
288 * Address margins set differently in IE 6/7.
289 */
290
291 dl,
292 menu,
293 ol,
294 ul {
295 margin: 1em 0;
296 }
297
298 dd {
299 margin: 0 0 0 40px;
300 }
301
302 /**
303 * Address paddings set differently in IE 6/7.
304 */
305
306 menu,
307 ol,
308 ul {
309 padding: 0 0 0 40px;
310 }
311
312 /**
313 * Correct list images handled incorrectly in IE 7.
314 */
315
316 nav ul,
317 nav ol {
318 list-style: none;
319 list-style-image: none;
320 }
321
322 /* ==========================================================================
323 Embedded content
324 ========================================================================== */
325
326 /**
327 * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
328 * 2. Improve image quality when scaled in IE 7.
329 */
330
331 img {
332 border: 0; /* 1 */
333 -ms-interpolation-mode: bicubic; /* 2 */
334 }
335
336 /**
337 * Correct overflow displayed oddly in IE 9.
338 */
339
340 svg:not(:root) {
341 overflow: hidden;
342 }
343
344 /* ==========================================================================
345 Figures
346 ========================================================================== */
347
348 /**
349 * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
350 */
351
352 figure {
353 margin: 0;
354 }
355
356 /* ==========================================================================
357 Forms
358 ========================================================================== */
359
360 /**
361 * Correct margin displayed oddly in IE 6/7.
362 */
363
364 form {
365 margin: 0;
366 }
367
368 /**
369 * Define consistent border, margin, and padding.
370 */
371
372 fieldset {
373 border: 1px solid #c0c0c0;
374 margin: 0 2px;
375 padding: 0.35em 0.625em 0.75em;
376 }
377
378 /**
379 * 1. Correct color not being inherited in IE 6/7/8/9.
380 * 2. Correct text not wrapping in Firefox 3.
381 * 3. Correct alignment displayed oddly in IE 6/7.
382 */
383
384 legend {
385 border: 0; /* 1 */
386 padding: 0;
387 white-space: normal; /* 2 */
388 *margin-left: -7px; /* 3 */
389 }
390
391 /**
392 * 1. Correct font size not being inherited in all browsers.
393 * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
394 * and Chrome.
395 * 3. Improve appearance and consistency in all browsers.
396 */
397
398 button,
399 input,
400 select,
401 textarea {
402 font-size: 100%; /* 1 */
403 margin: 0; /* 2 */
404 vertical-align: baseline; /* 3 */
405 *vertical-align: middle; /* 3 */
406 }
407
408 /**
409 * Address Firefox 3+ setting `line-height` on `input` using `!important` in
410 * the UA stylesheet.
411 */
412
413 button,
414 input {
415 line-height: normal;
416 }
417
418 /**
419 * Address inconsistent `text-transform` inheritance for `button` and `select`.
420 * All other form control elements do not inherit `text-transform` values.
421 * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
422 * Correct `select` style inheritance in Firefox 4+ and Opera.
423 */
424
425 button,
426 select {
427 text-transform: none;
428 }
429
430 /**
431 * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
432 * and `video` controls.
433 * 2. Correct inability to style clickable `input` types in iOS.
434 * 3. Improve usability and consistency of cursor style between image-type
435 * `input` and others.
436 * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
437 * Known issue: inner spacing remains in IE 6.
438 */
439
440 button,
441 html input[type="button"], /* 1 */
442 input[type="reset"],
443 input[type="submit"] {
444 -webkit-appearance: button; /* 2 */
445 cursor: pointer; /* 3 */
446 *overflow: visible; /* 4 */
447 }
448
449 /**
450 * Re-set default cursor for disabled elements.
451 */
452
453 button[disabled],
454 html input[disabled] {
455 cursor: default;
456 }
457
458 /**
459 * 1. Address box sizing set to content-box in IE 8/9.
460 * 2. Remove excess padding in IE 8/9.
461 * 3. Remove excess padding in IE 7.
462 * Known issue: excess padding remains in IE 6.
463 */
464
465 input[type="checkbox"],
466 input[type="radio"] {
467 box-sizing: border-box; /* 1 */
468 padding: 0; /* 2 */
469 *height: 13px; /* 3 */
470 *width: 13px; /* 3 */
471 }
472
473 /**
474 * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
475 * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
476 * (include `-moz` to future-proof).
477 */
478
479 input[type="search"] {
480 -webkit-appearance: textfield; /* 1 */
481 -moz-box-sizing: content-box;
482 -webkit-box-sizing: content-box; /* 2 */
483 box-sizing: content-box;
484 }
485
486 /**
487 * Remove inner padding and search cancel button in Safari 5 and Chrome
488 * on OS X.
489 */
490
491 input[type="search"]::-webkit-search-cancel-button,
492 input[type="search"]::-webkit-search-decoration {
493 -webkit-appearance: none;
494 }
495
496 /**
497 * Remove inner padding and border in Firefox 3+.
498 */
499
500 button::-moz-focus-inner,
501 input::-moz-focus-inner {
502 border: 0;
503 padding: 0;
504 }
505
506 /**
507 * 1. Remove default vertical scrollbar in IE 6/7/8/9.
508 * 2. Improve readability and alignment in all browsers.
509 */
510
511 textarea {
512 overflow: auto; /* 1 */
513 vertical-align: top; /* 2 */
514 }
515
516 /* ==========================================================================
517 Tables
518 ========================================================================== */
519
520 /**
521 * Remove most spacing between table cells.
522 */
523
524 table {
525 border-collapse: collapse;
526 border-spacing: 0;
527 }
1 /*! normalize.css v1.1.2 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}
...\ No newline at end of file ...\ No newline at end of file
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title></title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17 </head>
18 <body>
19 <!--[if lt IE 7]>
20 <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
21 <![endif]-->
22
23 <div class="header-container">
24 <header class="wrapper clearfix">
25 <h1 class="title">Transmux Analyzer</h1>
26 </header>
27 </div>
28
29 <div class="main-container">
30 <div class="main wrapper clearfix">
31
32 <article>
33 <header>
34 <p>
35 This page can help you compare the results of the
36 transmuxing performed by videojs-contrib-hls with a known,
37 working file produced by another tool. You could use
38 ffmpeg to transform an MPEG-2 transport stream into an FLV
39 with a command like this:
40 <pre>ffmpeg -i input.ts -acodec copy -vcodec copy output.flv</pre>
41 </p>
42 <p>
43 This page only compares FLVs files. There is
44 a <a href="mp4.html">similar utility</a> for testing the mp4
45 conversion.
46 </p>
47 </header>
48 <section>
49 <h2>Inputs</h2>
50 <form id="inputs">
51 <fieldset>
52 <label>
53 Your original MP2T segment:
54 <input type="file" id="original">
55 </label>
56 <label>
57 Key (optional):
58 <input type="text" id="key">
59 </label>
60 <label>
61 IV (optional):
62 <input type="text" id="iv">
63 </label>
64 </fieldset>
65 <label>
66 A working, FLV version of the underlying stream
67 produced by another tool:
68 <input type="file" id="working">
69 </label>
70 </form>
71 </section>
72 <section>
73 <h2>Tag Comparison</h2>
74 <div class="result-wrapper">
75 <h3>videojs-contrib-hls</h3>
76 <ol class="vjs-tags">
77 </ol>
78 </div>
79 <div class="result-wrapper">
80 <h3>Working</h3>
81 <ol class="working-tags">
82 </ol>
83 </div>
84 </section>
85 <section>
86 <h2>Results</h2>
87 <div class="result-wrapper">
88 <h3>videojs-contrib-hls</h3>
89 <div class="vjs-hls-output result">
90 <p>
91 The results of transmuxing your input file with
92 videojs-contrib-hls will show up here.
93 </p>
94 </div>
95 </div>
96 <div class="result-wrapper">
97 <h3>Working</h3>
98 <div class="working-output result">
99 <p>
100 The "good" version of the file will show up here.
101 </p>
102 </div>
103 </div>
104 </section>
105 </article>
106
107 </div> <!-- #main -->
108 </div> <!-- #main-container -->
109
110 <div class="footer-container">
111 <footer class="wrapper">
112 <h3>footer</h3>
113 </footer>
114 </div>
115
116
117 <script>
118 window.videojs = {
119 Hls: {}
120 };
121 </script>
122 <!-- transmuxing -->
123 <script src="../../src/stream.js"></script>
124 <script src="../../src/flv-tag.js"></script>
125 <script src="../../src/exp-golomb.js"></script>
126 <script src="../../src/h264-stream.js"></script>
127 <script src="../../src/aac-stream.js"></script>
128 <script src="../../src/metadata-stream.js"></script>
129 <script src="../../src/segment-parser.js"></script>
130 <script src="../../node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
131 <script src="../../src/decrypter.js"></script>
132
133 <script src="../../src/bin-utils.js"></script>
134 <script>
135 var inputs = document.getElementById('inputs'),
136 original = document.getElementById('original'),
137 key = document.getElementById('key'),
138 iv = document.getElementById('iv'),
139 working = document.getElementById('working'),
140
141 vjsTags = document.querySelector('.vjs-tags'),
142 workingTags = document.querySelector('.working-tags'),
143
144 vjsOutput = document.querySelector('.vjs-hls-output'),
145 workingOutput = document.querySelector('.working-output'),
146
147 tagTypes = {
148 0x08: 'audio',
149 0x09: 'video',
150 0x12: 'metadata'
151 };
152
153 videojs.log = console.log.bind(console);
154
155 original.addEventListener('change', function() {
156 var reader = new FileReader();
157 reader.addEventListener('loadend', function() {
158 var parser = new videojs.Hls.SegmentParser(),
159 tags = [parser.getFlvHeader()],
160 tag,
161 bytes,
162 hex,
163 li,
164 byteLength = 0,
165 data,
166 i,
167 pos;
168
169 // clear old tag info
170 vjsTags.innerHTML = '';
171
172 // optionally, decrypt the segment
173 if (key.value && iv.value) {
174 bytes = videojs.Hls.decrypt(new Uint8Array(reader.result),
175 key.value.match(/([0-9a-f]{8})/gi).map(function(e) {
176 return parseInt(e, 16);
177 }),
178 iv.value.match(/([0-9a-f]{8})/gi).map(function(e) {
179 return parseInt(e, 16);
180 }));
181 } else {
182 bytes = new Uint8Array(reader.result);
183 }
184
185 parser.parseSegmentBinaryData(bytes);
186
187 // collect all the tags
188 while (parser.tagsAvailable()) {
189 tag = parser.getNextTag();
190 tags.push(tag.bytes);
191 li = document.createElement('li');
192 li.innerHTML = tagTypes[tag.bytes[0]] + ': ' + tag.bytes.byteLength + 'B';
193 vjsTags.appendChild(li);
194 }
195 // create a uint8array for the entire segment and copy everything over
196 i = tags.length;
197 while (i--) {
198 byteLength += tags[i].byteLength;
199 }
200 data = new Uint8Array(byteLength);
201 i = tags.length;
202 pos = byteLength;
203 while (i--) {
204 pos -= tags[i].byteLength;
205 data.set(tags[i], pos);
206 }
207
208 hex = '<pre>'
209 hex += videojs.Hls.utils.hexDump(data);
210 hex += '</pre>'
211
212 vjsOutput.innerHTML = hex;
213 });
214 reader.readAsArrayBuffer(this.files[0]);
215 }, false);
216
217 working.addEventListener('change', function() {
218 var reader = new FileReader();
219 reader.addEventListener('loadend', function() {
220 var hex = '<pre>',
221 bytes = new Uint8Array(reader.result),
222 i = 9, // header
223 dataSize,
224 li;
225
226 // clear old tag info
227 workingTags.innerHTML = '';
228
229 // traverse the tags
230 i += 4; // previous tag size
231 while (i < bytes.byteLength) {
232 dataSize = bytes[i + 1] << 16;
233 dataSize |= bytes[i + 2] << 8;
234 dataSize |= bytes[i + 3];
235 dataSize += 11;
236
237 li = document.createElement('li');
238 li.innerHTML = tagTypes[bytes[i]] + ': ' + dataSize + 'B';
239 workingTags.appendChild(li);
240
241 i += dataSize; // tag size
242 i += 4; // previous tag size
243 }
244
245 // output the hex dump
246 hex += videojs.Hls.utils.hexDump(bytes);
247 hex += '</pre>';
248 workingOutput.innerHTML = hex;
249 });
250 reader.readAsArrayBuffer(this.files[0]);
251 }, false);
252 </script>
253 <script type="text/plain">
254 // map nal_unit_types to friendly names
255 console.log([
256 'unspecified',
257 'slice_layer_without_partitioning',
258 'slice_data_partition_a_layer',
259 'slice_data_partition_b_layer',
260 'slice_data_partition_c_layer',
261 'slice_layer_without_partitioning_idr',
262 'sei',
263 'seq_parameter_set',
264 'pic_parameter_set',
265 'access_unit_delimiter',
266 'end_of_seq',
267 'end_of_stream',
268 'filler',
269 'seq_parameter_set_ext',
270 'prefix_nal_unit',
271 'subset_seq_parameter_set',
272 'reserved',
273 'reserved',
274 'reserved'
275 ][nalUnitType]);
276 </script>
277 </body>
278 </html>
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title>HLS Media Sources Demo</title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17 </head>
18 <body>
19 <!--[if lt IE 7]>
20 <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
21 <![endif]-->
22
23 <div class="header-container">
24 <header class="wrapper clearfix">
25 <h1 class="title">Media Sources Demo</h1>
26 </header>
27 </div>
28
29 <div class="main-container">
30 <div class="main wrapper clearfix">
31
32 <article>
33 <header>
34 <p>
35 videojs-contrib-hls can convert MPEG transport streams
36 into Media Source Extension-compatible media segments on
37 the fly. Check out the demo below in Chrome!
38 </p>
39 </header>
40 <section>
41 <video id=demo width=640 height=360 controls>
42 <p>
43 Your browser is too old to view this demo. How about
44 upgrading?
45 </p>
46 </video>
47 </section>
48 </article>
49
50 </div> <!-- #main -->
51 </div> <!-- #main-container -->
52
53 <div class="footer-container">
54 <footer class="wrapper">
55 </footer>
56 </div>
57
58
59 <script src="zencoder-haze-1.js"></script>
60 <!-- transmuxing -->
61 <script>
62 window.videojs = window.videojs || {
63 Hls: {}
64 };
65 </script>
66 <script src="../../src/stream.js"></script>
67 <script src="../../src/mp4-generator.js"></script>
68 <script src="../../src/transmuxer.js"></script>
69 <script src="../../src/exp-golomb.js"></script>
70 <script src="js/mp4-inspector.js"></script>
71
72 <script src="../../src/bin-utils.js"></script>
73 <script>
74 var mediaSource = new MediaSource(),
75 demo = document.getElementById('demo');
76
77 // setup the media source
78 mediaSource.addEventListener('sourceopen', function() {
79 var videoBuffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d'),
80 audioBuffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'),
81 transmuxer = new videojs.mp2t.Transmuxer(),
82 videoSegments = [],
83 audioSegments = [];
84
85 // expose the machinery for debugging
86 window.vjsMediaSource = mediaSource;
87 window.vjsSourceBuffer = videoBuffer;
88 window.vjsVideo = demo;
89
90 // transmux the MPEG-TS data to BMFF segments
91 transmuxer.on('data', function(segment) {
92 if (segment.type === 'video') {
93 videoSegments.push(segment);
94 } else {
95 audioSegments.push(segment);
96 }
97 });
98 transmuxer.push(hazeVideo);
99 transmuxer.end();
100
101 // buffer up the video data
102 videoBuffer.appendBuffer(videoSegments.shift().data);
103 videoBuffer.addEventListener('updateend', function() {
104 if (videoSegments.length) {
105 videoBuffer.appendBuffer(videoSegments.shift().data);
106 }
107 });
108
109 // buffer up the audio data
110 audioBuffer.appendBuffer(audioSegments.shift().data);
111 audioBuffer.addEventListener('updateend', function() {
112 if (audioSegments.length) {
113 audioBuffer.appendBuffer(audioSegments.shift().data);
114 }
115 });
116 });
117 demo.src = URL.createObjectURL(mediaSource);
118 demo.addEventListener('error', console.log.bind(console));
119 </script>
120 </body>
121 </html>
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title></title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17
18 <script>
19 window.videojs = window.videojs || {
20 Hls: {}
21 };
22 </script>
23 <!--<script src="min-m4s.js"></script>-->
24 <script src="./js/mdat.js"></script>
25 <script src="../../src/mp4-generator.js"></script>
26 </head>
27 <body>
28 <!--[if lt IE 7]>
29 <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
30 <![endif]-->
31
32 <div class="header-container">
33 <header class="wrapper clearfix">
34 <h1 class="title">Transmux Analyzer</h1>
35 </header>
36 </div>
37
38 <div class="main-container">
39 <div class="main wrapper clearfix">
40
41 <article>
42 <header>
43 <p>
44 This page will show a video player that loads a header of an mp4 followed by an mp4 segment.
45 </p>
46 </header>
47 <section>
48 <video controls autoplay width="320" height="240"></video>
49 </section>
50 </article>
51
52 </div> <!-- #main -->
53 </div> <!-- #main-container -->
54
55 <div class="footer-container">
56 <footer class="wrapper">
57 <h3></h3>
58 </footer>
59 </div>
60
61
62
63
64
65 <script>
66 var ms,
67 sourceBuffer,
68 video;
69
70 function closed() {
71 console.log("MediaSource Closed.");
72 }
73
74 var done = false;
75 function loadFragment() {
76 if (done) {
77 return;
78 }
79
80 console.log("LOAD FRAGMENT");
81
82 var moov = videojs.mp4.moov(100, 600, 300, "video");
83 var moof = videojs.mp4.moof([{trackId: 1}]);
84 var frag = Array.prototype.slice.call(moov);
85 frag = frag.concat(Array.prototype.slice.call(moof));
86 frag = frag.concat(Array.prototype.slice.call(mdat));
87
88
89
90 sourceBuffer.appendBuffer( new Uint8Array( frag ) );
91
92 done = true;
93
94 }
95
96 function loadMdat(){
97 console.log('mdat', mdat);
98 setTimeout( function(){
99 sourceBuffer.appendBuffer(new Uint8Amdat);
100 },0);
101
102 }
103
104 function loadInit() {
105 console.log("LOAD INIT");
106 var req = new XMLHttpRequest();
107 req.responseType = "arraybuffer";
108 req.open("GET", "./ts2/Header.m4s", true);
109
110 req.onload = function() {
111 console.log("INIT DONE LOADING");
112 sourceBuffer.appendBuffer(new Uint8Array(req.response));
113 sourceBuffer.addEventListener('update', loadFragment);
114 };
115
116 req.onerror = function () {
117 window.alert("Could not load init.");
118 };
119
120 req.send();
121
122 }
123
124 function opened() {
125
126 console.log("OPENED");
127 sourceBuffer = ms.addSourceBuffer('video/mp4;codecs="avc1.42c00d"');
128
129 sourceBuffer.addEventListener('updateend', console.log.bind(console));
130 sourceBuffer.addEventListener('updatestart', console.log.bind(console));
131 sourceBuffer.addEventListener('update', console.log.bind(console));
132 loadInit();
133 }
134
135 function load() {
136 console.log("START");
137 window.MediaSource = window.MediaSource || window.WebKitMediaSource;
138
139 ms = new window.MediaSource();
140
141 video = document.querySelector('video');
142 video.src = window.URL.createObjectURL(ms);
143
144 ms.addEventListener('webkitsourceopen', opened, false);
145 ms.addEventListener('webkitsourceclose', closed, false);
146
147 ms.addEventListener('sourceopen', opened, false);
148 ms.addEventListener('sourceclose', closed, false);
149
150 ms.addEventListener('update', console.log.bind(console));
151 ms.addEventListener('updateend', console.log.bind(console));
152 }
153 load();
154 </script>
155 </body>
156 </html>
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title></title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17 </head>
18 <body>
19 <!--[if lt IE 7]>
20 <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
21 <![endif]-->
22
23 <div class="header-container">
24 <header class="wrapper clearfix">
25 <h1 class="title">MP4 Media Sources API Test</h1>
26 </header>
27 </div>
28
29 <div class="main-container">
30 <div class="main wrapper clearfix">
31
32 <article>
33 <header>
34 <p>
35 This page is used to demo the playing of an mp4 file via the mediaSources API
36 </p>
37 </header>
38 <section>
39 <h2>Inputs</h2>
40 <form id="inputs">
41 <label>
42 Your original MP2T segment:
43 <input type="file" id="original">
44 </label>
45 <label>
46 A working, MP4 version of the underlying stream
47 produced by another tool:
48 <input type="file" id="working">
49 </label>
50 </form>
51 </section>
52 <section>
53
54 <video controls width="320" height="240">>
55 <source src="sintel_trailer-480p.mp4" type="video/mp4">
56 Your browser does not support the <code>video</code> element.
57 </video>
58 </section>
59 </article>
60
61 </div> <!-- #main -->
62 </div> <!-- #main-container -->
63
64 <div class="footer-container">
65 <footer class="wrapper">
66 <h3></h3>
67 </footer>
68 </div>
69
70 <script>
71 var inputs = document.getElementById('inputs'),
72 original = document.getElementById('original'),
73 working = document.getElementById('working'),
74
75 video = document.createElement('video');
76
77
78 working.addEventListener('change', function() {
79 console.log('File Selectd');
80 var reader = new FileReader();
81 reader.addEventListener('loadend', function() {
82 console.log('File has been loaded');
83 var bytes = new Uint8Array(reader.result),
84 ms = new window.MediaSource(),
85 video = document.querySelector('video');
86
87 video.addEventListener("error", function onError(err) {
88 console.dir("error", err, video.error);
89 });
90
91 video.pause();
92 video.src = window.URL.createObjectURL(ms);
93
94 var onMediaSourceOpen = function() {
95 console.log('on media open');
96 ms.removeEventListener('sourceopen', onMediaSourceOpen);
97 var videoBuffer = ms.addSourceBuffer('video/mp4;codecs="avc1.4D400D"');
98 videoBuffer.appendBuffer(bytes);
99
100 var audioBuffer = ms.addSourceBuffer('audio/mp4;codecs=mp4a.40.2');
101 };
102
103 ms.addEventListener('sourceopen', onMediaSourceOpen);
104
105 console.log('appended bytes', bytes);
106 });
107 reader.readAsArrayBuffer(this.files[0]);
108 });
109
110 </script>
111
112 </body>
113 </html>
1 [
2 {
3 "type": "ftyp"
4 },
5 {
6 "type": "free"
7 },
8 {
9 "boxes": [
10 {
11 "type": "mvhd"
12 },
13 {
14 "boxes": [
15 {
16 "type": "mehd"
17 },
18 {
19 "type": "trex"
20 }
21 ],
22 "type": "mvex"
23 },
24 {
25 "boxes": [
26 {
27 "type": "tkhd"
28 },
29 {
30 "boxes": [
31 {
32 "type": "mdhd"
33 },
34 {
35
36 "type": "hdlr"
37 },
38 {
39 "boxes": [
40 {
41 "type": "vmhd"
42 },
43 {
44 "boxes": [
45 {
46 "dataReferences": [
47 {
48 "type": "url "
49 }
50 ],
51 "type": "dref"
52 }
53 ],
54 "type": "dinf"
55 },
56 {
57 "boxes": [
58 {
59 "sampleDescriptions": [
60 {
61 "config": [
62 {
63 "type": "avcC"
64 },
65 {
66 "type": "btrt"
67 }
68 ],
69 "type": "avc1"
70 }
71 ],
72 "type": "stsd"
73 },
74 {
75 "type": "stts"
76 },
77 {
78 "type": "stsc"
79 },
80 {
81 "type": "stsz"
82 },
83 {
84 "type": "stco"
85 }
86 ],
87 "type": "stbl"
88 }
89 ],
90 "type": "minf"
91 }
92 ],
93 "type": "mdia"
94 }
95 ],
96 "type": "trak"
97 }
98 ],
99 "type": "moov"
100 }
101 ]
...\ No newline at end of file ...\ No newline at end of file
This diff could not be displayed because it is too large.
...@@ -19,13 +19,6 @@ ...@@ -19,13 +19,6 @@
19 <script src="../src/videojs-hls.js"></script> 19 <script src="../src/videojs-hls.js"></script>
20 <script src="../src/xhr.js"></script> 20 <script src="../src/xhr.js"></script>
21 <script src="../src/stream.js"></script> 21 <script src="../src/stream.js"></script>
22 <script src="../src/flv-tag.js"></script>
23 <script src="../src/exp-golomb.js"></script>
24 <script src="../src/h264-extradata.js"></script>
25 <script src="../src/h264-stream.js"></script>
26 <script src="../src/aac-stream.js"></script>
27 <script src="../src/metadata-stream.js"></script>
28 <script src="../src/segment-parser.js"></script>
29 22
30 <!-- M3U8 --> 23 <!-- M3U8 -->
31 <script src="../src/m3u8/m3u8-parser.js"></script> 24 <script src="../src/m3u8/m3u8-parser.js"></script>
...@@ -42,11 +35,6 @@ ...@@ -42,11 +35,6 @@
42 <script src="tsSegment-bc.js"></script> 35 <script src="tsSegment-bc.js"></script>
43 <script src="../src/bin-utils.js"></script> 36 <script src="../src/bin-utils.js"></script>
44 37
45 <!-- mp4 utilities -->
46 <script src="../src/mp4-generator.js"></script>
47 <script src="../src/transmuxer.js"></script>
48 <script src="muxer/js/mp4-inspector.js"></script>
49
50 <!-- Test cases --> 38 <!-- Test cases -->
51 <script> 39 <script>
52 module('environment'); 40 module('environment');
...@@ -56,18 +44,10 @@ ...@@ -56,18 +44,10 @@
56 }); 44 });
57 </script> 45 </script>
58 <script src="videojs-hls_test.js"></script> 46 <script src="videojs-hls_test.js"></script>
59 <script src="segment-parser.js"></script>
60 <script src="h264-stream_test.js"></script>
61 <script src="exp-golomb_test.js"></script>
62 <script src="flv-tag_test.js"></script>
63 <script src="metadata-stream_test.js"></script>
64 <script src="m3u8_test.js"></script> 47 <script src="m3u8_test.js"></script>
65 <script src="playlist_test.js"></script> 48 <script src="playlist_test.js"></script>
66 <script src="playlist-loader_test.js"></script> 49 <script src="playlist-loader_test.js"></script>
67 <script src="decrypter_test.js"></script> 50 <script src="decrypter_test.js"></script>
68 <script src="transmuxer_test.js"></script>
69 <script src="mp4-inspector_test.js"></script>
70 <script src="mp4-generator_test.js"></script>
71 </head> 51 </head>
72 <body> 52 <body>
73 <div id="qunit"></div> 53 <div id="qunit"></div>
......