├── .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 | [](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 |
18 | videojs.log.debug
Adds a debug
message
19 | videojs.log.warn
Adds a warn
message
20 | videojs.log.info
Adds an info
message. This one is equivalent to videojs.log
itself
21 | videojs.log.error
Adds an error
message
22 | videojs.log.resize
Toggles the size of the debugger window
23 | videojs.log.clear
Clears the current output in the debugger window
24 | videojs.log.move
Moves the debugger window to the next corner
25 | videojs.log.profile
Adds a profile
message
26 |
27 |
28 |
29 |
30 |
31 |
32 |
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 | '',
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 |
--------------------------------------------------------------------------------