├── README.md
├── app.yaml
├── config.rb
├── demos
├── .DS_Store
├── audio_streamer
│ ├── README.md
│ ├── audio_streamer.js
│ ├── index.html
│ ├── jdataview.js
│ ├── server.js
│ ├── speaker.svg
│ └── start_servers.sh
├── gamepad.html
├── popup.html
└── screenshare
│ ├── .DS_Store
│ ├── README.md
│ ├── app.js
│ ├── background.html
│ ├── background.js
│ ├── img
│ ├── .DS_Store
│ └── screen.png
│ ├── manifest.json
│ ├── options.html
│ ├── popup.html
│ ├── screenshare.crx
│ ├── server.js
│ ├── viewer.html
│ └── viewer.js
├── images
├── HTML5_Badge.svg
├── HTML5_Badge_128.png
├── HTML5_Badge_64.png
├── audio-routing-analysis.png
├── audio-routing-crossfade.png
├── audio-routing.png
├── barchart.png
├── browser_logos
│ ├── .DS_Store
│ ├── ff_logo.png
│ ├── ie10_logo.png
│ ├── opera_logo.png
│ └── safari_logo.png
├── chart.png
├── chrome-logo-tiny.png
├── chrome_logo.png
├── google_developers_icon_128.png
├── google_developers_logo.png
├── google_developers_logo_white.png
├── html_can_not_do_that.jpg
├── icons
│ ├── bug.png
│ ├── bug_closed.png
│ ├── gears.svg
│ └── radar.svg
├── io2012_logo.png
├── my_book_cover.jpg
├── slides
│ ├── .DS_Store
│ ├── binding.jpg
│ ├── blueprint.jpg
│ ├── fast.jpg
│ ├── flexbox-holygrail.svg
│ ├── flexbox-tools.svg
│ ├── rocket.jpg
│ ├── stream.jpg
│ ├── tools.jpg
│ └── tools2.jpg
├── transferables.jpg
└── twitter_newbird_blue.png
├── index.html
├── js
├── angular-1.0.0.min.js
├── app.js
├── hammer.js
├── modernizr.custom.45394.js
├── order.js
├── polyfills
│ ├── classList.min.js
│ ├── dataset.min.js
│ └── history.min.js
├── prettify
│ ├── lang-apollo.js
│ ├── lang-clj.js
│ ├── lang-css.js
│ ├── lang-go.js
│ ├── lang-hs.js
│ ├── lang-lisp.js
│ ├── lang-lua.js
│ ├── lang-ml.js
│ ├── lang-n.js
│ ├── lang-proto.js
│ ├── lang-scala.js
│ ├── lang-sql.js
│ ├── lang-tex.js
│ ├── lang-vb.js
│ ├── lang-vhdl.js
│ ├── lang-wiki.js
│ ├── lang-xq.js
│ ├── lang-yaml.js
│ ├── prettify.css
│ └── prettify.js
├── require-1.0.8.min.js
├── slide-controller.js
├── slide-deck.js
└── slides.js
├── serve.sh
├── slide_config.js
└── theme
├── css
├── .DS_Store
├── animate.css
├── custom.css
├── default.css
└── phone.css
└── scss
├── _base.scss
├── _variables.scss
├── custom.scss
├── default.scss
└── phone.scss
/README.md:
--------------------------------------------------------------------------------
1 | htmlfivecan.com
2 | ========
3 |
4 | The Web Can Do That!? presentation from Google IO 2012
5 |
6 | [](https://github.com/igrigorik/ga-beacon)
7 |
--------------------------------------------------------------------------------
/app.yaml:
--------------------------------------------------------------------------------
1 | application: html5can
2 | version: 1
3 | runtime: python27
4 | api_version: 1
5 | threadsafe: yes
6 |
7 | handlers:
8 | - url: /images/chrome-logo-tiny\.png
9 | static_files: images/chrome-logo-tiny.png
10 | upload: images/chrome-logo-tiny\.png
11 |
12 | - url: /js
13 | static_dir: js
14 |
15 | - url: /images
16 | static_dir: images
17 |
18 | - url: /demos
19 | static_dir: demos
20 |
21 | - url: /theme
22 | static_dir: theme
23 |
24 | - url: /slide_config.js
25 | static_files: slide_config.js
26 | upload: slide_config.js
27 |
28 | - url: /
29 | static_files: index.html
30 | upload: index.html
31 |
32 | #- url: .*
33 | # script: main.app
34 |
35 | # libraries:
36 | # - name: webapp2
37 | # version: "2.5.1"
38 |
--------------------------------------------------------------------------------
/config.rb:
--------------------------------------------------------------------------------
1 | # Require any additional compass plugins here.
2 |
3 | # Set this to the root of your project when deployed:
4 | http_path = "/"
5 | css_dir = "theme/css"
6 | sass_dir = "theme/scss"
7 | images_dir = "images"
8 | javascripts_dir = "js"
9 |
10 | # You can select your preferred output style here (can be overridden via the command line):
11 | output_style = :compressed #:expanded or :nested or :compact or :compressed
12 |
13 | # To enable relative paths to assets via compass helper functions. Uncomment:
14 | # relative_assets = true
15 |
16 | # To disable debugging comments that display the original location of your selectors. Uncomment:
17 | # line_comments = false
18 |
19 |
20 | # If you prefer the indented syntax, you might want to regenerate this
21 | # project again passing --syntax sass, or you can uncomment this:
22 | # preferred_syntax = :sass
23 | # and then run:
24 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
25 |
--------------------------------------------------------------------------------
/demos/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ebidel/html5can/0df43e2890bb122aad2f31376a8327ec2482d8ca/demos/.DS_Store
--------------------------------------------------------------------------------
/demos/audio_streamer/README.md:
--------------------------------------------------------------------------------
1 | To run:
2 |
3 | node server.js
4 | python -m SimpleHTTPServer 8000;
--------------------------------------------------------------------------------
/demos/audio_streamer/audio_streamer.js:
--------------------------------------------------------------------------------
1 | var PLAY_ON_DJ_MACHINE = false;
2 | var PLAY_ON_REMOTE_MACHINES = true;
3 | var SHOW_PROGRESS = true; // progress bar.
4 | var SHOW_VISUALIZATIONS = true; // canvas visual.
5 | var NUM_CHUNKS = 100; // TODO: should be optimized based on song's size.
6 |
7 | function Progress(selector, max) {
8 | this.container = document.querySelector(selector);
9 | this.add(max);
10 | this.counter = 1;
11 | }
12 |
13 | Progress.prototype = {
14 | add: function(totalDuration) {
15 | var frag = document.createDocumentFragment();
16 | this.progress = document.createElement('progress');
17 | this.progress.value = 0;
18 | this.progress.min = 0;
19 | this.progress.max = totalDuration;
20 | frag.appendChild(this.progress);
21 |
22 | this.input = document.createElement('input');
23 | this.input.type = 'number';
24 | this.input.id = 'curr-time';
25 | this.input.value = 0;
26 | this.input.max = totalDuration;
27 | this.input.readOnly = true;
28 | frag.appendChild(this.input);
29 |
30 | this.count = document.createElement('input');
31 | this.count.type = 'number';
32 | this.count.value = 0;
33 | this.count.readOnly = true;
34 | frag.appendChild(this.count);
35 |
36 | this.container.appendChild(frag);
37 | },
38 |
39 | update: function(currTime, duration) {
40 | this.progress.value = currTime;
41 | this.input.value = currTime;
42 |
43 | var count = Math.ceil(currTime / duration);
44 | this.count.value = count;
45 |
46 | if (count > this.counter) {
47 | this.counter = count;
48 | }
49 | }
50 | };
51 |
52 |
53 | function AudioLoader(selector) {
54 | this.sm = null;
55 | document.querySelector(selector).addEventListener(
56 | 'change', this.onChange.bind(this), false);
57 | }
58 |
59 | AudioLoader.prototype = {
60 |
61 | get TXT() {
62 | return 0;
63 | },
64 | get BIN_STR() {
65 | return 1;
66 | },
67 | get ARRAY_BUFFER() {
68 | return 2;
69 | },
70 | get DATA_URL() {
71 | return 3;
72 | },
73 |
74 | onChange: function(e) {
75 | if (this.sm) {
76 | this.sm.kill();
77 | }
78 |
79 | var file = e.target.files[0];
80 | if (!file) {
81 | alert('Nice try! Please select a file.');
82 | return;
83 | } else if (!file.type.match('audio.*')) {
84 | alert('Please select an audio file.');
85 | return;
86 | }
87 |
88 | this.sm = new SoundManager();
89 | this.sm.displayID3Info({info: 'Loading...'});
90 |
91 | var self = this;
92 | this.readFile(this.ARRAY_BUFFER, file, function(arrayBuffer) {
93 | self.sm.load(arrayBuffer, function(audioBuffers, totalDuration) {
94 | var id3 = self.getID3Data(arrayBuffer);
95 | self.sm.displayID3Info(id3);
96 | self.sm.play(audioBuffers, totalDuration, id3);
97 | });
98 | });
99 | },
100 |
101 | readFile: function(type, file, callback) {
102 | var reader = new FileReader();
103 |
104 | reader.onload = function(e) {
105 | callback(this.result);
106 | };
107 |
108 | reader.onerror = function(e) {
109 | console.log(e);
110 | };
111 |
112 | switch(type) {
113 | case this.TXT:
114 | reader.readAsText(file);
115 | break;
116 | case this.BIN_STR:
117 | reader.readAsBinaryString(file);
118 | break;
119 | case this.ARRAY_BUFFER:
120 | reader.readAsArrayBuffer(file);
121 | break;
122 | case this.DATA_URL:
123 | reader.readAsDataURL(file);
124 | break;
125 | default:
126 | reader.readAsArrayBuffer(file);
127 | }
128 | },
129 |
130 | getID3Data: function(arrayBuffer) {
131 | var dv = new jDataView(arrayBuffer);
132 | // "TAG" starts at byte -128 from EOF.
133 | // See http://en.wikipedia.org/wiki/ID3
134 | if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
135 | var title = dv.getString(30, dv.tell());
136 | var artist = dv.getString(30, dv.tell());
137 | var album = dv.getString(30, dv.tell());
138 | var year = dv.getString(4, dv.tell());
139 | return {
140 | title: title,
141 | artist: artist,
142 | album: album,
143 | year: year
144 | };
145 | } else {
146 | return {}; // no ID3v1 data found.
147 | }
148 | }
149 |
150 | };
151 |
152 |
153 | function Visualizer() {
154 | this.canvas = document.getElementById('fft');
155 | this.ctx = this.canvas.getContext('2d');
156 | this.canvas.width = document.body.clientWidth / 1.4;
157 |
158 | var NUM_SAMPLES = 2048;
159 | var CANVAS_HEIGHT = this.canvas.height;
160 | var CANVAS_WIDTH = this.canvas.width;
161 | var SPACER_WIDTH = 5;
162 | var NUM_BARS = Math.round(CANVAS_WIDTH / SPACER_WIDTH);
163 |
164 | this.render = function(analyser) {
165 | var freqByteData = new Uint8Array(analyser.frequencyBinCount);
166 | analyser.getByteFrequencyData(freqByteData);
167 | //analyser.getByteTimeDomainData(freqByteData);
168 |
169 | //var numBars = Math.round(CANVAS_WIDTH / SPACER_WIDTH); //freqByteData.length
170 |
171 | this.ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
172 |
173 | //freqByteData = freqByteData.subarray(Math.round((NUM_SAMPLES / 2) - CANVAS_WIDTH / 4));
174 |
175 | // Draw rectangle for each frequency bin.
176 | for (var i = 0; i < NUM_BARS; ++i) {
177 | this.ctx.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, 3, -freqByteData[i]);
178 | }
179 | };
180 | }
181 |
182 |
183 | function send(data) {
184 | // Stringify JSON data.
185 | if ((typeof data == 'object') && (data.__proto__ !== ArrayBuffer.prototype)) {
186 | data = JSON.stringify(data);
187 | }
188 | ws.send(data);
189 | }
190 |
191 | function SoundManager() {
192 | var self_ = this;
193 | var audioBuffers_ = [];
194 | var sources_ = [];
195 | var reqId_ = null;
196 | var sendQueue_ = [];
197 |
198 | this.audioCtx = null;
199 | this.progress = null;
200 |
201 | var SAMPLE_RATE = 44100;
202 |
203 | window.AudioContext = window.AudioContext || window.webkitAudioContext;
204 | if (window.AudioContext) {
205 | this.audioCtx = new window.AudioContext();
206 | if (SHOW_VISUALIZATIONS) {
207 | this.analyser = this.audioCtx.createAnalyser();
208 |
209 | //this.gainNode = this.audioCtx.createGainNode();
210 | //this.gainNode.connect(this.analyser);
211 |
212 | this.analyser.connect(this.audioCtx.destination);
213 |
214 | }
215 | }
216 |
217 | this.visualizer = new Visualizer();
218 |
219 | var sendAudioChunk_ = function(audioBuffer, meta) {
220 |
221 | var buffer = audioBuffer.getChannelData(0).buffer;
222 |
223 | sendQueue_.push({buffer: buffer, meta: meta});
224 |
225 | // Rate limit how much we're sending. Send every 2s when there's nothing buffered.
226 | var id = setInterval(function() {
227 | if (ws.bufferedAmount == 0) {
228 | clearInterval(id);
229 |
230 | var chunk = sendQueue_.shift();
231 |
232 | console.log('Sending chunk');
233 | send(chunk.meta); // Send metadata before sending actual audio chunk.
234 | send(chunk.buffer);
235 | }
236 | }, 2000); // TODO: optimize this number
237 |
238 | };
239 |
240 | this.load = function(data, opt_callback) {
241 | if (!this.audioCtx) {
242 | return;
243 | }
244 |
245 | if (typeof data == 'string') {
246 | var xhr = new XMLHttpRequest();
247 | xhr.open('GET', data, true);
248 | xhr.responseType = 'arraybuffer';
249 | xhr.onload = function(e) {
250 | self_.audioCtx.decodeAudioData(this.response, function(audioBuffer) {
251 | audioBuffers.push(audioBuffer);
252 | opt_callback && opt_callback(audioBuffer);
253 | }, function(e) {
254 | console.log(e);
255 | });
256 | };
257 | xhr.send();
258 |
259 | } else {
260 | this.audioCtx.decodeAudioData(data, function(audioBuffer) {
261 |
262 | console.log('Total duration: ' + audioBuffer.duration);
263 |
264 | var channel1 = audioBuffer.getChannelData(0);
265 | var channel2 = audioBuffer.getChannelData(1);
266 |
267 | var CHUNK_SIZE = Math.ceil(channel1.length / NUM_CHUNKS);
268 | var NUM_SAMPLES = CHUNK_SIZE;
269 | var NUM_CHANNELS = audioBuffer.numberOfChannels;
270 |
271 | var audioBuffers = [];
272 |
273 | for (var i = 0; i < NUM_CHUNKS; ++i) {
274 | var begin = i * CHUNK_SIZE;
275 | var end = begin + CHUNK_SIZE;
276 | var aBuffer = self_.audioCtx.createBuffer(
277 | NUM_CHANNELS, NUM_SAMPLES, SAMPLE_RATE);
278 | aBuffer.getChannelData(0).set(channel1.subarray(begin, end));
279 | if (audioBuffer.numberOfChannels == 2) {
280 | aBuffer.getChannelData(1).set(channel2.subarray(begin, end));
281 | }
282 |
283 | audioBuffers_.push(aBuffer);
284 | }
285 |
286 | opt_callback && opt_callback(audioBuffers_, audioBuffer.duration);
287 |
288 | }, function(e) {
289 | alert('Error decoding audio');
290 | console.error(e);
291 | });
292 | }
293 | };
294 |
295 | this.schedulePlayback = function(startTime, audioBuffer) {
296 | if (!this.audioCtx) {
297 | return;
298 | }
299 |
300 | var source = this.audioCtx.createBufferSource();
301 | source.buffer = audioBuffer;
302 |
303 | if (SHOW_VISUALIZATIONS) {
304 | source.connect(this.analyser);
305 | //source.connect(this.gainNode);
306 | } else {
307 | source.connect(this.audioCtx.destination);
308 | }
309 |
310 | source.noteOn(startTime);
311 |
312 | sources_.push(source);
313 |
314 | console.log('scheduled: ' + startTime, 'until: ' +
315 | (startTime + audioBuffer.duration));
316 | };
317 |
318 | this.periodSend = function() {
319 |
320 | // Rate limit how much we're sending. Send every 2s when there's nothing buffered.
321 | var id = setInterval(function() {
322 | if (sendQueue_.length == 0) {
323 | clearInterval(id);
324 | }
325 | if (ws.bufferedAmount == 0) {
326 | var next = sendQueue_.shift();
327 |
328 | console.log('Sending chunk');
329 |
330 | send(next.meta); // Send metadata before sending actual audio chunk.
331 | send(next.buffer);
332 | }
333 | }, 2000); // TODO: optimize this number
334 |
335 | };
336 |
337 | this.play = function(audioBuffers, totalDuration, id3) {
338 | var startTime = 0;
339 | for (var i = 0, audioBuffer; audioBuffer = audioBuffers[i]; ++i) {
340 | if (PLAY_ON_DJ_MACHINE) {
341 | this.schedulePlayback(startTime, audioBuffer);
342 | }
343 |
344 | sendQueue_.push({
345 | buffer: audioBuffer.getChannelData(0).buffer,
346 | meta: {
347 | startTime: startTime,
348 | numChunks: audioBuffers.length,
349 | totalDuration: totalDuration,
350 | id3: id3
351 | }
352 | });
353 |
354 | startTime += audioBuffer.duration;
355 | }
356 |
357 | this.periodSend();
358 |
359 | if (SHOW_VISUALIZATIONS || SHOW_PROGRESS) {
360 | this.visualize(audioBuffers[0].duration, totalDuration, audioBuffers[0]);
361 | }
362 | };
363 |
364 | this.kill = function() {
365 | window.webkitCancelAnimationFrame(reqId_);
366 |
367 | this.analyser.disconnect(0);
368 |
369 | for (var i = 0, source; source = sources_[i]; ++i) {
370 | source.noteOff(0);
371 | source.disconnect(0);
372 | }
373 | sources_ = [];
374 | };
375 |
376 | this.playFromArrayBuffer = function(arrayBuffer, props) {
377 |
378 | var float32Array = new Float32Array(arrayBuffer);
379 |
380 | // TODO(ericbidelman): clean up duplicate vars.
381 | var NUM_SAMPLES = float32Array.length;
382 | var NUM_CHANNELS = 2;
383 |
384 | var audioBuffer = this.audioCtx.createBuffer(
385 | NUM_CHANNELS, NUM_SAMPLES, SAMPLE_RATE);
386 | audioBuffer.getChannelData(0).set(float32Array);
387 |
388 | //TODO: send over channel2 data instead of duplicating channel's data.
389 | if (audioBuffer.numberOfChannels == 2) {
390 | audioBuffer.getChannelData(1).set(float32Array);
391 | }
392 |
393 | /*buffer a few chunks
394 | audioBuffers_.push(audioBuffer);
395 | if (audioBuffers_.length >= 3) {
396 | this.schedulePlayback(props.startTime, audioBuffers_.shift());
397 | }*/
398 |
399 | if (PLAY_ON_REMOTE_MACHINES) {
400 | this.schedulePlayback(props.startTime, audioBuffer);
401 | }
402 |
403 | if (SHOW_VISUALIZATIONS || SHOW_PROGRESS) {
404 | this.visualize(audioBuffer.duration, props.totalDuration);
405 | }
406 |
407 | this.displayID3Info(props.id3);
408 | };
409 |
410 | this.visualize = function(chunkDuration, totalDuration) {
411 |
412 | if (this.progress) {
413 | return;
414 | }
415 |
416 | this.progress = new Progress('#container', totalDuration);
417 |
418 | var self = this;
419 | (function callback(time) {
420 | var currTime = self.audioCtx.currentTime;
421 |
422 | // Unhook if we're played all chunks.
423 | if (currTime >= totalDuration) {
424 | self.kill();
425 | } else {
426 | reqId_ = window.webkitRequestAnimationFrame(callback);
427 | }
428 |
429 | if (SHOW_PROGRESS) {
430 | self.progress.update(currTime, chunkDuration);
431 | }
432 | if (SHOW_VISUALIZATIONS) {
433 | self.visualizer.render(self.analyser);
434 | }
435 | })();
436 |
437 | };
438 |
439 | this.displayID3Info = function(id3) {
440 | var title = id3.title ? id3.title : '';
441 | var album = id3.album ? id3.album : '';
442 | var artist = id3.artist ? ' ( ' + id3.artist + ' )' : '';
443 |
444 | var html = [];
445 | if (id3.info) {
446 | html.push(id3.info);
447 | } else {
448 | html.push('', title, '', artist);
449 | }
450 |
451 | if (id3.title) {
452 | document.querySelector('#song-title').innerHTML = html.join('');
453 | }
454 | };
455 |
456 | }
457 |
--------------------------------------------------------------------------------
/demos/audio_streamer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 | Binary WebSocket Audio Streamer
22 |
23 |
24 |
25 |
200 |
201 |
202 |
203 |
204 | WebSocket Audio Streamer
205 |
206 |
207 |
217 |
221 |
222 |
223 |
224 |
354 |
358 |
359 |
360 |
--------------------------------------------------------------------------------
/demos/audio_streamer/jdataview.js:
--------------------------------------------------------------------------------
1 | //
2 | // jDataView by Vjeux - Jan 2010
3 | //
4 | // A unique way to read a binary file in the browser
5 | // http://github.com/vjeux/jDataView
6 | // http://blog.vjeux.com/
7 | //
8 |
9 | (function (global) {
10 |
11 | var compatibility = {
12 | ArrayBuffer: typeof ArrayBuffer !== 'undefined',
13 | DataView: typeof DataView !== 'undefined' && 'getFloat64' in DataView.prototype,
14 | NodeBuffer: typeof Buffer !== 'undefined',
15 | // 0.6.0 -> readInt8LE(offset)
16 | NodeBufferFull: typeof Buffer !== 'undefined' && 'readInt8LE' in Buffer,
17 | // 0.5.0 -> readInt8(offset, endian)
18 | NodeBufferEndian: typeof Buffer !== 'undefined' && 'readInt8' in Buffer
19 | };
20 |
21 | var jDataView = function (buffer, byteOffset, byteLength, littleEndian) {
22 | if (!(this instanceof arguments.callee)) {
23 | throw new Error("Constructor may not be called as a function");
24 | }
25 |
26 | this.buffer = buffer;
27 |
28 | // Handle Type Errors
29 | if (!(compatibility.NodeBuffer && buffer instanceof Buffer) &&
30 | !(compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) &&
31 | typeof buffer !== 'string') {
32 | throw new TypeError('Type error');
33 | }
34 |
35 | // Check parameters and existing functionnalities
36 | this._isArrayBuffer = compatibility.ArrayBuffer && buffer instanceof ArrayBuffer;
37 | this._isDataView = compatibility.DataView && this._isArrayBuffer;
38 | this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer;
39 |
40 | // Default Values
41 | this._littleEndian = littleEndian === undefined ? true : littleEndian;
42 |
43 | var bufferLength = this._isArrayBuffer ? buffer.byteLength : buffer.length;
44 | if (byteOffset === undefined) {
45 | byteOffset = 0;
46 | }
47 | this.byteOffset = byteOffset;
48 |
49 | if (byteLength === undefined) {
50 | byteLength = bufferLength - byteOffset;
51 | }
52 | this.byteLength = byteLength;
53 |
54 | if (!this._isDataView) {
55 | // Do additional checks to simulate DataView
56 | if (typeof byteOffset !== 'number') {
57 | throw new TypeError('Type error');
58 | }
59 | if (typeof byteLength !== 'number') {
60 | throw new TypeError('Type error');
61 | }
62 | if (typeof byteOffset < 0) {
63 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
64 | }
65 | if (typeof byteLength < 0) {
66 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
67 | }
68 | }
69 |
70 | // Instanciate
71 | if (this._isDataView) {
72 | this._view = new DataView(buffer, byteOffset, byteLength);
73 | this._start = 0;
74 | }
75 | this._start = byteOffset;
76 | if (byteOffset + byteLength > bufferLength) {
77 | throw new Error("INDEX_SIZE_ERR: DOM Exception 1");
78 | }
79 |
80 | this._offset = 0;
81 | };
82 |
83 | jDataView.createBuffer = function () {
84 | if (compatibility.NodeBuffer) {
85 | var buffer = new Buffer(arguments.length);
86 | for (var i = 0; i < arguments.length; ++i) {
87 | buffer[i] = arguments[i];
88 | }
89 | return buffer;
90 | }
91 | if (compatibility.ArrayBuffer) {
92 | var buffer = new ArrayBuffer(arguments.length);
93 | var view = new Int8Array(buffer);
94 | for (var i = 0; i < arguments.length; ++i) {
95 | view[i] = arguments[i];
96 | }
97 | return buffer;
98 | }
99 |
100 | return String.fromCharCode.apply(null, arguments);
101 | };
102 |
103 | jDataView.prototype = {
104 |
105 | // Helpers
106 |
107 | getString: function (length, byteOffset) {
108 | var value;
109 |
110 | // Handle the lack of byteOffset
111 | if (byteOffset === undefined) {
112 | byteOffset = this._offset;
113 | }
114 |
115 | // Error Checking
116 | if (typeof byteOffset !== 'number') {
117 | throw new TypeError('Type error');
118 | }
119 | if (length < 0 || byteOffset + length > this.byteLength) {
120 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
121 | }
122 |
123 | if (this._isNodeBuffer) {
124 | value = this.buffer.toString('ascii', this._start + byteOffset, this._start + byteOffset + length);
125 | }
126 | else {
127 | value = '';
128 | for (var i = 0; i < length; ++i) {
129 | var char = this.getUint8(byteOffset + i);
130 | value += String.fromCharCode(char > 127 ? 65533 : char);
131 | }
132 | }
133 |
134 | this._offset = byteOffset + length;
135 | return value;
136 | },
137 |
138 | getChar: function (byteOffset) {
139 | return this.getString(1, byteOffset);
140 | },
141 |
142 | tell: function () {
143 | return this._offset;
144 | },
145 |
146 | seek: function (byteOffset) {
147 | if (typeof byteOffset !== 'number') {
148 | throw new TypeError('Type error');
149 | }
150 | if (byteOffset < 0 || byteOffset > this.byteLength) {
151 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
152 | }
153 |
154 | return this._offset = byteOffset;
155 | },
156 |
157 | // Compatibility functions on a String Buffer
158 |
159 | _endianness: function (byteOffset, pos, max, littleEndian) {
160 | return byteOffset + (littleEndian ? max - pos - 1 : pos);
161 | },
162 |
163 | _getFloat64: function (byteOffset, littleEndian) {
164 | var b0 = this._getUint8(this._endianness(byteOffset, 0, 8, littleEndian)),
165 | b1 = this._getUint8(this._endianness(byteOffset, 1, 8, littleEndian)),
166 | b2 = this._getUint8(this._endianness(byteOffset, 2, 8, littleEndian)),
167 | b3 = this._getUint8(this._endianness(byteOffset, 3, 8, littleEndian)),
168 | b4 = this._getUint8(this._endianness(byteOffset, 4, 8, littleEndian)),
169 | b5 = this._getUint8(this._endianness(byteOffset, 5, 8, littleEndian)),
170 | b6 = this._getUint8(this._endianness(byteOffset, 6, 8, littleEndian)),
171 | b7 = this._getUint8(this._endianness(byteOffset, 7, 8, littleEndian)),
172 |
173 | sign = 1 - (2 * (b0 >> 7)),
174 | exponent = ((((b0 << 1) & 0xff) << 3) | (b1 >> 4)) - (Math.pow(2, 10) - 1),
175 |
176 | // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead
177 | mantissa = ((b1 & 0x0f) * Math.pow(2, 48)) + (b2 * Math.pow(2, 40)) + (b3 * Math.pow(2, 32)) +
178 | (b4 * Math.pow(2, 24)) + (b5 * Math.pow(2, 16)) + (b6 * Math.pow(2, 8)) + b7;
179 |
180 | if (exponent === 1024) {
181 | if (mantissa !== 0) {
182 | return NaN;
183 | } else {
184 | return sign * Infinity;
185 | }
186 | }
187 |
188 | if (exponent === -1023) { // Denormalized
189 | return sign * mantissa * Math.pow(2, -1022 - 52);
190 | }
191 |
192 | return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent);
193 | },
194 |
195 | _getFloat32: function (byteOffset, littleEndian) {
196 | var b0 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)),
197 | b1 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)),
198 | b2 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)),
199 | b3 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian)),
200 |
201 | sign = 1 - (2 * (b0 >> 7)),
202 | exponent = (((b0 << 1) & 0xff) | (b1 >> 7)) - 127,
203 | mantissa = ((b1 & 0x7f) << 16) | (b2 << 8) | b3;
204 |
205 | if (exponent === 128) {
206 | if (mantissa !== 0) {
207 | return NaN;
208 | } else {
209 | return sign * Infinity;
210 | }
211 | }
212 |
213 | if (exponent === -127) { // Denormalized
214 | return sign * mantissa * Math.pow(2, -126 - 23);
215 | }
216 |
217 | return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent);
218 | },
219 |
220 | _getInt32: function (byteOffset, littleEndian) {
221 | var b = this._getUint32(byteOffset, littleEndian);
222 | return b > Math.pow(2, 31) - 1 ? b - Math.pow(2, 32) : b;
223 | },
224 |
225 | _getUint32: function (byteOffset, littleEndian) {
226 | var b3 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)),
227 | b2 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)),
228 | b1 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)),
229 | b0 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian));
230 |
231 | return (b3 * Math.pow(2, 24)) + (b2 << 16) + (b1 << 8) + b0;
232 | },
233 |
234 | _getInt16: function (byteOffset, littleEndian) {
235 | var b = this._getUint16(byteOffset, littleEndian);
236 | return b > Math.pow(2, 15) - 1 ? b - Math.pow(2, 16) : b;
237 | },
238 |
239 | _getUint16: function (byteOffset, littleEndian) {
240 | var b1 = this._getUint8(this._endianness(byteOffset, 0, 2, littleEndian)),
241 | b0 = this._getUint8(this._endianness(byteOffset, 1, 2, littleEndian));
242 |
243 | return (b1 << 8) + b0;
244 | },
245 |
246 | _getInt8: function (byteOffset) {
247 | var b = this._getUint8(byteOffset);
248 | return b > Math.pow(2, 7) - 1 ? b - Math.pow(2, 8) : b;
249 | },
250 |
251 | _getUint8: function (byteOffset) {
252 | if (this._isArrayBuffer) {
253 | return new Uint8Array(this.buffer, byteOffset, 1)[0];
254 | }
255 | else if (this._isNodeBuffer) {
256 | return this.buffer[byteOffset];
257 | } else {
258 | return this.buffer.charCodeAt(byteOffset) & 0xff;
259 | }
260 | }
261 | };
262 |
263 | // Create wrappers
264 |
265 | var dataTypes = {
266 | 'Int8': 1,
267 | 'Int16': 2,
268 | 'Int32': 4,
269 | 'Uint8': 1,
270 | 'Uint16': 2,
271 | 'Uint32': 4,
272 | 'Float32': 4,
273 | 'Float64': 8
274 | };
275 | var nodeNaming = {
276 | 'Int8': 'Int8',
277 | 'Int16': 'Int16',
278 | 'Int32': 'Int32',
279 | 'Uint8': 'UInt8',
280 | 'Uint16': 'UInt16',
281 | 'Uint32': 'UInt32',
282 | 'Float32': 'Float',
283 | 'Float64': 'Double'
284 | };
285 |
286 | for (var type in dataTypes) {
287 | if (!dataTypes.hasOwnProperty(type)) {
288 | continue;
289 | }
290 |
291 | // Bind the variable type
292 | (function (type) {
293 | var size = dataTypes[type];
294 |
295 | // Create the function
296 | jDataView.prototype['get' + type] =
297 | function (byteOffset, littleEndian) {
298 | var value;
299 |
300 | // Handle the lack of endianness
301 | if (littleEndian === undefined) {
302 | littleEndian = this._littleEndian;
303 | }
304 |
305 | // Handle the lack of byteOffset
306 | if (byteOffset === undefined) {
307 | byteOffset = this._offset;
308 | }
309 |
310 | // Dispatch on the good method
311 | if (this._isDataView) {
312 | // DataView: we use the direct method
313 | value = this._view['get' + type](byteOffset, littleEndian);
314 | }
315 | // ArrayBuffer: we use a typed array of size 1 if the alignment is good
316 | // ArrayBuffer does not support endianess flag (for size > 1)
317 | else if (this._isArrayBuffer && (this._start + byteOffset) % size === 0 && (size === 1 || littleEndian)) {
318 | value = new global[type + 'Array'](this.buffer, this._start + byteOffset, 1)[0];
319 | }
320 | // NodeJS Buffer
321 | else if (this._isNodeBuffer && compatibility.NodeBufferFull) {
322 | if (littleEndian) {
323 | value = this.buffer['read' + nodeNaming[type] + 'LE'](this._start + byteOffset);
324 | } else {
325 | value = this.buffer['read' + nodeNaming[type] + 'BE'](this._start + byteOffset);
326 | }
327 | } else if (this._isNodeBuffer && compatibility.NodeBufferEndian) {
328 | value = this.buffer['read' + nodeNaming[type]](this._start + byteOffset, littleEndian);
329 | }
330 | else {
331 | // Error Checking
332 | if (typeof byteOffset !== 'number') {
333 | throw new TypeError('Type error');
334 | }
335 | if (byteOffset + size > this.byteLength) {
336 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
337 | }
338 | value = this['_get' + type](this._start + byteOffset, littleEndian);
339 | }
340 |
341 | // Move the internal offset forward
342 | this._offset = byteOffset + size;
343 |
344 | return value;
345 | };
346 | })(type);
347 | }
348 |
349 | if (typeof jQuery !== 'undefined' && jQuery.fn.jquery >= "1.6.2") {
350 | var convertResponseBodyToText = function (byteArray) {
351 | // http://jsperf.com/vbscript-binary-download/6
352 | var scrambledStr;
353 | try {
354 | scrambledStr = IEBinaryToArray_ByteStr(byteArray);
355 | } catch (e) {
356 | // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie
357 | // http://miskun.com/javascript/internet-explorer-and-binary-files-data-access/
358 | var IEBinaryToArray_ByteStr_Script =
359 | "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
360 | " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
361 | "End Function\r\n"+
362 | "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+
363 | " Dim lastIndex\r\n"+
364 | " lastIndex = LenB(Binary)\r\n"+
365 | " if lastIndex mod 2 Then\r\n"+
366 | " IEBinaryToArray_ByteStr_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+
367 | " Else\r\n"+
368 | " IEBinaryToArray_ByteStr_Last = -1\r\n"+
369 | " End If\r\n"+
370 | "End Function\r\n";
371 |
372 | // http://msdn.microsoft.com/en-us/library/ms536420(v=vs.85).aspx
373 | // proprietary IE function
374 | window.execScript(IEBinaryToArray_ByteStr_Script, 'vbscript');
375 |
376 | scrambledStr = IEBinaryToArray_ByteStr(byteArray);
377 | }
378 |
379 | var lastChr = IEBinaryToArray_ByteStr_Last(byteArray),
380 | result = "",
381 | i = 0,
382 | l = scrambledStr.length % 8,
383 | thischar;
384 | while (i < l) {
385 | thischar = scrambledStr.charCodeAt(i++);
386 | result += String.fromCharCode(thischar & 0xff, thischar >> 8);
387 | }
388 | l = scrambledStr.length
389 | while (i < l) {
390 | result += String.fromCharCode(
391 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
392 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
393 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
394 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
395 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
396 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
397 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
398 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8);
399 | }
400 | if (lastChr > -1) {
401 | result += String.fromCharCode(lastChr);
402 | }
403 | return result;
404 | };
405 |
406 | jQuery.ajaxSetup({
407 | converters: {
408 | '* dataview': function(data) {
409 | return new jDataView(data);
410 | }
411 | },
412 | accepts: {
413 | dataview: "text/plain; charset=x-user-defined"
414 | },
415 | responseHandler: {
416 | dataview: function (responses, options, xhr) {
417 | // Array Buffer Firefox
418 | if ('mozResponseArrayBuffer' in xhr) {
419 | responses.text = xhr.mozResponseArrayBuffer;
420 | }
421 | // Array Buffer Chrome
422 | else if ('responseType' in xhr && xhr.responseType === 'arraybuffer' && xhr.response) {
423 | responses.text = xhr.response;
424 | }
425 | // Internet Explorer (Byte array accessible through VBScript -- convert to text)
426 | else if ('responseBody' in xhr) {
427 | responses.text = convertResponseBodyToText(xhr.responseBody);
428 | }
429 | // Older Browsers
430 | else {
431 | responses.text = xhr.responseText;
432 | }
433 | }
434 | }
435 | });
436 |
437 | jQuery.ajaxPrefilter('dataview', function(options, originalOptions, jqXHR) {
438 | // trying to set the responseType on IE 6 causes an error
439 | if (jQuery.support.ajaxResponseType) {
440 | if (!options.hasOwnProperty('xhrFields')) {
441 | options.xhrFields = {};
442 | }
443 | options.xhrFields.responseType = 'arraybuffer';
444 | }
445 | options.mimeType = 'text/plain; charset=x-user-defined';
446 | });
447 | }
448 |
449 | global.jDataView = (global.module || {}).exports = jDataView;
450 |
451 | })(this);
452 |
--------------------------------------------------------------------------------
/demos/audio_streamer/server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | //var WebSocketServer = require('../websocket_node/lib/WebSocketServer');
4 | var WebSocketServer = require('ws').Server;
5 |
6 | var http = require('http');
7 | var url = require('url');
8 | var fs = require('fs');
9 |
10 | var args = { /* defaults */
11 | port: '8080'
12 | };
13 |
14 | /* Parse command line options */
15 | var pattern = /^--(.*?)(?:=(.*))?$/;
16 | process.argv.forEach(function(value) {
17 | var match = pattern.exec(value);
18 | if (match) {
19 | args[match[1]] = match[2] ? match[2] : true;
20 | }
21 | });
22 |
23 | var port = parseInt(args.port, 10);
24 |
25 | console.log("Usage: ./server.js [--port=8080]");
26 |
27 | var connections = {};
28 |
29 | /* //websocket_node
30 | var server = http.createServer(function(request, response) {
31 | console.log((new Date()) + " Received request for " + request.url);
32 | response.writeHead(404);
33 | response.end();
34 | });
35 |
36 | server.listen(port, function() {
37 | console.log((new Date()) + " Server is listening on port " + port);
38 | });
39 |
40 | var wsServer = new WebSocketServer({
41 | httpServer: server,
42 | autoAcceptConnections: true,
43 | maxReceivedFrameSize: 64 * 1024 * 1024, // 64MiB
44 | maxReceivedMessageSize: 64 * 1024 * 1024, // 64MiB
45 | fragmentOutgoingMessages: false,
46 | keepalive: true,
47 | keepaliveInterval: 20000,
48 | disableNagleAlgorithm: false
49 | });
50 |
51 | wsServer.on('connect', function(connection) {
52 | connection.id = Date.now(); // Assign unique id to this connection.
53 |
54 | console.log((new Date()) + ' Connection accepted: ' + connection.id);
55 |
56 | connections[connection.id] = connection;
57 |
58 | connection.on('message', function(message) {
59 | var length;
60 | switch (message.type) {
61 | case 'utf8':
62 | length = message.utf8Data.length;
63 | break;
64 | case 'binary':
65 | length = message.binaryData.length;
66 | break;
67 | }
68 |
69 | console.log('Received ' + message.type + ' message of ' + length + ' characters.');
70 |
71 | broadcast(message, connection);
72 | });
73 |
74 | connection.on('close', function(connection) {
75 | console.log((new Date()) + " Peer " + connection.remoteAddress + " disconnected.");
76 | delete connections[connection.id];
77 | });
78 | });
79 |
80 | // Broadcasts a message to all connected sockets accept for the sender.
81 | function broadcast(message, fromConnection) {
82 | for (var id in connections) {
83 | if (id != fromConnection.id) {
84 | if (message.type === 'binary') {
85 | connections[id].sendBytes(message.binaryData);
86 | } else {
87 | connections[id].sendUTF(message.utf8Data);
88 | }
89 | }
90 | }
91 | }
92 | */
93 |
94 | // ws is the fastest websocket lib:
95 | // http://einaros.github.com/ws/
96 | var wsServer = new WebSocketServer({port: port});
97 |
98 | wsServer.on('connection', function(ws) {
99 |
100 | ws.id = Date.now(); // Assign unique id to this ws connection.
101 | connections[ws.id] = ws;
102 |
103 | console.log((new Date()) + ' Connection accepted: ' + ws.id);
104 |
105 | ws.on('message', function(message, flags) {
106 | console.log('Received ' + (flags.binary ? 'binary' : '') + ' message: ' +
107 | message.length + ' bytes.');
108 | broadcast(message, this, flags);
109 | });
110 |
111 | ws.on('close', function() {
112 | console.log((new Date()) + " Peer " + this.id + " disconnected.");
113 | delete connections[this.id];
114 | });
115 | });
116 |
117 | // Broadcasts a message to all connected sockets accept for the sender.
118 | function broadcast(message, fromWs, flags) {
119 | for (var id in connections) {
120 | if (id != fromWs.id) {
121 | connections[id].send(message, {
122 | binary: flags.binary ? true : false,
123 | mask: false
124 | });
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/demos/audio_streamer/speaker.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
--------------------------------------------------------------------------------
/demos/audio_streamer/start_servers.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | node server.js --port=8080
4 | serve 8000
5 | #open localhost:3000
--------------------------------------------------------------------------------
/demos/gamepad.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | GamePad Window
5 |
55 |
56 |
57 |
58 |
63 |
64 |
65 |
66 |
67 |
68 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/demos/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Lil Guy
4 |
5 | HI
6 |
7 |
8 |
--------------------------------------------------------------------------------
/demos/screenshare/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ebidel/html5can/0df43e2890bb122aad2f31376a8327ec2482d8ca/demos/screenshare/.DS_Store
--------------------------------------------------------------------------------
/demos/screenshare/README.md:
--------------------------------------------------------------------------------
1 | To run this demo:
2 |
3 | 1. Start the WebSocket server: `node server.js`
4 | 2. Install screenshare.crx by dragging it onto `chrome://extensions` page.
5 | 3. Click the browser action icon. This should opener the viewer page in a new tab.
6 | 4. Open another window (the presenter) and click the browser action icon.
7 | This should connect the apps and present the web page this tab visits.
8 |
--------------------------------------------------------------------------------
/demos/screenshare/app.js:
--------------------------------------------------------------------------------
1 | var WS_HOST = 'localhost:3000';
2 |
3 | function connect() {
4 | ws = new WebSocket('ws://' + WS_HOST, 'dumby-protocol');
5 | ws.binaryType = 'blob';
6 |
7 | ws.onopen = function(e) {
8 | console.log('Connection OPEN');
9 | send({cmd: 'START'});
10 | };
11 |
12 | ws.onmessage = function(e) {
13 | var data = e.data;
14 | console.log(data);
15 | };
16 |
17 | ws.onclose = function(e) {
18 | console.log('Connection CLOSED');
19 | };
20 |
21 | ws.onerror = function(e) {
22 | console.log('Connection ERROR', e);
23 | };
24 |
25 | return ws;
26 | }
27 |
28 | function disconnect() {
29 | ws.close();
30 | }
31 |
32 | function send(data) {
33 | // Stringify JSON data if we try to send it.
34 | if ((typeof data == 'object') && (data.__proto__ !== Blob.prototype)) {
35 | data = JSON.stringify(data);
36 | }
37 | ws.send(data);
38 | }
39 |
40 | function convertDataURIToBlob(dataURI, mimetype) {
41 | if (!dataURI) {
42 | return new Uint8Array(0);
43 | }
44 |
45 | var BASE64_MARKER = ';base64,';
46 | var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
47 | var base64 = dataURI.substring(base64Index);
48 | var raw = window.atob(base64);
49 | var uInt8Array = new Uint8Array(raw.length);
50 |
51 | for (var i = 0; i < uInt8Array.length; ++i) {
52 | uInt8Array[i] = raw.charCodeAt(i);
53 | }
54 |
55 | return new Blob([uInt8Array], {type: mimetype});
56 | }
57 |
--------------------------------------------------------------------------------
/demos/screenshare/background.html:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 | Background Page
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/demos/screenshare/background.js:
--------------------------------------------------------------------------------
1 | var IMG_FORMAT = 'jpeg';
2 | var IMG_MIMETYPE = 'image/' + IMG_FORMAT;
3 | var IMG_QUALITY = 80;
4 | var SEND_INTERVAL_MS = 250;
5 | var ws = null;
6 | var intervalId = null;
7 | var VIEWER_TAB_ID = null;
8 |
9 | chrome.browserAction.setBadgeBackgroundColor({color: [255, 0, 0, 100]});
10 |
11 | function captureAndSendTab() {
12 | // Update to webp when crbug.com/112957 is fixed.
13 | // captureVisibleTab only returns a dataURL. Need to decode it and create a
14 | // typed array.
15 | var opts = {
16 | format: IMG_FORMAT,
17 | quality: IMG_QUALITY
18 | };
19 |
20 | chrome.tabs.captureVisibleTab(null, opts, function(dataUrl) {
21 | send(convertDataURIToBlob(dataUrl, IMG_MIMETYPE));
22 | });
23 | }
24 |
25 | chrome.browserAction.onClicked.addListener(function(tab) {
26 | if (!intervalId) {
27 | ws = connect();
28 |
29 | if (!VIEWER_TAB_ID) {
30 | chrome.tabs.create({url: 'viewer.html'}, function(tab) {
31 | VIEWER_TAB_ID = tab.id;
32 | });
33 | return;
34 | }
35 |
36 | chrome.browserAction.setBadgeText({text: 'ON'});
37 |
38 | // Rate limit how much we're sending through the websocket.
39 | intervalId = setInterval(function() {
40 | if (ws.bufferedAmount == 0) {
41 | captureAndSendTab();
42 | }
43 | }, SEND_INTERVAL_MS); // TODO: optimize this number
44 | } else {
45 | clearInterval(intervalId);
46 | chrome.browserAction.setBadgeText({text: ''});
47 | send({cmd: 'DONE'});
48 | intervalId = null;
49 | ws = null;
50 | }
51 | });
52 |
53 | chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
54 | if (tabId == VIEWER_TAB_ID) {
55 | VIEWER_TAB_ID = null;
56 | }
57 | });
58 |
--------------------------------------------------------------------------------
/demos/screenshare/img/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ebidel/html5can/0df43e2890bb122aad2f31376a8327ec2482d8ca/demos/screenshare/img/.DS_Store
--------------------------------------------------------------------------------
/demos/screenshare/img/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ebidel/html5can/0df43e2890bb122aad2f31376a8327ec2482d8ca/demos/screenshare/img/screen.png
--------------------------------------------------------------------------------
/demos/screenshare/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Screenshare",
3 | "description": "Screen capture browser using binary WebSockets",
4 | "version": "0.0.1",
5 | "icons": {
6 | "16": "img/screen.png",
7 | "32": "img/screen.png",
8 | "48": "img/screen.png",
9 | "128": "img/screen.png"
10 | },
11 | "manifest_version": 2,
12 | "browser_action": {
13 | "default_title": "Test",
14 | "default_icon": "img/screen.png"
15 | /*"default_popup": "popup.html"*/
16 | },
17 | "background": {
18 | "page": "background.html"
19 | },
20 | "options_page": "options.html",
21 | "permissions": [
22 | "tabs",
23 | "",
24 | "http://*/*"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/demos/screenshare/options.html:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 |
43 |
44 |
45 |
46 | Refresh rate:
47 | Image quality:
48 |
49 |
62 |
63 |