ae60f03a by David LaPalomento

Merge branch 'master' into feature/live-um

2 parents d5f938a0 8fbb05f0
...@@ -30,10 +30,11 @@ module.exports = function(grunt) { ...@@ -30,10 +30,11 @@ module.exports = function(grunt) {
30 'src/h264-stream.js', 30 'src/h264-stream.js',
31 'src/aac-stream.js', 31 'src/aac-stream.js',
32 'src/segment-parser.js', 32 'src/segment-parser.js',
33 'src/stream.js',
33 'src/m3u8/m3u8-parser.js' 34 'src/m3u8/m3u8-parser.js'
34 ], 35 ],
35 dest: 'dist/videojs.hls.js' 36 dest: 'dist/videojs.hls.js'
36 }, 37 }
37 }, 38 },
38 uglify: { 39 uglify: {
39 options: { 40 options: {
...@@ -42,10 +43,10 @@ module.exports = function(grunt) { ...@@ -42,10 +43,10 @@ module.exports = function(grunt) {
42 dist: { 43 dist: {
43 src: '<%= concat.dist.dest %>', 44 src: '<%= concat.dist.dest %>',
44 dest: 'dist/videojs.hls.min.js' 45 dest: 'dist/videojs.hls.min.js'
45 }, 46 }
46 }, 47 },
47 qunit: { 48 qunit: {
48 files: ['test/**/*.html', '!test/perf.html'] 49 files: ['test/**/*.html', '!test/perf.html', '!test/muxer/**']
49 }, 50 },
50 jshint: { 51 jshint: {
51 gruntfile: { 52 gruntfile: {
...@@ -67,8 +68,23 @@ module.exports = function(grunt) { ...@@ -67,8 +68,23 @@ module.exports = function(grunt) {
67 src: ['test/**/*.js', 68 src: ['test/**/*.js',
68 '!test/tsSegment.js', 69 '!test/tsSegment.js',
69 '!test/fixtures/*.js', 70 '!test/fixtures/*.js',
70 '!test/manifest/**'] 71 '!test/manifest/**',
71 }, 72 '!test/muxer/**']
73 }
74 },
75 connect: {
76 dev: {
77 options: {
78 port: 9999,
79 keepalive: true
80 }
81 }
82 },
83 open : {
84 dev : {
85 path: 'http://127.0.0.1:<%= connect.dev.options.port %>/example.html',
86 app: 'Google Chrome'
87 }
72 }, 88 },
73 watch: { 89 watch: {
74 gruntfile: { 90 gruntfile: {
...@@ -82,8 +98,16 @@ module.exports = function(grunt) { ...@@ -82,8 +98,16 @@ module.exports = function(grunt) {
82 test: { 98 test: {
83 files: '<%= jshint.test.src %>', 99 files: '<%= jshint.test.src %>',
84 tasks: ['jshint:test', 'qunit'] 100 tasks: ['jshint:test', 'qunit']
85 }, 101 }
86 }, 102 },
103 concurrent: {
104 dev: {
105 tasks: ['connect', 'open', 'watch'],
106 options: {
107 logConcurrentOutput: true
108 }
109 }
110 }
87 }); 111 });
88 112
89 // These plugins provide necessary tasks. 113 // These plugins provide necessary tasks.
...@@ -93,6 +117,9 @@ module.exports = function(grunt) { ...@@ -93,6 +117,9 @@ module.exports = function(grunt) {
93 grunt.loadNpmTasks('grunt-contrib-qunit'); 117 grunt.loadNpmTasks('grunt-contrib-qunit');
94 grunt.loadNpmTasks('grunt-contrib-jshint'); 118 grunt.loadNpmTasks('grunt-contrib-jshint');
95 grunt.loadNpmTasks('grunt-contrib-watch'); 119 grunt.loadNpmTasks('grunt-contrib-watch');
120 grunt.loadNpmTasks('grunt-contrib-connect');
121 grunt.loadNpmTasks('grunt-open');
122 grunt.loadNpmTasks('grunt-concurrent');
96 123
97 grunt.registerTask('manifests-to-js', 'Wrap the test fixtures and output' + 124 grunt.registerTask('manifests-to-js', 'Wrap the test fixtures and output' +
98 ' so they can be loaded in a browser', 125 ' so they can be loaded in a browser',
...@@ -138,6 +165,9 @@ module.exports = function(grunt) { ...@@ -138,6 +165,9 @@ module.exports = function(grunt) {
138 grunt.file.write('tmp/expected.js', jsExpected); 165 grunt.file.write('tmp/expected.js', jsExpected);
139 }); 166 });
140 167
168 // Launch a Development Environment
169 grunt.registerTask('dev', 'Launching Dev Environment', 'concurrent:dev');
170
141 // Default task. 171 // Default task.
142 grunt.registerTask('default', 172 grunt.registerTask('default',
143 ['clean', 173 ['clean',
......
1 [![Build Status](https://travis-ci.org/brightcove/videojs-contrib-hls.png)](https://travis-ci.org/brightcove/videojs-contrib-hls) 1 [![Build Status](https://travis-ci.org/videojs/videojs-contrib-hls.png)](https://travis-ci.org/videojs/videojs-contrib-hls)
2 2
3 # video.js HLS Plugin 3 # video.js HLS Plugin
4 4
5 A video.js plugin that plays HLS video on platforms that don't support it but have Flash. 5 A video.js plugin that plays HLS video on platforms that don't support it but have Flash.
6 6
7 ## Getting Started 7 ## Getting Started
8 Download the [plugin](https://raw.github.com/videojs/videojs-contrib-hls/master/dist/videojs-hls.min.js). On your web page: 8 Download the [plugin](https://github.com/videojs/videojs-contrib-hls/releases). On your web page:
9 9
10 ```html 10 ```html
11 <script src="video.js"></script> 11 <script src="video.js"></script>
...@@ -119,4 +119,6 @@ bandwidth and viewport dimensions. ...@@ -119,4 +119,6 @@ bandwidth and viewport dimensions.
119 - [Best RESOLUTION variant] OR [Best BANDWIDTH variant] OR [inital playlist in manifest] 119 - [Best RESOLUTION variant] OR [Best BANDWIDTH variant] OR [inital playlist in manifest]
120 120
121 ## Release History 121 ## Release History
122 _(Nothing yet)_ 122 - 0.3.0: Performance fixes for high-bitrate streams
123 - 0.2.0: Basic playback and adaptive bitrate selection
124 - 0.1.0: Initial release
......
...@@ -33,8 +33,24 @@ ...@@ -33,8 +33,24 @@
33 <!-- bunnies --> 33 <!-- bunnies -->
34 <script src="test/tsSegment-bc.js"></script> 34 <script src="test/tsSegment-bc.js"></script>
35 35
36 <style>
37 body {
38 font-family: Arial, sans-serif;
39 }
40 .info {
41 background-color: #eee;
42 border: thin solid #333;
43 border-radius: 3px;
44 padding: 0 5px;
45 }
46 </style>
47
36 </head> 48 </head>
37 <body> 49 <body>
50 <div class="info">
51 <p>The video below is an <a href="https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008332-CH1-SW1">HTTP Live Stream</a>. On desktop browsers other than Safari, the HLS plugin will polyfill support for the format on top of the video.js Flash tech.</p>
52 <p>Due to security restrictions in Flash, you will have to load this page over HTTP(S) to see the example in action.</p>
53 </div>
38 <video id="video" 54 <video id="video"
39 class="video-js vjs-default-skin" 55 class="video-js vjs-default-skin"
40 height="300" 56 height="300"
...@@ -45,7 +61,7 @@ ...@@ -45,7 +61,7 @@
45 type="application/x-mpegURL"> 61 type="application/x-mpegURL">
46 </video> 62 </video>
47 <script> 63 <script>
48 videojs.options.flash.swf = 'node_modules/video.js/dist/video-js/video-js.swf'; 64 videojs.options.flash.swf = 'node_modules/videojs-swf/dist/video-js.swf';
49 // initialize the player 65 // initialize the player
50 var player = videojs('video'); 66 var player = videojs('video');
51 67
......
1 { 1 {
2 "name": "videojs-contrib-hls", 2 "name": "videojs-contrib-hls",
3 "version": "0.2.0", 3 "version": "0.3.1",
4 "engines": { 4 "engines": {
5 "node": ">= 0.8.0" 5 "node": ">= 0.10.12"
6 },
7 "repository": {
8 "type": "git",
9 "url": "git@github.com:videojs/videojs-contrib-hls.git"
6 }, 10 },
7 "license": "Apache 2", 11 "license": "Apache 2",
8 "scripts": { 12 "scripts": {
...@@ -15,10 +19,15 @@ ...@@ -15,10 +19,15 @@
15 "grunt-contrib-uglify": "~0.2.0", 19 "grunt-contrib-uglify": "~0.2.0",
16 "grunt-contrib-watch": "~0.4.0", 20 "grunt-contrib-watch": "~0.4.0",
17 "grunt-contrib-clean": "~0.4.0", 21 "grunt-contrib-clean": "~0.4.0",
22 "grunt-contrib-connect": "~0.6.0",
23 "grunt-concurrent": "0.4.3",
24 "grunt-open": "0.2.3",
25 "grunt-shell": "0.6.1",
18 "grunt": "~0.4.1" 26 "grunt": "~0.4.1"
19 }, 27 },
20 "dependencies": { 28 "dependencies": {
21 "video.js": "git+https://github.com/dmlap/video-js.git#v4.3.0-10", 29 "video.js": "git+https://github.com/videojs/video.js.git#v4.4.1",
22 "videojs-contrib-media-sources": "git+https://github.com/dmlap/videojs-contrib-media-sources.git#hotfix/misc-fixes" 30 "videojs-swf": "git+https://github.com/videojs/video-js-swf.git#v4.4.0",
31 "videojs-contrib-media-sources": "git+https://github.com/videojs/videojs-contrib-media-sources.git"
23 } 32 }
24 } 33 }
......
1 /* 1 /*
2 * aac-stream 2 * aac-stream
3 * 3 *
4 * 4 *
5 * Copyright (c) 2013 Brightcove 5 * Copyright (c) 2013 Brightcove
6 * All rights reserved. 6 * All rights reserved.
...@@ -18,9 +18,9 @@ var ...@@ -18,9 +18,9 @@ var
18 ]; 18 ];
19 19
20 window.videojs.hls.AacStream = function() { 20 window.videojs.hls.AacStream = function() {
21 var 21 var
22 next_pts, // :uint 22 next_pts, // :uint
23 pts_delta = -1, // :int 23 pts_offset, // :int
24 state, // :uint 24 state, // :uint
25 pes_length, // :int 25 pes_length, // :int
26 26
...@@ -39,115 +39,119 @@ window.videojs.hls.AacStream = function() { ...@@ -39,115 +39,119 @@ window.videojs.hls.AacStream = function() {
39 39
40 // (pts:uint, pes_size:int, dataAligned:Boolean):void 40 // (pts:uint, pes_size:int, dataAligned:Boolean):void
41 this.setNextTimeStamp = function(pts, pes_size, dataAligned) { 41 this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
42 if (0 > pts_delta) {
43 // We assume the very firts pts is less than 0x80000000
44 pts_delta = pts;
45 }
46 42
47 next_pts = pts - pts_delta; 43 // on the first invocation, capture the starting PTS value
48 pes_length = pes_size; 44 pts_offset = pts;
49 45
50 // If data is aligned, flush all internal buffers 46 // on subsequent invocations, calculate the PTS based on the starting offset
51 if (dataAligned) { 47 this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
52 state = 0; 48 next_pts = pts - pts_offset;
53 } 49 pes_length = pes_size;
50
51 // If data is aligned, flush all internal buffers
52 if (dataAligned) {
53 state = 0;
54 }
55 };
56
57 this.setNextTimeStamp(pts, pes_size, dataAligned);
54 }; 58 };
55 59
56 // (data:ByteArray, o:int = 0, l:int = 0):void 60 // (data:ByteArray, o:int = 0, l:int = 0):void
57 this.writeBytes = function(data, o, l) { 61 this.writeBytes = function(data, offset, length) {
58 var 62 var
59 e, // :int 63 end, // :int
60 newExtraData, // :uint 64 newExtraData, // :uint
61 bytesToCopy; // :int 65 bytesToCopy; // :int
62 66
63 // default arguments 67 // default arguments
64 o = o || 0; 68 offset = offset || 0;
65 l = l || 0; 69 length = length || 0;
66 70
67 // Do not allow more than 'pes_length' bytes to be written 71 // Do not allow more than 'pes_length' bytes to be written
68 l = (pes_length < l ? pes_length : l); 72 length = (pes_length < length ? pes_length : length);
69 pes_length -= l; 73 pes_length -= length;
70 e = o + l; 74 end = offset + length;
71 while (o < e) { 75 while (offset < end) {
72 switch (state) { 76 switch (state) {
73 default: 77 default:
74 state = 0; 78 state = 0;
75 break; 79 break;
76 case 0: 80 case 0:
77 if (o >= e) { 81 if (offset >= end) {
78 return; 82 return;
79 } 83 }
80 if (0xFF !== data[o]) { 84 if (0xFF !== data[offset]) {
81 console.assert(false, 'Error no ATDS header found'); 85 console.assert(false, 'Error no ATDS header found');
82 o += 1; 86 offset += 1;
83 state = 0; 87 state = 0;
84 return; 88 return;
85 } 89 }
86 90
87 o += 1; 91 offset += 1;
88 state = 1; 92 state = 1;
89 break; 93 break;
90 case 1: 94 case 1:
91 if (o >= e) { 95 if (offset >= end) {
92 return; 96 return;
93 } 97 }
94 if (0xF0 !== (data[o] & 0xF0)) { 98 if (0xF0 !== (data[offset] & 0xF0)) {
95 console.assert(false, 'Error no ATDS header found'); 99 console.assert(false, 'Error no ATDS header found');
96 o +=1; 100 offset +=1;
97 state = 0; 101 state = 0;
98 return; 102 return;
99 } 103 }
100 104
101 adtsProtectionAbsent = !!(data[o] & 0x01); 105 adtsProtectionAbsent = !!(data[offset] & 0x01);
102 106
103 o += 1; 107 offset += 1;
104 state = 2; 108 state = 2;
105 break; 109 break;
106 case 2: 110 case 2:
107 if (o >= e) { 111 if (offset >= end) {
108 return; 112 return;
109 } 113 }
110 adtsObjectType = ((data[o] & 0xC0) >>> 6) + 1; 114 adtsObjectType = ((data[offset] & 0xC0) >>> 6) + 1;
111 adtsSampleingIndex = ((data[o] & 0x3C) >>> 2); 115 adtsSampleingIndex = ((data[offset] & 0x3C) >>> 2);
112 adtsChanelConfig = ((data[o] & 0x01) << 2); 116 adtsChanelConfig = ((data[offset] & 0x01) << 2);
113 117
114 o += 1; 118 offset += 1;
115 state = 3; 119 state = 3;
116 break; 120 break;
117 case 3: 121 case 3:
118 if (o >= e) { 122 if (offset >= end) {
119 return; 123 return;
120 } 124 }
121 adtsChanelConfig |= ((data[o] & 0xC0) >>> 6); 125 adtsChanelConfig |= ((data[offset] & 0xC0) >>> 6);
122 adtsFrameSize = ((data[o] & 0x03) << 11); 126 adtsFrameSize = ((data[offset] & 0x03) << 11);
123 127
124 o += 1; 128 offset += 1;
125 state = 4; 129 state = 4;
126 break; 130 break;
127 case 4: 131 case 4:
128 if (o >= e) { 132 if (offset >= end) {
129 return; 133 return;
130 } 134 }
131 adtsFrameSize |= (data[o] << 3); 135 adtsFrameSize |= (data[offset] << 3);
132 136
133 o += 1; 137 offset += 1;
134 state = 5; 138 state = 5;
135 break; 139 break;
136 case 5: 140 case 5:
137 if(o >= e) { 141 if(offset >= end) {
138 return; 142 return;
139 } 143 }
140 adtsFrameSize |= ((data[o] & 0xE0) >>> 5); 144 adtsFrameSize |= ((data[offset] & 0xE0) >>> 5);
141 adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9); 145 adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9);
142 146
143 o += 1; 147 offset += 1;
144 state = 6; 148 state = 6;
145 break; 149 break;
146 case 6: 150 case 6:
147 if (o >= e) { 151 if (offset >= end) {
148 return; 152 return;
149 } 153 }
150 adtsSampleCount = ((data[o] & 0x03) + 1) * 1024; 154 adtsSampleCount = ((data[offset] & 0x03) + 1) * 1024;
151 adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex]; 155 adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex];
152 156
153 newExtraData = (adtsObjectType << 11) | 157 newExtraData = (adtsObjectType << 11) |
...@@ -159,11 +163,11 @@ window.videojs.hls.AacStream = function() { ...@@ -159,11 +163,11 @@ window.videojs.hls.AacStream = function() {
159 aacFrame.dts = next_pts; 163 aacFrame.dts = next_pts;
160 164
161 // AAC is always 10 165 // AAC is always 10
162 aacFrame.writeMetaDataDouble("audiocodecid", 10); 166 aacFrame.writeMetaDataDouble("audiocodecid", 10);
163 aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig); 167 aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig);
164 aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]); 168 aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]);
165 // Is AAC always 16 bit? 169 // Is AAC always 16 bit?
166 aacFrame.writeMetaDataDouble ("audiosamplesize", 16); 170 aacFrame.writeMetaDataDouble ("audiosamplesize", 16);
167 171
168 this.tags.push(aacFrame); 172 this.tags.push(aacFrame);
169 173
...@@ -173,7 +177,7 @@ window.videojs.hls.AacStream = function() { ...@@ -173,7 +177,7 @@ window.videojs.hls.AacStream = function() {
173 // For audio, DTS is always the same as PTS. We want to set the DTS 177 // For audio, DTS is always the same as PTS. We want to set the DTS
174 // however so we can compare with video DTS to determine approximate 178 // however so we can compare with video DTS to determine approximate
175 // packet order 179 // packet order
176 aacFrame.pts = next_pts; 180 aacFrame.pts = next_pts;
177 aacFrame.view.setUint16(aacFrame.position, newExtraData); 181 aacFrame.view.setUint16(aacFrame.position, newExtraData);
178 aacFrame.position += 2; 182 aacFrame.position += 2;
179 aacFrame.length = Math.max(aacFrame.length, aacFrame.position); 183 aacFrame.length = Math.max(aacFrame.length, aacFrame.position);
...@@ -182,15 +186,15 @@ window.videojs.hls.AacStream = function() { ...@@ -182,15 +186,15 @@ window.videojs.hls.AacStream = function() {
182 } 186 }
183 187
184 // Skip the checksum if there is one 188 // Skip the checksum if there is one
185 o += 1; 189 offset += 1;
186 state = 7; 190 state = 7;
187 break; 191 break;
188 case 7: 192 case 7:
189 if (!adtsProtectionAbsent) { 193 if (!adtsProtectionAbsent) {
190 if (2 > (e - o)) { 194 if (2 > (end - offset)) {
191 return; 195 return;
192 } else { 196 } else {
193 o += 2; 197 offset += 2;
194 } 198 }
195 } 199 }
196 200
...@@ -201,12 +205,12 @@ window.videojs.hls.AacStream = function() { ...@@ -201,12 +205,12 @@ window.videojs.hls.AacStream = function() {
201 break; 205 break;
202 case 8: 206 case 8:
203 while (adtsFrameSize) { 207 while (adtsFrameSize) {
204 if (o >= e) { 208 if (offset >= end) {
205 return; 209 return;
206 } 210 }
207 bytesToCopy = (e - o) < adtsFrameSize ? (e - o) : adtsFrameSize; 211 bytesToCopy = (end - offset) < adtsFrameSize ? (end - offset) : adtsFrameSize;
208 aacFrame.writeBytes(data, o, bytesToCopy); 212 aacFrame.writeBytes(data, offset, bytesToCopy);
209 o += bytesToCopy; 213 offset += bytesToCopy;
210 adtsFrameSize -= bytesToCopy; 214 adtsFrameSize -= bytesToCopy;
211 } 215 }
212 216
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
20 window.setTimeout(process, 0); 20 window.setTimeout(process, 0);
21 q.running = true; 21 q.running = true;
22 } 22 }
23 }, 23 }
24 }, 24 },
25 process = function() { 25 process = function() {
26 var task; 26 var task;
......
...@@ -14,13 +14,15 @@ ...@@ -14,13 +14,15 @@
14 } 14 }
15 return '.'; 15 return '.';
16 }, 16 },
17 result = '',
17 hex, 18 hex,
18 ascii; 19 ascii;
19 for (var j = 0; j < bytes.length / step; j++) { 20 for (var j = 0; j < bytes.length / step; j++) {
20 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(' '); 21 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(' ');
21 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 22 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
22 return hex + ' ' + ascii; 23 result += hex + ' ' + ascii + '\n';
23 } 24 }
25 return result;
24 }, 26 },
25 tagDump: function(tag) { 27 tagDump: function(tag) {
26 return module.hexDump(tag.bytes); 28 return module.hexDump(tag.bytes);
......
...@@ -32,7 +32,6 @@ window.videojs.hls.ExpGolomb = function(workingData) { ...@@ -32,7 +32,6 @@ window.videojs.hls.ExpGolomb = function(workingData) {
32 workingBytes = new Uint8Array(4), 32 workingBytes = new Uint8Array(4),
33 availableBytes = Math.min(4, workingBytesAvailable); 33 availableBytes = Math.min(4, workingBytesAvailable);
34 34
35 // console.assert(availableBytes > 0);
36 if (availableBytes === 0) { 35 if (availableBytes === 0) {
37 throw new Error('no bytes available'); 36 throw new Error('no bytes available');
38 } 37 }
......
1 (function(window) { 1 (function(window) {
2 2
3 var 3 window.videojs = window.videojs || {};
4 hls = window.videojs.hls, 4 window.videojs.hls = window.videojs.hls || {};
5 5
6 // commonly used metadata properties 6 var hls = window.videojs.hls;
7 widthBytes = new Uint8Array('width'.length),
8 heightBytes = new Uint8Array('height'.length),
9 videocodecidBytes = new Uint8Array('videocodecid'.length),
10 i;
11
12 // calculating the bytes of common metadata names ahead of time makes the
13 // corresponding writes faster because we don't have to loop over the
14 // characters
15 // re-test with test/perf.html if you're planning on changing this
16 for (i in 'width') {
17 widthBytes[i] = 'width'.charCodeAt(i);
18 }
19 for (i in 'height') {
20 heightBytes[i] = 'height'.charCodeAt(i);
21 }
22 for (i in 'videocodecid') {
23 videocodecidBytes[i] = 'videocodecid'.charCodeAt(i);
24 }
25 7
26 // (type:uint, extraData:Boolean = false) extends ByteArray 8 // (type:uint, extraData:Boolean = false) extends ByteArray
27 hls.FlvTag = function(type, extraData) { 9 hls.FlvTag = function(type, extraData) {
...@@ -46,7 +28,33 @@ hls.FlvTag = function(type, extraData) { ...@@ -46,7 +28,33 @@ hls.FlvTag = function(type, extraData) {
46 bytes.set(flv.bytes.subarray(0, flv.position), 0); 28 bytes.set(flv.bytes.subarray(0, flv.position), 0);
47 flv.bytes = bytes; 29 flv.bytes = bytes;
48 flv.view = new DataView(flv.bytes.buffer); 30 flv.view = new DataView(flv.bytes.buffer);
49 }; 31 },
32
33 // commonly used metadata properties
34 widthBytes = hls.FlvTag.widthBytes || new Uint8Array('width'.length),
35 heightBytes = hls.FlvTag.heightBytes || new Uint8Array('height'.length),
36 videocodecidBytes = hls.FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length),
37 i;
38
39 if (!hls.FlvTag.widthBytes) {
40 // calculating the bytes of common metadata names ahead of time makes the
41 // corresponding writes faster because we don't have to loop over the
42 // characters
43 // re-test with test/perf.html if you're planning on changing this
44 for (i in 'width') {
45 widthBytes[i] = 'width'.charCodeAt(i);
46 }
47 for (i in 'height') {
48 heightBytes[i] = 'height'.charCodeAt(i);
49 }
50 for (i in 'videocodecid') {
51 videocodecidBytes[i] = 'videocodecid'.charCodeAt(i);
52 }
53
54 hls.FlvTag.widthBytes = widthBytes;
55 hls.FlvTag.heightBytes = heightBytes;
56 hls.FlvTag.videocodecidBytes = videocodecidBytes;
57 }
50 58
51 this.keyFrame = false; // :Boolean 59 this.keyFrame = false; // :Boolean
52 60
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
8 8
9 (function(window) { 9 (function(window) {
10 var 10 var
11
12 ExpGolomb = window.videojs.hls.ExpGolomb, 11 ExpGolomb = window.videojs.hls.ExpGolomb,
13 FlvTag = window.videojs.hls.FlvTag, 12 FlvTag = window.videojs.hls.FlvTag,
14 13
...@@ -16,22 +15,8 @@ ...@@ -16,22 +15,8 @@
16 this.sps = []; // :Array 15 this.sps = []; // :Array
17 this.pps = []; // :Array 16 this.pps = []; // :Array
18 17
19 this.addSPS = function(size) { // :ByteArray
20 console.assert(size > 0);
21 var tmp = new Uint8Array(size); // :ByteArray
22 this.sps.push(tmp);
23 return tmp;
24 };
25
26 this.addPPS = function(size) { // :ByteArray
27 console.assert(size);
28 var tmp = new Uint8Array(size); // :ByteArray
29 this.pps.push(tmp);
30 return tmp;
31 };
32
33 this.extraDataExists = function() { // :Boolean 18 this.extraDataExists = function() { // :Boolean
34 return 0 < this.sps.length; 19 return this.sps.length > 0;
35 }; 20 };
36 21
37 // (sizeOfScalingList:int, expGolomb:ExpGolomb):void 22 // (sizeOfScalingList:int, expGolomb:ExpGolomb):void
...@@ -56,61 +41,37 @@ ...@@ -56,61 +41,37 @@
56 }; 41 };
57 42
58 /** 43 /**
59 * NAL unit
60 * |- NAL header -|------ RBSP ------|
61 *
62 * NAL unit: Network abstraction layer unit. The combination of a NAL
63 * header and an RBSP.
64 * NAL header: the encapsulation unit for transport-specific metadata in
65 * an h264 stream. Exactly one byte.
66 * RBSP: raw bit-stream payload. The actual encoded video data. 44 * RBSP: raw bit-stream payload. The actual encoded video data.
67 * 45 *
68 * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied 46 * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
69 * to a complete video sequence, like width and height. 47 * to a complete video sequence, like width and height.
70 */ 48 */
71 this.getSps0Rbsp = function() { // :ByteArray 49 this.getSps0Rbsp = function() { // :ByteArray
72 // remove emulation bytes. Is this nesessary? is there ever emulation
73 // bytes in the SPS?
74 var 50 var
75 spsCount = 0, 51 sps = this.sps[0],
76 sps0 = this.sps[0], // :ByteArray 52 offset = 1,
77 rbspCount = 0, 53 start = 1,
78 start = 1, // :uint 54 written = 0,
79 end = sps0.byteLength - 2, // :uint 55 end = sps.byteLength - 2,
80 rbsp = new Uint8Array(sps0.byteLength), // :ByteArray 56 result = new Uint8Array(sps.byteLength);
81 offset = 0; // :uint 57
82 58 // In order to prevent 0x0000 01 from being interpreted as a
83 // H264 requires emulation bytes (0x03) be dropped to interpret NAL 59 // NAL start code, occurences of that byte sequence in the
84 // units. For instance, 0x8a03b4 should be read as 0x8ab4. 60 // RBSP are escaped with an "emulation byte". That turns
85 for (offset = start ; offset < end ;) { 61 // sequences of 0x0000 01 into 0x0000 0301. When interpreting
86 if (3 !== sps0[offset + 2]) { 62 // a NAL payload, they must be filtered back out.
87 offset += 3; 63 while (offset < end) {
88 } else if (0 !== sps0[offset + 1]) { 64 if (sps[offset] === 0x00 &&
89 offset += 2; 65 sps[offset + 1] === 0x00 &&
90 } else if (0 !== sps0[offset + 0]) { 66 sps[offset + 2] === 0x03) {
91 offset += 1; 67 result.set(sps.subarray(start, offset + 1), written);
92 } else { 68 written += offset + 1 - start;
93 rbsp.set([0x00, 0x00], rbspCount); 69 start = offset + 3;
94 spsCount += 2;
95 rbspCount += 2;
96
97 if (offset > start) {
98 // If there are bytes to write, write them
99 rbsp.set(sps0.subarray(start, offset - start), rbspCount);
100 spsCount += offset - start;
101 rbspCount += offset - start;
102 }
103
104 // skip the emulation bytes
105 offset += 3;
106 start = offset;
107 } 70 }
71 offset++;
108 } 72 }
109 73 result.set(sps.subarray(start), written);
110 // copy any remaining bytes 74 return result.subarray(0, written + (sps.byteLength - start));
111 rbsp.set(sps0.subarray(spsCount), rbspCount); // sps0.readBytes(rbsp, rbsp.length);
112
113 return rbsp;
114 }; 75 };
115 76
116 // (pts:uint):FlvTag 77 // (pts:uint):FlvTag
...@@ -257,27 +218,42 @@ ...@@ -257,27 +218,42 @@
257 }; 218 };
258 }, 219 },
259 220
260 // incomplete, see Table 7.1 of ITU-T H.264 for 12-32 221 NALUnitType;
261 NALUnitType = { 222
262 unspecified: 0, 223 /**
263 slice_layer_without_partitioning_rbsp_non_idr: 1, 224 * Network Abstraction Layer (NAL) units are the packets of an H264
264 slice_data_partition_a_layer_rbsp: 2, 225 * stream. NAL units are divided into types based on their payload
265 slice_data_partition_b_layer_rbsp: 3, 226 * data. Each type has a unique numeric identifier.
266 slice_data_partition_c_layer_rbsp: 4, 227 *
267 slice_layer_without_partitioning_rbsp_idr: 5, 228 * NAL unit
268 sei_rbsp: 6, 229 * |- NAL header -|------ RBSP ------|
269 seq_parameter_set_rbsp: 7, 230 *
270 pic_parameter_set_rbsp: 8, 231 * NAL unit: Network abstraction layer unit. The combination of a NAL
271 access_unit_delimiter_rbsp: 9, 232 * header and an RBSP.
272 end_of_seq_rbsp: 10, 233 * NAL header: the encapsulation unit for transport-specific metadata in
273 end_of_stream_rbsp: 11 234 * an h264 stream. Exactly one byte.
274 }; 235 */
236 // incomplete, see Table 7.1 of ITU-T H.264 for 12-32
237 window.videojs.hls.NALUnitType = NALUnitType = {
238 unspecified: 0,
239 slice_layer_without_partitioning_rbsp_non_idr: 1,
240 slice_data_partition_a_layer_rbsp: 2,
241 slice_data_partition_b_layer_rbsp: 3,
242 slice_data_partition_c_layer_rbsp: 4,
243 slice_layer_without_partitioning_rbsp_idr: 5,
244 sei_rbsp: 6,
245 seq_parameter_set_rbsp: 7,
246 pic_parameter_set_rbsp: 8,
247 access_unit_delimiter_rbsp: 9,
248 end_of_seq_rbsp: 10,
249 end_of_stream_rbsp: 11
250 };
275 251
276 window.videojs.hls.H264Stream = function() { 252 window.videojs.hls.H264Stream = function() {
277 var 253 var
278 next_pts, // :uint; 254 next_pts, // :uint;
279 next_dts, // :uint; 255 next_dts, // :uint;
280 pts_delta = -1, // :int 256 pts_offset, // :int
281 257
282 h264Frame, // :FlvTag 258 h264Frame, // :FlvTag
283 259
...@@ -292,20 +268,22 @@ ...@@ -292,20 +268,22 @@
292 268
293 //(pts:uint, dts:uint, dataAligned:Boolean):void 269 //(pts:uint, dts:uint, dataAligned:Boolean):void
294 this.setNextTimeStamp = function(pts, dts, dataAligned) { 270 this.setNextTimeStamp = function(pts, dts, dataAligned) {
295 if (pts_delta < 0) { 271 // on the first invocation, capture the starting PTS value
296 // We assume the very first pts is less than 0x8FFFFFFF (max signed 272 pts_offset = pts;
297 // int32) 273
298 pts_delta = pts; 274 // on subsequent invocations, calculate the PTS based on the starting offset
299 } 275 this.setNextTimeStamp = function(pts, dts, dataAligned) {
300 276 // We could end up with a DTS less than 0 here. We need to deal with that!
301 // We could end up with a DTS less than 0 here. We need to deal with that! 277 next_pts = pts - pts_offset;
302 next_pts = pts - pts_delta; 278 next_dts = dts - pts_offset;
303 next_dts = dts - pts_delta; 279
280 // If data is aligned, flush all internal buffers
281 if (dataAligned) {
282 this.finishFrame();
283 }
284 };
304 285
305 // If data is aligned, flush all internal buffers 286 this.setNextTimeStamp(pts, dts, dataAligned);
306 if (dataAligned) {
307 this.finishFrame();
308 }
309 }; 287 };
310 288
311 this.finishFrame = function() { 289 this.finishFrame = function() {
...@@ -395,7 +373,7 @@ ...@@ -395,7 +373,7 @@
395 data[offset + 1] === 0 && 373 data[offset + 1] === 0 &&
396 data[offset + 2] === 1) { 374 data[offset + 2] === 1) {
397 // 00 : 00 00 01 375 // 00 : 00 00 01
398 h264Frame.length -= 1; 376 // h264Frame.length -= 1;
399 state = 3; 377 state = 3;
400 return this.writeBytes(data, offset + 3, length - 3); 378 return this.writeBytes(data, offset + 3, length - 3);
401 } 379 }
...@@ -466,7 +444,6 @@ ...@@ -466,7 +444,6 @@
466 h264Frame.endNalUnit(newExtraData.pps); 444 h264Frame.endNalUnit(newExtraData.pps);
467 break; 445 break;
468 case NALUnitType.slice_layer_without_partitioning_rbsp_idr: 446 case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
469 h264Frame.keyFrame = true;
470 h264Frame.endNalUnit(); 447 h264Frame.endNalUnit();
471 break; 448 break;
472 default: 449 default:
...@@ -477,8 +454,13 @@ ...@@ -477,8 +454,13 @@
477 454
478 // setup to begin processing the new NAL unit 455 // setup to begin processing the new NAL unit
479 nalUnitType = data[offset] & 0x1F; 456 nalUnitType = data[offset] & 0x1F;
480 if (h264Frame && 9 === nalUnitType) { 457 if (h264Frame) {
481 this.finishFrame(); // We are starting a new access unit. Flush the previous one 458 if (nalUnitType === NALUnitType.access_unit_delimiter_rbsp) {
459 // starting a new access unit, flush the previous one
460 this.finishFrame();
461 } else if (nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
462 h264Frame.keyFrame = true;
463 }
482 } 464 }
483 465
484 // finishFrame may render h264Frame null, so we must test again 466 // finishFrame may render h264Frame null, so we must test again
......
...@@ -18,8 +18,7 @@ ...@@ -18,8 +18,7 @@
18 streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH), 18 streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH),
19 streamBufferByteCount = 0, 19 streamBufferByteCount = 0,
20 h264Stream = new H264Stream(), 20 h264Stream = new H264Stream(),
21 aacStream = new AacStream(), 21 aacStream = new AacStream();
22 seekToKeyFrame = false;
23 22
24 // expose the stream metadata 23 // expose the stream metadata
25 self.stream = { 24 self.stream = {
...@@ -84,60 +83,35 @@ ...@@ -84,60 +83,35 @@
84 self.flushTags = function() { 83 self.flushTags = function() {
85 h264Stream.finishFrame(); 84 h264Stream.finishFrame();
86 }; 85 };
87 self.doSeek = function() {
88 self.flushTags();
89 aacStream.tags.length = 0;
90 h264Stream.tags.length = 0;
91 seekToKeyFrame = true;
92 };
93 86
87 /**
88 * Returns whether a call to `getNextTag()` will be successful.
89 * @return {boolean} whether there is at least one transmuxed FLV
90 * tag ready
91 */
94 self.tagsAvailable = function() { // :int { 92 self.tagsAvailable = function() { // :int {
95 var i, pts; // :uint
96
97 if (seekToKeyFrame) {
98 for (i = 0 ; i < h264Stream.tags.length && seekToKeyFrame; ++i) {
99 if (h264Stream.tags[i].keyFrame) {
100 seekToKeyFrame = false; // We found, a keyframe, stop seeking
101 }
102 }
103
104 if (seekToKeyFrame) {
105 // we didnt find a keyframe. yet
106 h264Stream.tags.length = 0;
107 return 0;
108 }
109
110 // TODO we MAY need to use dts, not pts
111 h264Stream.tags = h264Stream.tags.slice(i);
112 pts = h264Stream.tags[0].pts;
113
114 // Remove any audio before the found keyframe
115 while( 0 < aacStream.tags.length && pts > aacStream.tags[0].pts ) {
116 aacStream.tags.shift();
117 }
118 }
119
120 return h264Stream.tags.length + aacStream.tags.length; 93 return h264Stream.tags.length + aacStream.tags.length;
121 }; 94 };
122 95
123 self.getNextTag = function() { // :ByteArray { 96 /**
124 var tag; // :FlvTag; // return tags in approximate dts order 97 * Returns the next tag in decoder-timestamp (DTS) order.
125 98 * @returns {object} the next tag to decoded.
126 if (0 === self.tagsAvailable()) { 99 */
127 throw new Error("getNextTag() called when 0 == tagsAvailable()"); 100 self.getNextTag = function() {
128 } 101 var tag;
129 102
130 if (0 < h264Stream.tags.length) { 103 if (!h264Stream.tags.length) {
131 if (0 < aacStream.tags.length && aacStream.tags[0].dts < h264Stream.tags[0].dts) { 104 // only audio tags remain
132 tag = aacStream.tags.shift(); 105 tag = aacStream.tags.shift();
133 } else { 106 } else if (!aacStream.tags.length) {
134 tag = h264Stream.tags.shift(); 107 // only video tags remain
135 } 108 tag = h264Stream.tags.shift();
136 } else if ( 0 < aacStream.tags.length ) { 109 } else if (aacStream.tags[0].dts < h264Stream.tags[0].dts) {
110 // audio should be decoded next
137 tag = aacStream.tags.shift(); 111 tag = aacStream.tags.shift();
138 } else { 112 } else {
139 // We dont have any tags available to return 113 // video should be decoded next
140 return new Uint8Array(); 114 tag = h264Stream.tags.shift();
141 } 115 }
142 116
143 return tag.finalize(); 117 return tag.finalize();
......
...@@ -722,6 +722,10 @@ var ...@@ -722,6 +722,10 @@ var
722 }; 722 };
723 723
724 videojs.plugin('hls', function() { 724 videojs.plugin('hls', function() {
725 if (typeof Uint8Array === 'undefined') {
726 return;
727 }
728
725 var initialize = function() { 729 var initialize = function() {
726 return function() { 730 return function() {
727 this.hls = initialize(); 731 this.hls = initialize();
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
21 */ 21 */
22 var 22 var
23 buffer, 23 buffer,
24 ExpGolomb = window.videojs.hls.ExpGolomb,
24 expGolomb; 25 expGolomb;
25 26
26 module('Exponential Golomb coding'); 27 module('Exponential Golomb coding');
...@@ -44,32 +45,62 @@ test('small numbers are coded correctly', function() { ...@@ -44,32 +45,62 @@ test('small numbers are coded correctly', function() {
44 45
45 while (i--) { 46 while (i--) {
46 buffer = new Uint8Array([expected[i][0]]); 47 buffer = new Uint8Array([expected[i][0]]);
47 expGolomb = new window.videojs.hls.ExpGolomb(buffer); 48 expGolomb = new ExpGolomb(buffer);
48 result = expGolomb.readUnsignedExpGolomb(); 49 result = expGolomb.readUnsignedExpGolomb();
49 equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]); 50 equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]);
50 } 51 }
51 }); 52 });
52 53
53 test('drops working data as it is parsed', function() { 54 test('drops working data as it is parsed', function() {
54 var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x00, 0xFF])); 55 var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0xFF]));
55 expGolomb.skipBits(8); 56 expGolomb.skipBits(8);
56 equal(8, expGolomb.bitsAvailable(), '8 bits remain'); 57 equal(8, expGolomb.bitsAvailable(), '8 bits remain');
57 equal(0xFF, expGolomb.readBits(8), 'the second byte is read'); 58 equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
58 }); 59 });
59 60
60 test('drops working data when skipping leading zeros', function() { 61 test('drops working data when skipping leading zeros', function() {
61 var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF])); 62 var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF]));
62 equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped'); 63 equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped');
63 equal(8, expGolomb.bitsAvailable(), '8 bits remain'); 64 equal(8, expGolomb.bitsAvailable(), '8 bits remain');
64 equal(0xFF, expGolomb.readBits(8), 'the second byte is read'); 65 equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
65 }); 66 });
66 67
67 test('drops working data when skipping leading zeros', function() { 68 test('drops working data when skipping leading zeros', function() {
68 var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF])); 69 var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF]));
69 equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped'); 70 equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped');
70 equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain'); 71 equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain');
71 expGolomb.skipBits(1); 72 expGolomb.skipBits(1);
72 equal(0x5a, expGolomb.readBits(8), 'the next bits are read'); 73 equal(0x5a, expGolomb.readBits(8), 'the next bits are read');
73 }); 74 });
74 75
75 })(this); 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(videojs) {
2 module('H264 Stream');
3
4 var
5 nalUnitTypes = window.videojs.hls.NALUnitType,
6 FlvTag = window.videojs.hls.FlvTag;
7
8 test('metadata is generated for IDRs after a full NAL unit is written', function() {
9 var
10 h264Stream = new videojs.hls.H264Stream(),
11 accessUnitDelimiter = new Uint8Array([
12 0x00,
13 0x00,
14 0x01,
15 nalUnitTypes.access_unit_delimiter_rbsp
16 ]),
17 seqParamSet = new Uint8Array([
18 0x00,
19 0x00,
20 0x01,
21 0x60 | nalUnitTypes.seq_parameter_set_rbsp,
22 0x00, // profile_idc
23 0x00, // constraint_set flags
24 0x00, // level_idc
25 // seq_parameter_set_id ue(v) 0 => 1
26 // log2_max_frame_num_minus4 ue(v) 1 => 010
27 // pic_order_cnt_type ue(v) 0 => 1
28 // log2_max_pic_order_cnt_lsb_minus4 ue(v) 1 => 010
29 // max_num_ref_frames ue(v) 1 => 010
30 // gaps_in_frame_num_value_allowed u(1) 0
31 // pic_width_in_mbs_minus1 ue(v) 0 => 1
32 // pic_height_in_map_units_minus1 ue(v) 0 => 1
33 // frame_mbs_only_flag u(1) 1
34 // direct_8x8_inference_flag u(1) 0
35 // frame_cropping_flag u(1) 0
36 // vui_parameters_present_flag u(1) 0
37 // 1010 1010 0100 1110 00(00 0000)
38 0xAA,
39 0x4E,
40 0x00
41 ]),
42 idr = new Uint8Array([
43 0x00,
44 0x00,
45 0x01,
46 nalUnitTypes.slice_layer_without_partitioning_rbsp_idr
47 ]);
48
49 h264Stream.setNextTimeStamp(0, 0, true);
50 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
51 h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength);
52 h264Stream.writeBytes(idr, 0, idr.byteLength);
53 h264Stream.setNextTimeStamp(1, 1, true);
54
55 strictEqual(h264Stream.tags.length, 3, 'three tags are written');
56 ok(FlvTag.isMetaData(h264Stream.tags[0].bytes),
57 'metadata is written');
58 ok(FlvTag.isVideoFrame(h264Stream.tags[1].bytes),
59 'picture parameter set is written');
60 ok(h264Stream.tags[2].keyFrame, 'key frame is written');
61 });
62
63 test('starting PTS values can be negative', function() {
64 var
65 h264Stream = new videojs.hls.H264Stream(),
66 accessUnitDelimiter = new Uint8Array([
67 0x00,
68 0x00,
69 0x01,
70 nalUnitTypes.access_unit_delimiter_rbsp
71 ]);
72
73 h264Stream.setNextTimeStamp(-100, -100, true);
74 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
75 h264Stream.setNextTimeStamp(-99, -99, true);
76 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
77 h264Stream.setNextTimeStamp(0, 0, true);
78 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
79 // flush out the last tag
80 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
81
82 strictEqual(h264Stream.tags.length, 3, 'three tags are ready');
83 strictEqual(h264Stream.tags[0].pts, 0, 'the first PTS is zero');
84 strictEqual(h264Stream.tags[0].dts, 0, 'the first DTS is zero');
85 strictEqual(h264Stream.tags[1].pts, 1, 'the second PTS is one');
86 strictEqual(h264Stream.tags[1].dts, 1, 'the second DTS is one');
87
88 strictEqual(h264Stream.tags[2].pts, 100, 'the third PTS is 100');
89 strictEqual(h264Stream.tags[2].dts, 100, 'the third DTS is 100');
90 });
91
92 })(window.videojs);
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 }
...\ 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 </header>
43 <section>
44 <h2>Inputs</h2>
45 <form id="inputs">
46 <label>
47 Your original MP2T segment:
48 <input type="file" id="original">
49 </label>
50 <label>
51 A working, FLV version of the underlying stream
52 produced by another tool:
53 <input type="file" id="working">
54 </label>
55 </form>
56 </section>
57 <section>
58 <h2>Tag Comparison</h2>
59 <div class="result-wrapper">
60 <h3>videojs-contrib-hls</h3>
61 <ol class="vjs-tags">
62 </ol>
63 </div>
64 <div class="result-wrapper">
65 <h3>Working</h3>
66 <ol class="working-tags">
67 </ol>
68 </div>
69 </section>
70 <section>
71 <h2>Results</h2>
72 <div class="result-wrapper">
73 <h3>videojs-contrib-hls</h3>
74 <div class="vjs-hls-output result">
75 <p>
76 The results of transmuxing your input file with
77 videojs-contrib-hls will show up here.
78 </p>
79 </div>
80 </div>
81 <div class="result-wrapper">
82 <h3>Working</h3>
83 <div class="working-output result">
84 <p>
85 The "good" version of the file will show up here.
86 </p>
87 </div>
88 </div>
89 </section>
90 </article>
91
92 </div> <!-- #main -->
93 </div> <!-- #main-container -->
94
95 <div class="footer-container">
96 <footer class="wrapper">
97 <h3>footer</h3>
98 </footer>
99 </div>
100
101
102 <!-- transmuxing -->
103 <script src="../../src/flv-tag.js"></script>
104 <script src="../../src/exp-golomb.js"></script>
105 <script src="../../src/h264-stream.js"></script>
106 <script src="../../src/aac-stream.js"></script>
107 <script src="../../src/segment-parser.js"></script>
108
109 <script src="../../src/bin-utils.js"></script>
110 <script>
111 var inputs = document.getElementById('inputs'),
112 original = document.getElementById('original'),
113 working = document.getElementById('working'),
114
115 vjsTags = document.querySelector('.vjs-tags'),
116 workingTags = document.querySelector('.working-tags'),
117
118 vjsOutput = document.querySelector('.vjs-hls-output'),
119 workingOutput = document.querySelector('.working-output'),
120
121 tagTypes = {
122 0x08: 'audio',
123 0x09: 'video',
124 0x12: 'metadata'
125 };
126
127 original.addEventListener('change', function() {
128 var reader = new FileReader();
129 reader.addEventListener('loadend', function() {
130 var parser = new videojs.hls.SegmentParser(),
131 tags = [parser.getFlvHeader()],
132 tag,
133 hex,
134 li,
135 byteLength = 0,
136 data,
137 i,
138 pos;
139
140 // clear old tag info
141 vjsTags.innerHTML = '';
142
143 parser.parseSegmentBinaryData(new Uint8Array(reader.result));
144
145 // collect all the tags
146 while (parser.tagsAvailable()) {
147 tag = parser.getNextTag();
148 tags.push(tag.bytes);
149 li = document.createElement('li');
150 li.innerHTML = tagTypes[tag.bytes[0]] + ': ' + tag.bytes.byteLength + 'B';
151 vjsTags.appendChild(li);
152 }
153 // create a uint8array for the entire segment and copy everything over
154 i = tags.length;
155 while (i--) {
156 byteLength += tags[i].byteLength;
157 }
158 data = new Uint8Array(byteLength);
159 i = tags.length;
160 pos = byteLength;
161 while (i--) {
162 pos -= tags[i].byteLength;
163 data.set(tags[i], pos);
164 }
165
166 hex = '<pre>'
167 hex += videojs.hls.utils.hexDump(data);
168 hex += '</pre>'
169
170 vjsOutput.innerHTML = hex;
171 });
172 reader.readAsArrayBuffer(this.files[0]);
173 }, false);
174
175 working.addEventListener('change', function() {
176 var reader = new FileReader();
177 reader.addEventListener('loadend', function() {
178 var hex = '<pre>',
179 bytes = new Uint8Array(reader.result),
180 i = 9, // header
181 dataSize,
182 li;
183
184 // clear old tag info
185 workingTags.innerHTML = '';
186
187 // traverse the tags
188 i += 4; // previous tag size
189 while (i < bytes.byteLength) {
190 dataSize = bytes[i + 1] << 16;
191 dataSize |= bytes[i + 2] << 8;
192 dataSize |= bytes[i + 3];
193 dataSize += 11;
194
195 li = document.createElement('li');
196 li.innerHTML = tagTypes[bytes[i]] + ': ' + dataSize + 'B';
197 workingTags.appendChild(li);
198
199 i += dataSize; // tag size
200 i += 4; // previous tag size
201 }
202
203 // output the hex dump
204 hex += videojs.hls.utils.hexDump(bytes);
205 hex += '</pre>';
206 workingOutput.innerHTML = hex;
207 });
208 reader.readAsArrayBuffer(this.files[0]);
209 }, false);
210 </script>
211 <script type="text/plain">
212 // map nal_unit_types to friendly names
213 console.log([
214 'unspecified',
215 'slice_layer_without_partitioning',
216 'slice_data_partition_a_layer',
217 'slice_data_partition_b_layer',
218 'slice_data_partition_c_layer',
219 'slice_layer_without_partitioning_idr',
220 'sei',
221 'seq_parameter_set',
222 'pic_parameter_set',
223 'access_unit_delimiter',
224 'end_of_seq',
225 'end_of_stream',
226 'filler',
227 'seq_parameter_set_ext',
228 'prefix_nal_unit',
229 'subset_seq_parameter_set',
230 'reserved',
231 'reserved',
232 'reserved'
233 ][nalUnitType]);
234 </script>
235 </body>
236 </html>
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
44 </script> 44 </script>
45 <script src="videojs-hls_test.js"></script> 45 <script src="videojs-hls_test.js"></script>
46 <script src="segment-parser.js"></script> 46 <script src="segment-parser.js"></script>
47 <script src="h264-stream_test.js"></script>
47 <script src="exp-golomb_test.js"></script> 48 <script src="exp-golomb_test.js"></script>
48 <script src="flv-tag_test.js"></script> 49 <script src="flv-tag_test.js"></script>
49 <script src="m3u8_test.js"></script> 50 <script src="m3u8_test.js"></script>
......