Merge pull request #96 from videojs/feature/metrics
Expose more diagnostic info
Showing
5 changed files
with
121 additions
and
1 deletions
... | @@ -116,6 +116,11 @@ The number of bits downloaded per second in the last segment download. | ... | @@ -116,6 +116,11 @@ The number of bits downloaded per second in the last segment download. |
116 | This value is used by the default implementation of `selectPlaylist` | 116 | This value is used by the default implementation of `selectPlaylist` |
117 | to select an appropriate bitrate to play. | 117 | to select an appropriate bitrate to play. |
118 | 118 | ||
119 | #### player.hls.bytesReceived | ||
120 | Type: `number` | ||
121 | |||
122 | The total number of content bytes downloaded by the HLS tech. | ||
123 | |||
119 | #### player.hls.selectPlaylist | 124 | #### player.hls.selectPlaylist |
120 | Type: `function` | 125 | Type: `function` |
121 | 126 | ||
... | @@ -136,6 +141,13 @@ Fired immediately after a new master or media playlist has been | ... | @@ -136,6 +141,13 @@ Fired immediately after a new master or media playlist has been |
136 | downloaded. By default, the tech only downloads playlists as they | 141 | downloaded. By default, the tech only downloads playlists as they |
137 | are needed. | 142 | are needed. |
138 | 143 | ||
144 | #### mediachange | ||
145 | |||
146 | Fired when a new playlist becomes the active media playlist. Note that | ||
147 | the actual rendering quality change does not occur simultaneously with | ||
148 | this event; a new segment must be requested and the existing buffer | ||
149 | depleted first. | ||
150 | |||
139 | ### Testing | 151 | ### Testing |
140 | 152 | ||
141 | For testing, you can either run `npm test` or use `grunt` directly. | 153 | For testing, you can either run `npm test` or use `grunt` directly. | ... | ... |
... | @@ -131,6 +131,7 @@ | ... | @@ -131,6 +131,7 @@ |
131 | * object to switch to | 131 | * object to switch to |
132 | */ | 132 | */ |
133 | loader.media = function(playlist) { | 133 | loader.media = function(playlist) { |
134 | var mediaChange = false; | ||
134 | // getter | 135 | // getter |
135 | if (!playlist) { | 136 | if (!playlist) { |
136 | return media; | 137 | return media; |
... | @@ -150,19 +151,27 @@ | ... | @@ -150,19 +151,27 @@ |
150 | playlist = loader.master.playlists[playlist]; | 151 | playlist = loader.master.playlists[playlist]; |
151 | } | 152 | } |
152 | 153 | ||
154 | mediaChange = playlist.uri !== media.uri; | ||
155 | |||
153 | // switch to fully loaded playlists immediately | 156 | // switch to fully loaded playlists immediately |
154 | if (loader.master.playlists[playlist.uri].endList) { | 157 | if (loader.master.playlists[playlist.uri].endList) { |
158 | // abort outstanding playlist requests | ||
155 | if (request) { | 159 | if (request) { |
156 | request.abort(); | 160 | request.abort(); |
157 | request = null; | 161 | request = null; |
158 | } | 162 | } |
159 | loader.state = 'HAVE_METADATA'; | 163 | loader.state = 'HAVE_METADATA'; |
160 | media = playlist; | 164 | media = playlist; |
165 | |||
166 | // trigger media change if the active media has been updated | ||
167 | if (mediaChange) { | ||
168 | loader.trigger('mediachange'); | ||
169 | } | ||
161 | return; | 170 | return; |
162 | } | 171 | } |
163 | 172 | ||
164 | // switching to the active playlist is a no-op | 173 | // switching to the active playlist is a no-op |
165 | if (playlist.uri === media.uri) { | 174 | if (!mediaChange) { |
166 | return; | 175 | return; |
167 | } | 176 | } |
168 | 177 | ||
... | @@ -185,6 +194,7 @@ | ... | @@ -185,6 +194,7 @@ |
185 | withCredentials: withCredentials | 194 | withCredentials: withCredentials |
186 | }, function(error) { | 195 | }, function(error) { |
187 | haveMetadata(error, this, playlist.uri); | 196 | haveMetadata(error, this, playlist.uri); |
197 | loader.trigger('mediachange'); | ||
188 | }); | 198 | }); |
189 | }; | 199 | }; |
190 | 200 | ... | ... |
... | @@ -433,6 +433,7 @@ var | ... | @@ -433,6 +433,7 @@ var |
433 | // calculate the download bandwidth | 433 | // calculate the download bandwidth |
434 | player.hls.segmentXhrTime = (+new Date()) - startTime; | 434 | player.hls.segmentXhrTime = (+new Date()) - startTime; |
435 | player.hls.bandwidth = (this.response.byteLength / player.hls.segmentXhrTime) * 8 * 1000; | 435 | player.hls.bandwidth = (this.response.byteLength / player.hls.segmentXhrTime) * 8 * 1000; |
436 | player.hls.bytesReceived += this.response.byteLength; | ||
436 | 437 | ||
437 | // transmux the segment data from MP2T to FLV | 438 | // transmux the segment data from MP2T to FLV |
438 | segmentParser.parseSegmentBinaryData(new Uint8Array(this.response)); | 439 | segmentParser.parseSegmentBinaryData(new Uint8Array(this.response)); |
... | @@ -575,6 +576,9 @@ var | ... | @@ -575,6 +576,9 @@ var |
575 | updatedPlaylist); | 576 | updatedPlaylist); |
576 | oldMediaPlaylist = updatedPlaylist; | 577 | oldMediaPlaylist = updatedPlaylist; |
577 | }); | 578 | }); |
579 | player.hls.playlists.on('mediachange', function() { | ||
580 | player.trigger('mediachange'); | ||
581 | }); | ||
578 | }); | 582 | }); |
579 | }; | 583 | }; |
580 | 584 | ||
... | @@ -591,6 +595,8 @@ videojs.Hls = videojs.Flash.extend({ | ... | @@ -591,6 +595,8 @@ videojs.Hls = videojs.Flash.extend({ |
591 | options.swf = settings.flash.swf; | 595 | options.swf = settings.flash.swf; |
592 | videojs.Flash.call(this, player, options, ready); | 596 | videojs.Flash.call(this, player, options, ready); |
593 | options.source = source; | 597 | options.source = source; |
598 | this.bytesReceived = 0; | ||
599 | |||
594 | videojs.Hls.prototype.src.call(this, options.source && options.source.src); | 600 | videojs.Hls.prototype.src.call(this, options.source && options.source.src); |
595 | } | 601 | } |
596 | }); | 602 | }); | ... | ... |
... | @@ -510,4 +510,45 @@ | ... | @@ -510,4 +510,45 @@ |
510 | strictEqual(errors, 1, 'fired one error'); | 510 | strictEqual(errors, 1, 'fired one error'); |
511 | strictEqual(loader.error.code, 2, 'fired a network error'); | 511 | strictEqual(loader.error.code, 2, 'fired a network error'); |
512 | }); | 512 | }); |
513 | |||
514 | test('triggers an event when the active media changes', function() { | ||
515 | var | ||
516 | loader = new videojs.Hls.PlaylistLoader('master.m3u8'), | ||
517 | mediaChanges = 0; | ||
518 | loader.on('mediachange', function() { | ||
519 | mediaChanges++; | ||
520 | }); | ||
521 | requests.pop().respond(200, null, | ||
522 | '#EXTM3U\n' + | ||
523 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + | ||
524 | 'low.m3u8\n' + | ||
525 | '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + | ||
526 | 'high.m3u8\n'); | ||
527 | requests.shift().respond(200, null, | ||
528 | '#EXTM3U\n' + | ||
529 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | ||
530 | '#EXTINF:10,\n' + | ||
531 | 'low-0.ts\n' + | ||
532 | '#EXT-X-ENDLIST\n'); | ||
533 | strictEqual(mediaChanges, 0, 'initial selection is not a media change'); | ||
534 | |||
535 | loader.media('high.m3u8'); | ||
536 | strictEqual(mediaChanges, 0, 'mediachange does not fire immediately'); | ||
537 | |||
538 | requests.shift().respond(200, null, | ||
539 | '#EXTM3U\n' + | ||
540 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | ||
541 | '#EXTINF:10,\n' + | ||
542 | 'high-0.ts\n' + | ||
543 | '#EXT-X-ENDLIST\n'); | ||
544 | strictEqual(mediaChanges, 1, 'fired a mediachange'); | ||
545 | |||
546 | // switch back to an already loaded playlist | ||
547 | loader.media('low.m3u8'); | ||
548 | strictEqual(mediaChanges, 2, 'fired a mediachange'); | ||
549 | |||
550 | // trigger a no-op switch | ||
551 | loader.media('low.m3u8'); | ||
552 | strictEqual(mediaChanges, 2, 'ignored a no-op media change'); | ||
553 | }); | ||
513 | })(window); | 554 | })(window); | ... | ... |
... | @@ -1227,4 +1227,55 @@ test('has no effect if native HLS is available', function() { | ... | @@ -1227,4 +1227,55 @@ test('has no effect if native HLS is available', function() { |
1227 | player.dispose(); | 1227 | player.dispose(); |
1228 | }); | 1228 | }); |
1229 | 1229 | ||
1230 | test('tracks the bytes downloaded', function() { | ||
1231 | player.src({ | ||
1232 | src: 'http://example.com/media.m3u8', | ||
1233 | type: 'application/vnd.apple.mpegurl' | ||
1234 | }); | ||
1235 | player.hls.mediaSource.trigger({ | ||
1236 | type: 'sourceopen' | ||
1237 | }); | ||
1238 | |||
1239 | strictEqual(player.hls.bytesReceived, 0, 'no bytes received'); | ||
1240 | |||
1241 | requests.shift().respond(200, null, | ||
1242 | '#EXTM3U\n' + | ||
1243 | '#EXTINF:10,\n' + | ||
1244 | '0.ts\n' + | ||
1245 | '#EXTINF:10,\n' + | ||
1246 | '1.ts\n' + | ||
1247 | '#EXT-X-ENDLIST\n'); | ||
1248 | // transmit some segment bytes | ||
1249 | requests[0].response = new ArrayBuffer(17); | ||
1250 | requests.shift().respond(200, null, ''); | ||
1251 | |||
1252 | strictEqual(player.hls.bytesReceived, 17, 'tracked bytes received'); | ||
1253 | |||
1254 | player.trigger('timeupdate'); | ||
1255 | |||
1256 | // transmit some more | ||
1257 | requests[0].response = new ArrayBuffer(5); | ||
1258 | requests.shift().respond(200, null, ''); | ||
1259 | |||
1260 | strictEqual(player.hls.bytesReceived, 22, 'tracked more bytes'); | ||
1261 | }); | ||
1262 | |||
1263 | test('re-emits mediachange events', function() { | ||
1264 | var mediaChanges = 0; | ||
1265 | player.on('mediachange', function() { | ||
1266 | mediaChanges++; | ||
1267 | }); | ||
1268 | |||
1269 | player.src({ | ||
1270 | src: 'http://example.com/media.m3u8', | ||
1271 | type: 'application/vnd.apple.mpegurl' | ||
1272 | }); | ||
1273 | player.hls.mediaSource.trigger({ | ||
1274 | type: 'sourceopen' | ||
1275 | }); | ||
1276 | |||
1277 | player.hls.playlists.trigger('mediachange'); | ||
1278 | strictEqual(mediaChanges, 1, 'fired mediachange'); | ||
1279 | }); | ||
1280 | |||
1230 | })(window, window.videojs); | 1281 | })(window, window.videojs); | ... | ... |
-
Please register or sign in to post a comment