Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Slide/demo commit

commit 1a64477e8b04fcf90f879bc6ad65c97c7ca6320a 1 parent 0b4bb65
@ebidel authored
Showing with 12,452 additions and 0 deletions.
  1. +25 −0 CHANGES.md
  2. +105 −0 README.html
  3. +37 −0 app.yaml
  4. +24 −0 config.rb
  5. BIN  demos/.DS_Store
  6. +4 −0 demos/audio_streamer/README.md
  7. +456 −0 demos/audio_streamer/audio_streamer.js
  8. +359 −0 demos/audio_streamer/index.html
  9. +451 −0 demos/audio_streamer/jdataview.js
  10. +127 −0 demos/audio_streamer/server.js
  11. +17 −0 demos/audio_streamer/speaker.svg
  12. +5 −0 demos/audio_streamer/start_servers.sh
  13. +148 −0 demos/gamepad.html
  14. +7 −0 demos/popup.html
  15. BIN  demos/screenshare/.DS_Store
  16. +7 −0 demos/screenshare/README.md
  17. +56 −0 demos/screenshare/app.js
  18. +27 −0 demos/screenshare/background.html
  19. +57 −0 demos/screenshare/background.js
  20. BIN  demos/screenshare/img/.DS_Store
  21. BIN  demos/screenshare/img/screen.png
  22. +26 −0 demos/screenshare/manifest.json
  23. +63 −0 demos/screenshare/options.html
  24. +38 −0 demos/screenshare/popup.html
  25. BIN  demos/screenshare/screenshare.crx
  26. +62 −0 demos/screenshare/server.js
  27. +72 −0 demos/screenshare/viewer.html
  28. +24 −0 demos/screenshare/viewer.js
  29. BIN  images/.DS_Store
  30. +98 −0 images/HTML5_Badge.svg
  31. BIN  images/HTML5_Badge_128.png
  32. BIN  images/HTML5_Badge_64.png
  33. BIN  images/audio-routing-analysis.png
  34. BIN  images/audio-routing-crossfade.png
  35. BIN  images/audio-routing.png
  36. BIN  images/barchart.png
  37. BIN  images/browser_logos/.DS_Store
  38. BIN  images/browser_logos/ff_logo.png
  39. BIN  images/browser_logos/ie10_logo.png
  40. BIN  images/browser_logos/opera_logo.png
  41. BIN  images/browser_logos/safari_logo.png
  42. BIN  images/chart.png
  43. BIN  images/chrome-logo-tiny.png
  44. BIN  images/chrome_logo.png
  45. BIN  images/google_developers_icon_128.png
  46. BIN  images/google_developers_logo.png
  47. BIN  images/google_developers_logo_white.png
  48. BIN  images/html_can_not_do_that.jpg
  49. BIN  images/icons/bug.png
  50. BIN  images/icons/bug_closed.png
  51. +25 −0 images/icons/gears.svg
  52. +8 −0 images/icons/radar.svg
  53. BIN  images/io2012_logo.png
  54. BIN  images/my_book_cover.jpg
  55. BIN  images/slides/.DS_Store
  56. BIN  images/slides/binding.jpg
  57. BIN  images/slides/blueprint.jpg
  58. BIN  images/slides/fast.jpg
  59. +32 −0 images/slides/flexbox-holygrail.svg
  60. +35 −0 images/slides/flexbox-tools.svg
  61. BIN  images/slides/rocket.jpg
  62. BIN  images/slides/stream.jpg
  63. BIN  images/slides/tools.jpg
  64. BIN  images/slides/tools2.jpg
  65. BIN  images/transferables.jpg
  66. BIN  images/twitter_newbird_blue.png
  67. +1,531 −0 index.html
  68. +157 −0 js/angular-1.0.0.min.js
  69. +372 −0 js/app.js
  70. +586 −0 js/hammer.js
  71. +4 −0 js/modernizr.custom.45394.js
  72. +8 −0 js/order.js
  73. +2 −0  js/polyfills/classList.min.js
  74. +2 −0  js/polyfills/dataset.min.js
  75. +1 −0  js/polyfills/history.min.js
  76. +2 −0  js/prettify/lang-apollo.js
  77. +18 −0 js/prettify/lang-clj.js
  78. +2 −0  js/prettify/lang-css.js
  79. +1 −0  js/prettify/lang-go.js
  80. +2 −0  js/prettify/lang-hs.js
  81. +3 −0  js/prettify/lang-lisp.js
  82. +2 −0  js/prettify/lang-lua.js
  83. +2 −0  js/prettify/lang-ml.js
  84. +4 −0 js/prettify/lang-n.js
  85. +1 −0  js/prettify/lang-proto.js
  86. +2 −0  js/prettify/lang-scala.js
  87. +2 −0  js/prettify/lang-sql.js
  88. +1 −0  js/prettify/lang-tex.js
  89. +2 −0  js/prettify/lang-vb.js
  90. +3 −0  js/prettify/lang-vhdl.js
  91. +2 −0  js/prettify/lang-wiki.js
  92. +3 −0  js/prettify/lang-xq.js
  93. +2 −0  js/prettify/lang-yaml.js
  94. +1 −0  js/prettify/prettify.css
  95. +28 −0 js/prettify/prettify.js
  96. +33 −0 js/require-1.0.8.min.js
  97. +109 −0 js/slide-controller.js
  98. +765 −0 js/slide-deck.js
  99. +5 −0 js/slides.js
  100. +15 −0 serve.sh
  101. +32 −0 slide_config.js
  102. +404 −0 template.html
  103. BIN  theme/css/.DS_Store
  104. +3,813 −0 theme/css/animate.css
  105. +1 −0  theme/css/custom.css
  106. +1 −0  theme/css/default.css
  107. +1 −0  theme/css/phone.css
  108. +104 −0 theme/scss/_base.scss
  109. +79 −0 theme/scss/_variables.scss
  110. +863 −0 theme/scss/custom.scss
  111. +1,051 −0 theme/scss/default.scss
  112. +35 −0 theme/scss/phone.scss
View
25 CHANGES.md
@@ -0,0 +1,25 @@
+Modifications to the original template
+
+- created variables.scss from variables in _base.scss so it could be included in
+ custom.scss sheet.
+- moved mixins from _base.scss to _variables.scss
+- text-shadow to slide
+- added .button to default.scss along with button
+- added .bold to default.scss along with default b
+- added animate.css
+- added @include column-break-after mixin to variables.scss
+
+Deck specific changes that should not be upstreamed.
+- removed document.body.classList.add('loaded'); in slide-deck.js. Moved to app.js
+
+
+Tech used:
+- Compass/ SaSS
+- Mutation Observers (flexbox columns)
+- rAF
+- flexbox (slide layouts)
+- transitions/transforms/animations
+- requirejs
+- window.postMessage() for speaker mode
+- CORs to bring in webkit bugs
+- transforms, user-select: none, pointer-events: none; for readonly live preview iframes
View
105 README.html
@@ -0,0 +1,105 @@
+<style>
+@import "http://fonts.googleapis.com/css?family=Open Sans:regular,semibold,italic,italicsemibold|Inconsolata&amp;v2";
+body {
+ font-family: "Open Sans";
+ margin: 6em 2em 2em 2em;
+}
+body:before {
+ content: '';
+ position: fixed;
+ top: 2%;
+ right: 3%;
+ height: 100px;
+ width: 100px;
+ background: url(http://www.html5rocks.com/static/images/identity/HTML5_Badge_128.png) no-repeat 50% 50%;
+ background-size: contain;
+ z-index: 10;
+ opacity: 0.1;
+}
+h1, h2, h3, h4 {
+ font-weight: 600;
+}
+h1 {
+ position: fixed;
+ background: -webkit-linear-gradient(top, white 65%, rgba(255,255,255,0));
+ background: -moz-linear-gradient(top, white 65%, rgba(255,255,255,0));
+ background: -ms-linear-gradient(top, white 65%, rgba(255,255,255,0));
+ background: -o-linear-gradient(top, white 65%, rgba(255,255,255,0));
+ width: 100%;
+ height: 80px;
+ padding: 10px 10px 10px 1em;
+ left: 0;
+ top: 0;
+ margin: 0;
+}
+h1 img {
+ height: 30px;
+ vertical-align: middle;
+ margin-bottom: 8px;
+}
+a { color: navy; }
+pre {
+ background: #eee;
+ margin-left: 2em;
+ padding: 5px;
+ border-left: 3px solid #ccc;
+}
+</style>
+
+<h1><img src="images/io2012_logo.png"> HTML5 Slide Template</h1>
+
+<h2>Configuring the slides</h2>
+<p>Much of the deck is customized by changing the settings in <a href="slide_config.js"><code>slide_config.js</code></a>.
+Some of the customizations include the title, Analytics tracking ID, speaker
+information (name, social urls, blog), web fonts to load, themes, and other
+general behavior.</p>
+<h3>Customizing the <code>#io2012</code> hash</h3>
+<p>The bottom of the slides include <code>#io2012</code> by default. If you'd like to change
+this, please update the variable <code>$social-tags: '#io2012';</code> in
+<a href="theme/scss/default.scss"><code>/theme/scss/default.scss</code></a>.</p>
+<p>See the next section on "Editing CSS" before you go editing things.</p>
+<h2>Editing CSS</h2>
+<p><a href="http://compass-style.org/install/">Compass</a> is a CSS preprocessor used to compile
+SCSS/SASS into CSS. We chose SCSS for the new slide deck for maintainability,
+easier browser compatibility, and because...it's the future!</p>
+<p>That said, if not comfortable working with SCSS or don't want to learn something
+new, not a problem. The generated .css files can already be found in
+(see <a href="theme/css"><code>/theme/css</code></a>). You can just edit those and bypass SCSS altogether.
+However, our recommendation is to use Compass. It's super easy to install and use.</p>
+<h3>Installing Compass and making changes</h3>
+<p>First, install compass:</p>
+<pre><code>sudo gem update --system
+sudo gem install compass
+</code></pre>
+<p>Next, you'll want to watch for changes to the exiting .scss files in <a href="theme/scss"><code>/theme/scss</code></a>
+and any new one you add:</p>
+<pre><code>$ cd io-2012-slides
+$ compass watch
+</code></pre>
+<p>This command automatically recompiles the .scss file when you make a change.
+Its corresponding .css file is output to <a href="theme/css"><code>/theme/css</code></a>. Slick.</p>
+<p>By default, <a href="config.rb"><code>config.rb</code></a> in the main project folder outputs minified
+.css. It's a best practice after all! However, if you want unminified files,
+run watch with the style output flag:</p>
+<pre><code>compass watch -s expanded
+</code></pre>
+<p><em>Note:</em> You should not need to edit <a href="theme/scss/_base.scss"><code>_base.scss</code></a>.</p>
+<h2>Running the slides</h2>
+<p>The slides can be run locally from <code>file://</code> making development easy :)</p>
+<h3>Running from a web server</h3>
+<p>If at some point you should need a web server, use <a href="serve.sh"><code>serve.sh</code></a>. It will
+launch a simple one and point your default browser to <a href="http://localhost:8000/template.html"><code>http://localhost:8000/template.html</code></a>:</p>
+<pre><code>$ cd io-2012-slides
+$ ./serve.sh
+</code></pre>
+<p>You can also specify a custom port:</p>
+<pre><code>$ ./serve.sh 8080
+</code></pre>
+<h3>Presenter mode</h3>
+<p>The slides contain a presenter mode feature (beta) to view + control the slides
+from a popup window.</p>
+<p>To enable presenter mode, add <code>presentme=true</code> to the URL: <a href="http://localhost:8000/template.html?presentme=true">http://localhost:8000/template.html?presentme=true</a></p>
+<p>To disable presenter mode, hit <a href="http://localhost:8000/template.html?presentme=false">http://localhost:8000/template.html?presentme=false</a></p>
+<p>Presenter mode is sticky, so refreshing the page will persist your settings.</p>
+<hr />
+<p>That's all she wrote!</p>
View
37 app.yaml
@@ -0,0 +1,37 @@
+application: html5can
+version: 1
+runtime: python27
+api_version: 1
+threadsafe: yes
+
+handlers:
+- url: /images/chrome-logo-tiny\.png
+ static_files: images/chrome-logo-tiny.png
+ upload: images/chrome-logo-tiny\.png
+
+- url: /js
+ static_dir: js
+
+- url: /images
+ static_dir: images
+
+- url: /demos
+ static_dir: demos
+
+- url: /theme
+ static_dir: theme
+
+- url: /slide_config.js
+ static_files: slide_config.js
+ upload: slide_config.js
+
+- url: /
+ static_files: index.html
+ upload: index.html
+
+#- url: .*
+# script: main.app
+
+# libraries:
+# - name: webapp2
+# version: "2.5.1"
View
24 config.rb
@@ -0,0 +1,24 @@
+# Require any additional compass plugins here.
+
+# Set this to the root of your project when deployed:
+http_path = "/"
+css_dir = "theme/css"
+sass_dir = "theme/scss"
+images_dir = "images"
+javascripts_dir = "js"
+
+# You can select your preferred output style here (can be overridden via the command line):
+output_style = :compressed #:expanded or :nested or :compact or :compressed
+
+# To enable relative paths to assets via compass helper functions. Uncomment:
+# relative_assets = true
+
+# To disable debugging comments that display the original location of your selectors. Uncomment:
+# line_comments = false
+
+
+# If you prefer the indented syntax, you might want to regenerate this
+# project again passing --syntax sass, or you can uncomment this:
+# preferred_syntax = :sass
+# and then run:
+# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
View
BIN  demos/.DS_Store
Binary file not shown
View
4 demos/audio_streamer/README.md
@@ -0,0 +1,4 @@
+To run:
+
+node server.js
+python -m SimpleHTTPServer 8000;
View
456 demos/audio_streamer/audio_streamer.js
@@ -0,0 +1,456 @@
+var PLAY_ON_DJ_MACHINE = false;
+var PLAY_ON_REMOTE_MACHINES = true;
+var SHOW_PROGRESS = true; // progress bar.
+var SHOW_VISUALIZATIONS = true; // canvas visual.
+var NUM_CHUNKS = 100; // TODO: should be optimized based on song's size.
+
+function Progress(selector, max) {
+ this.container = document.querySelector(selector);
+ this.add(max);
+ this.counter = 1;
+}
+
+Progress.prototype = {
+ add: function(totalDuration) {
+ var frag = document.createDocumentFragment();
+ this.progress = document.createElement('progress');
+ this.progress.value = 0;
+ this.progress.min = 0;
+ this.progress.max = totalDuration;
+ frag.appendChild(this.progress);
+
+ this.input = document.createElement('input');
+ this.input.type = 'number';
+ this.input.id = 'curr-time';
+ this.input.value = 0;
+ this.input.max = totalDuration;
+ this.input.readOnly = true;
+ frag.appendChild(this.input);
+
+ this.count = document.createElement('input');
+ this.count.type = 'number';
+ this.count.value = 0;
+ this.count.readOnly = true;
+ frag.appendChild(this.count);
+
+ this.container.appendChild(frag);
+ },
+
+ update: function(currTime, duration) {
+ this.progress.value = currTime;
+ this.input.value = currTime;
+
+ var count = Math.ceil(currTime / duration);
+ this.count.value = count;
+
+ if (count > this.counter) {
+ this.counter = count;
+ }
+ }
+};
+
+
+function AudioLoader(selector) {
+ this.sm = null;
+ document.querySelector(selector).addEventListener(
+ 'change', this.onChange.bind(this), false);
+}
+
+AudioLoader.prototype = {
+
+ get TXT() {
+ return 0;
+ },
+ get BIN_STR() {
+ return 1;
+ },
+ get ARRAY_BUFFER() {
+ return 2;
+ },
+ get DATA_URL() {
+ return 3;
+ },
+
+ onChange: function(e) {
+ if (this.sm) {
+ this.sm.kill();
+ }
+
+ var file = e.target.files[0];
+ if (!file) {
+ alert('Nice try! Please select a file.');
+ return;
+ } else if (!file.type.match('audio.*')) {
+ alert('Please select an audio file.');
+ return;
+ }
+
+ this.sm = new SoundManager();
+ this.sm.displayID3Info({info: '<em>Loading...</em>'});
+
+ var self = this;
+ this.readFile(this.ARRAY_BUFFER, file, function(arrayBuffer) {
+ self.sm.load(arrayBuffer, function(audioBuffers, totalDuration) {
+ var id3 = self.getID3Data(arrayBuffer);
+ self.sm.displayID3Info(id3);
+ self.sm.play(audioBuffers, totalDuration, id3);
+ });
+ });
+ },
+
+ readFile: function(type, file, callback) {
+ var reader = new FileReader();
+
+ reader.onload = function(e) {
+ callback(this.result);
+ };
+
+ reader.onerror = function(e) {
+ console.log(e);
+ };
+
+ switch(type) {
+ case this.TXT:
+ reader.readAsText(file);
+ break;
+ case this.BIN_STR:
+ reader.readAsBinaryString(file);
+ break;
+ case this.ARRAY_BUFFER:
+ reader.readAsArrayBuffer(file);
+ break;
+ case this.DATA_URL:
+ reader.readAsDataURL(file);
+ break;
+ default:
+ reader.readAsArrayBuffer(file);
+ }
+ },
+
+ getID3Data: function(arrayBuffer) {
+ var dv = new jDataView(arrayBuffer);
+ // "TAG" starts at byte -128 from EOF.
+ // See http://en.wikipedia.org/wiki/ID3
+ if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
+ var title = dv.getString(30, dv.tell());
+ var artist = dv.getString(30, dv.tell());
+ var album = dv.getString(30, dv.tell());
+ var year = dv.getString(4, dv.tell());
+ return {
+ title: title,
+ artist: artist,
+ album: album,
+ year: year
+ };
+ } else {
+ return {}; // no ID3v1 data found.
+ }
+ }
+
+};
+
+
+function Visualizer() {
+ this.canvas = document.getElementById('fft');
+ this.ctx = this.canvas.getContext('2d');
+ this.canvas.width = document.body.clientWidth / 1.4;
+
+ var NUM_SAMPLES = 2048;
+ var CANVAS_HEIGHT = this.canvas.height;
+ var CANVAS_WIDTH = this.canvas.width;
+ var SPACER_WIDTH = 5;
+ var NUM_BARS = Math.round(CANVAS_WIDTH / SPACER_WIDTH);
+
+ this.render = function(analyser) {
+ var freqByteData = new Uint8Array(analyser.frequencyBinCount);
+ analyser.getByteFrequencyData(freqByteData);
+ //analyser.getByteTimeDomainData(freqByteData);
+
+ //var numBars = Math.round(CANVAS_WIDTH / SPACER_WIDTH); //freqByteData.length
+
+ this.ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+
+ //freqByteData = freqByteData.subarray(Math.round((NUM_SAMPLES / 2) - CANVAS_WIDTH / 4));
+
+ // Draw rectangle for each frequency bin.
+ for (var i = 0; i < NUM_BARS; ++i) {
+ this.ctx.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, 3, -freqByteData[i]);
+ }
+ };
+}
+
+
+function send(data) {
+ // Stringify JSON data.
+ if ((typeof data == 'object') && (data.__proto__ !== ArrayBuffer.prototype)) {
+ data = JSON.stringify(data);
+ }
+ ws.send(data);
+}
+
+function SoundManager() {
+ var self_ = this;
+ var audioBuffers_ = [];
+ var sources_ = [];
+ var reqId_ = null;
+ var sendQueue_ = [];
+
+ this.audioCtx = null;
+ this.progress = null;
+
+ var SAMPLE_RATE = 44100;
+
+ window.AudioContext = window.AudioContext || window.webkitAudioContext;
+ if (window.AudioContext) {
+ this.audioCtx = new window.AudioContext();
+ if (SHOW_VISUALIZATIONS) {
+ this.analyser = this.audioCtx.createAnalyser();
+
+ //this.gainNode = this.audioCtx.createGainNode();
+ //this.gainNode.connect(this.analyser);
+
+ this.analyser.connect(this.audioCtx.destination);
+
+ }
+ }
+
+ this.visualizer = new Visualizer();
+
+ var sendAudioChunk_ = function(audioBuffer, meta) {
+
+ var buffer = audioBuffer.getChannelData(0).buffer;
+
+ sendQueue_.push({buffer: buffer, meta: meta});
+
+ // Rate limit how much we're sending. Send every 2s when there's nothing buffered.
+ var id = setInterval(function() {
+ if (ws.bufferedAmount == 0) {
+ clearInterval(id);
+
+ var chunk = sendQueue_.shift();
+
+ console.log('Sending chunk');
+ send(chunk.meta); // Send metadata before sending actual audio chunk.
+ send(chunk.buffer);
+ }
+ }, 2000); // TODO: optimize this number
+
+ };
+
+ this.load = function(data, opt_callback) {
+ if (!this.audioCtx) {
+ return;
+ }
+
+ if (typeof data == 'string') {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', data, true);
+ xhr.responseType = 'arraybuffer';
+ xhr.onload = function(e) {
+ self_.audioCtx.decodeAudioData(this.response, function(audioBuffer) {
+ audioBuffers.push(audioBuffer);
+ opt_callback && opt_callback(audioBuffer);
+ }, function(e) {
+ console.log(e);
+ });
+ };
+ xhr.send();
+
+ } else {
+ this.audioCtx.decodeAudioData(data, function(audioBuffer) {
+
+ console.log('Total duration: ' + audioBuffer.duration);
+
+ var channel1 = audioBuffer.getChannelData(0);
+ var channel2 = audioBuffer.getChannelData(1);
+
+ var CHUNK_SIZE = Math.ceil(channel1.length / NUM_CHUNKS);
+ var NUM_SAMPLES = CHUNK_SIZE;
+ var NUM_CHANNELS = audioBuffer.numberOfChannels;
+
+ var audioBuffers = [];
+
+ for (var i = 0; i < NUM_CHUNKS; ++i) {
+ var begin = i * CHUNK_SIZE;
+ var end = begin + CHUNK_SIZE;
+ var aBuffer = self_.audioCtx.createBuffer(
+ NUM_CHANNELS, NUM_SAMPLES, SAMPLE_RATE);
+ aBuffer.getChannelData(0).set(channel1.subarray(begin, end));
+ if (audioBuffer.numberOfChannels == 2) {
+ aBuffer.getChannelData(1).set(channel2.subarray(begin, end));
+ }
+
+ audioBuffers_.push(aBuffer);
+ }
+
+ opt_callback && opt_callback(audioBuffers_, audioBuffer.duration);
+
+ }, function(e) {
+ alert('Error decoding audio');
+ console.error(e);
+ });
+ }
+ };
+
+ this.schedulePlayback = function(startTime, audioBuffer) {
+ if (!this.audioCtx) {
+ return;
+ }
+
+ var source = this.audioCtx.createBufferSource();
+ source.buffer = audioBuffer;
+
+ if (SHOW_VISUALIZATIONS) {
+ source.connect(this.analyser);
+ //source.connect(this.gainNode);
+ } else {
+ source.connect(this.audioCtx.destination);
+ }
+
+ source.noteOn(startTime);
+
+ sources_.push(source);
+
+ console.log('scheduled: ' + startTime, 'until: ' +
+ (startTime + audioBuffer.duration));
+ };
+
+ this.periodSend = function() {
+
+ // Rate limit how much we're sending. Send every 2s when there's nothing buffered.
+ var id = setInterval(function() {
+ if (sendQueue_.length == 0) {
+ clearInterval(id);
+ }
+ if (ws.bufferedAmount == 0) {
+ var next = sendQueue_.shift();
+
+ console.log('Sending chunk');
+
+ send(next.meta); // Send metadata before sending actual audio chunk.
+ send(next.buffer);
+ }
+ }, 2000); // TODO: optimize this number
+
+ };
+
+ this.play = function(audioBuffers, totalDuration, id3) {
+ var startTime = 0;
+ for (var i = 0, audioBuffer; audioBuffer = audioBuffers[i]; ++i) {
+ if (PLAY_ON_DJ_MACHINE) {
+ this.schedulePlayback(startTime, audioBuffer);
+ }
+
+ sendQueue_.push({
+ buffer: audioBuffer.getChannelData(0).buffer,
+ meta: {
+ startTime: startTime,
+ numChunks: audioBuffers.length,
+ totalDuration: totalDuration,
+ id3: id3
+ }
+ });
+
+ startTime += audioBuffer.duration;
+ }
+
+ this.periodSend();
+
+ if (SHOW_VISUALIZATIONS || SHOW_PROGRESS) {
+ this.visualize(audioBuffers[0].duration, totalDuration, audioBuffers[0]);
+ }
+ };
+
+ this.kill = function() {
+ window.webkitCancelAnimationFrame(reqId_);
+
+ this.analyser.disconnect(0);
+
+ for (var i = 0, source; source = sources_[i]; ++i) {
+ source.noteOff(0);
+ source.disconnect(0);
+ }
+ sources_ = [];
+ };
+
+ this.playFromArrayBuffer = function(arrayBuffer, props) {
+
+ var float32Array = new Float32Array(arrayBuffer);
+
+ // TODO(ericbidelman): clean up duplicate vars.
+ var NUM_SAMPLES = float32Array.length;
+ var NUM_CHANNELS = 2;
+
+ var audioBuffer = this.audioCtx.createBuffer(
+ NUM_CHANNELS, NUM_SAMPLES, SAMPLE_RATE);
+ audioBuffer.getChannelData(0).set(float32Array);
+
+ //TODO: send over channel2 data instead of duplicating channel's data.
+ if (audioBuffer.numberOfChannels == 2) {
+ audioBuffer.getChannelData(1).set(float32Array);
+ }
+
+ /*buffer a few chunks
+ audioBuffers_.push(audioBuffer);
+ if (audioBuffers_.length >= 3) {
+ this.schedulePlayback(props.startTime, audioBuffers_.shift());
+ }*/
+
+ if (PLAY_ON_REMOTE_MACHINES) {
+ this.schedulePlayback(props.startTime, audioBuffer);
+ }
+
+ if (SHOW_VISUALIZATIONS || SHOW_PROGRESS) {
+ this.visualize(audioBuffer.duration, props.totalDuration);
+ }
+
+ this.displayID3Info(props.id3);
+ };
+
+ this.visualize = function(chunkDuration, totalDuration) {
+
+ if (this.progress) {
+ return;
+ }
+
+ this.progress = new Progress('#container', totalDuration);
+
+ var self = this;
+ (function callback(time) {
+ var currTime = self.audioCtx.currentTime;
+
+ // Unhook if we're played all chunks.
+ if (currTime >= totalDuration) {
+ self.kill();
+ } else {
+ reqId_ = window.webkitRequestAnimationFrame(callback);
+ }
+
+ if (SHOW_PROGRESS) {
+ self.progress.update(currTime, chunkDuration);
+ }
+ if (SHOW_VISUALIZATIONS) {
+ self.visualizer.render(self.analyser);
+ }
+ })();
+
+ };
+
+ this.displayID3Info = function(id3) {
+ var title = id3.title ? id3.title : '';
+ var album = id3.album ? id3.album : '';
+ var artist = id3.artist ? ' ( ' + id3.artist + ' )' : '';
+
+ var html = [];
+ if (id3.info) {
+ html.push(id3.info);
+ } else {
+ html.push('<span class="title">', title, '</span>', artist);
+ }
+
+ if (id3.title) {
+ document.querySelector('#song-title').innerHTML = html.join('');
+ }
+ };
+
+}
View
359 demos/audio_streamer/index.html
@@ -0,0 +1,359 @@
+<!DOCTYPE html>
+<!--
+Copyright 2012 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Author: Eric Bidelman (ebidel@gmail.com)
+-->
+<html>
+<head>
+ <title>Binary WebSocket Audio Streamer</title>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="chrome=1">
+ <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Orbitron|Kameron|Muli|Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono&v2">
+<style>
+::-webkit-scrollbar {
+ width: 15px;
+ background: white;
+}
+::-webkit-scrollbar-thumb {
+ -webkit-box-shadow: inset 0 0 99px rgba(0,0,0,.2);
+ border: solid transparent;
+ border-width: 6px 4px;
+}
+html, body {
+ margin: 0;
+}
+body {
+ font-family: 'Open Sans';
+ -webkit-font-smoothing: antialiased;
+}
+h2 {
+ border: 1px solid #BABCC0;
+ border-width: 0 0 0 1px;
+ padding: 0 12px;
+ font-weight: 300;
+ margin: 0;
+ text-shadow: 0 1px 1px white;
+ white-space: nowrap;
+ display: inline-block;
+}
+header {
+ background: whiteSmoke;
+ background-image: -moz-linear-gradient(top,#FAFAFA,whiteSmoke);
+ background-image: -ms-linear-gradient(top,#FAFAFA,whiteSmoke);
+ background-image: -o-linear-gradient(top,#FAFAFA,whiteSmoke);
+ background-image: -webkit-linear-gradient(top,#FAFAFA,whiteSmoke);
+ background-image: linear-gradient(top,#FAFAFA,whiteSmoke);
+ border-bottom: 1px solid #EBEBEB;
+ color: #91959C;
+ height: 45px;
+ padding: 17px 15px 5px;
+}
+header img {
+ height: 25px;
+ width: 25px;
+ opacity: 0.1;
+ margin-left: 5px;
+ vertical-align: sub;
+}
+.panel {
+ margin: 1em;
+}
+#chat {
+ position: absolute;
+ background: -webkit-linear-gradient(#fff 40%, #eee);
+ width: 100%;
+ height: 200px;
+ bottom: 0;
+ overflow: auto;
+ overflow-x: hidden;
+ padding: 10px;
+ box-sizing: border-box;
+ box-shadow: -3px 0 10px #ddd;
+ font-size: 10pt;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
+#chat input[type="text"] {
+ border: 1px solid #ccc;
+ padding: 5px;
+ display: -webkit-box;
+ display: -moz-box;
+ display: -o-box;
+ display: box;
+}
+#chat input[type="text"]:not(:disabled):hover {
+ border-color: #777;
+}
+#chat .log {
+ overflow-y: auto;
+ overflow-x: hidden;
+ -webkit-box-flex: 1;
+ -moz-box-flex: 1;
+ -o-box-flex: 1;
+ box-flex: 1;
+}
+#chat .log div {
+ padding: 3px;
+}
+#chat .log > div:nth-child(odd) {
+ background-color: #eee;
+}
+#chat .log .username {
+ font-weight: bold;
+}
+#connect-button {
+ color: darkgreen;
+}
+#disconnect-button {
+ color: darkred;
+}
+#connect-button:not(:disabled):hover {
+ border-color: darkgreen;
+}
+#disconnect-button:not(:disabled):hover {
+ border-color: darkred;
+}
+input {
+ vertical-align: middle;
+}
+input[type="number"] {
+ border: none;
+ padding: 5px;
+ font-size: 13pt;
+ box-shadow: 0 1px 4px #999 inset;
+ border-radius: 3px;
+ vertical-align: middle;
+ font-size: inherit;
+}
+::-webkit-outer-spin-button,
+::-webkit-inner-spin-button {
+ display: none;
+ -webkit-appearance: none;
+ margin: 0;
+}
+:-webkit-any(button, .fakebutton),
+input[type="file"].button:before {
+ background: -webkit-linear-gradient(#F9F9F9 40%, #E3E3E3 70%);
+ border: 1px solid #888;
+ border-radius: 3px;
+ margin: 0 8px 0 0;
+ color: black;
+ padding: 5px 8px;
+ outline: none;
+ white-space: nowrap;
+ vertical-align: middle;
+ -webkit-user-select:none;
+ user-select: none;
+ cursor: pointer;
+ text-shadow: 1px 1px #fff;
+ font-weight: 700;
+}
+:-webkit-any(button, .fakebutton):not(:disabled):hover,
+input[type="file"].button:not(:disabled):hover:before {
+ border: 1px solid #000;
+}
+:-webkit-any(button, .fakebutton):not(:disabled):active,
+input[type="file"].button:not(:disabled):active:before {
+ background: -webkit-linear-gradient(#E3E3E3 40%, #F9F9F9 70%);
+}
+input[type="file"].button {
+ width: 112px;
+ height: 25px;
+}
+input[type="file"].button::-webkit-file-upload-button {
+ visibility: hidden;
+}
+input[type="file"].button:before {
+ content: 'Load audio file';
+ display: inline-block;
+ outline: none;
+ white-space: nowrap;
+ -webkit-user-select: none;
+ user-select: none;
+ cursor: pointer;
+}
+:disabled,
+input[type="file"].button:disabled:before {
+ color: #ccc !important;
+ background-color: #eee;
+}
+progress {
+ width: 400px;
+}
+.title {
+ font-weight: bold;
+}
+</style>
+</head>
+<body>
+
+<header>
+ <h2>WebSocket Audio Streamer</h3><img src="speaker.svg">
+</header>
+
+<section class="panel">
+ <p>
+ <button onclick="connect()" id="connect-button">Connect</button>
+ <button onclick="disconnect()" id="disconnect-button">Disconnect</button>
+ <input type="file" id="audio-file" class="button" accept="audio/*">
+ <span id="song-title"></span>
+ </p>
+ <div id="container"></div>
+ <canvas id="fft" width="512" height="250"></canvas>
+</section>
+<div id="chat">
+ <div class="log"></div>
+ <input type="text" id="msg" disabled placeholder="Send a chat message....">
+</div>
+
+<script src="jdataview.js"></script>
+<script src="audio_streamer.js"></script>
+<script>
+// Take care of vendor prefixes.
+window.WebSocket = window.WebSocket || window.MozWebSocket;
+window.URL = window.URL || window.webkitURL;
+window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
+ window.MozBlobBuilder;
+
+const WS_HOST = 'localhost:8080';
+
+var ws = null;
+var songProps = {};
+var songStarted = false;
+var startTimeAdjusted = null;
+var sm = new SoundManager();
+
+//var queue = [];
+
+function connect() {
+ ws = new WebSocket('ws://' + WS_HOST, ['dumby-protocol']);
+ ws.binaryType = 'arraybuffer';
+
+ ws.onopen = function(e) {
+ console.log('Connection OPEN');
+ document.querySelector('#connect-button').disabled = true;
+ document.querySelector('#disconnect-button').disabled = false;
+ document.querySelector('#msg').disabled = false;
+ };
+
+ ws.onmessage = function(e) {
+ var data = e.data;
+
+ if (typeof data == 'string') {
+ try {
+ data = JSON.parse(data);
+ if ('startTime' in data) {
+ songProps = data;
+
+ // First startTime? Set adjusted time and indicate a song started.
+ // Only want to set this once (after the first chunk is streamed).
+ if (!songStarted) {
+ startTimeAdjusted = songProps.startTime;
+ songStarted = true;
+ }
+
+ } else if ('username' in data) {
+ logChat(data.username, data.msg);
+ }
+ } catch(e) {
+ console.log(e);
+ }
+ } else {
+ if (data.__proto__ !== ArrayBuffer.prototype) { // Assert for the heck of it.
+ console.log(e);
+ alert('Expected ArrayBuffer from WebSocket.');
+ }
+
+ // On page refresh, pick up streaming audio. The start time needs to be
+ // adjusted so subsequent chunks are scheduled at the correct time.
+ if (startTimeAdjusted) {
+ songProps.startTime = songProps.startTime - startTimeAdjusted;
+ }
+
+ // Queue a few chunks
+ // queue.push([data, songProps]);
+ // console.log(queue.length)
+ // if (queue.length > 3) {
+ // var d = queue.shift();
+ // sm.playFromArrayBuffer(d[0], d[1]);
+ // }
+
+ sm.playFromArrayBuffer(data, songProps);
+ }
+ };
+
+ ws.onclose = function(e) {
+ console.log('Connection CLOSED');
+ };
+
+ ws.onerror = function(e) {
+ console.log('Connection ERROR');
+ console.log(e);
+ };
+}
+
+function disconnect() {
+ ws.close();
+ document.querySelector('#connect-button').disabled = false;
+ document.querySelector('#disconnect-button').disabled = true;
+ document.querySelector('#msg').disabled = true;
+}
+
+function logChat(user, msg) {
+ var div = document.createElement('div');
+
+ var span = document.createElement('span');
+ span.classList.add('username');
+ span.textContent = user + '> ';
+
+ div.appendChild(span);
+
+ span = document.createElement('span');
+ span.textContent = msg;
+
+ div.appendChild(span);
+
+ var log = document.querySelector('#chat .log');
+ log.insertBefore(div, log.children[0]);
+}
+
+connect();
+var loader = new AudioLoader('#audio-file');
+
+var username = null;
+document.querySelector('#msg').addEventListener('keypress', function(e) {
+ if (e.keyCode == 13 && this.value.length) { // Enter
+ e.preventDefault();
+
+ // On first chat, have user enter a name.
+ if (!username) {
+ username = prompt('Enter a username') || Date.now();
+ }
+
+ // Log chat message locally and send across websocket.
+ logChat(username, this.value);
+ send({username: username, msg: this.value});
+
+ this.value = '';
+ }
+}, false);
+</script>
+<!--[if IE]>
+<script src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
+<script>CFInstall.check({mode: 'overlay'});</script>
+<![endif]-->
+</body>
+</html>
View
451 demos/audio_streamer/jdataview.js
@@ -0,0 +1,451 @@
+//
+// jDataView by Vjeux - Jan 2010
+//
+// A unique way to read a binary file in the browser
+// http://github.com/vjeux/jDataView
+// http://blog.vjeux.com/ <vjeuxx@gmail.com>
+//
+
+(function (global) {
+
+var compatibility = {
+ ArrayBuffer: typeof ArrayBuffer !== 'undefined',
+ DataView: typeof DataView !== 'undefined' && 'getFloat64' in DataView.prototype,
+ NodeBuffer: typeof Buffer !== 'undefined',
+// 0.6.0 -> readInt8LE(offset)
+ NodeBufferFull: typeof Buffer !== 'undefined' && 'readInt8LE' in Buffer,
+// 0.5.0 -> readInt8(offset, endian)
+ NodeBufferEndian: typeof Buffer !== 'undefined' && 'readInt8' in Buffer
+};
+
+var jDataView = function (buffer, byteOffset, byteLength, littleEndian) {
+ if (!(this instanceof arguments.callee)) {
+ throw new Error("Constructor may not be called as a function");
+ }
+
+ this.buffer = buffer;
+
+ // Handle Type Errors
+ if (!(compatibility.NodeBuffer && buffer instanceof Buffer) &&
+ !(compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) &&
+ typeof buffer !== 'string') {
+ throw new TypeError('Type error');
+ }
+
+ // Check parameters and existing functionnalities
+ this._isArrayBuffer = compatibility.ArrayBuffer && buffer instanceof ArrayBuffer;
+ this._isDataView = compatibility.DataView && this._isArrayBuffer;
+ this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer;
+
+ // Default Values
+ this._littleEndian = littleEndian === undefined ? true : littleEndian;
+
+ var bufferLength = this._isArrayBuffer ? buffer.byteLength : buffer.length;
+ if (byteOffset === undefined) {
+ byteOffset = 0;
+ }
+ this.byteOffset = byteOffset;
+
+ if (byteLength === undefined) {
+ byteLength = bufferLength - byteOffset;
+ }
+ this.byteLength = byteLength;
+
+ if (!this._isDataView) {
+ // Do additional checks to simulate DataView
+ if (typeof byteOffset !== 'number') {
+ throw new TypeError('Type error');
+ }
+ if (typeof byteLength !== 'number') {
+ throw new TypeError('Type error');
+ }
+ if (typeof byteOffset < 0) {
+ throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
+ }
+ if (typeof byteLength < 0) {
+ throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
+ }
+ }
+
+ // Instanciate
+ if (this._isDataView) {
+ this._view = new DataView(buffer, byteOffset, byteLength);
+ this._start = 0;
+ }
+ this._start = byteOffset;
+ if (byteOffset + byteLength > bufferLength) {
+ throw new Error("INDEX_SIZE_ERR: DOM Exception 1");
+ }
+
+ this._offset = 0;
+};
+
+jDataView.createBuffer = function () {
+ if (compatibility.NodeBuffer) {
+ var buffer = new Buffer(arguments.length);
+ for (var i = 0; i < arguments.length; ++i) {
+ buffer[i] = arguments[i];
+ }
+ return buffer;
+ }
+ if (compatibility.ArrayBuffer) {
+ var buffer = new ArrayBuffer(arguments.length);
+ var view = new Int8Array(buffer);
+ for (var i = 0; i < arguments.length; ++i) {
+ view[i] = arguments[i];
+ }
+ return buffer;
+ }
+
+ return String.fromCharCode.apply(null, arguments);
+};
+
+jDataView.prototype = {
+
+ // Helpers
+
+ getString: function (length, byteOffset) {
+ var value;
+
+ // Handle the lack of byteOffset
+ if (byteOffset === undefined) {
+ byteOffset = this._offset;
+ }
+
+ // Error Checking
+ if (typeof byteOffset !== 'number') {
+ throw new TypeError('Type error');
+ }
+ if (length < 0 || byteOffset + length > this.byteLength) {
+ throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
+ }
+
+ if (this._isNodeBuffer) {
+ value = this.buffer.toString('ascii', this._start + byteOffset, this._start + byteOffset + length);
+ }
+ else {
+ value = '';
+ for (var i = 0; i < length; ++i) {
+ var char = this.getUint8(byteOffset + i);
+ value += String.fromCharCode(char > 127 ? 65533 : char);
+ }
+ }
+
+ this._offset = byteOffset + length;
+ return value;
+ },
+
+ getChar: function (byteOffset) {
+ return this.getString(1, byteOffset);
+ },
+
+ tell: function () {
+ return this._offset;
+ },
+
+ seek: function (byteOffset) {
+ if (typeof byteOffset !== 'number') {
+ throw new TypeError('Type error');
+ }
+ if (byteOffset < 0 || byteOffset > this.byteLength) {
+ throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
+ }
+
+ return this._offset = byteOffset;
+ },
+
+ // Compatibility functions on a String Buffer
+
+ _endianness: function (byteOffset, pos, max, littleEndian) {
+ return byteOffset + (littleEndian ? max - pos - 1 : pos);
+ },
+
+ _getFloat64: function (byteOffset, littleEndian) {
+ var b0 = this._getUint8(this._endianness(byteOffset, 0, 8, littleEndian)),
+ b1 = this._getUint8(this._endianness(byteOffset, 1, 8, littleEndian)),
+ b2 = this._getUint8(this._endianness(byteOffset, 2, 8, littleEndian)),
+ b3 = this._getUint8(this._endianness(byteOffset, 3, 8, littleEndian)),
+ b4 = this._getUint8(this._endianness(byteOffset, 4, 8, littleEndian)),
+ b5 = this._getUint8(this._endianness(byteOffset, 5, 8, littleEndian)),
+ b6 = this._getUint8(this._endianness(byteOffset, 6, 8, littleEndian)),
+ b7 = this._getUint8(this._endianness(byteOffset, 7, 8, littleEndian)),
+
+ sign = 1 - (2 * (b0 >> 7)),
+ exponent = ((((b0 << 1) & 0xff) << 3) | (b1 >> 4)) - (Math.pow(2, 10) - 1),
+
+ // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead
+ mantissa = ((b1 & 0x0f) * Math.pow(2, 48)) + (b2 * Math.pow(2, 40)) + (b3 * Math.pow(2, 32)) +
+ (b4 * Math.pow(2, 24)) + (b5 * Math.pow(2, 16)) + (b6 * Math.pow(2, 8)) + b7;
+
+ if (exponent === 1024) {
+ if (mantissa !== 0) {
+ return NaN;
+ } else {
+ return sign * Infinity;
+ }
+ }
+
+ if (exponent === -1023) { // Denormalized
+ return sign * mantissa * Math.pow(2, -1022 - 52);
+ }
+
+ return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent);
+ },
+
+ _getFloat32: function (byteOffset, littleEndian) {
+ var b0 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)),
+ b1 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)),
+ b2 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)),
+ b3 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian)),
+
+ sign = 1 - (2 * (b0 >> 7)),
+ exponent = (((b0 << 1) & 0xff) | (b1 >> 7)) - 127,
+ mantissa = ((b1 & 0x7f) << 16) | (b2 << 8) | b3;
+
+ if (exponent === 128) {
+ if (mantissa !== 0) {
+ return NaN;
+ } else {
+ return sign * Infinity;
+ }
+ }
+
+ if (exponent === -127) { // Denormalized
+ return sign * mantissa * Math.pow(2, -126 - 23);
+ }
+
+ return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent);
+ },
+
+ _getInt32: function (byteOffset, littleEndian) {
+ var b = this._getUint32(byteOffset, littleEndian);
+ return b > Math.pow(2, 31) - 1 ? b - Math.pow(2, 32) : b;
+ },
+
+ _getUint32: function (byteOffset, littleEndian) {
+ var b3 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)),
+ b2 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)),
+ b1 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)),
+ b0 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian));
+
+ return (b3 * Math.pow(2, 24)) + (b2 << 16) + (b1 << 8) + b0;
+ },
+
+ _getInt16: function (byteOffset, littleEndian) {
+ var b = this._getUint16(byteOffset, littleEndian);
+ return b > Math.pow(2, 15) - 1 ? b - Math.pow(2, 16) : b;
+ },
+
+ _getUint16: function (byteOffset, littleEndian) {
+ var b1 = this._getUint8(this._endianness(byteOffset, 0, 2, littleEndian)),
+ b0 = this._getUint8(this._endianness(byteOffset, 1, 2, littleEndian));
+
+ return (b1 << 8) + b0;
+ },
+
+ _getInt8: function (byteOffset) {
+ var b = this._getUint8(byteOffset);
+ return b > Math.pow(2, 7) - 1 ? b - Math.pow(2, 8) : b;
+ },
+
+ _getUint8: function (byteOffset) {
+ if (this._isArrayBuffer) {
+ return new Uint8Array(this.buffer, byteOffset, 1)[0];
+ }
+ else if (this._isNodeBuffer) {
+ return this.buffer[byteOffset];
+ } else {
+ return this.buffer.charCodeAt(byteOffset) & 0xff;
+ }
+ }
+};
+
+// Create wrappers
+
+var dataTypes = {
+ 'Int8': 1,
+ 'Int16': 2,
+ 'Int32': 4,
+ 'Uint8': 1,
+ 'Uint16': 2,
+ 'Uint32': 4,
+ 'Float32': 4,
+ 'Float64': 8
+};
+var nodeNaming = {
+ 'Int8': 'Int8',
+ 'Int16': 'Int16',
+ 'Int32': 'Int32',
+ 'Uint8': 'UInt8',
+ 'Uint16': 'UInt16',
+ 'Uint32': 'UInt32',
+ 'Float32': 'Float',
+ 'Float64': 'Double'
+};
+
+for (var type in dataTypes) {
+ if (!dataTypes.hasOwnProperty(type)) {
+ continue;
+ }
+
+ // Bind the variable type
+ (function (type) {
+ var size = dataTypes[type];
+
+ // Create the function
+ jDataView.prototype['get' + type] =
+ function (byteOffset, littleEndian) {
+ var value;
+
+ // Handle the lack of endianness
+ if (littleEndian === undefined) {
+ littleEndian = this._littleEndian;
+ }
+
+ // Handle the lack of byteOffset
+ if (byteOffset === undefined) {
+ byteOffset = this._offset;
+ }
+
+ // Dispatch on the good method
+ if (this._isDataView) {
+ // DataView: we use the direct method
+ value = this._view['get' + type](byteOffset, littleEndian);
+ }
+ // ArrayBuffer: we use a typed array of size 1 if the alignment is good
+ // ArrayBuffer does not support endianess flag (for size > 1)
+ else if (this._isArrayBuffer && (this._start + byteOffset) % size === 0 && (size === 1 || littleEndian)) {
+ value = new global[type + 'Array'](this.buffer, this._start + byteOffset, 1)[0];
+ }
+ // NodeJS Buffer
+ else if (this._isNodeBuffer && compatibility.NodeBufferFull) {
+ if (littleEndian) {
+ value = this.buffer['read' + nodeNaming[type] + 'LE'](this._start + byteOffset);
+ } else {
+ value = this.buffer['read' + nodeNaming[type] + 'BE'](this._start + byteOffset);
+ }
+ } else if (this._isNodeBuffer && compatibility.NodeBufferEndian) {
+ value = this.buffer['read' + nodeNaming[type]](this._start + byteOffset, littleEndian);
+ }
+ else {
+ // Error Checking
+ if (typeof byteOffset !== 'number') {
+ throw new TypeError('Type error');
+ }
+ if (byteOffset + size > this.byteLength) {
+ throw new Error('INDEX_SIZE_ERR: DOM Exception 1');
+ }
+ value = this['_get' + type](this._start + byteOffset, littleEndian);
+ }
+
+ // Move the internal offset forward
+ this._offset = byteOffset + size;
+
+ return value;
+ };
+ })(type);
+}
+
+if (typeof jQuery !== 'undefined' && jQuery.fn.jquery >= "1.6.2") {
+ var convertResponseBodyToText = function (byteArray) {
+ // http://jsperf.com/vbscript-binary-download/6
+ var scrambledStr;
+ try {
+ scrambledStr = IEBinaryToArray_ByteStr(byteArray);
+ } catch (e) {
+ // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie
+ // http://miskun.com/javascript/internet-explorer-and-binary-files-data-access/
+ var IEBinaryToArray_ByteStr_Script =
+ "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
+ " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
+ "End Function\r\n"+
+ "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+
+ " Dim lastIndex\r\n"+
+ " lastIndex = LenB(Binary)\r\n"+
+ " if lastIndex mod 2 Then\r\n"+
+ " IEBinaryToArray_ByteStr_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+
+ " Else\r\n"+
+ " IEBinaryToArray_ByteStr_Last = -1\r\n"+
+ " End If\r\n"+
+ "End Function\r\n";
+
+ // http://msdn.microsoft.com/en-us/library/ms536420(v=vs.85).aspx
+ // proprietary IE function
+ window.execScript(IEBinaryToArray_ByteStr_Script, 'vbscript');
+
+ scrambledStr = IEBinaryToArray_ByteStr(byteArray);
+ }
+
+ var lastChr = IEBinaryToArray_ByteStr_Last(byteArray),
+ result = "",
+ i = 0,
+ l = scrambledStr.length % 8,
+ thischar;
+ while (i < l) {
+ thischar = scrambledStr.charCodeAt(i++);
+ result += String.fromCharCode(thischar & 0xff, thischar >> 8);
+ }
+ l = scrambledStr.length
+ while (i < l) {
+ result += String.fromCharCode(
+ (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
+ (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
+ (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
+ (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
+ (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
+ (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
+ (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8,
+ (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8);
+ }
+ if (lastChr > -1) {
+ result += String.fromCharCode(lastChr);
+ }
+ return result;
+ };
+
+ jQuery.ajaxSetup({
+ converters: {
+ '* dataview': function(data) {
+ return new jDataView(data);
+ }
+ },
+ accepts: {
+ dataview: "text/plain; charset=x-user-defined"
+ },
+ responseHandler: {
+ dataview: function (responses, options, xhr) {
+ // Array Buffer Firefox
+ if ('mozResponseArrayBuffer' in xhr) {
+ responses.text = xhr.mozResponseArrayBuffer;
+ }
+ // Array Buffer Chrome
+ else if ('responseType' in xhr && xhr.responseType === 'arraybuffer' && xhr.response) {
+ responses.text = xhr.response;
+ }
+ // Internet Explorer (Byte array accessible through VBScript -- convert to text)
+ else if ('responseBody' in xhr) {
+ responses.text = convertResponseBodyToText(xhr.responseBody);
+ }
+ // Older Browsers
+ else {
+ responses.text = xhr.responseText;
+ }
+ }
+ }
+ });
+
+ jQuery.ajaxPrefilter('dataview', function(options, originalOptions, jqXHR) {
+ // trying to set the responseType on IE 6 causes an error
+ if (jQuery.support.ajaxResponseType) {
+ if (!options.hasOwnProperty('xhrFields')) {
+ options.xhrFields = {};
+ }
+ options.xhrFields.responseType = 'arraybuffer';
+ }
+ options.mimeType = 'text/plain; charset=x-user-defined';
+ });
+}
+
+global.jDataView = (global.module || {}).exports = jDataView;
+
+})(this);
View
127 demos/audio_streamer/server.js
@@ -0,0 +1,127 @@
+#!/usr/bin/env node
+
+//var WebSocketServer = require('../websocket_node/lib/WebSocketServer');
+var WebSocketServer = require('ws').Server;
+
+var http = require('http');
+var url = require('url');
+var fs = require('fs');
+
+var args = { /* defaults */
+ port: '8080'
+};
+
+/* Parse command line options */
+var pattern = /^--(.*?)(?:=(.*))?$/;
+process.argv.forEach(function(value) {
+ var match = pattern.exec(value);
+ if (match) {
+ args[match[1]] = match[2] ? match[2] : true;
+ }
+});
+
+var port = parseInt(args.port, 10);
+
+console.log("Usage: ./server.js [--port=8080]");
+
+var connections = {};
+
+/* //websocket_node
+var server = http.createServer(function(request, response) {
+ console.log((new Date()) + " Received request for " + request.url);
+ response.writeHead(404);
+ response.end();
+});
+
+server.listen(port, function() {
+ console.log((new Date()) + " Server is listening on port " + port);
+});
+
+var wsServer = new WebSocketServer({
+ httpServer: server,
+ autoAcceptConnections: true,
+ maxReceivedFrameSize: 64 * 1024 * 1024, // 64MiB
+ maxReceivedMessageSize: 64 * 1024 * 1024, // 64MiB
+ fragmentOutgoingMessages: false,
+ keepalive: true,
+ keepaliveInterval: 20000,
+ disableNagleAlgorithm: false
+});
+
+wsServer.on('connect', function(connection) {
+ connection.id = Date.now(); // Assign unique id to this connection.
+
+ console.log((new Date()) + ' Connection accepted: ' + connection.id);
+
+ connections[connection.id] = connection;
+
+ connection.on('message', function(message) {
+ var length;
+ switch (message.type) {
+ case 'utf8':
+ length = message.utf8Data.length;
+ break;
+ case 'binary':
+ length = message.binaryData.length;
+ break;
+ }
+
+ console.log('Received ' + message.type + ' message of ' + length + ' characters.');
+
+ broadcast(message, connection);
+ });
+
+ connection.on('close', function(connection) {
+ console.log((new Date()) + " Peer " + connection.remoteAddress + " disconnected.");
+ delete connections[connection.id];
+ });
+});
+
+// Broadcasts a message to all connected sockets accept for the sender.
+function broadcast(message, fromConnection) {
+ for (var id in connections) {
+ if (id != fromConnection.id) {
+ if (message.type === 'binary') {
+ connections[id].sendBytes(message.binaryData);
+ } else {
+ connections[id].sendUTF(message.utf8Data);
+ }
+ }
+ }
+}
+*/
+
+// ws is the fastest websocket lib:
+// http://einaros.github.com/ws/
+var wsServer = new WebSocketServer({port: port});
+
+wsServer.on('connection', function(ws) {
+
+ ws.id = Date.now(); // Assign unique id to this ws connection.
+ connections[ws.id] = ws;
+
+ console.log((new Date()) + ' Connection accepted: ' + ws.id);
+
+ ws.on('message', function(message, flags) {
+ console.log('Received ' + (flags.binary ? 'binary' : '') + ' message: ' +
+ message.length + ' bytes.');
+ broadcast(message, this, flags);
+ });
+
+ ws.on('close', function() {
+ console.log((new Date()) + " Peer " + this.id + " disconnected.");
+ delete connections[this.id];
+ });
+});
+
+// Broadcasts a message to all connected sockets accept for the sender.
+function broadcast(message, fromWs, flags) {
+ for (var id in connections) {
+ if (id != fromWs.id) {
+ connections[id].send(message, {
+ binary: flags.binary ? true : false,
+ mask: false
+ });
+ }
+ }
+}
View
17 demos/audio_streamer/speaker.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="93.32px" height="100px" viewBox="0 0 93.32 100" enable-background="new 0 0 93.32 100" xml:space="preserve">
+<polygon points="0,37.457 0,62.542 22.076,62.542 39.135,79.601 39.135,20.398 22.076,37.457 "/>
+<path d="M63.217,50C63.217,50,63.217,50,63.217,50L63.217,50c0-0.001,0-0.001,0-0.002c-0.16-11.028-3.777-20.315-11.345-28.367
+ c-2.663-2.834-6.913,1.431-4.257,4.257c6.487,6.903,9.443,14.674,9.581,24.111c-0.138,9.439-3.094,17.208-9.581,24.113
+ c-2.656,2.826,1.594,7.092,4.257,4.256C59.439,70.317,63.057,61.03,63.217,50C63.217,50,63.217,50,63.217,50z"/>
+<path d="M78.269,50C78.269,50,78.269,50,78.269,50L78.269,50c0-0.001,0-0.001,0-0.002c-0.208-14.764-5.27-28.018-15.477-38.723
+ c-2.684-2.815-6.936,1.449-4.257,4.257C67.647,25.091,72.062,36.821,72.248,50c-0.186,13.179-4.601,24.91-13.713,34.468
+ c-2.679,2.809,1.573,7.072,4.257,4.256C72.999,78.019,78.061,64.765,78.269,50C78.269,50,78.269,50,78.269,50z"/>
+<path d="M93.32,50L93.32,50C93.32,50,93.32,50,93.32,50c-0.208-18.576-6.708-35.666-19.606-49.083
+ c-2.694-2.804-6.946,1.459-4.257,4.257C81.258,17.45,87.109,33.004,87.3,50c-0.19,16.996-6.042,32.55-17.843,44.828
+ c-2.689,2.797,1.562,7.059,4.257,4.256C86.612,85.665,93.112,68.577,93.32,50.001C93.32,50,93.32,50,93.32,50
+ C93.32,50,93.32,50,93.32,50z"/>
+</svg>
View
5 demos/audio_streamer/start_servers.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+node server.js --port=8080
+serve 8000
+#open localhost:3000
View
148 demos/gamepad.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>GamePad Window</title>
+<style>
+body {
+ background-color: black;
+ color: white;
+}
+#pad {
+ background: #fff;
+ padding: 10px;
+ border-radius: 5px;
+ border: 1px solid #ccc;
+ z-index: 1000;
+ position: absolute;
+ bottom: 25px;
+ right: 25px;
+}
+#pad td {
+ height: 25px;
+ width: 25px;
+ border-radius: 5px;
+}
+#pad td[data-pos] {
+ border: 1px solid rgba(255,0,0,0.5);
+ background: rgba(255,0,0,0.1);
+}
+#pad td.on {
+ background: red;
+ /*box-shadow: 1px 1px 3px black;*/
+ /*border: 1px solid darkred;*/
+}
+#pad tr:first-of-type :nth-child(2) {
+ border-top-left-radius: 15px;
+ border-top-right-radius: 15px;
+}
+#pad tr:nth-child(2) :nth-child(1) {
+
+ border-top-left-radius: 15px;
+ border-bottom-left-radius: 15px;
+}
+#pad tr:nth-child(2) :nth-child(2) {
+
+}
+#pad tr:nth-child(2) :nth-child(3) {
+ border-top-right-radius: 15px;
+ border-bottom-right-radius: 15px;
+}
+#pad tr:nth-child(3) :nth-child(2) {
+ border-bottom-left-radius: 15px;
+ border-bottom-right-radius: 15px;
+}
+</style>
+</head>
+<body>
+
+<table id="pad">
+ <tr><td></td><td data-pos="UP"></td><td></td></tr>
+ <tr><td data-pos="LEFT"></td><td></td><td data-pos="RIGHT"></td></tr>
+ <tr><td></td><td data-pos="DOWN"></td><td></td></tr>
+</table>
+
+<p><button onclick="openWin();">Open Window</button></p>
+
+<pre></pre>
+
+<script>
+var output = document.querySelector('pre');
+var leftButton = document.querySelector('[data-pos="LEFT"]');
+var rightButton = document.querySelector('[data-pos="RIGHT"]');
+var upButton = document.querySelector('[data-pos="UP"]');
+var downButton = document.querySelector('[data-pos="DOWN"]');
+var popup = null;
+
+var pos = {
+ UP: -1,
+ DOWN: 1,
+ LEFT: -1,
+ RIGHT: 1
+};
+
+function openWin() {
+ var width = 300;
+ var height = 300;
+ popup = window.open('popup.html', '', 'width=' + width + ',height=' + height);
+ popup.moveTo((screen.width / 2) - width / 2, (screen.height / 2) - height / 2);
+ window.webkitRequestAnimationFrame(updateStatus);
+}
+
+function movePopupBy(xSign, ySign) {
+ if (!popup) {
+ return;
+ }
+
+ var DELTA = 10;
+ popup.moveBy(xSign * DELTA, ySign * DELTA);
+}
+
+function updateStatus() {
+ window.webkitRequestAnimationFrame(updateStatus);
+
+ var gamepads = navigator.webkitGamepads;
+
+ var data = '';
+ leftButton.classList.remove('on');
+ rightButton.classList.remove('on');
+ upButton.classList.remove('on');
+ downButton.classList.remove('on');
+
+ var pad = gamepads[0]; // ASSUME 1 GAMEPAD IS CONNECTED.
+ if (!pad) {
+ return;
+ }
+
+ var leftRightButtonPos = Math.round(pad.axes[0]);
+ var upDownButtonPos = Math.round(pad.axes[1]);
+
+ var pressedCoords = [0, 0, 0, 0];
+
+ if (leftRightButtonPos == pos.LEFT) {
+ pressedCoords[3] = 1;
+ leftButton.classList.add('on');
+ } else if (leftRightButtonPos == pos.RIGHT) {
+ pressedCoords[1] = 1;
+ rightButton.classList.add('on');
+ }
+
+ if (upDownButtonPos == pos.UP) {
+ pressedCoords[0] = 1;
+ upButton.classList.add('on');
+ } else if (upDownButtonPos == pos.DOWN) {
+ pressedCoords[2] = 1;
+ downButton.classList.add('on');
+ }
+
+ movePopupBy(leftRightButtonPos, upDownButtonPos);
+
+ data += pad.index + ": " + pad.id + "<br/>";
+ for (var i = 0; i < pad.buttons.length; ++i) {
+ data += "button" + i + ": " + pad.buttons[i] + "<br/>";
+ }
+
+ output.innerHTML = data;
+}
+</script>
+</body>
+</html>
View
7 demos/popup.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<title>Lil Guy</title>
+<body>
+HI
+</body>
+</html>
View
BIN  demos/screenshare/.DS_Store
Binary file not shown
View
7 demos/screenshare/README.md
@@ -0,0 +1,7 @@
+To run this demo:
+
+1. Start the WebSocket server: `node server.js`
+2. Install screenshare.crx by dragging it onto `chrome://extensions` page.
+3. Click the browser action icon. This should opener the viewer page in a new tab.
+4. Open another window (the presenter) and click the browser action icon.
+ This should connect the apps and present the web page this tab visits.
View
56 demos/screenshare/app.js
@@ -0,0 +1,56 @@
+var WS_HOST = 'localhost:3000';
+
+function connect() {
+ ws = new WebSocket('ws://' + WS_HOST, 'dumby-protocol');
+ ws.binaryType = 'blob';
+
+ ws.onopen = function(e) {
+ console.log('Connection OPEN');
+ send({cmd: 'START'});
+ };
+
+ ws.onmessage = function(e) {
+ var data = e.data;
+ console.log(data);
+ };
+
+ ws.onclose = function(e) {
+ console.log('Connection CLOSED');
+ };
+
+ ws.onerror = function(e) {
+ console.log('Connection ERROR', e);
+ };
+
+ return ws;
+}
+
+function disconnect() {
+ ws.close();
+}
+
+function send(data) {
+ // Stringify JSON data if we try to send it.
+ if ((typeof data == 'object') && (data.__proto__ !== Blob.prototype)) {
+ data = JSON.stringify(data);
+ }
+ ws.send(data);
+}
+
+function convertDataURIToBlob(dataURI, mimetype) {
+ if (!dataURI) {
+ return new Uint8Array(0);
+ }
+
+ var BASE64_MARKER = ';base64,';
+ var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
+ var base64 = dataURI.substring(base64Index);
+ var raw = window.atob(base64);
+ var uInt8Array = new Uint8Array(raw.length);
+
+ for (var i = 0; i < uInt8Array.length; ++i) {
+ uInt8Array[i] = raw.charCodeAt(i);
+ }
+
+ return new Blob([uInt8Array], {type: mimetype});
+}
View
27 demos/screenshare/background.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!--
+Copyright 2012 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Author: Eric Bidelman (ericbidelman@chromium.org)
+-->
+<html>
+<head>
+ <title>Background Page</title>
+</head>
+<body>
+</body>
+<script src="app.js"></script>
+<script src="background.js"></script>
+</html>
View
57 demos/screenshare/background.js
@@ -0,0 +1,57 @@
+var IMG_FORMAT = 'jpeg';
+var IMG_MIMETYPE = 'image/' + IMG_FORMAT;
+var IMG_QUALITY = 80;
+var SEND_INTERVAL_MS = 250;
+var ws = null;
+var intervalId = null;
+var VIEWER_TAB_ID = null;
+
+chrome.browserAction.setBadgeBackgroundColor({color: [255, 0, 0, 100]});
+
+function captureAndSendTab() {
+ // Update to webp when crbug.com/112957 is fixed.
+ // captureVisibleTab only returns a dataURL. Need to decode it and create a
+ // typed array.
+ var opts = {
+ format: IMG_FORMAT,
+ quality: IMG_QUALITY
+ };
+
+ chrome.tabs.captureVisibleTab(null, opts, function(dataUrl) {
+ send(convertDataURIToBlob(dataUrl, IMG_MIMETYPE));
+ });
+}
+
+chrome.browserAction.onClicked.addListener(function(tab) {
+ if (!intervalId) {
+ ws = connect();
+
+ if (!VIEWER_TAB_ID) {
+ chrome.tabs.create({url: 'viewer.html'}, function(tab) {
+ VIEWER_TAB_ID = tab.id;
+ });
+ return;
+ }
+
+ chrome.browserAction.setBadgeText({text: 'ON'});
+
+ // Rate limit how much we're sending through the websocket.
+ intervalId = setInterval(function() {
+ if (ws.bufferedAmount == 0) {
+ captureAndSendTab();
+ }
+ }, SEND_INTERVAL_MS); // TODO: optimize this number
+ } else {
+ clearInterval(intervalId);
+ chrome.browserAction.setBadgeText({text: ''});
+ send({cmd: 'DONE'});
+ intervalId = null;
+ ws = null;
+ }
+});
+
+chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
+ if (tabId == VIEWER_TAB_ID) {
+ VIEWER_TAB_ID = null;
+ }
+});
View
BIN  demos/screenshare/img/.DS_Store
Binary file not shown
View
BIN  demos/screenshare/img/screen.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
26 demos/screenshare/manifest.json
@@ -0,0 +1,26 @@
+{
+ "name": "Screenshare",
+ "description": "Screen capture browser using binary WebSockets",
+ "version": "0.0.1",
+ "icons": {
+ "16": "img/screen.png",
+ "32": "img/screen.png",
+ "48": "img/screen.png",
+ "128": "img/screen.png"
+ },
+ "manifest_version": 2,
+ "browser_action": {
+ "default_title": "Test",
+ "default_icon": "img/screen.png"
+ /*"default_popup": "popup.html"*/
+ },
+ "background": {
+ "page": "background.html"
+ },
+ "options_page": "options.html",
+ "permissions": [
+ "tabs",
+ "<all_urls>",
+ "http://*/*"
+ ]
+}
View
63 demos/screenshare/options.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<!--
+Copyright 2011 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Author: Eric Bidelman (ericbidelman@chromium.org)
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<style>
+body {
+ font-size: 14px;
+}
+input {
+ vertical-align: middle;
+ height: 20px;
+}
+input[type="range"]:after {
+ position: relative;
+ left: 135px;
+ top: -17px;
+}
+#refresh-rate:after {
+ content: attr(data-value) 'ms';
+}
+#quality:after {
+ content: attr(data-value) '%';
+}
+</style>
+</head>
+<body>
+
+<p>Refresh rate: <input type="range" data-value="250" id="refresh-rate" min="50" max="1000" value="250"></p>
+<p>Image quality: <input type="range" data-value="100" id="quality" min="0" max="100" value="100"></p>
+
+<script>
+var bgPage = chrome.extension.getBackgroundPage();
+
+document.querySelector('#refresh-rate').onchange = function(e) {
+ bgPage.SEND_INTERVAL_MS = this.valueAsNumber;
+ this.dataset.value = this.valueAsNumber;
+};
+
+document.querySelector('#quality').onchange = function(e) {
+ bgPage.IMG_QUALITY = this.valueAsNumber;
+ this.dataset.value = this.valueAsNumber;
+}
+</script>
+</body>
+</html>
View
38 demos/screenshare/popup.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!--
+Copyright 2012 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Author: Eric Bidelman (ericbidelman@chromium.org)
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<style>
+
+</style>
+</head>
+<body>
+
+<input type="text">
+
+<script>
+//var bgPage = chrome.extension.getBackgroundPage();
+
+var id = Date.now();
+document.querySelector('input').value = id;
+</script>
+</body>
+</html>
View
BIN  demos/screenshare/screenshare.crx
Binary file not shown
View
62 demos/screenshare/server.js
@@ -0,0 +1,62 @@
+#!/usr/bin/env node
+
+//var WebSocketServer = require('../websocket_node/lib/WebSocketServer');
+var WebSocketServer = require('ws').Server;
+
+var http = require('http');
+var url = require('url');
+var fs = require('fs');
+
+var args = { /* defaults */
+ port: '3000'
+};
+
+/* Parse command line options */
+var pattern = /^--(.*?)(?:=(.*))?$/;
+process.argv.forEach(function(value) {
+ var match = pattern.exec(value);
+ if (match) {
+ args[match[1]] = match[2] ? match[2] : true;
+ }
+});
+
+var port = parseInt(args.port, 10);
+
+console.log("Usage: ./server.js [--port=3000]");
+
+var connections = {};
+
+// ws is the fastest websocket lib:
+// http://einaros.github.com/ws/
+var wsServer = new WebSocketServer({port: port});
+
+wsServer.on('connection', function(ws) {
+
+ ws.id = Date.now(); // Assign unique id to this ws connection.
+ connections[ws.id] = ws;
+
+ console.log((new Date()) + ' Connection accepted: ' + ws.id);
+
+ ws.on('message', function(message, flags) {
+ console.log('Received ' + (flags.binary ? 'binary' : '') + ' message: ' +
+ message.length + ' bytes.');
+ broadcast(message, this, flags);
+ });
+
+ ws.on('close', function() {
+ console.log((new Date()) + " Peer " + this.id + " disconnected.");
+ delete connections[this.id];
+ });
+});
+
+// Broadcasts a message to all connected sockets accept for the sender.
+function broadcast(message, fromWs, flags) {
+ for (var id in connections) {
+ if (id != fromWs.id) {
+ connections[id].send(message, {
+ binary: flags.binary ? true : false,
+ mask: f