├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── README.md ├── example.html ├── index.html ├── package.json ├── src ├── css │ └── debugger.css └── js │ ├── bootstrap.js │ └── debugger.js └── test ├── index.html ├── karma.config.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | Thumbs.db 3 | ehthumbs.db 4 | Desktop.ini 5 | .DS_Store 6 | ._* 7 | 8 | # Editors 9 | *~ 10 | *.swp 11 | *.tmproj 12 | *.tmproject 13 | *.sublime-* 14 | .idea/ 15 | .project/ 16 | .settings/ 17 | .vscode/ 18 | 19 | # Logs 20 | logs 21 | *.log 22 | npm-debug.log* 23 | 24 | # Dependency directories 25 | bower_components/ 26 | node_modules/ 27 | 28 | # Yeoman meta-data 29 | .yo-rc.json 30 | 31 | # Build-related directories 32 | dist/ 33 | dist-test/ 34 | docs/api/ 35 | es5/ 36 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Intentionally left blank, so that npm does not ignore anything by default, 2 | # but relies on the package.json "files" array to explicitly define what ends 3 | # up in the package. 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | - '4.2' 5 | - '0.12' 6 | - '0.10' 7 | 8 | # Set up a virtual screen for Firefox. 9 | before_install: 10 | - export DISPLAY=:99.0 11 | - sh -e /etc/init.d/xvfb start 12 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var releaseType = process.env.RELEASE_TYPE || 'prerelease'; 3 | 4 | module.exports = function(grunt) { 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | 8 | jshint: { 9 | all: ['src/js/*.js'], 10 | }, 11 | 12 | copy: { 13 | build: { 14 | files: [{ 15 | src: 'src/css/debugger.css', 16 | dest: 'dist/debugger.css' 17 | }, { 18 | src: 'src/js/bootstrap.js', 19 | dest: 'dist/bootstrap.js' 20 | }, { 21 | src: 'src/js/debugger.js', 22 | dest: 'dist/debugger.js' 23 | } 24 | ] 25 | }, 26 | version: { 27 | files: [ 28 | { expand: true, cwd: 'dist', src: ['*'], dest: 'dist/<%= pkg.version %>/' } 29 | ] 30 | } 31 | }, 32 | 33 | cssmin: { 34 | options: { 35 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 36 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 37 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> Brightcove */\n' 38 | }, 39 | build: { 40 | files: { 41 | 'dist/debugger.css': ['src/css/debugger.css'] 42 | } 43 | } 44 | }, 45 | 46 | uglify: { 47 | options: { 48 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 49 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 50 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> Brightcove */\n' 51 | }, 52 | build: { 53 | files: [{ 54 | src: 'src/js/bootstrap.js', 55 | dest: 'dist/bootstrap.js' 56 | }, { 57 | src: 'src/js/debugger.js', 58 | dest: 'dist/debugger.js' 59 | } 60 | ] 61 | } 62 | }, 63 | 64 | clean: ['dist'], 65 | 66 | watch: { 67 | files: ['src/js/*.js'], 68 | tasks: ['jshint'] 69 | }, 70 | 71 | compress: { 72 | package: { 73 | options: { 74 | archive: '<%= pkg.name %>.tgz', 75 | mode: 'tgz' 76 | }, 77 | cwd: 'dist', 78 | expand: true, 79 | src: ['**'] 80 | } 81 | }, 82 | 83 | karma: { 84 | test: { 85 | options:{ 86 | configFile: 'test/karma.config.js' 87 | } 88 | } 89 | }, 90 | 91 | release: { 92 | options: { 93 | npm: false 94 | } 95 | } 96 | }); 97 | 98 | require('load-grunt-tasks')(grunt); 99 | 100 | grunt.registerTask('test', ['jshint', 'karma']); 101 | grunt.registerTask('build',['clean', 'copy:build', 'uglify', 'cssmin']); 102 | grunt.registerTask('default', ['test', 'build']); 103 | grunt.registerTask('package', ['default', 'copy:version', 'compress:package']); 104 | grunt.registerTask('version', ['release:' + releaseType ]); 105 | }; 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # videojs-debugger 2 | 3 | [![Build Status](https://travis-ci.org/brightcove/videojs-debugger.svg?branch=master)](https://travis-ci.org/brightcove/videojs-debugger) 4 | 5 | ## Including 6 | 7 | 1. Include `bootstrap.js` in after `video.js`: `` 8 | 2. Include `debugger.css`: `` 9 | 3. Initialize the debugger with the url to the `debugger.js` file: 10 | 11 | ```js 12 | var player = videojs('video'); 13 | player.debuggerWindow({ 14 | js: "src/js/debugger.js", 15 | css: "src/css/debugger.css" 16 | }); 17 | ``` 18 | 19 | ## F2 or Triple Tap to open debugger 20 | 21 | The debugger is loaded on demand via a certain trigger. 22 | * On desktops, the trigger is *``*. 23 | * On mobile devices, the trigger is a *`three finger tap`* 24 | 25 | ## Options 26 | 27 | * `js`: The url to the debugger script 28 | * `css`: The url to the debugger stylesheet 29 | 30 | ## Usage 31 | 32 | `videojs.log` 33 | 34 | Once the the plugin gets loaded, there are several extra logging methods available for use. 35 | ### Available always 36 | * `videojs.log.debug` - Adds a `debug` message 37 | * `videojs.log.warn` - Adds a `warn` message 38 | * `videojs.log.info` - Adds an `info` message. This one is equivalent to `videojs.log` itself 39 | * `videojs.log.error` - Adds an `error` message 40 | 41 | ### Available after the debugger has been opened 42 | * `videojs.log.resize` - Toggles the size of the debugger window 43 | * `videojs.log.clear` - Clears the current output in the debugger window 44 | * `videojs.log.move` - Moves the debugger window to the next corner 45 | * `videojs.log.profile` - Adds a profile message 46 | 47 | ## Known issues 48 | 49 | * Email: To test emailing the log, you will need to supply a recipient's email address when your email client opens. 50 | * Email: Currently, email does not format the log dump. The contents of the email are the contents of the array of Strings, separated by commas. Would formatting be handled better on the server side? 51 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | Player Debugger Demo 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

10 | To open the debugger, you codess F2 on desktops, or do a two finger tap on mobile. 11 |

12 |

13 | To use the debugger, you have to call videojs.log with your logging statements. 14 |

15 |

16 | After the debugger plugin has loaded, you can use several more functions (the logging functions are available with just the bootstrap as well): 17 |

27 |

28 | 29 | 33 | 34 | 44 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-debugger", 3 | "version": "2.0.1", 4 | "description": "A debugger for videojs", 5 | "main": "src/js/bootstrap.js", 6 | "scripts": { 7 | "prepublish": "grunt build", 8 | "preversion": "grunt", 9 | "test": "grunt" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com/brightcove/videojs-debugger.git" 14 | }, 15 | "author": { 16 | "name": "Brightcove", 17 | "email": "donotreply@brightcove.com" 18 | }, 19 | "license": "Apache-2.0", 20 | "readmeFilename": "README.md", 21 | "devDependencies": { 22 | "grunt": "~0.4.2", 23 | "grunt-cli": "~0.1.13", 24 | "grunt-contrib-clean": "~0.5.0", 25 | "grunt-contrib-compress": "~0.6.1", 26 | "grunt-contrib-copy": "~0.5.0", 27 | "grunt-contrib-cssmin": "~0.7.0", 28 | "grunt-contrib-jshint": "~0.8.0", 29 | "grunt-contrib-uglify": "~0.3.2", 30 | "grunt-karma": "^0.12.1", 31 | "grunt-release": "0.7.0", 32 | "karma": "^0.13.19", 33 | "karma-firefox-launcher": "^0.1.7", 34 | "karma-qunit": "^0.1.9", 35 | "load-grunt-tasks": "~0.3.0", 36 | "qunitjs": "^1.20.0", 37 | "video.js": "^5.0.0" 38 | }, 39 | "files": [ 40 | "dist/", 41 | "src/", 42 | "Gruntfile.js", 43 | "index.html" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /src/css/debugger.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Originally based on Blackbird 3 | * MIT License - Copyright (c) Blackbird Project 4 | */ 5 | #blackbird { 6 | margin: 0; 7 | padding: 0; 8 | position: fixed; 9 | font: 11px/1.3 Consolas, 'Lucida Console', Monaco, monospace; 10 | z-index: 100; 11 | } 12 | #blackbird.vjs-touch { 13 | font: 18px/1.3 Consolas, 'Lucida Console', Monaco, monospace; 14 | } 15 | 16 | #blackbird.bbTopLeft { 17 | top: 0; 18 | left: 0; 19 | } 20 | 21 | #blackbird.bbTopRight { 22 | top: 0; 23 | right: 0; 24 | } 25 | 26 | #blackbird.bbBottomLeft { 27 | bottom: 0; 28 | left: 0; 29 | } 30 | 31 | #blackbird.bbBottomRight { 32 | bottom: 0; 33 | right: 0; 34 | } 35 | 36 | #blackbird.bbSmall { 37 | width: 300px; 38 | } 39 | #blackbird.vjs-touch.bbSmall { 40 | width: 528px; 41 | } 42 | 43 | #blackbird.bbSmall .main { 44 | height: 200px; 45 | width: 272px; 46 | } 47 | #blackbird.vjs-touch.bbSmall .main { 48 | height: 300px; 49 | width: 500px; 50 | } 51 | 52 | #blackbird.bbLarge { 53 | width: 500px; 54 | } 55 | #blackbird.vjs-touch.bbLarge, 56 | #blackbird.vjs-touch.bbLarge .main { 57 | width: 660px; 58 | } 59 | 60 | #blackbird.bbLarge .header .left { 61 | width: 336px; 62 | } 63 | #blackbird.vjs-touch.bbLarge .header .left, 64 | #blackbird.vjs-touch.bbLarge .header .right { 65 | width: 316px; 66 | } 67 | 68 | #blackbird.bbLarge .main { 69 | height: 500px; 70 | } 71 | 72 | #blackbird.bbLarge .header, 73 | #blackbird.bbLarge .footer, 74 | #blackbird.bbLarge .main { 75 | width: 472px; 76 | } 77 | #blackbird.vjs-touch.bbLarge .header, 78 | #blackbird.vjs-touch.bbLarge .footer, 79 | #blackbird.vjs-touch.bbLarge .main { 80 | width: 632px; 81 | } 82 | 83 | 84 | 85 | #blackbird span.fa { 86 | position: relative; 87 | width: 16px; 88 | height: 16px; 89 | font-size: 16px; 90 | margin: 0 4px; 91 | float: left; 92 | color: white; 93 | } 94 | #blackbird.vjs-touch span.fa { 95 | width: 32px; 96 | height: 32px; 97 | font-size: 32px; 98 | margin: 0 8px; 99 | } 100 | 101 | #blackbird span.fa.error.disabled:before, 102 | #blackbird span.fa.warn.disabled:before, 103 | #blackbird span.fa.info.disabled:before, 104 | #blackbird span.fa.debug.disabled:before, 105 | #blackbird span.fa.profile.disabled:before { 106 | color: white; 107 | } 108 | 109 | #blackbird span.fa.error:before { 110 | color: red; 111 | content: "\f057"; /* fa-times-circle */ 112 | } 113 | 114 | #blackbird span.fa.warn:before { 115 | color: yellow; 116 | content: "\f071"; /* fa-exclamation-triangle */ 117 | } 118 | 119 | #blackbird span.fa.info:before { 120 | color: blue; 121 | content: "\f05a"; /* fa-info-circle */ 122 | } 123 | 124 | #blackbird span.fa.debug:before { 125 | color: green; 126 | content: "\f14a"; /* fa-check-square */ 127 | } 128 | 129 | #blackbird span.fa.profile:before { 130 | color: orange; 131 | content: "\f017"; /* fa-clock-o */ 132 | } 133 | 134 | #blackbird span.fa.email:before { 135 | content: "\f003"; /* fa-envelope-o */ 136 | } 137 | 138 | #blackbird span.fa.close:before { 139 | content: "\f00d"; /* fa-envelope-o */ 140 | } 141 | 142 | #blackbird span.fa.clear:before { 143 | content: "\f014"; /* fa-trash-o */ 144 | } 145 | 146 | #blackbird span.fa.small:before { 147 | content: "\f066"; /* fa-compress */ 148 | } 149 | 150 | #blackbird span.fa.large:before { 151 | content: "\f065"; /* fa-expand */ 152 | } 153 | 154 | #blackbird .header, 155 | #blackbird .main, 156 | #blackbird .footer { 157 | border-left: solid #F4F4F4 3px; 158 | border-right: solid #F4F4F4 3px; 159 | } 160 | 161 | #blackbird .header { 162 | background-color: #000; 163 | border-top: solid #F4F4F4 3px; 164 | border-bottom: solid #333 1px; 165 | height: 32px; 166 | float: left; 167 | width: 272px; 168 | margin: 14px 8px 0 8px; 169 | border-radius: 10px 10px 0 0; 170 | float: left; 171 | } 172 | #blackbird.vjs-touch .header { 173 | width: 500px; 174 | height: 50px; 175 | } 176 | 177 | #blackbird .header .left, 178 | #blackbird .header .right { 179 | float: left; 180 | width: 136px; 181 | } 182 | #blackbird.vjs-touch .header .left, 183 | #blackbird.vjs-touch .header .right { 184 | width: 250px; 185 | } 186 | 187 | #blackbird .header .left { 188 | background-position: top left; 189 | } 190 | 191 | #blackbird .header .left .filters { 192 | padding: 10px 0 0 10px; 193 | float: left; 194 | } 195 | 196 | #blackbird .header .right .controls { 197 | padding: 10px 10px 0 0; 198 | float: right; 199 | } 200 | 201 | #blackbird .header .right .controls span.email, 202 | #blackbird .header .right .controls span.clear { 203 | margin-right: 15px; 204 | } 205 | #blackbird.vjs-touch .header .right .controls span.email, 206 | #blackbird.vjs-touch .header .right .controls span.clear { 207 | margin-right: 25px; 208 | } 209 | 210 | #blackbird .main { 211 | background-color: #0E0E0E; 212 | margin: 0 14px 0 8px; 213 | float: left; 214 | } 215 | 216 | #blackbird .main ol { 217 | line-height: 1.45; 218 | height: 100%; 219 | overflow: auto; 220 | width: 100%; 221 | list-style-type: none; 222 | margin: 0; 223 | padding: 0; 224 | } 225 | 226 | #blackbird .main ol li { 227 | padding: 1px 4px 1px 20px; 228 | border-bottom: 1px solid #333; 229 | color: #CCC; 230 | margin: 2px 2px; 231 | } 232 | #blackbird.vjs-touch .main ol li { 233 | padding-left: 40px; 234 | } 235 | 236 | #blackbird .main ol li span { 237 | display: block; 238 | margin-left: -18px; 239 | } 240 | #blackbird.vjs-touch .main ol li span { 241 | margin-left: -36px; 242 | } 243 | 244 | #blackbird .main ol li.profile { 245 | color: #DDD; 246 | font-style: italic; 247 | } 248 | 249 | #blackbird .errorHidden li.error, 250 | #blackbird .warnHidden li.warn, 251 | #blackbird .infoHidden li.info, 252 | #blackbird .debugHidden li.debug, 253 | #blackbird .profileHidden li.profile { 254 | display: none; 255 | } 256 | 257 | #blackbird .footer { 258 | background-color: #0E0E0E; 259 | border-bottom: solid #F4F4F4 3px; 260 | border-top: solid #333 1px; 261 | height: 10px; 262 | float: left; 263 | width: 272px; 264 | margin: 0 8px 0 8px; 265 | border-radius: 0 0 10px 10px; 266 | } 267 | #blackbird.vjs-touch .footer { 268 | width: 500px; 269 | } 270 | -------------------------------------------------------------------------------- /src/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | (function(videojs, window, undefined) { 2 | var options, 3 | player, 4 | readKey, 5 | readGesture; 6 | 7 | function filter(arr, callback, context) { 8 | var 9 | result = [], 10 | value, 11 | i = 0, 12 | l = arr.length; 13 | 14 | for (; i < l; i++) { 15 | if (arr.hasOwnProperty(i)) { 16 | value = arr[i]; 17 | if (callback.call(context, value, i, arr)) { 18 | result.push(value); 19 | } 20 | } 21 | } 22 | 23 | return result; 24 | } 25 | 26 | //event management (thanks John Resig) 27 | function addEvent(obj, type, fn) { 28 | obj = (obj.constructor === String) ? document.getElementById(obj) : obj; 29 | if (obj.attachEvent) { 30 | obj['e' + type + fn] = fn; 31 | obj[type + fn] = function(){ obj['e' + type + fn](window.event); }; 32 | obj.attachEvent('on' + type, obj[type + fn]); 33 | } else { 34 | obj.addEventListener(type, fn, false); 35 | } 36 | } 37 | 38 | function removeEvent(obj, type, fn) { 39 | obj = (obj.constructor === String) ? document.getElementById(obj) : obj; 40 | if (obj.detachEvent) { 41 | obj.detachEvent('on' + type, obj[type + fn]); 42 | obj[type + fn] = null; 43 | } else { 44 | obj.removeEventListener(type, fn, false); 45 | } 46 | } 47 | 48 | function unbindEvents() { 49 | removeEvent(document, 'keyup', readKey); 50 | removeEvent(document, 'touchend', readGesture); 51 | } 52 | 53 | function getEvents(callback) { 54 | return { 55 | readKey: function readKey(evt) { 56 | if (!evt) evt = window.event; 57 | var code = 113; //F2 key 58 | 59 | if (evt && evt.keyCode == code) { 60 | callback(); 61 | } 62 | }, 63 | 64 | readGesture: function readGesture(evt) { 65 | if (!evt) { 66 | evt = window.event; 67 | evt.preventDefault(); 68 | } 69 | 70 | if (evt.targetTouches.length + evt.changedTouches.length > 2) { 71 | callback(); 72 | } 73 | } 74 | }; 75 | } 76 | 77 | function loadDebugger() { 78 | if (player.debuggerWindowMain) { 79 | return; 80 | } 81 | 82 | var s = document.createElement('script'), 83 | debuggerStyle = document.createElement('link'), 84 | fontawesome = document.createElement('link'), 85 | loaded = false; 86 | 87 | s.onload = s.onreadystatechange = function() { 88 | if (!loaded && (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) { 89 | loaded = true; 90 | s.onload = s.onreadystatechange = null; 91 | 92 | /* jshint -W021 */ 93 | loadDebugger = function(){}; 94 | 95 | player.debuggerWindowMain(); 96 | unbindEvents(); 97 | } 98 | }; 99 | 100 | s.src = options.js || 'debugger.js'; 101 | 102 | debuggerStyle.rel = 'stylesheet'; 103 | fontawesome.rel = 'stylesheet'; 104 | debuggerStyle.href = options.css || 'debugger.css'; 105 | fontawesome.href = '//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css'; 106 | 107 | document.body.appendChild(s); 108 | document.body.appendChild(debuggerStyle); 109 | document.body.appendChild(fontawesome); 110 | } 111 | 112 | videojs.plugin('debuggerWindow', function(opts) { 113 | var events = getEvents(loadDebugger), 114 | videoEvents = filter(videojs.getComponent('Html5').Events, function(event) { return event !== 'timeupdate' && event !== 'progress' && event !== 'suspend'; }), 115 | i = videoEvents.length, 116 | eventHandlerFunction; 117 | 118 | options = opts; 119 | player = this; 120 | readKey = events.readKey; 121 | readGesture = events.readGesture; 122 | 123 | addEvent(document, 'keyup', readKey); 124 | addEvent(document, 'touchend', readGesture); 125 | 126 | addEvent(window, 'unload', function() { 127 | unbindEvents(); 128 | }); 129 | 130 | player.debuggerWindow.getEvents = getEvents; 131 | player.debuggerWindow.loadDebugger = loadDebugger; 132 | 133 | function makeCachedLogger(fn) { 134 | function logger() { 135 | if (fn) { 136 | fn.apply(null, arguments); 137 | } 138 | 139 | logger.history.push(arguments); 140 | } 141 | logger.history = []; 142 | return logger; 143 | } 144 | 145 | videojs.log.debug = makeCachedLogger(); 146 | videojs.log.warn = makeCachedLogger(videojs.log.warn); 147 | videojs.log.info = makeCachedLogger(videojs.log); 148 | videojs.log.error = makeCachedLogger(videojs.log.error); 149 | 150 | eventHandlerFunction = function(event) { 151 | videojs.log.debug({ 152 | type: event.type, 153 | time: new Date() 154 | }); 155 | }; 156 | 157 | while (i--) { 158 | player.on(videoEvents[i], eventHandlerFunction); 159 | } 160 | }); 161 | })(videojs, window); 162 | -------------------------------------------------------------------------------- /src/js/debugger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Originally based on Blackbird 3 | * MIT License - Copyright (c) Blackbird Project 4 | */ 5 | (function(videojs, window, undefined) { 6 | 'use strict'; 7 | 8 | var events = videojs.getComponent('Player').prototype.debuggerWindow.getEvents; 9 | 10 | function reduce(arr, callback, initial) { 11 | var returnValue = initial, 12 | i = 0, 13 | l = arr.length; 14 | 15 | for (; i < l; i++) { 16 | returnValue = callback(returnValue, arr[i], i, arr); 17 | } 18 | 19 | return returnValue; 20 | } 21 | 22 | //event management (thanks John Resig) 23 | function addEvent(obj, type, fn) { 24 | obj = (obj.constructor === String) ? document.getElementById(obj) : obj; 25 | if (obj.attachEvent) { 26 | obj['e' + type + fn] = fn; 27 | obj[type + fn] = function(){ obj['e' + type + fn](window.event); }; 28 | obj.attachEvent('on' + type, obj[type + fn]); 29 | } else { 30 | obj.addEventListener(type, fn, false); 31 | } 32 | } 33 | 34 | function removeEvent(obj, type, fn) { 35 | obj = (obj.constructor === String) ? document.getElementById(obj) : obj; 36 | if (obj.detachEvent) { 37 | obj.detachEvent('on' + type, obj[type + fn]); 38 | obj[type + fn] = null; 39 | } else { 40 | obj.removeEventListener(type, fn, false); 41 | } 42 | } 43 | 44 | 45 | function debuggerWindowMain(options) { 46 | 47 | var 48 | 49 | /* jshint -W040 */ 50 | player = this, 51 | bbird, 52 | outputList, 53 | cache = [], 54 | emailArray = [], 55 | state = { 56 | pos: 1, 57 | size: 0 58 | }, 59 | classes = [], 60 | profiler = {}, 61 | currentTime, 62 | readKey, 63 | readGesture, 64 | logger, 65 | historyLogger, 66 | oldLog, 67 | history, 68 | 69 | IDs = { 70 | blackbird: 'blackbird', 71 | filters: 'bbFilters', 72 | controls: 'bbControls', 73 | size: 'bbSize', 74 | sendEmail: 'sendEmail' 75 | }, 76 | 77 | messageTypes = { //order of these properties imply render order of filter controls 78 | debug: true, 79 | info: true, 80 | warn: true, 81 | error: true, 82 | profile: true 83 | }; 84 | 85 | function pad(s, n) { 86 | return ((new Array(n+1)).join('0') + s).slice(-1*n); 87 | } 88 | function timeString() { 89 | var d = new Date(); 90 | return [ 91 | '[', 92 | [pad(d.getMonth() + 1, 2), pad(d.getDate(), 2)].join('/'), 93 | ' ', 94 | [pad(d.getHours(), 2), pad(d.getMinutes(), 2), pad(d.getSeconds(), 2)].join(':'), 95 | ']', 96 | ].join(''); 97 | } 98 | 99 | function generateMarkup() { //build markup 100 | var type, spans = []; 101 | for (type in messageTypes) { 102 | spans.push([''].join('')); 103 | } 104 | 105 | var newNode = document.createElement('DIV'); 106 | newNode.id = IDs.blackbird; 107 | newNode.style.display = 'none'; 108 | newNode.innerHTML = [ 109 | '
', 110 | '
', 111 | '
', spans.join(''), 112 | '
', 113 | '
', 114 | '
', 115 | '
', 116 | '', 117 | '', 118 | '', 119 | '', 120 | '
', 121 | '
', 122 | '
', 123 | '
', 124 | '
    ', cache.join(''), '
', 125 | '
', 126 | '' 128 | ].join(''); 129 | return newNode; 130 | } 131 | 132 | function addMessage(type, content) { //adds a message to the output list 133 | var innerContent, 134 | allContent, 135 | timeStr = timeString(), 136 | newMsg; 137 | content = (content.constructor == Array) ? content.join('') : content; 138 | 139 | innerContent = [ 140 | '', 141 | timeStr, 142 | content 143 | ].join(' '); 144 | 145 | allContent = ['
  • ', innerContent, '
  • '].join(''); 146 | 147 | if (outputList) { 148 | newMsg = document.createElement('LI'); 149 | newMsg.className = type; 150 | newMsg.innerHTML = innerContent; 151 | outputList.appendChild(newMsg); 152 | scrollToBottom(); 153 | } else { 154 | cache.push(allContent); 155 | } 156 | emailArray.push([timeStr, ' ', type, ': ', content].join('')); 157 | } 158 | 159 | function clear() { //clear list output 160 | outputList.innerHTML = ''; 161 | } 162 | 163 | function clickControl(evt) { 164 | var el; 165 | 166 | if (!evt) { 167 | evt = window.event; 168 | } 169 | el = (evt.target) ? evt.target : evt.srcElement; 170 | 171 | if (el.tagName == 'SPAN') { 172 | switch (el.getAttributeNode('op').nodeValue) { 173 | case 'resize': resize(); break; 174 | case 'clear': clear(); break; 175 | case 'close': hide(); break; 176 | } 177 | } 178 | } 179 | 180 | function clickFilter(evt) { //show/hide a specific message type 181 | var entry, span, type, filters, active, oneActiveFilter, i, spanType, disabledTypes; 182 | 183 | if (!evt) { 184 | evt = window.event; 185 | } 186 | span = (evt.target) ? evt.target : evt.srcElement; 187 | 188 | if (span && span.tagName == 'SPAN') { 189 | 190 | type = span.getAttributeNode('type').nodeValue; 191 | 192 | if (evt.altKey) { 193 | filters = document.getElementById(IDs.filters).getElementsByTagName('SPAN'); 194 | 195 | active = 0; 196 | for (entry in messageTypes) { 197 | if (messageTypes[entry]) active++; 198 | } 199 | oneActiveFilter = (active == 1 && messageTypes[type]); 200 | 201 | for (i = 0; filters[i]; i++) { 202 | spanType = filters[i].getAttributeNode('type').nodeValue; 203 | 204 | filters[i].className = 'fa ' + spanType + ((oneActiveFilter || (spanType == type)) ? '' : ' disabled'); 205 | messageTypes[spanType] = oneActiveFilter || (spanType == type); 206 | } 207 | } 208 | else { 209 | messageTypes[type] = ! messageTypes[type]; 210 | span.className = 'fa ' + type + ((messageTypes[type]) ? '' : ' disabled'); 211 | } 212 | 213 | //build outputList's class from messageTypes object 214 | disabledTypes = []; 215 | for (type in messageTypes) { 216 | if (! messageTypes[type]) { 217 | disabledTypes.push(type); 218 | } 219 | } 220 | disabledTypes.push(''); 221 | outputList.className = disabledTypes.join('Hidden '); 222 | 223 | scrollToBottom(); 224 | } 225 | } 226 | 227 | function clickSendEmail(evt) { 228 | var el; 229 | if (!evt) { 230 | evt = window.event; 231 | } 232 | el = (evt.target) ? evt.target : evt.srcElement; 233 | window.open('mailto:email@example.com?subject=Brightcove Player Debugger Log&body=' + encodeURIComponent(emailArray.join('\n'))); 234 | } 235 | 236 | function scrollToBottom() { //scroll list output to the bottom 237 | outputList.scrollTop = outputList.scrollHeight; 238 | } 239 | 240 | function isVisible() { //determine the visibility 241 | return (bbird.style.display == 'block'); 242 | } 243 | 244 | function hide() { 245 | bbird.style.display = 'none'; 246 | } 247 | 248 | function show() { 249 | document.body.removeChild(bbird); 250 | document.body.appendChild(bbird); 251 | bbird.style.display = 'block'; 252 | } 253 | 254 | function toggleVisibility() { 255 | if (isVisible()) { 256 | hide(); 257 | } else { 258 | show(); 259 | } 260 | } 261 | 262 | //sets the position 263 | function reposition(position) { 264 | if (position === undefined || position === null) { 265 | //set to initial position ('topRight') or move to next position 266 | position = (state && state.pos === null) ? 1 : (state.pos + 1) % 4; 267 | } 268 | 269 | switch (position) { 270 | case 0: classes[0] = 'bbTopLeft'; break; 271 | case 1: classes[0] = 'bbTopRight'; break; 272 | case 2: classes[0] = 'bbBottomLeft'; break; 273 | case 3: classes[0] = 'bbBottomRight'; break; 274 | } 275 | state.pos = position; 276 | setState(); 277 | } 278 | 279 | function resize(size) { 280 | var span; 281 | 282 | if (size === undefined || size === null) { 283 | size = (state && state.size === null) ? 0 : (state.size + 1) % 2; 284 | } 285 | 286 | classes[1] = (size === 0) ? 'bbSmall' : 'bbLarge'; 287 | 288 | span = document.getElementById(IDs.size); 289 | span.title = (size === 1) ? 'small' : 'large'; 290 | span.className = "fa " + span.title; 291 | 292 | state.size = size; 293 | setState(); 294 | scrollToBottom(); 295 | } 296 | 297 | function setState() { 298 | var touchClass = ""; 299 | if (videojs.browser.TOUCH_ENABLED) { 300 | touchClass = 'vjs-touch '; 301 | } 302 | bbird.className = touchClass + classes.join(' '); 303 | } 304 | 305 | oldLog = videojs.log; 306 | videojs.log = {}; 307 | videojs.log.oldLog = oldLog; 308 | 309 | logger = function(type, messages) { 310 | if (videojs.log.oldLog[type]) { 311 | videojs.log.oldLog[type].apply(null, arguments); 312 | } 313 | 314 | addMessage(type, reduce((messages || []), function(str, msg, i) { 315 | var sMsg; 316 | try { 317 | sMsg = JSON.stringify(msg); 318 | } catch (e) { 319 | sMsg = msg.toString(); 320 | } 321 | return str + sMsg + (i === messages.length - 1 ? "" : ", "); 322 | }, "")); 323 | }; 324 | historyLogger = function(type, messages) { 325 | (messages || []).forEach(function(arrgs) { 326 | var args = Array.prototype.slice.call(arrgs); 327 | logger(type, args); 328 | }); 329 | }; 330 | 331 | historyLogger("info", videojs.log.history); 332 | historyLogger("debug", videojs.log.debug.history); 333 | historyLogger("warn", videojs.log.warn.history); 334 | historyLogger("error", videojs.log.error.history); 335 | 336 | videojs.log = function() { 337 | var args = Array.prototype.slice.call(arguments); 338 | logger("info", args); 339 | 340 | videojs.log.oldLog.apply(videojs, arguments); 341 | }; 342 | 343 | videojs.log.resize = function() { resize(); }; 344 | videojs.log.clear = function() { clear(); }; 345 | videojs.log.move = function() { reposition(); }; 346 | videojs.log.debug = function() { logger('debug', arguments); }; 347 | videojs.log.warn = function() { logger('warn', arguments); }; 348 | videojs.log.info = videojs.log; 349 | videojs.log.error = function() { logger('error', arguments); }; 350 | videojs.log.profile = function(label) { 351 | currentTime = new Date(); //record the current time when profile() is executed 352 | if (label === undefined || label === null || label === '') { 353 | addMessage('error', 'ERROR: Please specify a label for your profile statement'); 354 | } 355 | else if (profiler[label]) { 356 | addMessage('profile', [label, ': ', currentTime - profiler[label], 'ms'].join('')); 357 | delete profiler[label]; 358 | } 359 | else { 360 | profiler[label] = currentTime; 361 | addMessage('profile', label); 362 | } 363 | return currentTime; 364 | }; 365 | 366 | bbird = document.body.appendChild(generateMarkup()); 367 | outputList = bbird.getElementsByTagName('OL')[0]; 368 | 369 | events = (events || player.debuggerWindow.events)(toggleVisibility); 370 | readKey = events.readKey; 371 | readGesture = events.readGesture; 372 | 373 | addEvent(IDs.sendEmail, 'click', clickSendEmail); 374 | addEvent(IDs.filters, 'click', clickFilter); 375 | addEvent(IDs.controls, 'click', clickControl); 376 | addEvent(document, 'keyup', readKey); 377 | addEvent(document, 'touchend', readGesture); 378 | 379 | resize(state.size); 380 | reposition(state.pos); 381 | show(); 382 | player.debuggerWindowMain.show = show; 383 | player.debuggerWindowMain.hide = hide; 384 | 385 | scrollToBottom(); 386 | 387 | addEvent(window, 'unload', function() { 388 | removeEvent(IDs.sendEmail, 'click', clickSendEmail); 389 | removeEvent(IDs.filters, 'click', clickFilter); 390 | removeEvent(IDs.controls, 'click', clickControl); 391 | removeEvent(document, 'keyup', readKey); 392 | removeEvent(document, 'touchend', readGesture); 393 | }); 394 | } 395 | 396 | videojs.plugin('debuggerWindowMain', debuggerWindowMain); 397 | })(videojs, window); 398 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Player 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 |
    19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/karma.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basePath: '..', 4 | 5 | files: [{ 6 | pattern: 'src/js/debugger.js', 7 | included: false 8 | }, { 9 | pattern: 'src/css/debugger.css', 10 | included: false 11 | }, 12 | 'node_modules/video.js/dist/video.js', 13 | 'node_modules/video.js/dist/video-js.css', 14 | 'src/js/bootstrap.js', 15 | 'test/test.js' 16 | ], 17 | 18 | frameworks: ['qunit'], 19 | 20 | browsers: ['Firefox'], 21 | 22 | singleRun: true 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var baseUrl = window.__karma__ ? '/base/' : '../'; 2 | 3 | module('Debugger', { 4 | beforeEach: function(assert) { 5 | var done = assert.async(); 6 | this.fixture = document.createElement('div'); 7 | document.body.appendChild(this.fixture); 8 | 9 | var video = document.createElement('video'); 10 | video.width = 600; 11 | video.height = 600; 12 | this.fixture.appendChild(video); 13 | this.player = videojs(video); 14 | this.player.ready(done); 15 | }, 16 | afterEach: function() { 17 | this.fixture.innerHTML = ''; 18 | this.player.dispose(); 19 | } 20 | }); 21 | 22 | test('debugger loads', function(assert) { 23 | var player = this.player; 24 | var done = assert.async(); 25 | 26 | assert.ok(player.debuggerWindow, 'debugger plugin is registered'); 27 | 28 | player.debuggerWindow({ 29 | js: baseUrl + 'src/js/debugger.js', 30 | css: baseUrl + 'src/css/debugger.css' 31 | }); 32 | 33 | player.debuggerWindow.loadDebugger(); 34 | 35 | assert.ok(document.querySelector('script[src*="debug"]'), 'debugger script loaded'); 36 | assert.ok(document.querySelector('link[href*="debug"]'), 'debugger stylesheet loaded'); 37 | 38 | done(); 39 | }); 40 | --------------------------------------------------------------------------------