├── .bowerrc ├── .gitignore ├── README.md ├── bower.json ├── build ├── talking-image.js └── talking-image.min.js ├── demos ├── index.js └── public │ ├── 2001.html │ ├── 2001.jpg │ ├── bruce.gif │ ├── bruce.html │ ├── demos.html │ ├── johnny.gif │ ├── johnny.html │ ├── nyan-safari.gif │ ├── nyan-safari.html │ ├── nyan.gif │ └── nyan.html ├── gruntfile.js ├── lib ├── jbinary.js └── jdataview.js ├── package.json ├── resources ├── 2001.jpg ├── 2001.mp3 ├── 2001.ogg ├── bruce.gif ├── bruce.mp3 ├── bruce.ogg ├── johnny.gif ├── johnny.mp3 ├── johnny.ogg ├── nyan.gif ├── nyan.mp3 └── nyan.ogg └── src └── talking-image.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "libs" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | libs/* 3 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Talking Image 2 | ============= 3 | 4 | *Add audio to Web images* 5 | 6 | ## What is Talking Image? 7 | 8 | Talking Image is a JavaScript library for playing audio appended to GIF, JPEG, and PNG images. Include the [talking-image.min.js](https://raw.github.com/hacksparrow/talking-image/master/build/talking-image.min.js) on a webpage with a talking image (image with appended audio) and hear the sound come alive. 9 | 10 | ## Give me a quick demo! 11 | 12 | Demos be here: [http://hacksparrow.github.io/talking-image/](http://hacksparrow.github.io/talking-image/) 13 | 14 | To see the demos from the repo, make sure you have Node.js installed on your system. Clone the repo and run the demo server to see the demos. Follow these instructions: 15 | 16 | $ git clone git@github.com:hacksparrow/talking-image.git 17 | $ npm install 18 | $ node demos 19 | 20 | Then load [http://localhost:3000/demos.html](http://localhost:3000/demos.html). 21 | 22 | If you don't want to clone the repo, download [talking-image.min.js](https://raw.github.com/hacksparrow/talking-image/master/build/talking-image.min.js) and the files from the [demo/public](https://github.com/hacksparrow/talking-image/tree/master/demos/public) directory and host them on any HTTP server of your own. 23 | 24 | ## What is a talking image 25 | 26 | Talking image refers to an image with an audio payload appended at the end of image data. Currently OGG and MP3 audio formats are supported. 27 | 28 | ## How do I add talking images to my webpage? 29 | 30 | You use the regular `` tag but add an additional attribute called `data-audio`, in which you set the options. Eg: ``. 31 | 32 | **Options for the `audio` attribute** 33 | 34 | 1. *autoplay* - start playing the audio as soon as possible. If autoplay is not set, you will have to click on the image to start playing the sound. 35 | 2. *sync* - try to sync the animation and the audio. There is no guarantee they will be in sync. 36 | 3. *loop* - loop the audio 37 | 4. *volume* - specify the volume. 1 for max volume. Eg: `` 38 | 39 | Note: You can click on the image to mute the audio any time you want. 40 | 41 | ## Where can I find some talking images? 42 | 43 | This project hosts some talking images in the [public directory](https://github.com/hacksparrow/talking-image/tree/master/demos/public) under the `demos` directory. 44 | 45 | ## How can I create talking images? 46 | 47 | The idea behind talking images is to append OGG or MP3 data to an existing image file - nothing more than that. 48 | 49 | On Linux / Mac: 50 | 51 | $ cat music.ogg >> funny.gif 52 | or 53 | 54 | $ cat beethoven.mp3 >> welcome.jpg 55 | 56 | On Windows: 57 | 58 | > copy /b funny.gif + music.ogg funny-music.gif 59 | 60 | There are some 'clean' images and audio snippets in the `resources` directory, play around with it. 61 | 62 | **Note:** Audio file should be appended to the image file to create a valid talking file. Reversing the order will generate a corrupted file. 63 | 64 | ## Why is it called Talking Image? 65 | 66 | Images on the Web have been 'mute' so far, the technique described here and the library add sound to Web images. The 'talking' in Talking Image comes from [talkies](http://en.wikipedia.org/wiki/Sound_film). 67 | 68 | ## Is it of any use? 69 | 70 | Now you can enjoy the audio-visual experience of [Nyan Cat](http://nyancatmusical.neocities.org/) for infinite hours without loading a YouTube video. 71 | 72 | ## How does it work? 73 | 74 | Using binary data in the browser powered by [jDataView](http://github.com/jDataView/jDataView) and [jBinary](https://github.com/jDataView/jBinary). 75 | 76 | ## License (MIT) 77 | 78 | Copyright (c) 2012 Hage Yaapa <[http://www.hacksparrow.com](http://www.hacksparrow.com)> 79 | 80 | Permission is hereby granted, free of charge, to any person obtaining a copy 81 | of this software and associated documentation files (the "Software"), to deal 82 | in the Software without restriction, including without limitation the rights 83 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 84 | copies of the Software, and to permit persons to whom the Software is 85 | furnished to do so, subject to the following conditions: 86 | 87 | The above copyright notice and this permission notice shall be included in 88 | all copies or substantial portions of the Software. 89 | 90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 91 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 92 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 93 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 94 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 95 | 96 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "talking-image", 3 | "dependencies": { 4 | "jdataview": "latest", 5 | "jbinary": "latest" 6 | } 7 | } -------------------------------------------------------------------------------- /build/talking-image.js: -------------------------------------------------------------------------------- 1 | // 2 | // jDataView by Vjeux - Jan 2010 3 | // Continued by RReverser - Feb 2013 4 | // 5 | // A unique way to work with a binary file in the browser 6 | // http://github.com/jDataView/jDataView 7 | // http://jDataView.github.io/ 8 | 9 | (function (global) { 10 | 11 | 'use strict'; 12 | 13 | var compatibility = { 14 | // NodeJS Buffer in v0.5.5 and newer 15 | NodeBuffer: 'Buffer' in global && 'readInt16LE' in Buffer.prototype, 16 | DataView: 'DataView' in global && ( 17 | 'getFloat64' in DataView.prototype || // Chrome 18 | 'getFloat64' in new DataView(new ArrayBuffer(1)) // Node 19 | ), 20 | ArrayBuffer: 'ArrayBuffer' in global, 21 | PixelData: 'CanvasPixelArray' in global && 'ImageData' in global && 'document' in global 22 | }; 23 | 24 | // we don't want to bother with old Buffer implementation 25 | if (compatibility.NodeBuffer) { 26 | (function (buffer) { 27 | try { 28 | buffer.writeFloatLE(Infinity, 0); 29 | } catch (e) { 30 | compatibility.NodeBuffer = false; 31 | } 32 | })(new Buffer(4)); 33 | } 34 | 35 | if (compatibility.PixelData) { 36 | var createPixelData = function (byteLength, buffer) { 37 | var data = createPixelData.context2d.createImageData((byteLength + 3) / 4, 1).data; 38 | data.byteLength = byteLength; 39 | if (buffer !== undefined) { 40 | for (var i = 0; i < byteLength; i++) { 41 | data[i] = buffer[i]; 42 | } 43 | } 44 | return data; 45 | }; 46 | createPixelData.context2d = document.createElement('canvas').getContext('2d'); 47 | } 48 | 49 | var dataTypes = { 50 | 'Int8': 1, 51 | 'Int16': 2, 52 | 'Int32': 4, 53 | 'Uint8': 1, 54 | 'Uint16': 2, 55 | 'Uint32': 4, 56 | 'Float32': 4, 57 | 'Float64': 8 58 | }; 59 | 60 | var nodeNaming = { 61 | 'Int8': 'Int8', 62 | 'Int16': 'Int16', 63 | 'Int32': 'Int32', 64 | 'Uint8': 'UInt8', 65 | 'Uint16': 'UInt16', 66 | 'Uint32': 'UInt32', 67 | 'Float32': 'Float', 68 | 'Float64': 'Double' 69 | }; 70 | 71 | function arrayFrom(arrayLike, forceCopy) { 72 | return (!forceCopy && (arrayLike instanceof Array)) ? arrayLike : Array.prototype.slice.call(arrayLike); 73 | } 74 | 75 | function defined(value, defaultValue) { 76 | return value !== undefined ? value : defaultValue; 77 | } 78 | 79 | function jDataView(buffer, byteOffset, byteLength, littleEndian) { 80 | /* jshint validthis:true */ 81 | 82 | if (buffer instanceof jDataView) { 83 | var result = buffer.slice(byteOffset, byteOffset + byteLength); 84 | result._littleEndian = defined(littleEndian, result._littleEndian); 85 | return result; 86 | } 87 | 88 | if (!(this instanceof jDataView)) { 89 | return new jDataView(buffer, byteOffset, byteLength, littleEndian); 90 | } 91 | 92 | this.buffer = buffer = jDataView.wrapBuffer(buffer); 93 | 94 | // Check parameters and existing functionnalities 95 | this._isArrayBuffer = compatibility.ArrayBuffer && buffer instanceof ArrayBuffer; 96 | this._isPixelData = compatibility.PixelData && buffer instanceof CanvasPixelArray; 97 | this._isDataView = compatibility.DataView && this._isArrayBuffer; 98 | this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer; 99 | 100 | // Handle Type Errors 101 | if (!this._isNodeBuffer && !this._isArrayBuffer && !this._isPixelData && !(buffer instanceof Array)) { 102 | throw new TypeError('jDataView buffer has an incompatible type'); 103 | } 104 | 105 | // Default Values 106 | this._littleEndian = !!littleEndian; 107 | 108 | var bufferLength = 'byteLength' in buffer ? buffer.byteLength : buffer.length; 109 | this.byteOffset = byteOffset = defined(byteOffset, 0); 110 | this.byteLength = byteLength = defined(byteLength, bufferLength - byteOffset); 111 | 112 | if (!this._isDataView) { 113 | this._checkBounds(byteOffset, byteLength, bufferLength); 114 | } else { 115 | this._view = new DataView(buffer, byteOffset, byteLength); 116 | } 117 | 118 | // Create uniform methods (action wrappers) for the following data types 119 | 120 | this._engineAction = 121 | this._isDataView 122 | ? this._dataViewAction 123 | : this._isNodeBuffer 124 | ? this._nodeBufferAction 125 | : this._isArrayBuffer 126 | ? this._arrayBufferAction 127 | : this._arrayAction; 128 | } 129 | 130 | function getCharCodes(string) { 131 | if (compatibility.NodeBuffer) { 132 | return new Buffer(string, 'binary'); 133 | } 134 | 135 | var Type = compatibility.ArrayBuffer ? Uint8Array : Array, 136 | codes = new Type(string.length); 137 | 138 | for (var i = 0, length = string.length; i < length; i++) { 139 | codes[i] = string.charCodeAt(i) & 0xff; 140 | } 141 | return codes; 142 | } 143 | 144 | // mostly internal function for wrapping any supported input (String or Array-like) to best suitable buffer format 145 | jDataView.wrapBuffer = function (buffer) { 146 | switch (typeof buffer) { 147 | case 'number': 148 | if (compatibility.NodeBuffer) { 149 | buffer = new Buffer(buffer); 150 | buffer.fill(0); 151 | } else 152 | if (compatibility.ArrayBuffer) { 153 | buffer = new Uint8Array(buffer).buffer; 154 | } else 155 | if (compatibility.PixelData) { 156 | buffer = createPixelData(buffer); 157 | } else { 158 | buffer = new Array(buffer); 159 | for (var i = 0; i < buffer.length; i++) { 160 | buffer[i] = 0; 161 | } 162 | } 163 | return buffer; 164 | 165 | case 'string': 166 | buffer = getCharCodes(buffer); 167 | /* falls through */ 168 | default: 169 | if ('length' in buffer && !((compatibility.NodeBuffer && buffer instanceof Buffer) || (compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) || (compatibility.PixelData && buffer instanceof CanvasPixelArray))) { 170 | if (compatibility.NodeBuffer) { 171 | buffer = new Buffer(buffer); 172 | } else 173 | if (compatibility.ArrayBuffer) { 174 | if (!(buffer instanceof ArrayBuffer)) { 175 | buffer = buffer instanceof Uint8Array ? buffer.buffer : new Uint8Array(buffer).buffer; 176 | } 177 | } else 178 | if (compatibility.PixelData) { 179 | buffer = createPixelData(buffer.length, buffer); 180 | } else { 181 | buffer = arrayFrom(buffer); 182 | } 183 | } 184 | return buffer; 185 | } 186 | }; 187 | 188 | function pow2(n) { 189 | return (n >= 0 && n < 31) ? (1 << n) : (pow2[n] || (pow2[n] = Math.pow(2, n))); 190 | } 191 | 192 | // left for backward compatibility 193 | jDataView.createBuffer = function () { 194 | return jDataView.wrapBuffer(arguments); 195 | }; 196 | 197 | function Uint64(lo, hi) { 198 | this.lo = lo; 199 | this.hi = hi; 200 | } 201 | 202 | jDataView.Uint64 = Uint64; 203 | 204 | Uint64.prototype = { 205 | valueOf: function () { 206 | return this.lo + pow2(32) * this.hi; 207 | }, 208 | 209 | toString: function () { 210 | return Number.prototype.toString.apply(this.valueOf(), arguments); 211 | } 212 | }; 213 | 214 | Uint64.fromNumber = function (number) { 215 | var hi = Math.floor(number / pow2(32)), 216 | lo = number - hi * pow2(32); 217 | 218 | return new Uint64(lo, hi); 219 | }; 220 | 221 | function Int64(lo, hi) { 222 | Uint64.apply(this, arguments); 223 | } 224 | 225 | jDataView.Int64 = Int64; 226 | 227 | Int64.prototype = 'create' in Object ? Object.create(Uint64.prototype) : new Uint64(); 228 | 229 | Int64.prototype.valueOf = function () { 230 | if (this.hi < pow2(31)) { 231 | return Uint64.prototype.valueOf.apply(this, arguments); 232 | } 233 | return -((pow2(32) - this.lo) + pow2(32) * (pow2(32) - 1 - this.hi)); 234 | }; 235 | 236 | Int64.fromNumber = function (number) { 237 | var lo, hi; 238 | if (number >= 0) { 239 | var unsigned = Uint64.fromNumber(number); 240 | lo = unsigned.lo; 241 | hi = unsigned.hi; 242 | } else { 243 | hi = Math.floor(number / pow2(32)); 244 | lo = number - hi * pow2(32); 245 | hi += pow2(32); 246 | } 247 | return new Int64(lo, hi); 248 | }; 249 | 250 | jDataView.prototype = { 251 | _offset: 0, 252 | _bitOffset: 0, 253 | 254 | compatibility: compatibility, 255 | 256 | _checkBounds: function (byteOffset, byteLength, maxLength) { 257 | // Do additional checks to simulate DataView 258 | if (typeof byteOffset !== 'number') { 259 | throw new TypeError('Offset is not a number.'); 260 | } 261 | if (typeof byteLength !== 'number') { 262 | throw new TypeError('Size is not a number.'); 263 | } 264 | if (byteLength < 0) { 265 | throw new RangeError('Length is negative.'); 266 | } 267 | if (byteOffset < 0 || byteOffset + byteLength > defined(maxLength, this.byteLength)) { 268 | throw new RangeError('Offsets are out of bounds.'); 269 | } 270 | }, 271 | 272 | _action: function (type, isReadAction, byteOffset, littleEndian, value) { 273 | return this._engineAction( 274 | type, 275 | isReadAction, 276 | defined(byteOffset, this._offset), 277 | defined(littleEndian, this._littleEndian), 278 | value 279 | ); 280 | }, 281 | 282 | _dataViewAction: function (type, isReadAction, byteOffset, littleEndian, value) { 283 | // Move the internal offset forward 284 | this._offset = byteOffset + dataTypes[type]; 285 | return isReadAction ? this._view['get' + type](byteOffset, littleEndian) : this._view['set' + type](byteOffset, value, littleEndian); 286 | }, 287 | 288 | _nodeBufferAction: function (type, isReadAction, byteOffset, littleEndian, value) { 289 | // Move the internal offset forward 290 | this._offset = byteOffset + dataTypes[type]; 291 | var nodeName = nodeNaming[type] + ((type === 'Int8' || type === 'Uint8') ? '' : littleEndian ? 'LE' : 'BE'); 292 | byteOffset += this.byteOffset; 293 | return isReadAction ? this.buffer['read' + nodeName](byteOffset) : this.buffer['write' + nodeName](value, byteOffset); 294 | }, 295 | 296 | _arrayBufferAction: function (type, isReadAction, byteOffset, littleEndian, value) { 297 | var size = dataTypes[type], TypedArray = global[type + 'Array'], typedArray; 298 | 299 | littleEndian = defined(littleEndian, this._littleEndian); 300 | 301 | // ArrayBuffer: we use a typed array of size 1 from original buffer if alignment is good and from slice when it's not 302 | if (size === 1 || ((this.byteOffset + byteOffset) % size === 0 && littleEndian)) { 303 | typedArray = new TypedArray(this.buffer, this.byteOffset + byteOffset, 1); 304 | this._offset = byteOffset + size; 305 | return isReadAction ? typedArray[0] : (typedArray[0] = value); 306 | } else { 307 | var bytes = new Uint8Array(isReadAction ? this.getBytes(size, byteOffset, littleEndian, true) : size); 308 | typedArray = new TypedArray(bytes.buffer, 0, 1); 309 | 310 | if (isReadAction) { 311 | return typedArray[0]; 312 | } else { 313 | typedArray[0] = value; 314 | this._setBytes(byteOffset, bytes, littleEndian); 315 | } 316 | } 317 | }, 318 | 319 | _arrayAction: function (type, isReadAction, byteOffset, littleEndian, value) { 320 | return isReadAction ? this['_get' + type](byteOffset, littleEndian) : this['_set' + type.replace('Uint', 'Int')](byteOffset, value, littleEndian); 321 | }, 322 | 323 | // Helpers 324 | 325 | _getBytes: function (length, byteOffset, littleEndian) { 326 | littleEndian = defined(littleEndian, this._littleEndian); 327 | byteOffset = defined(byteOffset, this._offset); 328 | length = defined(length, this.byteLength - byteOffset); 329 | 330 | this._checkBounds(byteOffset, length); 331 | 332 | byteOffset += this.byteOffset; 333 | 334 | this._offset = byteOffset - this.byteOffset + length; 335 | 336 | var result = this._isArrayBuffer 337 | ? new Uint8Array(this.buffer, byteOffset, length) 338 | : (this.buffer.slice || Array.prototype.slice).call(this.buffer, byteOffset, byteOffset + length); 339 | 340 | return littleEndian || length <= 1 ? result : arrayFrom(result).reverse(); 341 | }, 342 | 343 | // wrapper for external calls (do not return inner buffer directly to prevent it's modifying) 344 | getBytes: function (length, byteOffset, littleEndian, toArray) { 345 | var result = this._getBytes(length, byteOffset, defined(littleEndian, true)); 346 | return toArray ? arrayFrom(result) : result; 347 | }, 348 | 349 | _setBytes: function (byteOffset, bytes, littleEndian) { 350 | var length = bytes.length; 351 | 352 | // needed for Opera 353 | if (length === 0) { 354 | return; 355 | } 356 | 357 | littleEndian = defined(littleEndian, this._littleEndian); 358 | byteOffset = defined(byteOffset, this._offset); 359 | 360 | this._checkBounds(byteOffset, length); 361 | 362 | if (!littleEndian && length > 1) { 363 | bytes = arrayFrom(bytes, true).reverse(); 364 | } 365 | 366 | byteOffset += this.byteOffset; 367 | 368 | if (this._isArrayBuffer) { 369 | new Uint8Array(this.buffer, byteOffset, length).set(bytes); 370 | } 371 | else { 372 | if (this._isNodeBuffer) { 373 | new Buffer(bytes).copy(this.buffer, byteOffset); 374 | } else { 375 | for (var i = 0; i < length; i++) { 376 | this.buffer[byteOffset + i] = bytes[i]; 377 | } 378 | } 379 | } 380 | 381 | this._offset = byteOffset - this.byteOffset + length; 382 | }, 383 | 384 | setBytes: function (byteOffset, bytes, littleEndian) { 385 | this._setBytes(byteOffset, bytes, defined(littleEndian, true)); 386 | }, 387 | 388 | writeBytes: function (bytes, littleEndian) { 389 | this.setBytes(undefined, bytes, littleEndian); 390 | }, 391 | 392 | getString: function (byteLength, byteOffset, encoding) { 393 | if (this._isNodeBuffer) { 394 | byteOffset = defined(byteOffset, this._offset); 395 | byteLength = defined(byteLength, this.byteLength - byteOffset); 396 | 397 | this._checkBounds(byteOffset, byteLength); 398 | 399 | this._offset = byteOffset + byteLength; 400 | return this.buffer.toString(encoding || 'binary', this.byteOffset + byteOffset, this.byteOffset + this._offset); 401 | } 402 | var bytes = this._getBytes(byteLength, byteOffset, true), string = ''; 403 | byteLength = bytes.length; 404 | for (var i = 0; i < byteLength; i++) { 405 | string += String.fromCharCode(bytes[i]); 406 | } 407 | if (encoding === 'utf8') { 408 | string = decodeURIComponent(escape(string)); 409 | } 410 | return string; 411 | }, 412 | 413 | setString: function (byteOffset, subString, encoding) { 414 | if (this._isNodeBuffer) { 415 | byteOffset = defined(byteOffset, this._offset); 416 | this._checkBounds(byteOffset, subString.length); 417 | this._offset = byteOffset + this.buffer.write(subString, this.byteOffset + byteOffset, encoding || 'binary'); 418 | return; 419 | } 420 | if (encoding === 'utf8') { 421 | subString = unescape(encodeURIComponent(subString)); 422 | } 423 | this._setBytes(byteOffset, getCharCodes(subString), true); 424 | }, 425 | 426 | writeString: function (subString, encoding) { 427 | this.setString(undefined, subString, encoding); 428 | }, 429 | 430 | getChar: function (byteOffset) { 431 | return this.getString(1, byteOffset); 432 | }, 433 | 434 | setChar: function (byteOffset, character) { 435 | this.setString(byteOffset, character); 436 | }, 437 | 438 | writeChar: function (character) { 439 | this.setChar(undefined, character); 440 | }, 441 | 442 | tell: function () { 443 | return this._offset; 444 | }, 445 | 446 | seek: function (byteOffset) { 447 | this._checkBounds(byteOffset, 0); 448 | /* jshint boss: true */ 449 | return this._offset = byteOffset; 450 | }, 451 | 452 | skip: function (byteLength) { 453 | return this.seek(this._offset + byteLength); 454 | }, 455 | 456 | slice: function (start, end, forceCopy) { 457 | return forceCopy 458 | ? new jDataView(this.getBytes(end - start, start, true, true), undefined, undefined, this._littleEndian) 459 | : new jDataView(this.buffer, this.byteOffset + start, end - start, this._littleEndian); 460 | }, 461 | 462 | // Compatibility functions 463 | 464 | _getFloat64: function (byteOffset, littleEndian) { 465 | var b = this._getBytes(8, byteOffset, littleEndian), 466 | 467 | sign = 1 - (2 * (b[7] >> 7)), 468 | exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1), 469 | 470 | // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead 471 | mantissa = ((b[6] & 0x0f) * pow2(48)) + (b[5] * pow2(40)) + (b[4] * pow2(32)) + 472 | (b[3] * pow2(24)) + (b[2] * pow2(16)) + (b[1] * pow2(8)) + b[0]; 473 | 474 | if (exponent === 1024) { 475 | if (mantissa !== 0) { 476 | return NaN; 477 | } else { 478 | return sign * Infinity; 479 | } 480 | } 481 | 482 | if (exponent === -1023) { // Denormalized 483 | return sign * mantissa * pow2(-1022 - 52); 484 | } 485 | 486 | return sign * (1 + mantissa * pow2(-52)) * pow2(exponent); 487 | }, 488 | 489 | _getFloat32: function (byteOffset, littleEndian) { 490 | var b = this._getBytes(4, byteOffset, littleEndian), 491 | 492 | sign = 1 - (2 * (b[3] >> 7)), 493 | exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127, 494 | mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0]; 495 | 496 | if (exponent === 128) { 497 | if (mantissa !== 0) { 498 | return NaN; 499 | } else { 500 | return sign * Infinity; 501 | } 502 | } 503 | 504 | if (exponent === -127) { // Denormalized 505 | return sign * mantissa * pow2(-126 - 23); 506 | } 507 | 508 | return sign * (1 + mantissa * pow2(-23)) * pow2(exponent); 509 | }, 510 | 511 | _get64: function (Type, byteOffset, littleEndian) { 512 | littleEndian = defined(littleEndian, this._littleEndian); 513 | byteOffset = defined(byteOffset, this._offset); 514 | 515 | var parts = littleEndian ? [0, 4] : [4, 0]; 516 | 517 | for (var i = 0; i < 2; i++) { 518 | parts[i] = this.getUint32(byteOffset + parts[i], littleEndian); 519 | } 520 | 521 | this._offset = byteOffset + 8; 522 | 523 | return new Type(parts[0], parts[1]); 524 | }, 525 | 526 | getInt64: function (byteOffset, littleEndian) { 527 | return this._get64(Int64, byteOffset, littleEndian); 528 | }, 529 | 530 | getUint64: function (byteOffset, littleEndian) { 531 | return this._get64(Uint64, byteOffset, littleEndian); 532 | }, 533 | 534 | _getInt32: function (byteOffset, littleEndian) { 535 | var b = this._getBytes(4, byteOffset, littleEndian); 536 | return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; 537 | }, 538 | 539 | _getUint32: function (byteOffset, littleEndian) { 540 | return this._getInt32(byteOffset, littleEndian) >>> 0; 541 | }, 542 | 543 | _getInt16: function (byteOffset, littleEndian) { 544 | return (this._getUint16(byteOffset, littleEndian) << 16) >> 16; 545 | }, 546 | 547 | _getUint16: function (byteOffset, littleEndian) { 548 | var b = this._getBytes(2, byteOffset, littleEndian); 549 | return (b[1] << 8) | b[0]; 550 | }, 551 | 552 | _getInt8: function (byteOffset) { 553 | return (this._getUint8(byteOffset) << 24) >> 24; 554 | }, 555 | 556 | _getUint8: function (byteOffset) { 557 | return this._getBytes(1, byteOffset)[0]; 558 | }, 559 | 560 | getSigned: function (bitLength, byteOffset) { 561 | var shift = 32 - bitLength; 562 | return (this.getUnsigned(bitLength, byteOffset) << shift) >> shift; 563 | }, 564 | 565 | getUnsigned: function (bitLength, byteOffset) { 566 | var startBit = (defined(byteOffset, this._offset) << 3) + this._bitOffset, 567 | endBit = startBit + bitLength, 568 | start = startBit >>> 3, 569 | end = (endBit + 7) >>> 3, 570 | b = this._getBytes(end - start, start, true), 571 | value = 0; 572 | 573 | /* jshint boss: true */ 574 | if (this._bitOffset = endBit & 7) { 575 | this._bitOffset -= 8; 576 | } 577 | 578 | for (var i = 0, length = b.length; i < length; i++) { 579 | value = (value << 8) | b[i]; 580 | } 581 | 582 | value >>>= -this._bitOffset; 583 | 584 | return bitLength < 32 ? (value & ~(-1 << bitLength)) : value; 585 | }, 586 | 587 | _setBinaryFloat: function (byteOffset, value, mantSize, expSize, littleEndian) { 588 | var signBit = value < 0 ? 1 : 0, 589 | exponent, 590 | mantissa, 591 | eMax = ~(-1 << (expSize - 1)), 592 | eMin = 1 - eMax; 593 | 594 | if (value < 0) { 595 | value = -value; 596 | } 597 | 598 | if (value === 0) { 599 | exponent = 0; 600 | mantissa = 0; 601 | } else if (isNaN(value)) { 602 | exponent = 2 * eMax + 1; 603 | mantissa = 1; 604 | } else if (value === Infinity) { 605 | exponent = 2 * eMax + 1; 606 | mantissa = 0; 607 | } else { 608 | exponent = Math.floor(Math.log(value) / Math.LN2); 609 | if (exponent >= eMin && exponent <= eMax) { 610 | mantissa = Math.floor((value * pow2(-exponent) - 1) * pow2(mantSize)); 611 | exponent += eMax; 612 | } else { 613 | mantissa = Math.floor(value / pow2(eMin - mantSize)); 614 | exponent = 0; 615 | } 616 | } 617 | 618 | var b = []; 619 | while (mantSize >= 8) { 620 | b.push(mantissa % 256); 621 | mantissa = Math.floor(mantissa / 256); 622 | mantSize -= 8; 623 | } 624 | exponent = (exponent << mantSize) | mantissa; 625 | expSize += mantSize; 626 | while (expSize >= 8) { 627 | b.push(exponent & 0xff); 628 | exponent >>>= 8; 629 | expSize -= 8; 630 | } 631 | b.push((signBit << expSize) | exponent); 632 | 633 | this._setBytes(byteOffset, b, littleEndian); 634 | }, 635 | 636 | _setFloat32: function (byteOffset, value, littleEndian) { 637 | this._setBinaryFloat(byteOffset, value, 23, 8, littleEndian); 638 | }, 639 | 640 | _setFloat64: function (byteOffset, value, littleEndian) { 641 | this._setBinaryFloat(byteOffset, value, 52, 11, littleEndian); 642 | }, 643 | 644 | _set64: function (Type, byteOffset, value, littleEndian) { 645 | if (!(value instanceof Type)) { 646 | value = Type.fromNumber(value); 647 | } 648 | 649 | littleEndian = defined(littleEndian, this._littleEndian); 650 | byteOffset = defined(byteOffset, this._offset); 651 | 652 | var parts = littleEndian ? {lo: 0, hi: 4} : {lo: 4, hi: 0}; 653 | 654 | for (var partName in parts) { 655 | this.setUint32(byteOffset + parts[partName], value[partName], littleEndian); 656 | } 657 | 658 | this._offset = byteOffset + 8; 659 | }, 660 | 661 | setInt64: function (byteOffset, value, littleEndian) { 662 | this._set64(Int64, byteOffset, value, littleEndian); 663 | }, 664 | 665 | writeInt64: function (value, littleEndian) { 666 | this.setInt64(undefined, value, littleEndian); 667 | }, 668 | 669 | setUint64: function (byteOffset, value, littleEndian) { 670 | this._set64(Uint64, byteOffset, value, littleEndian); 671 | }, 672 | 673 | writeUint64: function (value, littleEndian) { 674 | this.setUint64(undefined, value, littleEndian); 675 | }, 676 | 677 | _setInt32: function (byteOffset, value, littleEndian) { 678 | this._setBytes(byteOffset, [ 679 | value & 0xff, 680 | (value >>> 8) & 0xff, 681 | (value >>> 16) & 0xff, 682 | value >>> 24 683 | ], littleEndian); 684 | }, 685 | 686 | _setInt16: function (byteOffset, value, littleEndian) { 687 | this._setBytes(byteOffset, [ 688 | value & 0xff, 689 | (value >>> 8) & 0xff 690 | ], littleEndian); 691 | }, 692 | 693 | _setInt8: function (byteOffset, value) { 694 | this._setBytes(byteOffset, [value & 0xff]); 695 | } 696 | }; 697 | 698 | var proto = jDataView.prototype; 699 | 700 | for (var type in dataTypes) { 701 | (function (type) { 702 | proto['get' + type] = function (byteOffset, littleEndian) { 703 | return this._action(type, true, byteOffset, littleEndian); 704 | }; 705 | proto['set' + type] = function (byteOffset, value, littleEndian) { 706 | this._action(type, false, byteOffset, littleEndian, value); 707 | }; 708 | proto['write' + type] = function (value, littleEndian) { 709 | this['set' + type](undefined, value, littleEndian); 710 | }; 711 | })(type); 712 | } 713 | 714 | if (typeof module === 'object' && module && typeof module.exports === 'object') { 715 | module.exports = jDataView; 716 | } else 717 | if (typeof define === 'function' && define.amd) { 718 | define([], function () { return jDataView }); 719 | } else { 720 | global.jDataView = jDataView; 721 | } 722 | 723 | })((function () { /* jshint strict: false */ return this })());;(function (global) { 724 | 725 | 'use strict'; 726 | 727 | // https://github.com/davidchambers/Base64.js (modified) 728 | if (!('atob' in global) || !('btoa' in global)) { 729 | // jshint:skipline 730 | (function(){var t=global,r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n=function(){try{document.createElement("$")}catch(t){return t}}();t.btoa||(t.btoa=function(t){for(var o,e,a=0,c=r,f="";t.charAt(0|a)||(c="=",a%1);f+=c.charAt(63&o>>8-8*(a%1))){if(e=t.charCodeAt(a+=.75),e>255)throw n;o=o<<8|e}return f}),t.atob||(t.atob=function(t){if(t=t.replace(/=+$/,""),1==t.length%4)throw n;for(var o,e,a=0,c=0,f="";e=t.charAt(c++);~e&&(o=a%4?64*o+e:e,a++%4)?f+=String.fromCharCode(255&o>>(6&-2*a)):0)e=r.indexOf(e);return f})})(); 731 | } 732 | 733 | function hasNodeRequire(name) { 734 | return typeof require === 'function' && !require.isBrowser && require(name); 735 | } 736 | 737 | var jDataView; 738 | 739 | function extend(obj) { 740 | for (var i = 1, length = arguments.length; i < length; ++i) { 741 | var source = arguments[i]; 742 | for (var prop in source) { 743 | if (source[prop] !== undefined) { 744 | obj[prop] = source[prop]; 745 | } 746 | } 747 | } 748 | return obj; 749 | } 750 | 751 | var _inherit = Object.create || function (obj) { 752 | var ClonedObject = function () {}; 753 | ClonedObject.prototype = obj; 754 | return new ClonedObject(); 755 | }; 756 | 757 | function inherit(obj) { 758 | arguments[0] = _inherit(obj); 759 | return extend.apply(null, arguments); 760 | } 761 | 762 | function toValue(obj, binary, value) { 763 | return value instanceof Function ? value.call(obj, binary.contexts[0]) : value; 764 | } 765 | 766 | function jBinary(view, typeSet) { 767 | /* jshint validthis:true */ 768 | if (!(view instanceof jDataView)) { 769 | view = new jDataView(view, undefined, undefined, typeSet ? typeSet['jBinary.littleEndian'] : undefined); 770 | } 771 | 772 | if (!(this instanceof jBinary)) { 773 | return new jBinary(view, typeSet); 774 | } 775 | 776 | this.view = view; 777 | this.view.seek(0); 778 | this._bitShift = 0; 779 | this.contexts = []; 780 | 781 | if (typeSet) { 782 | this.typeSet = (proto.typeSet === typeSet || proto.typeSet.isPrototypeOf(typeSet)) ? typeSet : inherit(proto.typeSet, typeSet); 783 | this.cacheKey = this._getCached(typeSet, function () { return proto.cacheKey + '.' + (++proto.id) }, true); 784 | } 785 | } 786 | 787 | var proto = jBinary.prototype; 788 | 789 | proto.cacheKey = 'jBinary.Cache'; 790 | proto.id = 0; 791 | 792 | var defineProperty = Object.defineProperty; 793 | 794 | if (defineProperty) { 795 | // this is needed to detect DOM-only version of Object.defineProperty in IE8: 796 | try { 797 | defineProperty({}, 'x', {}); 798 | } catch (e) { 799 | defineProperty = null; 800 | } 801 | } 802 | 803 | if (!defineProperty) { 804 | defineProperty = function (obj, key, descriptor, allowVisible) { 805 | if (allowVisible) { 806 | obj[key] = descriptor.value; 807 | } 808 | }; 809 | } 810 | 811 | proto._getCached = function (obj, valueAccessor, allowVisible) { 812 | if (!obj.hasOwnProperty(this.cacheKey)) { 813 | var value = valueAccessor.call(this, obj); 814 | defineProperty(obj, this.cacheKey, {value: value}, allowVisible); 815 | return value; 816 | } else { 817 | return obj[this.cacheKey]; 818 | } 819 | }; 820 | 821 | proto.getContext = function (filter) { 822 | switch (typeof filter) { 823 | case 'undefined': 824 | filter = 0; 825 | /* falls through */ 826 | case 'number': 827 | return this.contexts[filter]; 828 | 829 | case 'string': 830 | return this.getContext(function (context) { return filter in context }); 831 | 832 | case 'function': 833 | for (var i = 0, length = this.contexts.length; i < length; i++) { 834 | var context = this.contexts[i]; 835 | if (filter.call(this, context)) { 836 | return context; 837 | } 838 | } 839 | return; 840 | } 841 | }; 842 | 843 | proto.inContext = function (newContext, callback) { 844 | this.contexts.unshift(newContext); 845 | var result = callback.call(this); 846 | this.contexts.shift(); 847 | return result; 848 | }; 849 | 850 | jBinary.Type = function (config) { 851 | return inherit(jBinary.Type.prototype, config); 852 | }; 853 | 854 | jBinary.Type.prototype = { 855 | inherit: function (args, getType) { 856 | var _type = this, type; 857 | 858 | function withProp(name, callback) { 859 | var value = _type[name]; 860 | if (value) { 861 | if (!type) { 862 | type = inherit(_type); 863 | } 864 | callback.call(type, value); 865 | type[name] = null; 866 | } 867 | } 868 | 869 | withProp('params', function (params) { 870 | for (var i = 0, length = Math.min(params.length, args.length); i < length; i++) { 871 | this[params[i]] = args[i]; 872 | } 873 | }); 874 | 875 | withProp('setParams', function (setParams) { 876 | setParams.apply(this, args); 877 | }); 878 | 879 | withProp('typeParams', function (typeParams) { 880 | for (var i = 0, length = typeParams.length; i < length; i++) { 881 | var param = typeParams[i], descriptor = this[param]; 882 | if (descriptor) { 883 | this[param] = getType(descriptor); 884 | } 885 | } 886 | }); 887 | 888 | withProp('resolve', function (resolve) { 889 | resolve.call(this, getType); 890 | }); 891 | 892 | return type || _type; 893 | }, 894 | createProperty: function (binary) { 895 | return inherit(this, {binary: binary}); 896 | }, 897 | toValue: function (val, allowResolve) { 898 | if (allowResolve !== false && typeof val === 'string') { 899 | return this.binary.getContext(val)[val]; 900 | } 901 | return toValue(this, this.binary, val); 902 | } 903 | }; 904 | 905 | jBinary.Template = function (config) { 906 | return inherit(jBinary.Template.prototype, config, { 907 | createProperty: function (binary) { 908 | var property = (config.createProperty || jBinary.Template.prototype.createProperty).apply(this, arguments); 909 | if (property.getBaseType) { 910 | property.baseType = property.binary.getType(property.getBaseType(property.binary.contexts[0])); 911 | } 912 | return property; 913 | } 914 | }); 915 | }; 916 | 917 | jBinary.Template.prototype = inherit(jBinary.Type.prototype, { 918 | setParams: function () { 919 | if (this.baseType) { 920 | this.typeParams = ['baseType'].concat(this.typeParams || []); 921 | } 922 | }, 923 | baseRead: function () { 924 | return this.binary.read(this.baseType); 925 | }, 926 | baseWrite: function (value) { 927 | return this.binary.write(this.baseType, value); 928 | } 929 | }); 930 | jBinary.Template.prototype.read = jBinary.Template.prototype.baseRead; 931 | jBinary.Template.prototype.write = jBinary.Template.prototype.baseWrite; 932 | 933 | proto.typeSet = { 934 | 'extend': jBinary.Type({ 935 | setParams: function () { 936 | this.parts = arguments; 937 | }, 938 | resolve: function (getType) { 939 | var parts = this.parts, length = parts.length, partTypes = new Array(length); 940 | for (var i = 0; i < length; i++) { 941 | partTypes[i] = getType(parts[i]); 942 | } 943 | this.parts = partTypes; 944 | }, 945 | read: function () { 946 | var parts = this.parts, obj = this.binary.read(parts[0]); 947 | this.binary.inContext(obj, function () { 948 | for (var i = 1, length = parts.length; i < length; i++) { 949 | extend(obj, this.read(parts[i])); 950 | } 951 | }); 952 | return obj; 953 | }, 954 | write: function (obj) { 955 | var parts = this.parts; 956 | this.binary.inContext(obj, function () { 957 | for (var i = 0, length = parts.length; i < length; i++) { 958 | this.write(parts[i], obj); 959 | } 960 | }); 961 | } 962 | }), 963 | 'enum': jBinary.Template({ 964 | params: ['baseType', 'matches'], 965 | setParams: function (baseType, matches) { 966 | this.backMatches = {}; 967 | for (var key in matches) { 968 | this.backMatches[matches[key]] = key; 969 | } 970 | }, 971 | read: function () { 972 | var value = this.baseRead(); 973 | return value in this.matches ? this.matches[value] : value; 974 | }, 975 | write: function (value) { 976 | this.baseWrite(value in this.backMatches ? this.backMatches[value] : value); 977 | } 978 | }), 979 | 'string': jBinary.Template({ 980 | params: ['length', 'encoding'], 981 | read: function () { 982 | return this.binary.view.getString(this.toValue(this.length), undefined, this.encoding); 983 | }, 984 | write: function (value) { 985 | this.binary.view.writeString(value, this.encoding); 986 | } 987 | }), 988 | 'string0': jBinary.Type({ 989 | params: ['length', 'encoding'], 990 | read: function () { 991 | var view = this.binary.view, maxLength = this.length; 992 | if (maxLength === undefined) { 993 | var startPos = view.tell(), length = 0, code; 994 | maxLength = view.byteLength - startPos; 995 | while (length < maxLength && (code = view.getUint8())) { 996 | length++; 997 | } 998 | var string = view.getString(length, startPos, this.encoding); 999 | if (length < maxLength) { 1000 | view.skip(1); 1001 | } 1002 | return string; 1003 | } else { 1004 | return view.getString(maxLength, undefined, this.encoding).replace(/\0.*$/, ''); 1005 | } 1006 | }, 1007 | write: function (value) { 1008 | var view = this.binary.view, zeroLength = this.length === undefined ? 1 : this.length - value.length; 1009 | view.writeString(value, undefined, this.encoding); 1010 | if (zeroLength > 0) { 1011 | view.writeUint8(0); 1012 | view.skip(zeroLength - 1); 1013 | } 1014 | } 1015 | }), 1016 | 'array': jBinary.Template({ 1017 | params: ['baseType', 'length'], 1018 | read: function () { 1019 | var length = this.toValue(this.length); 1020 | if (this.baseType === proto.typeSet.uint8) { 1021 | return this.binary.view.getBytes(length, undefined, true, true); 1022 | } 1023 | var results; 1024 | if (length !== undefined) { 1025 | results = new Array(length); 1026 | for (var i = 0; i < length; i++) { 1027 | results[i] = this.baseRead(); 1028 | } 1029 | } else { 1030 | var end = this.binary.view.byteLength; 1031 | results = []; 1032 | while (this.binary.tell() < end) { 1033 | results.push(this.baseRead()); 1034 | } 1035 | } 1036 | return results; 1037 | }, 1038 | write: function (values) { 1039 | if (this.baseType === proto.typeSet.uint8) { 1040 | return this.binary.view.writeBytes(values); 1041 | } 1042 | for (var i = 0, length = values.length; i < length; i++) { 1043 | this.baseWrite(values[i]); 1044 | } 1045 | } 1046 | }), 1047 | 'object': jBinary.Type({ 1048 | params: ['structure', 'proto'], 1049 | resolve: function (getType) { 1050 | var structure = {}; 1051 | for (var key in this.structure) { 1052 | structure[key] = 1053 | !(this.structure[key] instanceof Function) 1054 | ? getType(this.structure[key]) 1055 | : this.structure[key]; 1056 | } 1057 | this.structure = structure; 1058 | }, 1059 | read: function () { 1060 | var self = this, structure = this.structure, output = this.proto ? inherit(this.proto) : {}; 1061 | this.binary.inContext(output, function () { 1062 | for (var key in structure) { 1063 | var value = !(structure[key] instanceof Function) 1064 | ? this.read(structure[key]) 1065 | : structure[key].call(self, this.contexts[0]); 1066 | // skipping undefined call results (useful for 'if' statement) 1067 | if (value !== undefined) { 1068 | output[key] = value; 1069 | } 1070 | } 1071 | }); 1072 | return output; 1073 | }, 1074 | write: function (data) { 1075 | var self = this, structure = this.structure; 1076 | this.binary.inContext(data, function () { 1077 | for (var key in structure) { 1078 | if (!(structure[key] instanceof Function)) { 1079 | this.write(structure[key], data[key]); 1080 | } else { 1081 | data[key] = structure[key].call(self, this.contexts[0]); 1082 | } 1083 | } 1084 | }); 1085 | } 1086 | }), 1087 | 'bitfield': jBinary.Type({ 1088 | params: ['bitSize'], 1089 | read: function () { 1090 | var bitSize = this.bitSize, 1091 | binary = this.binary, 1092 | fieldValue = 0; 1093 | 1094 | if (binary._bitShift < 0 || binary._bitShift >= 8) { 1095 | var byteShift = binary._bitShift >> 3; // Math.floor(_bitShift / 8) 1096 | binary.skip(byteShift); 1097 | binary._bitShift &= 7; // _bitShift + 8 * Math.floor(_bitShift / 8) 1098 | } 1099 | if (binary._bitShift > 0 && bitSize >= 8 - binary._bitShift) { 1100 | fieldValue = binary.view.getUint8() & ~(-1 << (8 - binary._bitShift)); 1101 | bitSize -= 8 - binary._bitShift; 1102 | binary._bitShift = 0; 1103 | } 1104 | while (bitSize >= 8) { 1105 | fieldValue = binary.view.getUint8() | (fieldValue << 8); 1106 | bitSize -= 8; 1107 | } 1108 | if (bitSize > 0) { 1109 | fieldValue = ((binary.view.getUint8() >>> (8 - (binary._bitShift + bitSize))) & ~(-1 << bitSize)) | (fieldValue << bitSize); 1110 | binary._bitShift += bitSize - 8; // passing negative value for next pass 1111 | } 1112 | 1113 | return fieldValue >>> 0; 1114 | }, 1115 | write: function (value) { 1116 | var bitSize = this.bitSize, 1117 | binary = this.binary, 1118 | pos, 1119 | curByte; 1120 | 1121 | if (binary._bitShift < 0 || binary._bitShift >= 8) { 1122 | var byteShift = binary._bitShift >> 3; // Math.floor(_bitShift / 8) 1123 | binary.skip(byteShift); 1124 | binary._bitShift &= 7; // _bitShift + 8 * Math.floor(_bitShift / 8) 1125 | } 1126 | if (binary._bitShift > 0 && bitSize >= 8 - binary._bitShift) { 1127 | pos = binary.tell(); 1128 | curByte = binary.view.getUint8(pos) & (-1 << (8 - binary._bitShift)); 1129 | curByte |= value >>> (bitSize - (8 - binary._bitShift)); 1130 | binary.view.setUint8(pos, curByte); 1131 | bitSize -= 8 - binary._bitShift; 1132 | binary._bitShift = 0; 1133 | } 1134 | while (bitSize >= 8) { 1135 | binary.view.writeUint8((value >>> (bitSize - 8)) & 0xff); 1136 | bitSize -= 8; 1137 | } 1138 | if (bitSize > 0) { 1139 | pos = binary.tell(); 1140 | curByte = binary.view.getUint8(pos) & ~(~(-1 << bitSize) << (8 - (binary._bitShift + bitSize))); 1141 | curByte |= (value & ~(-1 << bitSize)) << (8 - (binary._bitShift + bitSize)); 1142 | binary.view.setUint8(pos, curByte); 1143 | binary._bitShift += bitSize - 8; // passing negative value for next pass 1144 | } 1145 | } 1146 | }), 1147 | 'if': jBinary.Template({ 1148 | params: ['condition', 'trueType', 'falseType'], 1149 | typeParams: ['trueType', 'falseType'], 1150 | getBaseType: function (context) { 1151 | return this.toValue(this.condition) ? this.trueType : this.falseType; 1152 | } 1153 | }), 1154 | 'if_not': jBinary.Template({ 1155 | setParams: function (condition, falseType, trueType) { 1156 | this.baseType = ['if', condition, trueType, falseType]; 1157 | } 1158 | }), 1159 | 'const': jBinary.Template({ 1160 | params: ['baseType', 'value', 'strict'], 1161 | read: function () { 1162 | var value = this.baseRead(); 1163 | if (this.strict && value !== this.value) { 1164 | if (this.strict instanceof Function) { 1165 | return this.strict(value); 1166 | } else { 1167 | throw new TypeError('Unexpected value.'); 1168 | } 1169 | } 1170 | return value; 1171 | }, 1172 | write: function (value) { 1173 | this.baseWrite((this.strict || value === undefined) ? this.value : value); 1174 | } 1175 | }), 1176 | 'skip': jBinary.Type({ 1177 | setParams: function (length) { 1178 | this.read = this.write = function () { 1179 | this.binary.view.skip(this.toValue(length)); 1180 | }; 1181 | } 1182 | }), 1183 | 'blob': jBinary.Type({ 1184 | params: ['length'], 1185 | read: function () { 1186 | return this.binary.view.getBytes(this.toValue(this.length)); 1187 | }, 1188 | write: function (bytes) { 1189 | this.binary.view.writeBytes(bytes, true); 1190 | } 1191 | }), 1192 | 'binary': jBinary.Template({ 1193 | params: ['length', 'typeSet'], 1194 | read: function () { 1195 | var startPos = this.binary.tell(); 1196 | var endPos = this.binary.skip(this.toValue(this.length)); 1197 | var view = this.binary.view.slice(startPos, endPos); 1198 | return new jBinary(view, this.typeSet); 1199 | }, 1200 | write: function (binary) { 1201 | this.binary.write('blob', binary.read('blob', 0)); 1202 | } 1203 | }), 1204 | 'lazy': jBinary.Template({ 1205 | marker: 'jBinary.Lazy', 1206 | params: ['innerType'], 1207 | setParams: function (innerType, length) { 1208 | this.baseType = ['binary', length]; 1209 | }, 1210 | typeParams: ['innerType'], 1211 | read: function () { 1212 | var accessor = function (newValue) { 1213 | if (arguments.length === 0) { 1214 | // returning cached or resolving value 1215 | return 'value' in accessor ? accessor.value : (accessor.value = accessor.binary.read(accessor.innerType)); 1216 | } else { 1217 | // marking resolver as dirty for `write` method 1218 | return extend(accessor, { 1219 | wasChanged: true, 1220 | value: newValue 1221 | }).value; 1222 | } 1223 | }; 1224 | accessor[this.marker] = true; 1225 | return extend(accessor, { 1226 | binary: this.baseRead(), 1227 | innerType: this.innerType 1228 | }); 1229 | }, 1230 | write: function (accessor) { 1231 | if (accessor.wasChanged || !accessor[this.marker]) { 1232 | // resolving value if it was changed or given accessor is external 1233 | this.binary.write(this.innerType, accessor()); 1234 | } else { 1235 | // copying blob from original binary slice otherwise 1236 | this.baseWrite(accessor.binary); 1237 | } 1238 | } 1239 | }) 1240 | }; 1241 | 1242 | var dataTypes = [ 1243 | 'Uint8', 1244 | 'Uint16', 1245 | 'Uint32', 1246 | 'Uint64', 1247 | 'Int8', 1248 | 'Int16', 1249 | 'Int32', 1250 | 'Int64', 1251 | 'Float32', 1252 | 'Float64', 1253 | 'Char' 1254 | ]; 1255 | 1256 | var simpleType = jBinary.Type({ 1257 | params: ['littleEndian'], 1258 | read: function () { 1259 | return this.binary.view['get' + this.dataType](undefined, this.littleEndian); 1260 | }, 1261 | write: function (value) { 1262 | this.binary.view['write' + this.dataType](value, this.littleEndian); 1263 | } 1264 | }); 1265 | 1266 | for (var i = 0, length = dataTypes.length; i < length; i++) { 1267 | var dataType = dataTypes[i]; 1268 | proto.typeSet[dataType.toLowerCase()] = inherit(simpleType, {dataType: dataType}); 1269 | } 1270 | 1271 | extend(proto.typeSet, { 1272 | 'byte': proto.typeSet.uint8, 1273 | 'float': proto.typeSet.float32, 1274 | 'double': proto.typeSet.float64 1275 | }); 1276 | 1277 | proto.toValue = function (value) { 1278 | return toValue(this, this, value); 1279 | }; 1280 | 1281 | proto.seek = function (position, callback) { 1282 | position = this.toValue(position); 1283 | if (callback !== undefined) { 1284 | var oldPos = this.view.tell(); 1285 | this.view.seek(position); 1286 | var result = callback.call(this); 1287 | this.view.seek(oldPos); 1288 | return result; 1289 | } else { 1290 | return this.view.seek(position); 1291 | } 1292 | }; 1293 | 1294 | proto.tell = function () { 1295 | return this.view.tell(); 1296 | }; 1297 | 1298 | proto.skip = function (offset, callback) { 1299 | return this.seek(this.tell() + this.toValue(offset), callback); 1300 | }; 1301 | 1302 | proto.getType = function (type, args) { 1303 | switch (typeof type) { 1304 | case 'string': 1305 | if (!(type in this.typeSet)) { 1306 | throw new ReferenceError('Unknown type `' + type + '`'); 1307 | } 1308 | return this.getType(this.typeSet[type], args); 1309 | 1310 | case 'number': 1311 | return this.getType(proto.typeSet.bitfield, [type]); 1312 | 1313 | case 'object': 1314 | if (type instanceof jBinary.Type) { 1315 | var binary = this; 1316 | return type.inherit(args || [], function (type) { return binary.getType(type) }); 1317 | } else { 1318 | var isArray = type instanceof Array; 1319 | return this._getCached( 1320 | type, 1321 | ( 1322 | isArray 1323 | ? function (type) { return this.getType(type[0], type.slice(1)) } 1324 | : function (structure) { return this.getType(proto.typeSet.object, [structure]) } 1325 | ), 1326 | isArray 1327 | ); 1328 | } 1329 | } 1330 | }; 1331 | 1332 | proto.createProperty = function (type) { 1333 | return this.getType(type).createProperty(this); 1334 | }; 1335 | 1336 | proto._action = function (type, offset, callback) { 1337 | if (type === undefined) { 1338 | return; 1339 | } 1340 | return offset !== undefined ? this.seek(offset, callback) : callback.call(this); 1341 | }; 1342 | 1343 | proto.read = function (type, offset) { 1344 | return this._action( 1345 | type, 1346 | offset, 1347 | function () { return this.createProperty(type).read(this.contexts[0]) } 1348 | ); 1349 | }; 1350 | 1351 | proto.write = function (type, data, offset) { 1352 | this._action( 1353 | type, 1354 | offset, 1355 | function () { this.createProperty(type).write(data, this.contexts[0]) } 1356 | ); 1357 | }; 1358 | 1359 | proto._toURI = 1360 | ('URL' in global && 'createObjectURL' in URL) 1361 | ? function (type) { 1362 | var data = this.seek(0, function () { return this.view.getBytes() }); 1363 | return URL.createObjectURL(new Blob([data], {type: type})); 1364 | } 1365 | : function (type) { 1366 | var string = this.seek(0, function () { return this.view.getString(undefined, undefined, this.view._isNodeBuffer ? 'base64' : 'binary') }); 1367 | return 'data:' + type + ';base64,' + (this.view._isNodeBuffer ? string : btoa(string)); 1368 | }; 1369 | 1370 | proto.toURI = function (mimeType) { 1371 | return this._toURI(mimeType || this.typeSet['jBinary.mimeType']); 1372 | }; 1373 | 1374 | proto.slice = function (start, end, forceCopy) { 1375 | return new jBinary(this.view.slice(start, end, forceCopy), this.typeSet); 1376 | }; 1377 | 1378 | var hasStreamSupport = hasNodeRequire('stream') && require('stream').Readable; 1379 | 1380 | jBinary.loadData = function (source, callback) { 1381 | if ('Blob' in global && source instanceof Blob) { 1382 | var reader = new FileReader(); 1383 | reader.onload = reader.onerror = function() { callback(this.error, this.result) }; 1384 | reader.readAsArrayBuffer(source); 1385 | } else 1386 | if (hasStreamSupport && source instanceof require('stream').Readable) { 1387 | var buffers = []; 1388 | 1389 | source 1390 | .on('readable', function () { buffers.push(this.read()) }) 1391 | .on('end', function () { callback(null, Buffer.concat(buffers)) }) 1392 | .on('error', callback); 1393 | } else { 1394 | if (typeof source !== 'string') { 1395 | return callback(new TypeError('Unsupported source type.')); 1396 | } 1397 | 1398 | var dataParts = source.match(/^data:(.+?)(;base64)?,(.*)$/); 1399 | if (dataParts) { 1400 | var isBase64 = dataParts[2], 1401 | content = dataParts[3]; 1402 | 1403 | try { 1404 | callback( 1405 | null, 1406 | ( 1407 | (isBase64 && jDataView.prototype.compatibility.NodeBuffer) 1408 | ? new Buffer(content, 'base64') 1409 | : (isBase64 ? atob : decodeURIComponent)(content) 1410 | ) 1411 | ); 1412 | } catch (e) { 1413 | callback(e); 1414 | } 1415 | } else 1416 | if ('XMLHttpRequest' in global) { 1417 | var xhr = new XMLHttpRequest(); 1418 | xhr.open('GET', source, true); 1419 | 1420 | // new browsers (XMLHttpRequest2-compliant) 1421 | if ('responseType' in xhr) { 1422 | xhr.responseType = 'arraybuffer'; 1423 | } 1424 | // old browsers (XMLHttpRequest-compliant) 1425 | else if ('overrideMimeType' in xhr) { 1426 | xhr.overrideMimeType('text/plain; charset=x-user-defined'); 1427 | } 1428 | // IE9 (Microsoft.XMLHTTP-compliant) 1429 | else { 1430 | xhr.setRequestHeader('Accept-Charset', 'x-user-defined'); 1431 | } 1432 | 1433 | // shim for onload for old IE 1434 | if (!('onload' in xhr)) { 1435 | xhr.onreadystatechange = function () { 1436 | if (this.readyState === 4) { 1437 | this.onload(); 1438 | } 1439 | }; 1440 | } 1441 | 1442 | xhr.onload = function() { 1443 | if (this.status !== 0 && this.status !== 200) { 1444 | return callback(new Error('HTTP Error #' + this.status + ': ' + this.statusText)); 1445 | } 1446 | 1447 | // emulating response field for IE9 1448 | if (!('response' in this)) { 1449 | this.response = new VBArray(this.responseBody).toArray(); 1450 | } 1451 | 1452 | callback(null, this.response); 1453 | }; 1454 | 1455 | xhr.send(); 1456 | } else { 1457 | var isHTTP = /^(https?):\/\//.test(source); 1458 | 1459 | if (isHTTP && hasNodeRequire('request')) { 1460 | require('request').get({ 1461 | uri: source, 1462 | encoding: null 1463 | }, function (error, response, body) { 1464 | if (!error && response.statusCode !== 200) { 1465 | var statusText = require('http').STATUS_CODES[response.statusCode]; 1466 | error = new Error('HTTP Error #' + response.statusCode + ': ' + statusText); 1467 | } 1468 | callback(error, body); 1469 | }); 1470 | } else 1471 | if (!isHTTP && hasNodeRequire('fs')) { 1472 | require('fs').readFile(source, callback); 1473 | } else { 1474 | callback(new TypeError('Unsupported source type.')); 1475 | } 1476 | } 1477 | } 1478 | }; 1479 | 1480 | function setJDataView(_jDataView) { 1481 | jDataView = _jDataView; 1482 | jDataView.prototype.toBinary = function (typeSet) { 1483 | return new jBinary(this, typeSet); 1484 | }; 1485 | } 1486 | 1487 | if (typeof module === 'object' && module && typeof module.exports === 'object') { 1488 | setJDataView(require('jdataview')); 1489 | module.exports = jBinary; 1490 | } else 1491 | if (typeof define === 'function' && define.amd) { 1492 | define(['jdataview'], function (_jDataView) { 1493 | setJDataView(_jDataView); 1494 | return jBinary; 1495 | }); 1496 | } else { 1497 | setJDataView(global.jDataView); 1498 | global.jBinary = jBinary; 1499 | } 1500 | 1501 | })((function () { /* jshint strict: false */ return this })()); 1502 | ;/********************************************************* 1503 | * Talking Image by Hage Yaapa * 1504 | * License: MIT * 1505 | **********************************************************/ 1506 | 1507 | /*jshint newcap: false */ 1508 | 1509 | ;(function(TalkingImage, global) { 1510 | 1511 | global.TalkingImage = TalkingImage; 1512 | 1513 | window.addEventListener('load', function() { 1514 | var images = document.querySelectorAll('img[data-audio]'); 1515 | Array.prototype.forEach.call(images, function(image) { 1516 | TalkingImage(image); 1517 | }); 1518 | }); 1519 | 1520 | })(function(img) { 1521 | 1522 | 'use strict'; 1523 | 1524 | var 1525 | 1526 | VERSION = '0.1.0', 1527 | hasOwnProp = Object.prototype.hasOwnProperty, 1528 | 1529 | audio = { volume: 1 }, 1530 | 1531 | formats = { 1532 | 'mp3': '\x49\x44\x33\x03\x00\x00\x00\x00', 1533 | 'ogg': '\x4F\x67\x67\x53\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00' 1534 | }, 1535 | 1536 | url = img.getAttribute('src'), 1537 | options = img.getAttribute('data-audio'), 1538 | 1539 | is_set = function(option, options) { 1540 | return options.indexOf(option) > -1; 1541 | }, 1542 | 1543 | detectFormat = function(d) { 1544 | var i, ii, j, n, format; 1545 | for (i = 0, ii = d.byteLength; i < ii; i++) { 1546 | for (j in formats) { 1547 | format = true; 1548 | if (hasOwnProp.call(formats, j) && formats[j].length + i < ii) { 1549 | for (n = 0; n < formats[j].length; n++) { 1550 | if (d.getChar(i + n) !== formats[j][n]) { 1551 | format = false; 1552 | break; 1553 | } 1554 | } 1555 | if (format) { 1556 | audio.format = j; 1557 | audio.offset = i; 1558 | return true; 1559 | } 1560 | } 1561 | } 1562 | } 1563 | return false; 1564 | }; 1565 | 1566 | jBinary.loadData(url, function(err, data) { 1567 | var d = new jDataView(data); 1568 | if (!err && detectFormat(d)) { 1569 | // Reset the cursor 1570 | d.seek(0); 1571 | // Extract the audio data 1572 | d.getString(audio.offset); 1573 | // Convert binary data to base64 encoded string and assign it to the audio object usind Data URI 1574 | var audio_data = 'data:audio/'+ audio.format +';base64,' + window.btoa(d.getString()); 1575 | var audio_el = new Audio(audio_data); 1576 | 1577 | if (is_set('sync', options)) img.style.visibility = 'hidden'; 1578 | 1579 | // Apply options 1580 | if (is_set('controls', options)) audio_el.setAttribute('controls', 'controls'); 1581 | if (is_set('autoplay', options)) audio_el.setAttribute('autoplay', 'true'); 1582 | if (is_set('loop', options)) audio_el.setAttribute('loop', 'true'); 1583 | if (is_set('volume', options)) { 1584 | var volume = options.split('volume=')[1].split(' ')[0]; 1585 | // We are prefxing with + to convert string to int 1586 | audio.volume = +volume; 1587 | audio_el.volume = +volume; 1588 | } 1589 | 1590 | if (is_set('sync', options)) { 1591 | // Reset the animation by re-loading the image 1592 | img.setAttribute('src', url); 1593 | img.style.visibility = 'visible'; 1594 | } 1595 | 1596 | // The sound can be muted by clicked on the image, and toggled - we don't pause because GIF images don't pause 1597 | img.addEventListener('click', function() { 1598 | if (audio_el.paused) { 1599 | audio_el.play(); 1600 | } else { 1601 | audio_el.volume = audio_el.volume === 0 ? audio_el.volume = audio.volume : 0; 1602 | } 1603 | }); 1604 | } 1605 | }); 1606 | 1607 | }, this); -------------------------------------------------------------------------------- /build/talking-image.min.js: -------------------------------------------------------------------------------- 1 | /*! Talking Image v0.1.0 by Hage Yaapa . Built on 09-09-2013 */ 2 | !function(a){"use strict";function b(a,b){return!b&&a instanceof Array?a:Array.prototype.slice.call(a)}function c(a,b){return void 0!==a?a:b}function d(a,b,e,f){if(a instanceof d){var g=a.slice(b,b+e);return g._littleEndian=c(f,g._littleEndian),g}if(!(this instanceof d))return new d(a,b,e,f);if(this.buffer=a=d.wrapBuffer(a),this._isArrayBuffer=i.ArrayBuffer&&a instanceof ArrayBuffer,this._isPixelData=i.PixelData&&a instanceof CanvasPixelArray,this._isDataView=i.DataView&&this._isArrayBuffer,this._isNodeBuffer=i.NodeBuffer&&a instanceof Buffer,!(this._isNodeBuffer||this._isArrayBuffer||this._isPixelData||a instanceof Array))throw new TypeError("jDataView buffer has an incompatible type");this._littleEndian=!!f;var h="byteLength"in a?a.byteLength:a.length;this.byteOffset=b=c(b,0),this.byteLength=e=c(e,h-b),this._isDataView?this._view=new DataView(a,b,e):this._checkBounds(b,e,h),this._engineAction=this._isDataView?this._dataViewAction:this._isNodeBuffer?this._nodeBufferAction:this._isArrayBuffer?this._arrayBufferAction:this._arrayAction}function e(a){if(i.NodeBuffer)return new Buffer(a,"binary");for(var b=i.ArrayBuffer?Uint8Array:Array,c=new b(a.length),d=0,e=a.length;e>d;d++)c[d]=255&a.charCodeAt(d);return c}function f(a){return a>=0&&31>a?1<d;d++)c[d]=b[d];return c};j.context2d=document.createElement("canvas").getContext("2d")}var k={Int8:1,Int16:2,Int32:4,Uint8:1,Uint16:2,Uint32:4,Float32:4,Float64:8},l={Int8:"Int8",Int16:"Int16",Int32:"Int32",Uint8:"UInt8",Uint16:"UInt16",Uint32:"UInt32",Float32:"Float",Float64:"Double"};d.wrapBuffer=function(a){switch(typeof a){case"number":if(i.NodeBuffer)a=new Buffer(a),a.fill(0);else if(i.ArrayBuffer)a=new Uint8Array(a).buffer;else if(i.PixelData)a=j(a);else{a=new Array(a);for(var c=0;c=0){var d=g.fromNumber(a);b=d.lo,c=d.hi}else c=Math.floor(a/f(32)),b=a-c*f(32),c+=f(32);return new h(b,c)},d.prototype={_offset:0,_bitOffset:0,compatibility:i,_checkBounds:function(a,b,d){if("number"!=typeof a)throw new TypeError("Offset is not a number.");if("number"!=typeof b)throw new TypeError("Size is not a number.");if(0>b)throw new RangeError("Length is negative.");if(0>a||a+b>c(d,this.byteLength))throw new RangeError("Offsets are out of bounds.")},_action:function(a,b,d,e,f){return this._engineAction(a,b,c(d,this._offset),c(e,this._littleEndian),f)},_dataViewAction:function(a,b,c,d,e){return this._offset=c+k[a],b?this._view["get"+a](c,d):this._view["set"+a](c,e,d)},_nodeBufferAction:function(a,b,c,d,e){this._offset=c+k[a];var f=l[a]+("Int8"===a||"Uint8"===a?"":d?"LE":"BE");return c+=this.byteOffset,b?this.buffer["read"+f](c):this.buffer["write"+f](e,c)},_arrayBufferAction:function(b,d,e,f,g){var h,i=k[b],j=a[b+"Array"];if(f=c(f,this._littleEndian),1===i||0===(this.byteOffset+e)%i&&f)return h=new j(this.buffer,this.byteOffset+e,1),this._offset=e+i,d?h[0]:h[0]=g;var l=new Uint8Array(d?this.getBytes(i,e,f,!0):i);return h=new j(l.buffer,0,1),d?h[0]:(h[0]=g,this._setBytes(e,l,f),void 0)},_arrayAction:function(a,b,c,d,e){return b?this["_get"+a](c,d):this["_set"+a.replace("Uint","Int")](c,e,d)},_getBytes:function(a,d,e){e=c(e,this._littleEndian),d=c(d,this._offset),a=c(a,this.byteLength-d),this._checkBounds(d,a),d+=this.byteOffset,this._offset=d-this.byteOffset+a;var f=this._isArrayBuffer?new Uint8Array(this.buffer,d,a):(this.buffer.slice||Array.prototype.slice).call(this.buffer,d,d+a);return e||1>=a?f:b(f).reverse()},getBytes:function(a,d,e,f){var g=this._getBytes(a,d,c(e,!0));return f?b(g):g},_setBytes:function(a,d,e){var f=d.length;if(0!==f){if(e=c(e,this._littleEndian),a=c(a,this._offset),this._checkBounds(a,f),!e&&f>1&&(d=b(d,!0).reverse()),a+=this.byteOffset,this._isArrayBuffer)new Uint8Array(this.buffer,a,f).set(d);else if(this._isNodeBuffer)new Buffer(d).copy(this.buffer,a);else for(var g=0;f>g;g++)this.buffer[a+g]=d[g];this._offset=a-this.byteOffset+f}},setBytes:function(a,b,d){this._setBytes(a,b,c(d,!0))},writeBytes:function(a,b){this.setBytes(void 0,a,b)},getString:function(a,b,d){if(this._isNodeBuffer)return b=c(b,this._offset),a=c(a,this.byteLength-b),this._checkBounds(b,a),this._offset=b+a,this.buffer.toString(d||"binary",this.byteOffset+b,this.byteOffset+this._offset);var e=this._getBytes(a,b,!0),f="";a=e.length;for(var g=0;a>g;g++)f+=String.fromCharCode(e[g]);return"utf8"===d&&(f=decodeURIComponent(escape(f))),f},setString:function(a,b,d){return this._isNodeBuffer?(a=c(a,this._offset),this._checkBounds(a,b.length),this._offset=a+this.buffer.write(b,this.byteOffset+a,d||"binary"),void 0):("utf8"===d&&(b=unescape(encodeURIComponent(b))),this._setBytes(a,e(b),!0),void 0)},writeString:function(a,b){this.setString(void 0,a,b)},getChar:function(a){return this.getString(1,a)},setChar:function(a,b){this.setString(a,b)},writeChar:function(a){this.setChar(void 0,a)},tell:function(){return this._offset},seek:function(a){return this._checkBounds(a,0),this._offset=a},skip:function(a){return this.seek(this._offset+a)},slice:function(a,b,c){return c?new d(this.getBytes(b-a,a,!0,!0),void 0,void 0,this._littleEndian):new d(this.buffer,this.byteOffset+a,b-a,this._littleEndian)},_getFloat64:function(a,b){var c=this._getBytes(8,a,b),d=1-2*(c[7]>>7),e=((255&c[7]<<1)<<3|c[6]>>4)-1023,g=(15&c[6])*f(48)+c[5]*f(40)+c[4]*f(32)+c[3]*f(24)+c[2]*f(16)+c[1]*f(8)+c[0];return 1024===e?0!==g?0/0:1/0*d:-1023===e?d*g*f(-1074):d*(1+g*f(-52))*f(e)},_getFloat32:function(a,b){var c=this._getBytes(4,a,b),d=1-2*(c[3]>>7),e=(255&c[3]<<1|c[2]>>7)-127,g=(127&c[2])<<16|c[1]<<8|c[0];return 128===e?0!==g?0/0:1/0*d:-127===e?d*g*f(-149):d*(1+g*f(-23))*f(e)},_get64:function(a,b,d){d=c(d,this._littleEndian),b=c(b,this._offset);for(var e=d?[0,4]:[4,0],f=0;2>f;f++)e[f]=this.getUint32(b+e[f],d);return this._offset=b+8,new a(e[0],e[1])},getInt64:function(a,b){return this._get64(h,a,b)},getUint64:function(a,b){return this._get64(g,a,b)},_getInt32:function(a,b){var c=this._getBytes(4,a,b);return c[3]<<24|c[2]<<16|c[1]<<8|c[0]},_getUint32:function(a,b){return this._getInt32(a,b)>>>0},_getInt16:function(a,b){return this._getUint16(a,b)<<16>>16},_getUint16:function(a,b){var c=this._getBytes(2,a,b);return c[1]<<8|c[0]},_getInt8:function(a){return this._getUint8(a)<<24>>24},_getUint8:function(a){return this._getBytes(1,a)[0]},getSigned:function(a,b){var c=32-a;return this.getUnsigned(a,b)<>c},getUnsigned:function(a,b){var d=(c(b,this._offset)<<3)+this._bitOffset,e=d+a,f=d>>>3,g=e+7>>>3,h=this._getBytes(g-f,f,!0),i=0;(this._bitOffset=7&e)&&(this._bitOffset-=8);for(var j=0,k=h.length;k>j;j++)i=i<<8|h[j];return i>>>=-this._bitOffset,32>a?i&~(-1<b?1:0,j=~(-1<b&&(b=-b),0===b?(g=0,h=0):isNaN(b)?(g=2*j+1,h=1):1/0===b?(g=2*j+1,h=0):(g=Math.floor(Math.log(b)/Math.LN2),g>=k&&j>=g?(h=Math.floor((b*f(-g)-1)*f(c)),g+=j):(h=Math.floor(b/f(k-c)),g=0));for(var l=[];c>=8;)l.push(h%256),h=Math.floor(h/256),c-=8;for(g=g<=8;)l.push(255&g),g>>>=8,d-=8;l.push(i<>>8,255&b>>>16,b>>>24],c)},_setInt16:function(a,b,c){this._setBytes(a,[255&b,255&b>>>8],c)},_setInt8:function(a,b){this._setBytes(a,[255&b])}};var m=d.prototype;for(var n in k)!function(a){m["get"+a]=function(b,c){return this._action(a,!0,b,c)},m["set"+a]=function(b,c,d){this._action(a,!1,b,d,c)},m["write"+a]=function(b,c){this["set"+a](void 0,b,c)}}(n);"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:"function"==typeof define&&define.amd?define([],function(){return d}):a.jDataView=d}(function(){return this}()),function(a){"use strict";function b(a){return"function"==typeof require&&!require.isBrowser&&require(a)}function c(a){for(var b=1,c=arguments.length;c>b;++b){var d=arguments[b];for(var e in d)void 0!==d[e]&&(a[e]=d[e])}return a}function d(a){return arguments[0]=i(a),c.apply(null,arguments)}function e(a,b,c){return c instanceof Function?c.call(a,b.contexts[0]):c}function f(a,b){return a instanceof h||(a=new h(a,void 0,void 0,b?b["jBinary.littleEndian"]:void 0)),this instanceof f?(this.view=a,this.view.seek(0),this._bitShift=0,this.contexts=[],b&&(this.typeSet=j.typeSet===b||j.typeSet.isPrototypeOf(b)?b:d(j.typeSet,b),this.cacheKey=this._getCached(b,function(){return j.cacheKey+"."+ ++j.id},!0)),void 0):new f(a,b)}function g(a){h=a,h.prototype.toBinary=function(a){return new f(this,a)}}"atob"in a&&"btoa"in a||!function(){var b=a,c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",d=function(){try{document.createElement("$")}catch(a){return a}}();b.btoa||(b.btoa=function(a){for(var b,e,f=0,g=c,h="";a.charAt(0|f)||(g="=",f%1);h+=g.charAt(63&b>>8-8*(f%1))){if(e=a.charCodeAt(f+=.75),e>255)throw d;b=b<<8|e}return h}),b.atob||(b.atob=function(a){if(a=a.replace(/=+$/,""),1==a.length%4)throw d;for(var b,e,f=0,g=0,h="";e=a.charAt(g++);~e&&(b=f%4?64*b+e:e,f++%4)?h+=String.fromCharCode(255&b>>(6&-2*f)):0)e=c.indexOf(e);return h})}();var h,i=Object.create||function(a){var b=function(){};return b.prototype=a,new b},j=f.prototype;j.cacheKey="jBinary.Cache",j.id=0;var k=Object.defineProperty;if(k)try{k({},"x",{})}catch(l){k=null}k||(k=function(a,b,c,d){d&&(a[b]=c.value)}),j._getCached=function(a,b,c){if(a.hasOwnProperty(this.cacheKey))return a[this.cacheKey];var d=b.call(this,a);return k(a,this.cacheKey,{value:d},c),d},j.getContext=function(a){switch(typeof a){case"undefined":a=0;case"number":return this.contexts[a];case"string":return this.getContext(function(b){return a in b});case"function":for(var b=0,c=this.contexts.length;c>b;b++){var d=this.contexts[b];if(a.call(this,d))return d}return}},j.inContext=function(a,b){this.contexts.unshift(a);var c=b.call(this);return this.contexts.shift(),c},f.Type=function(a){return d(f.Type.prototype,a)},f.Type.prototype={inherit:function(a,b){function c(a,b){var c=f[a];c&&(e||(e=d(f)),b.call(e,c),e[a]=null)}var e,f=this;return c("params",function(b){for(var c=0,d=Math.min(b.length,a.length);d>c;c++)this[b[c]]=a[c]}),c("setParams",function(b){b.apply(this,a)}),c("typeParams",function(a){for(var c=0,d=a.length;d>c;c++){var e=a[c],f=this[e];f&&(this[e]=b(f))}}),c("resolve",function(a){a.call(this,b)}),e||f},createProperty:function(a){return d(this,{binary:a})},toValue:function(a,b){return b!==!1&&"string"==typeof a?this.binary.getContext(a)[a]:e(this,this.binary,a)}},f.Template=function(a){return d(f.Template.prototype,a,{createProperty:function(){var b=(a.createProperty||f.Template.prototype.createProperty).apply(this,arguments);return b.getBaseType&&(b.baseType=b.binary.getType(b.getBaseType(b.binary.contexts[0]))),b}})},f.Template.prototype=d(f.Type.prototype,{setParams:function(){this.baseType&&(this.typeParams=["baseType"].concat(this.typeParams||[]))},baseRead:function(){return this.binary.read(this.baseType)},baseWrite:function(a){return this.binary.write(this.baseType,a)}}),f.Template.prototype.read=f.Template.prototype.baseRead,f.Template.prototype.write=f.Template.prototype.baseWrite,j.typeSet={extend:f.Type({setParams:function(){this.parts=arguments},resolve:function(a){for(var b=this.parts,c=b.length,d=new Array(c),e=0;c>e;e++)d[e]=a(b[e]);this.parts=d},read:function(){var a=this.parts,b=this.binary.read(a[0]);return this.binary.inContext(b,function(){for(var d=1,e=a.length;e>d;d++)c(b,this.read(a[d]))}),b},write:function(a){var b=this.parts;this.binary.inContext(a,function(){for(var c=0,d=b.length;d>c;c++)this.write(b[c],a)})}}),"enum":f.Template({params:["baseType","matches"],setParams:function(a,b){this.backMatches={};for(var c in b)this.backMatches[b[c]]=c},read:function(){var a=this.baseRead();return a in this.matches?this.matches[a]:a},write:function(a){this.baseWrite(a in this.backMatches?this.backMatches[a]:a)}}),string:f.Template({params:["length","encoding"],read:function(){return this.binary.view.getString(this.toValue(this.length),void 0,this.encoding)},write:function(a){this.binary.view.writeString(a,this.encoding)}}),string0:f.Type({params:["length","encoding"],read:function(){var a=this.binary.view,b=this.length;if(void 0===b){var c,d=a.tell(),e=0;for(b=a.byteLength-d;b>e&&(c=a.getUint8());)e++;var f=a.getString(e,d,this.encoding);return b>e&&a.skip(1),f}return a.getString(b,void 0,this.encoding).replace(/\0.*$/,"")},write:function(a){var b=this.binary.view,c=void 0===this.length?1:this.length-a.length;b.writeString(a,void 0,this.encoding),c>0&&(b.writeUint8(0),b.skip(c-1))}}),array:f.Template({params:["baseType","length"],read:function(){var a=this.toValue(this.length);if(this.baseType===j.typeSet.uint8)return this.binary.view.getBytes(a,void 0,!0,!0);var b;if(void 0!==a){b=new Array(a);for(var c=0;a>c;c++)b[c]=this.baseRead()}else{var d=this.binary.view.byteLength;for(b=[];this.binary.tell()b;b++)this.baseWrite(a[b])}}),object:f.Type({params:["structure","proto"],resolve:function(a){var b={};for(var c in this.structure)b[c]=this.structure[c]instanceof Function?this.structure[c]:a(this.structure[c]);this.structure=b},read:function(){var a=this,b=this.structure,c=this.proto?d(this.proto):{};return this.binary.inContext(c,function(){for(var d in b){var e=b[d]instanceof Function?b[d].call(a,this.contexts[0]):this.read(b[d]);void 0!==e&&(c[d]=e)}}),c},write:function(a){var b=this,c=this.structure;this.binary.inContext(a,function(){for(var d in c)c[d]instanceof Function?a[d]=c[d].call(b,this.contexts[0]):this.write(c[d],a[d])})}}),bitfield:f.Type({params:["bitSize"],read:function(){var a=this.bitSize,b=this.binary,c=0;if(b._bitShift<0||b._bitShift>=8){var d=b._bitShift>>3;b.skip(d),b._bitShift&=7}for(b._bitShift>0&&a>=8-b._bitShift&&(c=b.view.getUint8()&~(-1<<8-b._bitShift),a-=8-b._bitShift,b._bitShift=0);a>=8;)c=b.view.getUint8()|c<<8,a-=8;return a>0&&(c=b.view.getUint8()>>>8-(b._bitShift+a)&~(-1<>>0},write:function(a){var b,c,d=this.bitSize,e=this.binary;if(e._bitShift<0||e._bitShift>=8){var f=e._bitShift>>3;e.skip(f),e._bitShift&=7}for(e._bitShift>0&&d>=8-e._bitShift&&(b=e.tell(),c=e.view.getUint8(b)&-1<<8-e._bitShift,c|=a>>>d-(8-e._bitShift),e.view.setUint8(b,c),d-=8-e._bitShift,e._bitShift=0);d>=8;)e.view.writeUint8(255&a>>>d-8),d-=8;d>0&&(b=e.tell(),c=e.view.getUint8(b)&~(~(-1<o;o++){var q=m[o];j.typeSet[q.toLowerCase()]=d(n,{dataType:q})}c(j.typeSet,{"byte":j.typeSet.uint8,"float":j.typeSet.float32,"double":j.typeSet.float64}),j.toValue=function(a){return e(this,this,a)},j.seek=function(a,b){if(a=this.toValue(a),void 0!==b){var c=this.view.tell();this.view.seek(a);var d=b.call(this);return this.view.seek(c),d}return this.view.seek(a)},j.tell=function(){return this.view.tell()},j.skip=function(a,b){return this.seek(this.tell()+this.toValue(a),b)},j.getType=function(a,b){switch(typeof a){case"string":if(!(a in this.typeSet))throw new ReferenceError("Unknown type `"+a+"`");return this.getType(this.typeSet[a],b);case"number":return this.getType(j.typeSet.bitfield,[a]);case"object":if(a instanceof f.Type){var c=this;return a.inherit(b||[],function(a){return c.getType(a)})}var d=a instanceof Array;return this._getCached(a,d?function(a){return this.getType(a[0],a.slice(1))}:function(a){return this.getType(j.typeSet.object,[a])},d)}},j.createProperty=function(a){return this.getType(a).createProperty(this)},j._action=function(a,b,c){return void 0!==a?void 0!==b?this.seek(b,c):c.call(this):void 0},j.read=function(a,b){return this._action(a,b,function(){return this.createProperty(a).read(this.contexts[0])})},j.write=function(a,b,c){this._action(a,c,function(){this.createProperty(a).write(b,this.contexts[0])})},j._toURI="URL"in a&&"createObjectURL"in URL?function(a){var b=this.seek(0,function(){return this.view.getBytes()});return URL.createObjectURL(new Blob([b],{type:a}))}:function(a){var b=this.seek(0,function(){return this.view.getString(void 0,void 0,this.view._isNodeBuffer?"base64":"binary")});return"data:"+a+";base64,"+(this.view._isNodeBuffer?b:btoa(b))},j.toURI=function(a){return this._toURI(a||this.typeSet["jBinary.mimeType"])},j.slice=function(a,b,c){return new f(this.view.slice(a,b,c),this.typeSet)};var r=b("stream")&&require("stream").Readable;f.loadData=function(c,d){if("Blob"in a&&c instanceof Blob){var e=new FileReader;e.onload=e.onerror=function(){d(this.error,this.result)},e.readAsArrayBuffer(c)}else if(r&&c instanceof require("stream").Readable){var f=[];c.on("readable",function(){f.push(this.read())}).on("end",function(){d(null,Buffer.concat(f))}).on("error",d)}else{if("string"!=typeof c)return d(new TypeError("Unsupported source type."));var g=c.match(/^data:(.+?)(;base64)?,(.*)$/);if(g){var i=g[2],j=g[3];try{d(null,i&&h.prototype.compatibility.NodeBuffer?new Buffer(j,"base64"):(i?atob:decodeURIComponent)(j))}catch(k){d(k)}}else if("XMLHttpRequest"in a){var l=new XMLHttpRequest;l.open("GET",c,!0),"responseType"in l?l.responseType="arraybuffer":"overrideMimeType"in l?l.overrideMimeType("text/plain; charset=x-user-defined"):l.setRequestHeader("Accept-Charset","x-user-defined"),"onload"in l||(l.onreadystatechange=function(){4===this.readyState&&this.onload()}),l.onload=function(){return 0!==this.status&&200!==this.status?d(new Error("HTTP Error #"+this.status+": "+this.statusText)):("response"in this||(this.response=new VBArray(this.responseBody).toArray()),d(null,this.response),void 0)},l.send()}else{var m=/^(https?):\/\//.test(c);m&&b("request")?require("request").get({uri:c,encoding:null},function(a,b,c){if(!a&&200!==b.statusCode){var e=require("http").STATUS_CODES[b.statusCode];a=new Error("HTTP Error #"+b.statusCode+": "+e)}d(a,c)}):!m&&b("fs")?require("fs").readFile(c,d):d(new TypeError("Unsupported source type."))}}},"object"==typeof module&&module&&"object"==typeof module.exports?(g(require("jdataview")),module.exports=f):"function"==typeof define&&define.amd?define(["jdataview"],function(a){return g(a),f}):(g(a.jDataView),a.jBinary=f)}(function(){return this}()),function(a,b){b.TalkingImage=a,window.addEventListener("load",function(){var b=document.querySelectorAll("img[data-audio]");Array.prototype.forEach.call(b,function(b){a(b)})})}(function(a){"use strict";var b=Object.prototype.hasOwnProperty,c={volume:1},d={mp3:"ID3\x00\x00\x00\x00",ogg:"OggS\x00\x00\x00\x00\x00\x00\x00\x00\x00"},e=a.getAttribute("src"),f=a.getAttribute("data-audio"),g=function(a,b){return b.indexOf(a)>-1},h=function(a){var e,f,g,h,i;for(e=0,f=a.byteLength;f>e;e++)for(g in d)if(i=!0,b.call(d,g)&&d[g].length+e 2 | 3 | Talking Image Demo - 2001: A Space Odyssey 4 | 5 | 6 | 7 | Home 8 |

2001: A Space Odyssey

9 | 10 |
11 | <img src="2001.jpg" data-audio="autoplay sync"> 12 | 13 | -------------------------------------------------------------------------------- /demos/public/2001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/demos/public/2001.jpg -------------------------------------------------------------------------------- /demos/public/bruce.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/demos/public/bruce.gif -------------------------------------------------------------------------------- /demos/public/bruce.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Talking Image Demo - Bruce Lee 4 | 5 | 6 | 7 | Home 8 |

Bruce Lee

9 | 10 |
11 | <img src="bruce.gif" data-audio="autoplay sync"> 12 |

13 | If you want the audio to be looped, specify "loop" in the audio attribute 14 |

15 | 16 | -------------------------------------------------------------------------------- /demos/public/demos.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Talking Image Demos 4 | 5 | 6 | 7 |

Talking Image Demos

8 | 15 |
16 | Note: All demos, except the last one, will work on Chrome / Firefox.
17 | Use MP3 payload instead of OGG to create talking images for Safari.
18 | 
19 | 20 | -------------------------------------------------------------------------------- /demos/public/johnny.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/demos/public/johnny.gif -------------------------------------------------------------------------------- /demos/public/johnny.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Talking Image Demo - Johnny B. Goode 4 | 5 | 6 | 7 | Home 8 |

Johnny B. Goode

9 | 10 |
11 | <img src="johnny.gif" data-audio="volume=0.5 autoplay loop"> 12 |

13 | Click on the image to mute / unmute the sound 14 |

15 | 16 | -------------------------------------------------------------------------------- /demos/public/nyan-safari.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/demos/public/nyan-safari.gif -------------------------------------------------------------------------------- /demos/public/nyan-safari.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Talking Image Demo - Nyan Cat 4 | 5 | 6 | 7 | Home 8 |

Nyan Cat

9 | 10 |
11 | <img src="nyan-safari.gif" data-audio="loop"> 12 |

13 | Click on the image to start playing the music 14 |

15 | 16 | -------------------------------------------------------------------------------- /demos/public/nyan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/demos/public/nyan.gif -------------------------------------------------------------------------------- /demos/public/nyan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Talking Image Demo - Nyan Cat 4 | 5 | 6 | 7 | Home 8 |

Nyan Cat

9 | 10 |
11 | <img src="nyan.gif" data-audio="loop"> 12 |

13 | Click on the image to start playing the music 14 |

15 | 16 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(grunt) { 3 | 4 | "use strict"; 5 | 6 | grunt.initConfig({ 7 | 8 | pkg: grunt.file.readJSON('package.json'), 9 | 10 | jshint: { 11 | sourse: { 12 | src: ['src/talking-image.js'] 13 | } 14 | }, 15 | 16 | concat: { 17 | options: { 18 | separator: ';' 19 | }, 20 | dist: { 21 | src: ['lib/jdataview.js', 'lib/jbinary.js', 'src/talking-image.js'], 22 | dest: 'build/<%= pkg.name %>.js' 23 | } 24 | }, 25 | 26 | uglify: { 27 | options: { 28 | banner: '/*! <%= pkg.title %> v<%= pkg.version %> by <%= pkg.author %>. Built on <%= grunt.template.today("dd-mm-yyyy") %> */\n' 29 | }, 30 | dist: { 31 | files: { 32 | 'build/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] 33 | } 34 | } 35 | } 36 | 37 | }); 38 | 39 | grunt.loadNpmTasks('grunt-contrib-concat'); 40 | grunt.loadNpmTasks('grunt-contrib-uglify'); 41 | grunt.loadNpmTasks('grunt-contrib-jshint'); 42 | 43 | grunt.registerTask('default', ['concat', 'uglify']); 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /lib/jbinary.js: -------------------------------------------------------------------------------- 1 | (function (global) { 2 | 3 | 'use strict'; 4 | 5 | // https://github.com/davidchambers/Base64.js (modified) 6 | if (!('atob' in global) || !('btoa' in global)) { 7 | // jshint:skipline 8 | (function(){var t=global,r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n=function(){try{document.createElement("$")}catch(t){return t}}();t.btoa||(t.btoa=function(t){for(var o,e,a=0,c=r,f="";t.charAt(0|a)||(c="=",a%1);f+=c.charAt(63&o>>8-8*(a%1))){if(e=t.charCodeAt(a+=.75),e>255)throw n;o=o<<8|e}return f}),t.atob||(t.atob=function(t){if(t=t.replace(/=+$/,""),1==t.length%4)throw n;for(var o,e,a=0,c=0,f="";e=t.charAt(c++);~e&&(o=a%4?64*o+e:e,a++%4)?f+=String.fromCharCode(255&o>>(6&-2*a)):0)e=r.indexOf(e);return f})})(); 9 | } 10 | 11 | function hasNodeRequire(name) { 12 | return typeof require === 'function' && !require.isBrowser && require(name); 13 | } 14 | 15 | var jDataView; 16 | 17 | function extend(obj) { 18 | for (var i = 1, length = arguments.length; i < length; ++i) { 19 | var source = arguments[i]; 20 | for (var prop in source) { 21 | if (source[prop] !== undefined) { 22 | obj[prop] = source[prop]; 23 | } 24 | } 25 | } 26 | return obj; 27 | } 28 | 29 | var _inherit = Object.create || function (obj) { 30 | var ClonedObject = function () {}; 31 | ClonedObject.prototype = obj; 32 | return new ClonedObject(); 33 | }; 34 | 35 | function inherit(obj) { 36 | arguments[0] = _inherit(obj); 37 | return extend.apply(null, arguments); 38 | } 39 | 40 | function toValue(obj, binary, value) { 41 | return value instanceof Function ? value.call(obj, binary.contexts[0]) : value; 42 | } 43 | 44 | function jBinary(view, typeSet) { 45 | /* jshint validthis:true */ 46 | if (!(view instanceof jDataView)) { 47 | view = new jDataView(view, undefined, undefined, typeSet ? typeSet['jBinary.littleEndian'] : undefined); 48 | } 49 | 50 | if (!(this instanceof jBinary)) { 51 | return new jBinary(view, typeSet); 52 | } 53 | 54 | this.view = view; 55 | this.view.seek(0); 56 | this._bitShift = 0; 57 | this.contexts = []; 58 | 59 | if (typeSet) { 60 | this.typeSet = (proto.typeSet === typeSet || proto.typeSet.isPrototypeOf(typeSet)) ? typeSet : inherit(proto.typeSet, typeSet); 61 | this.cacheKey = this._getCached(typeSet, function () { return proto.cacheKey + '.' + (++proto.id) }, true); 62 | } 63 | } 64 | 65 | var proto = jBinary.prototype; 66 | 67 | proto.cacheKey = 'jBinary.Cache'; 68 | proto.id = 0; 69 | 70 | var defineProperty = Object.defineProperty; 71 | 72 | if (defineProperty) { 73 | // this is needed to detect DOM-only version of Object.defineProperty in IE8: 74 | try { 75 | defineProperty({}, 'x', {}); 76 | } catch (e) { 77 | defineProperty = null; 78 | } 79 | } 80 | 81 | if (!defineProperty) { 82 | defineProperty = function (obj, key, descriptor, allowVisible) { 83 | if (allowVisible) { 84 | obj[key] = descriptor.value; 85 | } 86 | }; 87 | } 88 | 89 | proto._getCached = function (obj, valueAccessor, allowVisible) { 90 | if (!obj.hasOwnProperty(this.cacheKey)) { 91 | var value = valueAccessor.call(this, obj); 92 | defineProperty(obj, this.cacheKey, {value: value}, allowVisible); 93 | return value; 94 | } else { 95 | return obj[this.cacheKey]; 96 | } 97 | }; 98 | 99 | proto.getContext = function (filter) { 100 | switch (typeof filter) { 101 | case 'undefined': 102 | filter = 0; 103 | /* falls through */ 104 | case 'number': 105 | return this.contexts[filter]; 106 | 107 | case 'string': 108 | return this.getContext(function (context) { return filter in context }); 109 | 110 | case 'function': 111 | for (var i = 0, length = this.contexts.length; i < length; i++) { 112 | var context = this.contexts[i]; 113 | if (filter.call(this, context)) { 114 | return context; 115 | } 116 | } 117 | return; 118 | } 119 | }; 120 | 121 | proto.inContext = function (newContext, callback) { 122 | this.contexts.unshift(newContext); 123 | var result = callback.call(this); 124 | this.contexts.shift(); 125 | return result; 126 | }; 127 | 128 | jBinary.Type = function (config) { 129 | return inherit(jBinary.Type.prototype, config); 130 | }; 131 | 132 | jBinary.Type.prototype = { 133 | inherit: function (args, getType) { 134 | var _type = this, type; 135 | 136 | function withProp(name, callback) { 137 | var value = _type[name]; 138 | if (value) { 139 | if (!type) { 140 | type = inherit(_type); 141 | } 142 | callback.call(type, value); 143 | type[name] = null; 144 | } 145 | } 146 | 147 | withProp('params', function (params) { 148 | for (var i = 0, length = Math.min(params.length, args.length); i < length; i++) { 149 | this[params[i]] = args[i]; 150 | } 151 | }); 152 | 153 | withProp('setParams', function (setParams) { 154 | setParams.apply(this, args); 155 | }); 156 | 157 | withProp('typeParams', function (typeParams) { 158 | for (var i = 0, length = typeParams.length; i < length; i++) { 159 | var param = typeParams[i], descriptor = this[param]; 160 | if (descriptor) { 161 | this[param] = getType(descriptor); 162 | } 163 | } 164 | }); 165 | 166 | withProp('resolve', function (resolve) { 167 | resolve.call(this, getType); 168 | }); 169 | 170 | return type || _type; 171 | }, 172 | createProperty: function (binary) { 173 | return inherit(this, {binary: binary}); 174 | }, 175 | toValue: function (val, allowResolve) { 176 | if (allowResolve !== false && typeof val === 'string') { 177 | return this.binary.getContext(val)[val]; 178 | } 179 | return toValue(this, this.binary, val); 180 | } 181 | }; 182 | 183 | jBinary.Template = function (config) { 184 | return inherit(jBinary.Template.prototype, config, { 185 | createProperty: function (binary) { 186 | var property = (config.createProperty || jBinary.Template.prototype.createProperty).apply(this, arguments); 187 | if (property.getBaseType) { 188 | property.baseType = property.binary.getType(property.getBaseType(property.binary.contexts[0])); 189 | } 190 | return property; 191 | } 192 | }); 193 | }; 194 | 195 | jBinary.Template.prototype = inherit(jBinary.Type.prototype, { 196 | setParams: function () { 197 | if (this.baseType) { 198 | this.typeParams = ['baseType'].concat(this.typeParams || []); 199 | } 200 | }, 201 | baseRead: function () { 202 | return this.binary.read(this.baseType); 203 | }, 204 | baseWrite: function (value) { 205 | return this.binary.write(this.baseType, value); 206 | } 207 | }); 208 | jBinary.Template.prototype.read = jBinary.Template.prototype.baseRead; 209 | jBinary.Template.prototype.write = jBinary.Template.prototype.baseWrite; 210 | 211 | proto.typeSet = { 212 | 'extend': jBinary.Type({ 213 | setParams: function () { 214 | this.parts = arguments; 215 | }, 216 | resolve: function (getType) { 217 | var parts = this.parts, length = parts.length, partTypes = new Array(length); 218 | for (var i = 0; i < length; i++) { 219 | partTypes[i] = getType(parts[i]); 220 | } 221 | this.parts = partTypes; 222 | }, 223 | read: function () { 224 | var parts = this.parts, obj = this.binary.read(parts[0]); 225 | this.binary.inContext(obj, function () { 226 | for (var i = 1, length = parts.length; i < length; i++) { 227 | extend(obj, this.read(parts[i])); 228 | } 229 | }); 230 | return obj; 231 | }, 232 | write: function (obj) { 233 | var parts = this.parts; 234 | this.binary.inContext(obj, function () { 235 | for (var i = 0, length = parts.length; i < length; i++) { 236 | this.write(parts[i], obj); 237 | } 238 | }); 239 | } 240 | }), 241 | 'enum': jBinary.Template({ 242 | params: ['baseType', 'matches'], 243 | setParams: function (baseType, matches) { 244 | this.backMatches = {}; 245 | for (var key in matches) { 246 | this.backMatches[matches[key]] = key; 247 | } 248 | }, 249 | read: function () { 250 | var value = this.baseRead(); 251 | return value in this.matches ? this.matches[value] : value; 252 | }, 253 | write: function (value) { 254 | this.baseWrite(value in this.backMatches ? this.backMatches[value] : value); 255 | } 256 | }), 257 | 'string': jBinary.Template({ 258 | params: ['length', 'encoding'], 259 | read: function () { 260 | return this.binary.view.getString(this.toValue(this.length), undefined, this.encoding); 261 | }, 262 | write: function (value) { 263 | this.binary.view.writeString(value, this.encoding); 264 | } 265 | }), 266 | 'string0': jBinary.Type({ 267 | params: ['length', 'encoding'], 268 | read: function () { 269 | var view = this.binary.view, maxLength = this.length; 270 | if (maxLength === undefined) { 271 | var startPos = view.tell(), length = 0, code; 272 | maxLength = view.byteLength - startPos; 273 | while (length < maxLength && (code = view.getUint8())) { 274 | length++; 275 | } 276 | var string = view.getString(length, startPos, this.encoding); 277 | if (length < maxLength) { 278 | view.skip(1); 279 | } 280 | return string; 281 | } else { 282 | return view.getString(maxLength, undefined, this.encoding).replace(/\0.*$/, ''); 283 | } 284 | }, 285 | write: function (value) { 286 | var view = this.binary.view, zeroLength = this.length === undefined ? 1 : this.length - value.length; 287 | view.writeString(value, undefined, this.encoding); 288 | if (zeroLength > 0) { 289 | view.writeUint8(0); 290 | view.skip(zeroLength - 1); 291 | } 292 | } 293 | }), 294 | 'array': jBinary.Template({ 295 | params: ['baseType', 'length'], 296 | read: function () { 297 | var length = this.toValue(this.length); 298 | if (this.baseType === proto.typeSet.uint8) { 299 | return this.binary.view.getBytes(length, undefined, true, true); 300 | } 301 | var results; 302 | if (length !== undefined) { 303 | results = new Array(length); 304 | for (var i = 0; i < length; i++) { 305 | results[i] = this.baseRead(); 306 | } 307 | } else { 308 | var end = this.binary.view.byteLength; 309 | results = []; 310 | while (this.binary.tell() < end) { 311 | results.push(this.baseRead()); 312 | } 313 | } 314 | return results; 315 | }, 316 | write: function (values) { 317 | if (this.baseType === proto.typeSet.uint8) { 318 | return this.binary.view.writeBytes(values); 319 | } 320 | for (var i = 0, length = values.length; i < length; i++) { 321 | this.baseWrite(values[i]); 322 | } 323 | } 324 | }), 325 | 'object': jBinary.Type({ 326 | params: ['structure', 'proto'], 327 | resolve: function (getType) { 328 | var structure = {}; 329 | for (var key in this.structure) { 330 | structure[key] = 331 | !(this.structure[key] instanceof Function) 332 | ? getType(this.structure[key]) 333 | : this.structure[key]; 334 | } 335 | this.structure = structure; 336 | }, 337 | read: function () { 338 | var self = this, structure = this.structure, output = this.proto ? inherit(this.proto) : {}; 339 | this.binary.inContext(output, function () { 340 | for (var key in structure) { 341 | var value = !(structure[key] instanceof Function) 342 | ? this.read(structure[key]) 343 | : structure[key].call(self, this.contexts[0]); 344 | // skipping undefined call results (useful for 'if' statement) 345 | if (value !== undefined) { 346 | output[key] = value; 347 | } 348 | } 349 | }); 350 | return output; 351 | }, 352 | write: function (data) { 353 | var self = this, structure = this.structure; 354 | this.binary.inContext(data, function () { 355 | for (var key in structure) { 356 | if (!(structure[key] instanceof Function)) { 357 | this.write(structure[key], data[key]); 358 | } else { 359 | data[key] = structure[key].call(self, this.contexts[0]); 360 | } 361 | } 362 | }); 363 | } 364 | }), 365 | 'bitfield': jBinary.Type({ 366 | params: ['bitSize'], 367 | read: function () { 368 | var bitSize = this.bitSize, 369 | binary = this.binary, 370 | fieldValue = 0; 371 | 372 | if (binary._bitShift < 0 || binary._bitShift >= 8) { 373 | var byteShift = binary._bitShift >> 3; // Math.floor(_bitShift / 8) 374 | binary.skip(byteShift); 375 | binary._bitShift &= 7; // _bitShift + 8 * Math.floor(_bitShift / 8) 376 | } 377 | if (binary._bitShift > 0 && bitSize >= 8 - binary._bitShift) { 378 | fieldValue = binary.view.getUint8() & ~(-1 << (8 - binary._bitShift)); 379 | bitSize -= 8 - binary._bitShift; 380 | binary._bitShift = 0; 381 | } 382 | while (bitSize >= 8) { 383 | fieldValue = binary.view.getUint8() | (fieldValue << 8); 384 | bitSize -= 8; 385 | } 386 | if (bitSize > 0) { 387 | fieldValue = ((binary.view.getUint8() >>> (8 - (binary._bitShift + bitSize))) & ~(-1 << bitSize)) | (fieldValue << bitSize); 388 | binary._bitShift += bitSize - 8; // passing negative value for next pass 389 | } 390 | 391 | return fieldValue >>> 0; 392 | }, 393 | write: function (value) { 394 | var bitSize = this.bitSize, 395 | binary = this.binary, 396 | pos, 397 | curByte; 398 | 399 | if (binary._bitShift < 0 || binary._bitShift >= 8) { 400 | var byteShift = binary._bitShift >> 3; // Math.floor(_bitShift / 8) 401 | binary.skip(byteShift); 402 | binary._bitShift &= 7; // _bitShift + 8 * Math.floor(_bitShift / 8) 403 | } 404 | if (binary._bitShift > 0 && bitSize >= 8 - binary._bitShift) { 405 | pos = binary.tell(); 406 | curByte = binary.view.getUint8(pos) & (-1 << (8 - binary._bitShift)); 407 | curByte |= value >>> (bitSize - (8 - binary._bitShift)); 408 | binary.view.setUint8(pos, curByte); 409 | bitSize -= 8 - binary._bitShift; 410 | binary._bitShift = 0; 411 | } 412 | while (bitSize >= 8) { 413 | binary.view.writeUint8((value >>> (bitSize - 8)) & 0xff); 414 | bitSize -= 8; 415 | } 416 | if (bitSize > 0) { 417 | pos = binary.tell(); 418 | curByte = binary.view.getUint8(pos) & ~(~(-1 << bitSize) << (8 - (binary._bitShift + bitSize))); 419 | curByte |= (value & ~(-1 << bitSize)) << (8 - (binary._bitShift + bitSize)); 420 | binary.view.setUint8(pos, curByte); 421 | binary._bitShift += bitSize - 8; // passing negative value for next pass 422 | } 423 | } 424 | }), 425 | 'if': jBinary.Template({ 426 | params: ['condition', 'trueType', 'falseType'], 427 | typeParams: ['trueType', 'falseType'], 428 | getBaseType: function (context) { 429 | return this.toValue(this.condition) ? this.trueType : this.falseType; 430 | } 431 | }), 432 | 'if_not': jBinary.Template({ 433 | setParams: function (condition, falseType, trueType) { 434 | this.baseType = ['if', condition, trueType, falseType]; 435 | } 436 | }), 437 | 'const': jBinary.Template({ 438 | params: ['baseType', 'value', 'strict'], 439 | read: function () { 440 | var value = this.baseRead(); 441 | if (this.strict && value !== this.value) { 442 | if (this.strict instanceof Function) { 443 | return this.strict(value); 444 | } else { 445 | throw new TypeError('Unexpected value.'); 446 | } 447 | } 448 | return value; 449 | }, 450 | write: function (value) { 451 | this.baseWrite((this.strict || value === undefined) ? this.value : value); 452 | } 453 | }), 454 | 'skip': jBinary.Type({ 455 | setParams: function (length) { 456 | this.read = this.write = function () { 457 | this.binary.view.skip(this.toValue(length)); 458 | }; 459 | } 460 | }), 461 | 'blob': jBinary.Type({ 462 | params: ['length'], 463 | read: function () { 464 | return this.binary.view.getBytes(this.toValue(this.length)); 465 | }, 466 | write: function (bytes) { 467 | this.binary.view.writeBytes(bytes, true); 468 | } 469 | }), 470 | 'binary': jBinary.Template({ 471 | params: ['length', 'typeSet'], 472 | read: function () { 473 | var startPos = this.binary.tell(); 474 | var endPos = this.binary.skip(this.toValue(this.length)); 475 | var view = this.binary.view.slice(startPos, endPos); 476 | return new jBinary(view, this.typeSet); 477 | }, 478 | write: function (binary) { 479 | this.binary.write('blob', binary.read('blob', 0)); 480 | } 481 | }), 482 | 'lazy': jBinary.Template({ 483 | marker: 'jBinary.Lazy', 484 | params: ['innerType'], 485 | setParams: function (innerType, length) { 486 | this.baseType = ['binary', length]; 487 | }, 488 | typeParams: ['innerType'], 489 | read: function () { 490 | var accessor = function (newValue) { 491 | if (arguments.length === 0) { 492 | // returning cached or resolving value 493 | return 'value' in accessor ? accessor.value : (accessor.value = accessor.binary.read(accessor.innerType)); 494 | } else { 495 | // marking resolver as dirty for `write` method 496 | return extend(accessor, { 497 | wasChanged: true, 498 | value: newValue 499 | }).value; 500 | } 501 | }; 502 | accessor[this.marker] = true; 503 | return extend(accessor, { 504 | binary: this.baseRead(), 505 | innerType: this.innerType 506 | }); 507 | }, 508 | write: function (accessor) { 509 | if (accessor.wasChanged || !accessor[this.marker]) { 510 | // resolving value if it was changed or given accessor is external 511 | this.binary.write(this.innerType, accessor()); 512 | } else { 513 | // copying blob from original binary slice otherwise 514 | this.baseWrite(accessor.binary); 515 | } 516 | } 517 | }) 518 | }; 519 | 520 | var dataTypes = [ 521 | 'Uint8', 522 | 'Uint16', 523 | 'Uint32', 524 | 'Uint64', 525 | 'Int8', 526 | 'Int16', 527 | 'Int32', 528 | 'Int64', 529 | 'Float32', 530 | 'Float64', 531 | 'Char' 532 | ]; 533 | 534 | var simpleType = jBinary.Type({ 535 | params: ['littleEndian'], 536 | read: function () { 537 | return this.binary.view['get' + this.dataType](undefined, this.littleEndian); 538 | }, 539 | write: function (value) { 540 | this.binary.view['write' + this.dataType](value, this.littleEndian); 541 | } 542 | }); 543 | 544 | for (var i = 0, length = dataTypes.length; i < length; i++) { 545 | var dataType = dataTypes[i]; 546 | proto.typeSet[dataType.toLowerCase()] = inherit(simpleType, {dataType: dataType}); 547 | } 548 | 549 | extend(proto.typeSet, { 550 | 'byte': proto.typeSet.uint8, 551 | 'float': proto.typeSet.float32, 552 | 'double': proto.typeSet.float64 553 | }); 554 | 555 | proto.toValue = function (value) { 556 | return toValue(this, this, value); 557 | }; 558 | 559 | proto.seek = function (position, callback) { 560 | position = this.toValue(position); 561 | if (callback !== undefined) { 562 | var oldPos = this.view.tell(); 563 | this.view.seek(position); 564 | var result = callback.call(this); 565 | this.view.seek(oldPos); 566 | return result; 567 | } else { 568 | return this.view.seek(position); 569 | } 570 | }; 571 | 572 | proto.tell = function () { 573 | return this.view.tell(); 574 | }; 575 | 576 | proto.skip = function (offset, callback) { 577 | return this.seek(this.tell() + this.toValue(offset), callback); 578 | }; 579 | 580 | proto.getType = function (type, args) { 581 | switch (typeof type) { 582 | case 'string': 583 | if (!(type in this.typeSet)) { 584 | throw new ReferenceError('Unknown type `' + type + '`'); 585 | } 586 | return this.getType(this.typeSet[type], args); 587 | 588 | case 'number': 589 | return this.getType(proto.typeSet.bitfield, [type]); 590 | 591 | case 'object': 592 | if (type instanceof jBinary.Type) { 593 | var binary = this; 594 | return type.inherit(args || [], function (type) { return binary.getType(type) }); 595 | } else { 596 | var isArray = type instanceof Array; 597 | return this._getCached( 598 | type, 599 | ( 600 | isArray 601 | ? function (type) { return this.getType(type[0], type.slice(1)) } 602 | : function (structure) { return this.getType(proto.typeSet.object, [structure]) } 603 | ), 604 | isArray 605 | ); 606 | } 607 | } 608 | }; 609 | 610 | proto.createProperty = function (type) { 611 | return this.getType(type).createProperty(this); 612 | }; 613 | 614 | proto._action = function (type, offset, callback) { 615 | if (type === undefined) { 616 | return; 617 | } 618 | return offset !== undefined ? this.seek(offset, callback) : callback.call(this); 619 | }; 620 | 621 | proto.read = function (type, offset) { 622 | return this._action( 623 | type, 624 | offset, 625 | function () { return this.createProperty(type).read(this.contexts[0]) } 626 | ); 627 | }; 628 | 629 | proto.write = function (type, data, offset) { 630 | this._action( 631 | type, 632 | offset, 633 | function () { this.createProperty(type).write(data, this.contexts[0]) } 634 | ); 635 | }; 636 | 637 | proto._toURI = 638 | ('URL' in global && 'createObjectURL' in URL) 639 | ? function (type) { 640 | var data = this.seek(0, function () { return this.view.getBytes() }); 641 | return URL.createObjectURL(new Blob([data], {type: type})); 642 | } 643 | : function (type) { 644 | var string = this.seek(0, function () { return this.view.getString(undefined, undefined, this.view._isNodeBuffer ? 'base64' : 'binary') }); 645 | return 'data:' + type + ';base64,' + (this.view._isNodeBuffer ? string : btoa(string)); 646 | }; 647 | 648 | proto.toURI = function (mimeType) { 649 | return this._toURI(mimeType || this.typeSet['jBinary.mimeType']); 650 | }; 651 | 652 | proto.slice = function (start, end, forceCopy) { 653 | return new jBinary(this.view.slice(start, end, forceCopy), this.typeSet); 654 | }; 655 | 656 | var hasStreamSupport = hasNodeRequire('stream') && require('stream').Readable; 657 | 658 | jBinary.loadData = function (source, callback) { 659 | if ('Blob' in global && source instanceof Blob) { 660 | var reader = new FileReader(); 661 | reader.onload = reader.onerror = function() { callback(this.error, this.result) }; 662 | reader.readAsArrayBuffer(source); 663 | } else 664 | if (hasStreamSupport && source instanceof require('stream').Readable) { 665 | var buffers = []; 666 | 667 | source 668 | .on('readable', function () { buffers.push(this.read()) }) 669 | .on('end', function () { callback(null, Buffer.concat(buffers)) }) 670 | .on('error', callback); 671 | } else { 672 | if (typeof source !== 'string') { 673 | return callback(new TypeError('Unsupported source type.')); 674 | } 675 | 676 | var dataParts = source.match(/^data:(.+?)(;base64)?,(.*)$/); 677 | if (dataParts) { 678 | var isBase64 = dataParts[2], 679 | content = dataParts[3]; 680 | 681 | try { 682 | callback( 683 | null, 684 | ( 685 | (isBase64 && jDataView.prototype.compatibility.NodeBuffer) 686 | ? new Buffer(content, 'base64') 687 | : (isBase64 ? atob : decodeURIComponent)(content) 688 | ) 689 | ); 690 | } catch (e) { 691 | callback(e); 692 | } 693 | } else 694 | if ('XMLHttpRequest' in global) { 695 | var xhr = new XMLHttpRequest(); 696 | xhr.open('GET', source, true); 697 | 698 | // new browsers (XMLHttpRequest2-compliant) 699 | if ('responseType' in xhr) { 700 | xhr.responseType = 'arraybuffer'; 701 | } 702 | // old browsers (XMLHttpRequest-compliant) 703 | else if ('overrideMimeType' in xhr) { 704 | xhr.overrideMimeType('text/plain; charset=x-user-defined'); 705 | } 706 | // IE9 (Microsoft.XMLHTTP-compliant) 707 | else { 708 | xhr.setRequestHeader('Accept-Charset', 'x-user-defined'); 709 | } 710 | 711 | // shim for onload for old IE 712 | if (!('onload' in xhr)) { 713 | xhr.onreadystatechange = function () { 714 | if (this.readyState === 4) { 715 | this.onload(); 716 | } 717 | }; 718 | } 719 | 720 | xhr.onload = function() { 721 | if (this.status !== 0 && this.status !== 200) { 722 | return callback(new Error('HTTP Error #' + this.status + ': ' + this.statusText)); 723 | } 724 | 725 | // emulating response field for IE9 726 | if (!('response' in this)) { 727 | this.response = new VBArray(this.responseBody).toArray(); 728 | } 729 | 730 | callback(null, this.response); 731 | }; 732 | 733 | xhr.send(); 734 | } else { 735 | var isHTTP = /^(https?):\/\//.test(source); 736 | 737 | if (isHTTP && hasNodeRequire('request')) { 738 | require('request').get({ 739 | uri: source, 740 | encoding: null 741 | }, function (error, response, body) { 742 | if (!error && response.statusCode !== 200) { 743 | var statusText = require('http').STATUS_CODES[response.statusCode]; 744 | error = new Error('HTTP Error #' + response.statusCode + ': ' + statusText); 745 | } 746 | callback(error, body); 747 | }); 748 | } else 749 | if (!isHTTP && hasNodeRequire('fs')) { 750 | require('fs').readFile(source, callback); 751 | } else { 752 | callback(new TypeError('Unsupported source type.')); 753 | } 754 | } 755 | } 756 | }; 757 | 758 | function setJDataView(_jDataView) { 759 | jDataView = _jDataView; 760 | jDataView.prototype.toBinary = function (typeSet) { 761 | return new jBinary(this, typeSet); 762 | }; 763 | } 764 | 765 | if (typeof module === 'object' && module && typeof module.exports === 'object') { 766 | setJDataView(require('jdataview')); 767 | module.exports = jBinary; 768 | } else 769 | if (typeof define === 'function' && define.amd) { 770 | define(['jdataview'], function (_jDataView) { 771 | setJDataView(_jDataView); 772 | return jBinary; 773 | }); 774 | } else { 775 | setJDataView(global.jDataView); 776 | global.jBinary = jBinary; 777 | } 778 | 779 | })((function () { /* jshint strict: false */ return this })()); 780 | -------------------------------------------------------------------------------- /lib/jdataview.js: -------------------------------------------------------------------------------- 1 | // 2 | // jDataView by Vjeux - Jan 2010 3 | // Continued by RReverser - Feb 2013 4 | // 5 | // A unique way to work with a binary file in the browser 6 | // http://github.com/jDataView/jDataView 7 | // http://jDataView.github.io/ 8 | 9 | (function (global) { 10 | 11 | 'use strict'; 12 | 13 | var compatibility = { 14 | // NodeJS Buffer in v0.5.5 and newer 15 | NodeBuffer: 'Buffer' in global && 'readInt16LE' in Buffer.prototype, 16 | DataView: 'DataView' in global && ( 17 | 'getFloat64' in DataView.prototype || // Chrome 18 | 'getFloat64' in new DataView(new ArrayBuffer(1)) // Node 19 | ), 20 | ArrayBuffer: 'ArrayBuffer' in global, 21 | PixelData: 'CanvasPixelArray' in global && 'ImageData' in global && 'document' in global 22 | }; 23 | 24 | // we don't want to bother with old Buffer implementation 25 | if (compatibility.NodeBuffer) { 26 | (function (buffer) { 27 | try { 28 | buffer.writeFloatLE(Infinity, 0); 29 | } catch (e) { 30 | compatibility.NodeBuffer = false; 31 | } 32 | })(new Buffer(4)); 33 | } 34 | 35 | if (compatibility.PixelData) { 36 | var createPixelData = function (byteLength, buffer) { 37 | var data = createPixelData.context2d.createImageData((byteLength + 3) / 4, 1).data; 38 | data.byteLength = byteLength; 39 | if (buffer !== undefined) { 40 | for (var i = 0; i < byteLength; i++) { 41 | data[i] = buffer[i]; 42 | } 43 | } 44 | return data; 45 | }; 46 | createPixelData.context2d = document.createElement('canvas').getContext('2d'); 47 | } 48 | 49 | var dataTypes = { 50 | 'Int8': 1, 51 | 'Int16': 2, 52 | 'Int32': 4, 53 | 'Uint8': 1, 54 | 'Uint16': 2, 55 | 'Uint32': 4, 56 | 'Float32': 4, 57 | 'Float64': 8 58 | }; 59 | 60 | var nodeNaming = { 61 | 'Int8': 'Int8', 62 | 'Int16': 'Int16', 63 | 'Int32': 'Int32', 64 | 'Uint8': 'UInt8', 65 | 'Uint16': 'UInt16', 66 | 'Uint32': 'UInt32', 67 | 'Float32': 'Float', 68 | 'Float64': 'Double' 69 | }; 70 | 71 | function arrayFrom(arrayLike, forceCopy) { 72 | return (!forceCopy && (arrayLike instanceof Array)) ? arrayLike : Array.prototype.slice.call(arrayLike); 73 | } 74 | 75 | function defined(value, defaultValue) { 76 | return value !== undefined ? value : defaultValue; 77 | } 78 | 79 | function jDataView(buffer, byteOffset, byteLength, littleEndian) { 80 | /* jshint validthis:true */ 81 | 82 | if (buffer instanceof jDataView) { 83 | var result = buffer.slice(byteOffset, byteOffset + byteLength); 84 | result._littleEndian = defined(littleEndian, result._littleEndian); 85 | return result; 86 | } 87 | 88 | if (!(this instanceof jDataView)) { 89 | return new jDataView(buffer, byteOffset, byteLength, littleEndian); 90 | } 91 | 92 | this.buffer = buffer = jDataView.wrapBuffer(buffer); 93 | 94 | // Check parameters and existing functionnalities 95 | this._isArrayBuffer = compatibility.ArrayBuffer && buffer instanceof ArrayBuffer; 96 | this._isPixelData = compatibility.PixelData && buffer instanceof CanvasPixelArray; 97 | this._isDataView = compatibility.DataView && this._isArrayBuffer; 98 | this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer; 99 | 100 | // Handle Type Errors 101 | if (!this._isNodeBuffer && !this._isArrayBuffer && !this._isPixelData && !(buffer instanceof Array)) { 102 | throw new TypeError('jDataView buffer has an incompatible type'); 103 | } 104 | 105 | // Default Values 106 | this._littleEndian = !!littleEndian; 107 | 108 | var bufferLength = 'byteLength' in buffer ? buffer.byteLength : buffer.length; 109 | this.byteOffset = byteOffset = defined(byteOffset, 0); 110 | this.byteLength = byteLength = defined(byteLength, bufferLength - byteOffset); 111 | 112 | if (!this._isDataView) { 113 | this._checkBounds(byteOffset, byteLength, bufferLength); 114 | } else { 115 | this._view = new DataView(buffer, byteOffset, byteLength); 116 | } 117 | 118 | // Create uniform methods (action wrappers) for the following data types 119 | 120 | this._engineAction = 121 | this._isDataView 122 | ? this._dataViewAction 123 | : this._isNodeBuffer 124 | ? this._nodeBufferAction 125 | : this._isArrayBuffer 126 | ? this._arrayBufferAction 127 | : this._arrayAction; 128 | } 129 | 130 | function getCharCodes(string) { 131 | if (compatibility.NodeBuffer) { 132 | return new Buffer(string, 'binary'); 133 | } 134 | 135 | var Type = compatibility.ArrayBuffer ? Uint8Array : Array, 136 | codes = new Type(string.length); 137 | 138 | for (var i = 0, length = string.length; i < length; i++) { 139 | codes[i] = string.charCodeAt(i) & 0xff; 140 | } 141 | return codes; 142 | } 143 | 144 | // mostly internal function for wrapping any supported input (String or Array-like) to best suitable buffer format 145 | jDataView.wrapBuffer = function (buffer) { 146 | switch (typeof buffer) { 147 | case 'number': 148 | if (compatibility.NodeBuffer) { 149 | buffer = new Buffer(buffer); 150 | buffer.fill(0); 151 | } else 152 | if (compatibility.ArrayBuffer) { 153 | buffer = new Uint8Array(buffer).buffer; 154 | } else 155 | if (compatibility.PixelData) { 156 | buffer = createPixelData(buffer); 157 | } else { 158 | buffer = new Array(buffer); 159 | for (var i = 0; i < buffer.length; i++) { 160 | buffer[i] = 0; 161 | } 162 | } 163 | return buffer; 164 | 165 | case 'string': 166 | buffer = getCharCodes(buffer); 167 | /* falls through */ 168 | default: 169 | if ('length' in buffer && !((compatibility.NodeBuffer && buffer instanceof Buffer) || (compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) || (compatibility.PixelData && buffer instanceof CanvasPixelArray))) { 170 | if (compatibility.NodeBuffer) { 171 | buffer = new Buffer(buffer); 172 | } else 173 | if (compatibility.ArrayBuffer) { 174 | if (!(buffer instanceof ArrayBuffer)) { 175 | buffer = buffer instanceof Uint8Array ? buffer.buffer : new Uint8Array(buffer).buffer; 176 | } 177 | } else 178 | if (compatibility.PixelData) { 179 | buffer = createPixelData(buffer.length, buffer); 180 | } else { 181 | buffer = arrayFrom(buffer); 182 | } 183 | } 184 | return buffer; 185 | } 186 | }; 187 | 188 | function pow2(n) { 189 | return (n >= 0 && n < 31) ? (1 << n) : (pow2[n] || (pow2[n] = Math.pow(2, n))); 190 | } 191 | 192 | // left for backward compatibility 193 | jDataView.createBuffer = function () { 194 | return jDataView.wrapBuffer(arguments); 195 | }; 196 | 197 | function Uint64(lo, hi) { 198 | this.lo = lo; 199 | this.hi = hi; 200 | } 201 | 202 | jDataView.Uint64 = Uint64; 203 | 204 | Uint64.prototype = { 205 | valueOf: function () { 206 | return this.lo + pow2(32) * this.hi; 207 | }, 208 | 209 | toString: function () { 210 | return Number.prototype.toString.apply(this.valueOf(), arguments); 211 | } 212 | }; 213 | 214 | Uint64.fromNumber = function (number) { 215 | var hi = Math.floor(number / pow2(32)), 216 | lo = number - hi * pow2(32); 217 | 218 | return new Uint64(lo, hi); 219 | }; 220 | 221 | function Int64(lo, hi) { 222 | Uint64.apply(this, arguments); 223 | } 224 | 225 | jDataView.Int64 = Int64; 226 | 227 | Int64.prototype = 'create' in Object ? Object.create(Uint64.prototype) : new Uint64(); 228 | 229 | Int64.prototype.valueOf = function () { 230 | if (this.hi < pow2(31)) { 231 | return Uint64.prototype.valueOf.apply(this, arguments); 232 | } 233 | return -((pow2(32) - this.lo) + pow2(32) * (pow2(32) - 1 - this.hi)); 234 | }; 235 | 236 | Int64.fromNumber = function (number) { 237 | var lo, hi; 238 | if (number >= 0) { 239 | var unsigned = Uint64.fromNumber(number); 240 | lo = unsigned.lo; 241 | hi = unsigned.hi; 242 | } else { 243 | hi = Math.floor(number / pow2(32)); 244 | lo = number - hi * pow2(32); 245 | hi += pow2(32); 246 | } 247 | return new Int64(lo, hi); 248 | }; 249 | 250 | jDataView.prototype = { 251 | _offset: 0, 252 | _bitOffset: 0, 253 | 254 | compatibility: compatibility, 255 | 256 | _checkBounds: function (byteOffset, byteLength, maxLength) { 257 | // Do additional checks to simulate DataView 258 | if (typeof byteOffset !== 'number') { 259 | throw new TypeError('Offset is not a number.'); 260 | } 261 | if (typeof byteLength !== 'number') { 262 | throw new TypeError('Size is not a number.'); 263 | } 264 | if (byteLength < 0) { 265 | throw new RangeError('Length is negative.'); 266 | } 267 | if (byteOffset < 0 || byteOffset + byteLength > defined(maxLength, this.byteLength)) { 268 | throw new RangeError('Offsets are out of bounds.'); 269 | } 270 | }, 271 | 272 | _action: function (type, isReadAction, byteOffset, littleEndian, value) { 273 | return this._engineAction( 274 | type, 275 | isReadAction, 276 | defined(byteOffset, this._offset), 277 | defined(littleEndian, this._littleEndian), 278 | value 279 | ); 280 | }, 281 | 282 | _dataViewAction: function (type, isReadAction, byteOffset, littleEndian, value) { 283 | // Move the internal offset forward 284 | this._offset = byteOffset + dataTypes[type]; 285 | return isReadAction ? this._view['get' + type](byteOffset, littleEndian) : this._view['set' + type](byteOffset, value, littleEndian); 286 | }, 287 | 288 | _nodeBufferAction: function (type, isReadAction, byteOffset, littleEndian, value) { 289 | // Move the internal offset forward 290 | this._offset = byteOffset + dataTypes[type]; 291 | var nodeName = nodeNaming[type] + ((type === 'Int8' || type === 'Uint8') ? '' : littleEndian ? 'LE' : 'BE'); 292 | byteOffset += this.byteOffset; 293 | return isReadAction ? this.buffer['read' + nodeName](byteOffset) : this.buffer['write' + nodeName](value, byteOffset); 294 | }, 295 | 296 | _arrayBufferAction: function (type, isReadAction, byteOffset, littleEndian, value) { 297 | var size = dataTypes[type], TypedArray = global[type + 'Array'], typedArray; 298 | 299 | littleEndian = defined(littleEndian, this._littleEndian); 300 | 301 | // ArrayBuffer: we use a typed array of size 1 from original buffer if alignment is good and from slice when it's not 302 | if (size === 1 || ((this.byteOffset + byteOffset) % size === 0 && littleEndian)) { 303 | typedArray = new TypedArray(this.buffer, this.byteOffset + byteOffset, 1); 304 | this._offset = byteOffset + size; 305 | return isReadAction ? typedArray[0] : (typedArray[0] = value); 306 | } else { 307 | var bytes = new Uint8Array(isReadAction ? this.getBytes(size, byteOffset, littleEndian, true) : size); 308 | typedArray = new TypedArray(bytes.buffer, 0, 1); 309 | 310 | if (isReadAction) { 311 | return typedArray[0]; 312 | } else { 313 | typedArray[0] = value; 314 | this._setBytes(byteOffset, bytes, littleEndian); 315 | } 316 | } 317 | }, 318 | 319 | _arrayAction: function (type, isReadAction, byteOffset, littleEndian, value) { 320 | return isReadAction ? this['_get' + type](byteOffset, littleEndian) : this['_set' + type.replace('Uint', 'Int')](byteOffset, value, littleEndian); 321 | }, 322 | 323 | // Helpers 324 | 325 | _getBytes: function (length, byteOffset, littleEndian) { 326 | littleEndian = defined(littleEndian, this._littleEndian); 327 | byteOffset = defined(byteOffset, this._offset); 328 | length = defined(length, this.byteLength - byteOffset); 329 | 330 | this._checkBounds(byteOffset, length); 331 | 332 | byteOffset += this.byteOffset; 333 | 334 | this._offset = byteOffset - this.byteOffset + length; 335 | 336 | var result = this._isArrayBuffer 337 | ? new Uint8Array(this.buffer, byteOffset, length) 338 | : (this.buffer.slice || Array.prototype.slice).call(this.buffer, byteOffset, byteOffset + length); 339 | 340 | return littleEndian || length <= 1 ? result : arrayFrom(result).reverse(); 341 | }, 342 | 343 | // wrapper for external calls (do not return inner buffer directly to prevent it's modifying) 344 | getBytes: function (length, byteOffset, littleEndian, toArray) { 345 | var result = this._getBytes(length, byteOffset, defined(littleEndian, true)); 346 | return toArray ? arrayFrom(result) : result; 347 | }, 348 | 349 | _setBytes: function (byteOffset, bytes, littleEndian) { 350 | var length = bytes.length; 351 | 352 | // needed for Opera 353 | if (length === 0) { 354 | return; 355 | } 356 | 357 | littleEndian = defined(littleEndian, this._littleEndian); 358 | byteOffset = defined(byteOffset, this._offset); 359 | 360 | this._checkBounds(byteOffset, length); 361 | 362 | if (!littleEndian && length > 1) { 363 | bytes = arrayFrom(bytes, true).reverse(); 364 | } 365 | 366 | byteOffset += this.byteOffset; 367 | 368 | if (this._isArrayBuffer) { 369 | new Uint8Array(this.buffer, byteOffset, length).set(bytes); 370 | } 371 | else { 372 | if (this._isNodeBuffer) { 373 | new Buffer(bytes).copy(this.buffer, byteOffset); 374 | } else { 375 | for (var i = 0; i < length; i++) { 376 | this.buffer[byteOffset + i] = bytes[i]; 377 | } 378 | } 379 | } 380 | 381 | this._offset = byteOffset - this.byteOffset + length; 382 | }, 383 | 384 | setBytes: function (byteOffset, bytes, littleEndian) { 385 | this._setBytes(byteOffset, bytes, defined(littleEndian, true)); 386 | }, 387 | 388 | writeBytes: function (bytes, littleEndian) { 389 | this.setBytes(undefined, bytes, littleEndian); 390 | }, 391 | 392 | getString: function (byteLength, byteOffset, encoding) { 393 | if (this._isNodeBuffer) { 394 | byteOffset = defined(byteOffset, this._offset); 395 | byteLength = defined(byteLength, this.byteLength - byteOffset); 396 | 397 | this._checkBounds(byteOffset, byteLength); 398 | 399 | this._offset = byteOffset + byteLength; 400 | return this.buffer.toString(encoding || 'binary', this.byteOffset + byteOffset, this.byteOffset + this._offset); 401 | } 402 | var bytes = this._getBytes(byteLength, byteOffset, true), string = ''; 403 | byteLength = bytes.length; 404 | for (var i = 0; i < byteLength; i++) { 405 | string += String.fromCharCode(bytes[i]); 406 | } 407 | if (encoding === 'utf8') { 408 | string = decodeURIComponent(escape(string)); 409 | } 410 | return string; 411 | }, 412 | 413 | setString: function (byteOffset, subString, encoding) { 414 | if (this._isNodeBuffer) { 415 | byteOffset = defined(byteOffset, this._offset); 416 | this._checkBounds(byteOffset, subString.length); 417 | this._offset = byteOffset + this.buffer.write(subString, this.byteOffset + byteOffset, encoding || 'binary'); 418 | return; 419 | } 420 | if (encoding === 'utf8') { 421 | subString = unescape(encodeURIComponent(subString)); 422 | } 423 | this._setBytes(byteOffset, getCharCodes(subString), true); 424 | }, 425 | 426 | writeString: function (subString, encoding) { 427 | this.setString(undefined, subString, encoding); 428 | }, 429 | 430 | getChar: function (byteOffset) { 431 | return this.getString(1, byteOffset); 432 | }, 433 | 434 | setChar: function (byteOffset, character) { 435 | this.setString(byteOffset, character); 436 | }, 437 | 438 | writeChar: function (character) { 439 | this.setChar(undefined, character); 440 | }, 441 | 442 | tell: function () { 443 | return this._offset; 444 | }, 445 | 446 | seek: function (byteOffset) { 447 | this._checkBounds(byteOffset, 0); 448 | /* jshint boss: true */ 449 | return this._offset = byteOffset; 450 | }, 451 | 452 | skip: function (byteLength) { 453 | return this.seek(this._offset + byteLength); 454 | }, 455 | 456 | slice: function (start, end, forceCopy) { 457 | return forceCopy 458 | ? new jDataView(this.getBytes(end - start, start, true, true), undefined, undefined, this._littleEndian) 459 | : new jDataView(this.buffer, this.byteOffset + start, end - start, this._littleEndian); 460 | }, 461 | 462 | // Compatibility functions 463 | 464 | _getFloat64: function (byteOffset, littleEndian) { 465 | var b = this._getBytes(8, byteOffset, littleEndian), 466 | 467 | sign = 1 - (2 * (b[7] >> 7)), 468 | exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1), 469 | 470 | // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead 471 | mantissa = ((b[6] & 0x0f) * pow2(48)) + (b[5] * pow2(40)) + (b[4] * pow2(32)) + 472 | (b[3] * pow2(24)) + (b[2] * pow2(16)) + (b[1] * pow2(8)) + b[0]; 473 | 474 | if (exponent === 1024) { 475 | if (mantissa !== 0) { 476 | return NaN; 477 | } else { 478 | return sign * Infinity; 479 | } 480 | } 481 | 482 | if (exponent === -1023) { // Denormalized 483 | return sign * mantissa * pow2(-1022 - 52); 484 | } 485 | 486 | return sign * (1 + mantissa * pow2(-52)) * pow2(exponent); 487 | }, 488 | 489 | _getFloat32: function (byteOffset, littleEndian) { 490 | var b = this._getBytes(4, byteOffset, littleEndian), 491 | 492 | sign = 1 - (2 * (b[3] >> 7)), 493 | exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127, 494 | mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0]; 495 | 496 | if (exponent === 128) { 497 | if (mantissa !== 0) { 498 | return NaN; 499 | } else { 500 | return sign * Infinity; 501 | } 502 | } 503 | 504 | if (exponent === -127) { // Denormalized 505 | return sign * mantissa * pow2(-126 - 23); 506 | } 507 | 508 | return sign * (1 + mantissa * pow2(-23)) * pow2(exponent); 509 | }, 510 | 511 | _get64: function (Type, byteOffset, littleEndian) { 512 | littleEndian = defined(littleEndian, this._littleEndian); 513 | byteOffset = defined(byteOffset, this._offset); 514 | 515 | var parts = littleEndian ? [0, 4] : [4, 0]; 516 | 517 | for (var i = 0; i < 2; i++) { 518 | parts[i] = this.getUint32(byteOffset + parts[i], littleEndian); 519 | } 520 | 521 | this._offset = byteOffset + 8; 522 | 523 | return new Type(parts[0], parts[1]); 524 | }, 525 | 526 | getInt64: function (byteOffset, littleEndian) { 527 | return this._get64(Int64, byteOffset, littleEndian); 528 | }, 529 | 530 | getUint64: function (byteOffset, littleEndian) { 531 | return this._get64(Uint64, byteOffset, littleEndian); 532 | }, 533 | 534 | _getInt32: function (byteOffset, littleEndian) { 535 | var b = this._getBytes(4, byteOffset, littleEndian); 536 | return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; 537 | }, 538 | 539 | _getUint32: function (byteOffset, littleEndian) { 540 | return this._getInt32(byteOffset, littleEndian) >>> 0; 541 | }, 542 | 543 | _getInt16: function (byteOffset, littleEndian) { 544 | return (this._getUint16(byteOffset, littleEndian) << 16) >> 16; 545 | }, 546 | 547 | _getUint16: function (byteOffset, littleEndian) { 548 | var b = this._getBytes(2, byteOffset, littleEndian); 549 | return (b[1] << 8) | b[0]; 550 | }, 551 | 552 | _getInt8: function (byteOffset) { 553 | return (this._getUint8(byteOffset) << 24) >> 24; 554 | }, 555 | 556 | _getUint8: function (byteOffset) { 557 | return this._getBytes(1, byteOffset)[0]; 558 | }, 559 | 560 | getSigned: function (bitLength, byteOffset) { 561 | var shift = 32 - bitLength; 562 | return (this.getUnsigned(bitLength, byteOffset) << shift) >> shift; 563 | }, 564 | 565 | getUnsigned: function (bitLength, byteOffset) { 566 | var startBit = (defined(byteOffset, this._offset) << 3) + this._bitOffset, 567 | endBit = startBit + bitLength, 568 | start = startBit >>> 3, 569 | end = (endBit + 7) >>> 3, 570 | b = this._getBytes(end - start, start, true), 571 | value = 0; 572 | 573 | /* jshint boss: true */ 574 | if (this._bitOffset = endBit & 7) { 575 | this._bitOffset -= 8; 576 | } 577 | 578 | for (var i = 0, length = b.length; i < length; i++) { 579 | value = (value << 8) | b[i]; 580 | } 581 | 582 | value >>>= -this._bitOffset; 583 | 584 | return bitLength < 32 ? (value & ~(-1 << bitLength)) : value; 585 | }, 586 | 587 | _setBinaryFloat: function (byteOffset, value, mantSize, expSize, littleEndian) { 588 | var signBit = value < 0 ? 1 : 0, 589 | exponent, 590 | mantissa, 591 | eMax = ~(-1 << (expSize - 1)), 592 | eMin = 1 - eMax; 593 | 594 | if (value < 0) { 595 | value = -value; 596 | } 597 | 598 | if (value === 0) { 599 | exponent = 0; 600 | mantissa = 0; 601 | } else if (isNaN(value)) { 602 | exponent = 2 * eMax + 1; 603 | mantissa = 1; 604 | } else if (value === Infinity) { 605 | exponent = 2 * eMax + 1; 606 | mantissa = 0; 607 | } else { 608 | exponent = Math.floor(Math.log(value) / Math.LN2); 609 | if (exponent >= eMin && exponent <= eMax) { 610 | mantissa = Math.floor((value * pow2(-exponent) - 1) * pow2(mantSize)); 611 | exponent += eMax; 612 | } else { 613 | mantissa = Math.floor(value / pow2(eMin - mantSize)); 614 | exponent = 0; 615 | } 616 | } 617 | 618 | var b = []; 619 | while (mantSize >= 8) { 620 | b.push(mantissa % 256); 621 | mantissa = Math.floor(mantissa / 256); 622 | mantSize -= 8; 623 | } 624 | exponent = (exponent << mantSize) | mantissa; 625 | expSize += mantSize; 626 | while (expSize >= 8) { 627 | b.push(exponent & 0xff); 628 | exponent >>>= 8; 629 | expSize -= 8; 630 | } 631 | b.push((signBit << expSize) | exponent); 632 | 633 | this._setBytes(byteOffset, b, littleEndian); 634 | }, 635 | 636 | _setFloat32: function (byteOffset, value, littleEndian) { 637 | this._setBinaryFloat(byteOffset, value, 23, 8, littleEndian); 638 | }, 639 | 640 | _setFloat64: function (byteOffset, value, littleEndian) { 641 | this._setBinaryFloat(byteOffset, value, 52, 11, littleEndian); 642 | }, 643 | 644 | _set64: function (Type, byteOffset, value, littleEndian) { 645 | if (!(value instanceof Type)) { 646 | value = Type.fromNumber(value); 647 | } 648 | 649 | littleEndian = defined(littleEndian, this._littleEndian); 650 | byteOffset = defined(byteOffset, this._offset); 651 | 652 | var parts = littleEndian ? {lo: 0, hi: 4} : {lo: 4, hi: 0}; 653 | 654 | for (var partName in parts) { 655 | this.setUint32(byteOffset + parts[partName], value[partName], littleEndian); 656 | } 657 | 658 | this._offset = byteOffset + 8; 659 | }, 660 | 661 | setInt64: function (byteOffset, value, littleEndian) { 662 | this._set64(Int64, byteOffset, value, littleEndian); 663 | }, 664 | 665 | writeInt64: function (value, littleEndian) { 666 | this.setInt64(undefined, value, littleEndian); 667 | }, 668 | 669 | setUint64: function (byteOffset, value, littleEndian) { 670 | this._set64(Uint64, byteOffset, value, littleEndian); 671 | }, 672 | 673 | writeUint64: function (value, littleEndian) { 674 | this.setUint64(undefined, value, littleEndian); 675 | }, 676 | 677 | _setInt32: function (byteOffset, value, littleEndian) { 678 | this._setBytes(byteOffset, [ 679 | value & 0xff, 680 | (value >>> 8) & 0xff, 681 | (value >>> 16) & 0xff, 682 | value >>> 24 683 | ], littleEndian); 684 | }, 685 | 686 | _setInt16: function (byteOffset, value, littleEndian) { 687 | this._setBytes(byteOffset, [ 688 | value & 0xff, 689 | (value >>> 8) & 0xff 690 | ], littleEndian); 691 | }, 692 | 693 | _setInt8: function (byteOffset, value) { 694 | this._setBytes(byteOffset, [value & 0xff]); 695 | } 696 | }; 697 | 698 | var proto = jDataView.prototype; 699 | 700 | for (var type in dataTypes) { 701 | (function (type) { 702 | proto['get' + type] = function (byteOffset, littleEndian) { 703 | return this._action(type, true, byteOffset, littleEndian); 704 | }; 705 | proto['set' + type] = function (byteOffset, value, littleEndian) { 706 | this._action(type, false, byteOffset, littleEndian, value); 707 | }; 708 | proto['write' + type] = function (value, littleEndian) { 709 | this['set' + type](undefined, value, littleEndian); 710 | }; 711 | })(type); 712 | } 713 | 714 | if (typeof module === 'object' && module && typeof module.exports === 'object') { 715 | module.exports = jDataView; 716 | } else 717 | if (typeof define === 'function' && define.amd) { 718 | define([], function () { return jDataView }); 719 | } else { 720 | global.jDataView = jDataView; 721 | } 722 | 723 | })((function () { /* jshint strict: false */ return this })()); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "talking-image", 3 | "version": "0.1.0", 4 | "title": "Talking Image", 5 | "description": "Play audio embedded in image files", 6 | "author": "Hage Yaapa ", 7 | "repository": "git@github.com:hacksparrow/talking-image.git", 8 | "license": "MIT", 9 | "bugs": { 10 | "url": "https://github.com/hacksparrow/talking-image/issues" 11 | }, 12 | "devDependencies": { 13 | "express": "*", 14 | "grunt": "*", 15 | "grunt-contrib-uglify": "*", 16 | "grunt-contrib-concat": "*", 17 | "grunt-contrib-jshint": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /resources/2001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/2001.jpg -------------------------------------------------------------------------------- /resources/2001.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/2001.mp3 -------------------------------------------------------------------------------- /resources/2001.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/2001.ogg -------------------------------------------------------------------------------- /resources/bruce.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/bruce.gif -------------------------------------------------------------------------------- /resources/bruce.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/bruce.mp3 -------------------------------------------------------------------------------- /resources/bruce.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/bruce.ogg -------------------------------------------------------------------------------- /resources/johnny.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/johnny.gif -------------------------------------------------------------------------------- /resources/johnny.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/johnny.mp3 -------------------------------------------------------------------------------- /resources/johnny.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/johnny.ogg -------------------------------------------------------------------------------- /resources/nyan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/nyan.gif -------------------------------------------------------------------------------- /resources/nyan.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/nyan.mp3 -------------------------------------------------------------------------------- /resources/nyan.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacksparrow/talking-image/7ce28551cdf6bdd2cd0f0bd64412c24b20e7044e/resources/nyan.ogg -------------------------------------------------------------------------------- /src/talking-image.js: -------------------------------------------------------------------------------- 1 | /********************************************************* 2 | * Talking Image by Hage Yaapa * 3 | * License: MIT * 4 | **********************************************************/ 5 | 6 | /*jshint newcap: false */ 7 | 8 | ;(function(TalkingImage, global) { 9 | 10 | global.TalkingImage = TalkingImage; 11 | 12 | window.addEventListener('load', function() { 13 | var images = document.querySelectorAll('img[data-audio]'); 14 | Array.prototype.forEach.call(images, function(image) { 15 | TalkingImage(image); 16 | }); 17 | }); 18 | 19 | })(function(img) { 20 | 21 | 'use strict'; 22 | 23 | var 24 | 25 | VERSION = '0.1.0', 26 | hasOwnProp = Object.prototype.hasOwnProperty, 27 | 28 | audio = { volume: 1 }, 29 | 30 | formats = { 31 | 'mp3': '\x49\x44\x33\x03\x00\x00\x00\x00', 32 | 'ogg': '\x4F\x67\x67\x53\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00' 33 | }, 34 | 35 | url = img.getAttribute('src'), 36 | options = img.getAttribute('data-audio'), 37 | 38 | is_set = function(option, options) { 39 | return options.indexOf(option) > -1; 40 | }, 41 | 42 | detectFormat = function(d) { 43 | var i, ii, j, n, format; 44 | for (i = 0, ii = d.byteLength; i < ii; i++) { 45 | for (j in formats) { 46 | format = true; 47 | if (hasOwnProp.call(formats, j) && formats[j].length + i < ii) { 48 | for (n = 0; n < formats[j].length; n++) { 49 | if (d.getChar(i + n) !== formats[j][n]) { 50 | format = false; 51 | break; 52 | } 53 | } 54 | if (format) { 55 | audio.format = j; 56 | audio.offset = i; 57 | return true; 58 | } 59 | } 60 | } 61 | } 62 | return false; 63 | }; 64 | 65 | jBinary.loadData(url, function(err, data) { 66 | var d = new jDataView(data); 67 | if (!err && detectFormat(d)) { 68 | // Reset the cursor 69 | d.seek(0); 70 | // Extract the audio data 71 | d.getString(audio.offset); 72 | // Convert binary data to base64 encoded string and assign it to the audio object usind Data URI 73 | var audio_data = 'data:audio/'+ audio.format +';base64,' + window.btoa(d.getString()); 74 | var audio_el = new Audio(audio_data); 75 | 76 | if (is_set('sync', options)) img.style.visibility = 'hidden'; 77 | 78 | // Apply options 79 | if (is_set('controls', options)) audio_el.setAttribute('controls', 'controls'); 80 | if (is_set('autoplay', options)) audio_el.setAttribute('autoplay', 'true'); 81 | if (is_set('loop', options)) audio_el.setAttribute('loop', 'true'); 82 | if (is_set('volume', options)) { 83 | var volume = options.split('volume=')[1].split(' ')[0]; 84 | // We are prefxing with + to convert string to int 85 | audio.volume = +volume; 86 | audio_el.volume = +volume; 87 | } 88 | 89 | if (is_set('sync', options)) { 90 | // Reset the animation by re-loading the image 91 | img.setAttribute('src', url); 92 | img.style.visibility = 'visible'; 93 | } 94 | 95 | // The sound can be muted by clicked on the image, and toggled - we don't pause because GIF images don't pause 96 | img.addEventListener('click', function() { 97 | if (audio_el.paused) { 98 | audio_el.play(); 99 | } else { 100 | audio_el.volume = audio_el.volume === 0 ? audio_el.volume = audio.volume : 0; 101 | } 102 | }); 103 | } 104 | }); 105 | 106 | }, this); --------------------------------------------------------------------------------