Browse Source
Fixed playback of voice messages from iOS app in Chrome Added playback ability to Safari Closes #1396 Closes #1464 Closes #1465master
Igor Zhukov
8 years ago
47 changed files with 12593 additions and 39 deletions
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
ogv.js & ogv.swf wrapper and player code |
||||
|
||||
Copyright (c) 2013-2014 Brion Vibber and other contributors |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
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. |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
Copyright (c) 2002, Xiph.org Foundation |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- Neither the name of the Xiph.org Foundation nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic, |
||||
Jean-Marc Valin, Timothy B. Terriberry, |
||||
CSIRO, Gregory Maxwell, Mark Borgerding, |
||||
Erik de Castro Lopo |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- Neither the name of Internet Society, IETF or IETF Trust, nor the |
||||
names of specific contributors, may be used to endorse or promote |
||||
products derived from this software without specific prior written |
||||
permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
Opus is subject to the royalty-free patent licenses which are |
||||
specified at: |
||||
|
||||
Xiph.Org Foundation: |
||||
https://datatracker.ietf.org/ipr/1524/ |
||||
|
||||
Microsoft Corporation: |
||||
https://datatracker.ietf.org/ipr/1914/ |
||||
|
||||
Broadcom Corporation: |
||||
https://datatracker.ietf.org/ipr/1526/ |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
Copyright (C) 2002-2009 Xiph.org Foundation |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- Neither the name of the Xiph.org Foundation nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
Copyright (c) 2002-2008 Xiph.org Foundation |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- Neither the name of the Xiph.org Foundation nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
Copyright © 2010 Mozilla Foundation |
||||
|
||||
Permission to use, copy, modify, and distribute this software for any |
||||
purpose with or without fee is hereby granted, provided that the above |
||||
copyright notice and this permission notice appear in all copies. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
Copyright (c) 2010, The WebM Project authors. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
* Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in |
||||
the documentation and/or other materials provided with the |
||||
distribution. |
||||
|
||||
* Neither the name of Google, nor the WebM Project, nor the names |
||||
of its contributors may be used to endorse or promote products |
||||
derived from this software without specific prior written |
||||
permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
Additional IP Rights Grant (Patents) |
||||
------------------------------------ |
||||
|
||||
"These implementations" means the copyrightable works that implement the WebM |
||||
codecs distributed by Google as part of the WebM Project. |
||||
|
||||
Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge, |
||||
royalty-free, irrevocable (except as stated in this section) patent license to |
||||
make, have made, use, offer to sell, sell, import, transfer, and otherwise |
||||
run, modify and propagate the contents of these implementations of WebM, where |
||||
such license applies only to those patent claims, both currently owned by |
||||
Google and acquired in the future, licensable by Google that are necessarily |
||||
infringed by these implementations of WebM. This grant does not include claims |
||||
that would be infringed only as a consequence of further modification of these |
||||
implementations. If you or your agent or exclusive licensee institute or order |
||||
or agree to the institution of patent litigation or any other patent |
||||
enforcement activity against any entity (including a cross-claim or |
||||
counterclaim in a lawsuit) alleging that any of these implementations of WebM |
||||
or any code incorporated within any of these implementations of WebM |
||||
constitute direct or contributory patent infringement, or inducement of |
||||
patent infringement, then any patent rights granted to you under this License |
||||
for these implementations of WebM shall terminate as of the date such |
||||
litigation is filed. |
@ -0,0 +1,371 @@
@@ -0,0 +1,371 @@
|
||||
ogv.js |
||||
====== |
||||
|
||||
Media decoder and player for Ogg Vorbis/Opus/Theora and (experimentally) WebM video. |
||||
|
||||
Based around libogg, libvorbis, libtheora, libopus, libvpx, and libnestegg compiled to JavaScript with Emscripten. |
||||
|
||||
## Updates |
||||
|
||||
1.4.2 - 2017-04-24 |
||||
* support 8-bit 4:2:2 and 4:4:4 subsampling in VP9 |
||||
|
||||
1.4.1 - 2017-04-07 |
||||
* fix for seek shortly after initialization |
||||
* fix for some missing instance constants |
||||
|
||||
1.4.0 - 2017-04-06 |
||||
* fastSeek() is now fast; seeks to first keyframe found. |
||||
* VP9 base profile support in WebM container (8-bit 4:2:0 only). |
||||
* Safari no longer complains about missing es6-promise.map source map |
||||
* Smoother playback on low-end machines prone to lag spikes: when A/V sync lags, keep audio running smoothly and resync video at the next keyframe. To restore previous behavior, set `sync: 'delay-audio'` in options. |
||||
* Experimental Web Assembly builds of all modules; set `wasm: true` in options to force on. |
||||
* `error` property now returns an `OGVMediaError` object instead of string. |
||||
* Decode pipeline up to 3 frames deep to aid in momentary spikes. |
||||
* Experimental multithreaded JS builds for VP8 and VP9; set `threading: true` in options to force on. |
||||
* Fixed bad autodetection of files in root dir |
||||
|
||||
1.3.1 - 2017-02-24 |
||||
* Fix for seeking before load completes |
||||
* Fix for bisection seeking in very short Ogg files |
||||
|
||||
1.3.0 - 2017-02-08 |
||||
* Separated XHR and caching out to stream-file package |
||||
* more aggressive in-memory buffering should improve audio seek performance |
||||
* improved seek precision on audio files |
||||
* fix for Ogg files with stream id of 0 |
||||
|
||||
1.2.1 - 2016-09-24 |
||||
* Performance fixed for playback of Ogg Theora with many duplicate frames ("1000fps" files from ffmpeg) |
||||
* Report actual fps (ignoring dupe frames) for Ogg Theora |
||||
* Delay loading when using preload="none" |
||||
* Fix regression in IE 10 network layer |
||||
|
||||
1.2.0 - 2016-09-19 |
||||
* Separated software and WebGL paths to yuv-canvas package |
||||
* fixed regression in WebM frame rate handling |
||||
* buffer up to 3 decoded frames for smoother playback |
||||
* smoother audio in the face of short delays (drop late frame if next one is already decoded) |
||||
* fixed regression in seeking non-indexed Ogg files |
||||
* updated libvpx |
||||
|
||||
1.1.3 - 2016-06-27 |
||||
* fix play-during-seek bug that interacted with video.js badly |
||||
|
||||
1.1.2 - 2016-06-27 |
||||
* better a/v sync |
||||
* muted autoplay works on iOS |
||||
* numerous seeking-related race-condition fixes |
||||
* more consistent performance on low-end machines |
||||
* supports cross-domain hosting of worker and Flash audio shim |
||||
* seeking now works in WebM as well as Ogg |
||||
* cleaner multithreading |
||||
* lots of little fixes |
||||
|
||||
See more details and history in [CHANGES.md](https://github.com/brion/ogv.js/blob/master/CHANGES.md) |
||||
|
||||
## Current status |
||||
|
||||
Since August 2015, ogv.js can be seen in action [on Wikipedia and Wikimedia Commons](https://commons.wikimedia.org/wiki/Commons:Video) in Safari and IE/Edge where native Ogg and WebM playback is not available. (See [technical details on MediaWiki integration](https://www.mediawiki.org/wiki/Extension:TimedMediaHandler/ogv.js).) |
||||
|
||||
See also a standalone demo with performance metrics at https://brionv.com/misc/ogv.js/demo/ |
||||
|
||||
* streaming: yes (with Range header) |
||||
* seeking: yes for Ogg and WebM (with Range header) |
||||
* color: yes |
||||
* audio: yes, with a/v sync (requires Web Audio or Flash) |
||||
* background threading: yes (video, audio decoders in Workers) |
||||
* [GPU accelerated drawing: yes (WebGL)](https://github.com/brion/ogv.js/wiki/GPU-acceleration) |
||||
* GPU accelerated decoding: no |
||||
* SIMD acceleration: no |
||||
* Web Assembly: yes (experimental; set `options.wasm` to `true`) |
||||
* multithreaded VP8, VP9: yes (experimental; set `options.threading` to `true`; requires `SharedArrayBuffer`) |
||||
* controls: no (currently provided by demo or other UI harness) |
||||
|
||||
Ogg files are fairly well supported, but WebM is still experimental and is disabled by default. |
||||
|
||||
|
||||
## Goals |
||||
|
||||
Long-form goal is to create a drop-in replacement for the HTML5 video and audio tags which can be used for basic playback of Ogg Theora and Vorbis or WebM media on browsers that don't support Ogg or WebM natively. |
||||
|
||||
The API isn't quite complete, but works pretty well. |
||||
|
||||
|
||||
## Compatibility |
||||
|
||||
ogv.js requires a fast JS engine with typed arrays, and either Web Audio or Flash for audio playback. |
||||
|
||||
The primary target browsers are (testing 360p/30fps and up): |
||||
* Safari 6.1/7/8/9/10 on Mac OS X 10.7-10.11 |
||||
* Safari on iOS 8/9/10 64-bit |
||||
* Edge on Windows 10 desktop/tablet |
||||
* Internet Explorer 10/11 on Windows 7/8/8.1/10 (desktop/tablet) |
||||
|
||||
And for lower-resolution files (testing 160p/15fps): |
||||
* Safari on iOS 8/9/10 32-bit |
||||
* Edge on Windows 10 Mobile |
||||
* Internet Explorer 10/11 on Windows RT |
||||
|
||||
Older versions of Safari have flaky JIT compilers. IE 9 and below lack typed arrays. |
||||
|
||||
(Note that Windows and Mac OS X can support Ogg and WebM by installing codecs or alternate browsers with built-in support, but this is not possible on iOS, Windows RT, or Windows 10 Mobile.) |
||||
|
||||
Testing browsers (these support .ogv and .webm natively): |
||||
* Firefox 52 |
||||
* Chrome 57 |
||||
|
||||
|
||||
## Package installation |
||||
|
||||
Pre-built releases of ogv.js are available as [.zip downloads from the GitHub releases page](https://github.com/brion/ogv.js/releases) and through the npm package manager. |
||||
|
||||
You can load the `ogv.js` main entry point directly in a script tag, or bundle it through whatever build process you like. The other .js files and the .swf file (for audio in IE) must be made available for runtime loading, together in the same directory. |
||||
|
||||
ogv.js will try to auto-detect the path to its resources based on the script element that loads ogv.js or ogv-support.js. If you load ogv.js through another bundler (such as browserify or MediaWiki's ResourceLoader) you may need to override this manually before instantiating players: |
||||
|
||||
``` |
||||
// Path to ogv-demuxer-ogg.js, ogv-worker-audio.js, dynamicaudio.swf etc |
||||
OGVLoader.base = '/path/to/resources'; |
||||
``` |
||||
|
||||
To fetch from npm: |
||||
|
||||
``` |
||||
npm install ogv |
||||
``` |
||||
|
||||
The distribution-ready files will appear in 'node_modules/ogv/dist'. |
||||
|
||||
To load the player library into your browserify or webpack project: |
||||
|
||||
``` |
||||
var ogv = require('ogv'); |
||||
|
||||
// Access public classes either as ogv.OGVPlayer or just OGVPlayer. |
||||
// Your build/lint tools may be happier with ogv.OGVPlayer! |
||||
ogv.OGVLoader.base = '/path/to/resources'; |
||||
var player = new ogv.OGVPlayer(); |
||||
``` |
||||
|
||||
## Usage |
||||
|
||||
The `OGVPlayer` class implements a player, and supports a subset of the events, properties and methods from [HTMLMediaElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) and [HTMLVideoElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement). |
||||
|
||||
``` |
||||
// Create a new player with the constructor |
||||
var player = new OGVPlayer(); |
||||
|
||||
// Or with options |
||||
var player = new OGVPlayer({ |
||||
enableWebM: true |
||||
}); |
||||
|
||||
// Now treat it just like a video or audio element |
||||
containerElement.appendChild(player); |
||||
player.src = 'path/to/media.ogv'; |
||||
player.play(); |
||||
player.addEventListener('ended', function() { |
||||
// ta-da! |
||||
}); |
||||
``` |
||||
|
||||
To check for compatibility before creating a player, include `ogv-support.js` and use the `OGVCompat` API: |
||||
|
||||
``` |
||||
if (OGVCompat.supported('OGVPlayer')) { |
||||
// go load the full player from ogv.js and instantiate stuff |
||||
} |
||||
``` |
||||
|
||||
This will check for typed arrays, audio/Flash, blacklisted iOS versions, and super-slow/broken JIT compilers. |
||||
|
||||
If you need a URL versioning/cache-buster parameter for dynamic loading of `ogv.js`, you can use the `OGVVersion` symbol provided by `ogv-support.js` or the even tinier `ogv-version.js`: |
||||
|
||||
``` |
||||
var script = document.createElement('script'); |
||||
script.src = 'ogv.js?version=' + encodeURIComponent(OGVVersion); |
||||
document.querySelector('head').appendChild(script); |
||||
``` |
||||
|
||||
## Distribution notes |
||||
|
||||
Entry points: |
||||
* `ogv.js` contains the main runtime classes, including OGVPlayer, OGVLoader, and OGVCompat. |
||||
* `ogv-support.js` contains the OGVCompat class and OGVVersion symbol, useful for checking for runtime support before loading the main `ogv.js`. |
||||
* `ogv-version.js` contains only the OGVVersion symbol. |
||||
|
||||
These entry points may be loaded directly from a script element, or concatenated into a larger project, or otherwise loaded as you like. |
||||
|
||||
Further code modules are loaded at runtime, which must be available with their defined names together in a directory. If the files are not hosted same-origin to the web page that includes them, you will need to set up appropriate CORS headers to allow loading of the worker JS modules. |
||||
|
||||
Dynamically loaded assets: |
||||
* `ogv-worker-audio.js`, `ogv-worker-video.js`, and `pthread-main.js` are Worker entry points, used to run video and audio decoders in the background. |
||||
* `ogv-demuxer-ogg.js` is used in playing .ogg, .oga, and .ogv files. |
||||
* `ogv-demuxer-webm.js` is used in playing .webm files. |
||||
* `ogv-decoder-audio-vorbis.js` and `ogv-decoder-audio-opus.js` are used in playing both Ogg and WebM files containing audio. |
||||
* `ogv-decoder-video-theora.js` is used in playing .ogg and .ogv video files. |
||||
* `ogv-decoder-video-vp8.js` and `ogv-decoder-video-vp9.js` are used in playing .webm video files. |
||||
* `*-wasm.js` and `*-wasm.wasm` files are the Web Assembly versions of the above modules. |
||||
* `*-mt.js` are the multithreaded versions of some of the above modules. |
||||
* `dynamicaudio.swf` is the Flash audio shim, used for Internet Explorer 10/11. |
||||
|
||||
If you know you will never use particular formats or codecs you can skip bundling them; for instance if you only need to play Ogg files you don't need `ogv-demuxer-webm.js` or `ogv-decoder-video-vp8.js` which are only used for WebM. Web Assembly and multithreaded modules are experimental and can be left out if not enabled in your runtime options. |
||||
|
||||
|
||||
## Performance |
||||
|
||||
As of 2015, for SD-or-less resolution basic Ogg Theora decoding speed is reliable on desktop and newer high-end mobile devices; current high-end desktops and laptops can even reach HD resolutions. Older and low-end mobile devices may have difficulty on any but audio and the lowest-resolution video files. |
||||
|
||||
WebM is much slower, and remains experimental. |
||||
|
||||
*Low-res targets* |
||||
|
||||
I've gotten acceptable performance for Vorbis audio and 160p/15fps Theora files on 32-bit iOS devices: iPhone 4s, iPod Touch 5th-gen and iPad 3. These have difficulty at 240p and above, and just won't keep up with higher resolutions. |
||||
|
||||
Meanwhile, newer 64-bit iPhones and iPads are comparable to low-end laptops, and videos at 360p and often 480p play acceptably. Since 32-bit and 64-bit iOS devices have the same user-agent, a benchmark must be used to approximately test minimum CPU speed. |
||||
|
||||
(On iOS, Safari performs significantly better than some alternative browsers that are unable to enable the JIT due to use of the old UIWebView API. Chrome 49 and Firefox for iOS are known to work using the newer WKWebView API internally. Again, a benchmark must be used to detect slow performance, as the browser remains otherwise compatible.) |
||||
|
||||
|
||||
Windows on 32-bit ARM platforms is similar... IE 11 on Windows RT 8.1 on a Surface tablet (NVidia Tegra 3), and Edge on Windows 10 Mobile build 10166 on a Lumia 635, perform acceptably with audio and with 160p/15fps videos but have trouble starting around 240p. |
||||
|
||||
|
||||
In both cases, a native application looms as a possibly better alternative. See [OGVKit ](https://github.com/brion/OGVKit) and [OgvRt](https://github.com/brion/OgvRT) projects for experiments in those directions. |
||||
|
||||
|
||||
Note that at these lower resolutions, Vorbis audio and Theora video decoding are about equally expensive operations -- dual-core phones and tablets should be able to eek out a little parallelism here thanks to audio and video being in separate Worker threads. |
||||
|
||||
|
||||
*WebGL drawing acceleration* |
||||
|
||||
Accelerated YCbCr->RGB conversion and drawing is done using WebGL on supporting browsers, or through software CPU conversion if not. This is abstracted in the [yuv-canvas](https://github.com/brion/yuv-canvas) package, now separately installable. |
||||
|
||||
It may be possible to do further acceleration of actual decoding operations using WebGL shaders, but this could be ... tricky. WebGL is also only available on the main thread, and there are no compute shaders yet so would have to use fragment shaders. |
||||
|
||||
|
||||
## Difficulties |
||||
|
||||
*Threading* |
||||
|
||||
Currently the video and audio codecs run in worker threads by default, while the demuxer |
||||
and player logic run on the UI thread. This seems to work pretty well. |
||||
|
||||
There is some overhead in extracting data out of each emscripten module's heap and in the thread-to-thread communications, but the parallelism and smoother main thread makes up for it. |
||||
|
||||
*Streaming download* |
||||
|
||||
In Firefox, the 'moz-chunked-array' responseType on XHR is used to read data as ArrayBuffer chunks during download. Safari and Chrome use a 'binary string' read which requires manually converting input to ArrayBuffer chunks. In IE and Edge have an (MS-prefixed) Stream/StreamReader interface which can be used to read data on demand into ArrayBuffer objects, but it has proved problematic especially with intermediate proxies; as of 1.1.2 IE and Edge use the same chunked binary-string method as Safari and Chrome. |
||||
|
||||
The Firefox and Safari/Chrome cases have been hacked up to do streaming buffering by chunking the requests at up to a megabyte each, using the HTTP Range header. For cross-site playback, this requires CORS setup to whitelist the Range header! |
||||
|
||||
[Safari has a bug with Range headers](https://bugs.webkit.org/show_bug.cgi?id=82672) which is worked around as necessary with a 'cache-busting' URL string parameter. Hopefully this will be fixed in future versions of Mac OS X and iOS. |
||||
|
||||
|
||||
*Seeking* |
||||
|
||||
Seeking is implemented via the HTTP Range: header. |
||||
|
||||
For Ogg files with keyframe indices in a skeleton index, seeking is very fast. Otherwise, a bisection search is used to locate the target frame or audio position, which is very slow over the internet as it creates a lot of short-lived HTTP requests. |
||||
|
||||
For WebM files with cues, efficient seeking is supported as well as of 1.1.2. |
||||
|
||||
As with chunked streaming, cross-site playback requires CORS support for the Range header. |
||||
|
||||
|
||||
*Audio output* |
||||
|
||||
Audio output is handled through the [AudioFeeder](https://github.com/brion/audio-feeder) library, which encapsulates use of Web Audio API or Flash depending on browser support: |
||||
|
||||
Firefox, Safari, Chrome, and Edge support the W3C Web Audio API. |
||||
|
||||
IE doesn't support Web Audio, but does bundle the Flash player in Windows 8/8.1/RT. A small Flash shim is included here and used as a fallback -- thanks to Maik Merten for hacking some pieces together and getting this working! |
||||
|
||||
A/V synchronization is performed on files with both audio and video, and seems to |
||||
actually work. Yay! |
||||
|
||||
Note that autoplay with audio doesn't work on iOS Safari due to limitations with starting audio playback from event handlers; if playback is started outside an event handler, the player will hang due to broken audio. |
||||
|
||||
As of 1.1.1, muting before script-triggered playback allows things to work: |
||||
|
||||
``` |
||||
player = new OGVPlayer(); |
||||
player.muted = true; |
||||
player.src = 'path/to/file-with-audio.ogv'; |
||||
player.play(); |
||||
``` |
||||
|
||||
You can then unmute the video in response to a touch or click handler. Alternately if audio is not required, do not include an audio track in the file. |
||||
|
||||
|
||||
*WebM* |
||||
|
||||
WebM support was added in June 2015, with some major issues finally worked out in May 2016. Initial VP9 support was added in February 2017. It remains experimental, but should be fully enabled in the future once a few more bugs are worked out. |
||||
|
||||
To enable, set `enableWebM: true` in your `options` array. |
||||
|
||||
Beware that performance of WebM VP8 is much slower than Ogg Theora, and VP9 is slower still. |
||||
|
||||
For best WebM decode speed, consider encoding VP8 with "profile 1" (simple deblocking filter) which will sacrifice quality modestly, mainly in high-motion scenes. When encoding with ffmpeg, this is the `-profile:v 1` option to the `libvpx` codec. |
||||
|
||||
It is also recommended to use the `-slices` option for VP8, or `-tile-columns` for VP9, to maximize ability to use multithreaded decoding when available. |
||||
|
||||
|
||||
## Upstream library notes |
||||
|
||||
We've experimented with tremor (libivorbis), an integer-only variant of libvorbis. This actually does *not* decode faster, but does save about 200kb off our generated JavaScript, presumably thanks to not including an encoder in the library. However on slow devices like iPod Touch 5th-generation, it makes a significant negative impact on the decode time so we've gone back to libvorbis. |
||||
|
||||
The Ogg Skeleton library (libskeleton) is a bit ... unfinished and is slightly modified here. |
||||
|
||||
libvpx is slightly modified to work around emscripten threading limitations in the VP8 decoder. |
||||
|
||||
|
||||
## Web Assembly |
||||
|
||||
Experimental Web Assembly (WASM) versions of the emscripten cross-compiled modules are also included, used if `options.wasm` is true. |
||||
|
||||
The WASM versions of the modules are more compact than the cross-compiled asm.js-style JavaScript, and should download and parse faster. Some browsers may also compile the module differently, providing more consistent performance at the beginning of playback. |
||||
|
||||
Currently Firefox and Chrome are the only release versions of browsers that support Web Assembly, but it's available in Safari Technical Preview and behind the 'experimental JS options' flag in Edge in Windows 10 version 1703. |
||||
|
||||
If you are making a slim build and will not use the `wasm` option, you can leave out the `*-wasm.js` and `*-wasm.wasm` files. |
||||
|
||||
|
||||
## Multithreading |
||||
|
||||
Experimental multithreaded VP8 and VP9 decoding up to 4 cores is available for VP8 and VP9 video, used if `options.threading` is true. This requires browser support for the new `SharedArrayBuffer` and `Atomics` APIs, currently available in Safari 10.1 / iOS 10.3 and in Firefox developer & nightly builds, and in Chrome behind a flag. |
||||
|
||||
Threading is not currently compatible with Web Assembly. |
||||
|
||||
Speedups will only be noticeable when using the "slices" or "token partitions" option for VP8 encoding, or the "tile columns" option for VP9 encoding. |
||||
|
||||
Currently, getting a successful multithreaded build requires a [patch to the emscripten compiler](https://github.com/kripken/emscripten/pull/5016); without this patch, the resulting multithreaded modules will build but fail to initialize correctly. |
||||
|
||||
If you are making a slim build and will not use the `threading` option, you can leave out the `*-mt.js` files, as well as `pthread-main.js`. |
||||
|
||||
|
||||
## Building JS components |
||||
|
||||
Building ogv.js is known to work on Mac OS X and Linux (tested Ubuntu 15.04). |
||||
|
||||
1. You will need autoconf, automake, libtool, pkg-config, and node (nodejs). These can be installed through Homebrew on Mac OS X, or through distribution-specific methods on Linux. |
||||
2. Install [Emscripten](http://kripken.github.io/emscripten-site/docs/getting_started/Tutorial.html); currently using the incoming branch (of what will be 1.38) for distribution builds for latest WASM support, plus [multithreading patch](https://github.com/kripken/emscripten/pull/5016) |
||||
3. `git submodule update --init` |
||||
4. Run `npm install` to install build utilities |
||||
5. Run `make js` to configure and build the libraries and the C wrapper |
||||
|
||||
|
||||
## Building the demo |
||||
|
||||
If you did all the setup above, just run `make demo` or `make`. Look in build/demo/ and enjoy! |
||||
|
||||
|
||||
## License |
||||
|
||||
libogg, libvorbis, libtheora, libopus, nestegg, and libvpx are available under their respective licenses, and the JavaScript and C wrapper code in this repo is licensed under MIT. |
||||
|
||||
Based on build scripts from https://github.com/devongovett/ogg.js |
||||
|
||||
[AudioFeeder](https://github.com/brion/audio-feeder)'s dynamicaudio.as and other Flash-related bits are based on code under BSD license, (c) 2010 Ben Firshman. |
||||
|
||||
See [AUTHORS.md](https://github.com/brion/ogv.js/blob/master/AUTHORS.md) and/or the git history for a list of contributors. |
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -0,0 +1,272 @@
@@ -0,0 +1,272 @@
|
||||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {}; |
||||
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) { |
||||
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) |
||||
/******/ return installedModules[moduleId].exports; |
||||
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = { |
||||
/******/ exports: {}, |
||||
/******/ id: moduleId, |
||||
/******/ loaded: false |
||||
/******/ }; |
||||
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); |
||||
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.loaded = true; |
||||
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports; |
||||
/******/ } |
||||
|
||||
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules; |
||||
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules; |
||||
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = ""; |
||||
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(0); |
||||
/******/ }) |
||||
/************************************************************************/ |
||||
/******/ ([ |
||||
/* 0 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
//
|
||||
// -- ogv-support.js
|
||||
// https://github.com/brion/ogv.js
|
||||
// Copyright (c) 2013-2016 Brion Vibber
|
||||
//
|
||||
|
||||
(function() { |
||||
|
||||
var OGVCompat = __webpack_require__(1), |
||||
OGVVersion = ("1.4.2-20170425024925-504d7197"); |
||||
|
||||
if (window) { |
||||
// 1.0-compat globals
|
||||
window.OGVCompat = OGVCompat; |
||||
window.OGVVersion = OGVVersion; |
||||
} |
||||
|
||||
module.exports = { |
||||
OGVCompat: OGVCompat, |
||||
OGVVersion: OGVVersion |
||||
}; |
||||
|
||||
})(); |
||||
|
||||
|
||||
/***/ }), |
||||
/* 1 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var BogoSlow = __webpack_require__(2); |
||||
|
||||
var OGVCompat = { |
||||
benchmark: new BogoSlow(), |
||||
|
||||
hasTypedArrays: function() { |
||||
// emscripten-compiled code requires typed arrays
|
||||
return !!window.Uint32Array; |
||||
}, |
||||
|
||||
hasWebAudio: function() { |
||||
return !!(window.AudioContext || window.webkitAudioContext); |
||||
}, |
||||
|
||||
hasFlash: function() { |
||||
if (navigator.userAgent.indexOf('Trident') !== -1) { |
||||
// We only do the ActiveX test because we only need Flash in
|
||||
// Internet Explorer 10/11. Other browsers use Web Audio directly
|
||||
// (Edge, Safari) or native playback, so there's no need to test
|
||||
// other ways of loading Flash.
|
||||
try { |
||||
var obj = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); |
||||
return true; |
||||
} catch(e) { |
||||
return false; |
||||
} |
||||
} |
||||
return false; |
||||
}, |
||||
|
||||
hasAudio: function() { |
||||
return this.hasWebAudio() || this.hasFlash(); |
||||
}, |
||||
|
||||
isBlacklisted: function(userAgent) { |
||||
// JIT bugs in old Safari versions
|
||||
var blacklist = [ |
||||
/\(i.* OS [67]_.* like Mac OS X\).* Mobile\/.* Safari\//, |
||||
/\(Macintosh.* Version\/6\..* Safari\/\d/ |
||||
]; |
||||
var blacklisted = false; |
||||
blacklist.forEach(function(regex) { |
||||
if (userAgent.match(regex)) { |
||||
blacklisted = true; |
||||
} |
||||
}); |
||||
return blacklisted; |
||||
}, |
||||
|
||||
isSlow: function() { |
||||
return this.benchmark.slow; |
||||
}, |
||||
|
||||
isTooSlow: function() { |
||||
return this.benchmark.tooSlow; |
||||
}, |
||||
|
||||
supported: function(component) { |
||||
if (component === 'OGVDecoder') { |
||||
return (this.hasTypedArrays() && !this.isBlacklisted(navigator.userAgent)); |
||||
} |
||||
if (component === 'OGVPlayer') { |
||||
return (this.supported('OGVDecoder') && this.hasAudio() && !this.isTooSlow()); |
||||
} |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
module.exports = OGVCompat; |
||||
|
||||
|
||||
/***/ }), |
||||
/* 2 */ |
||||
/***/ (function(module, exports) { |
||||
|
||||
/** |
||||
* A quick CPU/JS engine benchmark to guesstimate whether we're |
||||
* fast enough to handle 360p video in JavaScript. |
||||
*/ |
||||
function BogoSlow() { |
||||
var self = this; |
||||
|
||||
var timer; |
||||
// FIXME: avoid to use window scope here
|
||||
if (window.performance && window.performance.now) { |
||||
timer = function() { |
||||
return window.performance.now(); |
||||
}; |
||||
} else { |
||||
timer = function() { |
||||
return Date.now(); |
||||
}; |
||||
} |
||||
|
||||
var savedSpeed = null; |
||||
function run() { |
||||
var ops = 0; |
||||
var fibonacci = function(n) { |
||||
ops++; |
||||
if (n < 2) { |
||||
return n; |
||||
} else { |
||||
return fibonacci(n - 2) + fibonacci(n - 1); |
||||
} |
||||
}; |
||||
|
||||
var start = timer(); |
||||
|
||||
fibonacci(30); |
||||
|
||||
var delta = timer() - start; |
||||
savedSpeed = (ops / delta); |
||||
} |
||||
|
||||
/** |
||||
* Return a scale value of operations/sec from the benchmark. |
||||
* If the benchmark has already been run, uses a memoized result. |
||||
* |
||||
* @property {number} |
||||
*/ |
||||
Object.defineProperty(self, 'speed', { |
||||
get: function() { |
||||
if (savedSpeed === null) { |
||||
run(); |
||||
} |
||||
return savedSpeed; |
||||
} |
||||
}); |
||||
|
||||
/** |
||||
* Return the defined cutoff speed value for 'slow' devices, |
||||
* based on results measured from some test devices. |
||||
* |
||||
* @property {number} |
||||
*/ |
||||
Object.defineProperty(self, 'slowCutoff', { |
||||
get: function() { |
||||
// 2012 Retina MacBook Pro (Safari 7) ~150,000
|
||||
// 2009 Dell T5500 (IE 11) ~100,000
|
||||
// iPad Air (iOS 7) ~65,000
|
||||
// 2010 MBP / OS X 10.9 (Safari 7) ~62,500
|
||||
// 2010 MBP / Win7 VM (IE 11) ~50,000+-
|
||||
// ^ these play 360p ok
|
||||
// ----------- line of moderate doom ----------
|
||||
return 50000; |
||||
// v these play 160p ok
|
||||
// iPad Mini non-Retina (iOS 8 beta) ~25,000
|
||||
// Dell Inspiron Duo (IE 11) ~25,000
|
||||
// Surface RT (IE 11) ~18,000
|
||||
// iPod Touch 5th-gen (iOS 8 beta) ~16,000
|
||||
} |
||||
}); |
||||
|
||||
/** |
||||
* Return the defined cutoff speed value for 'too slow' devices, |
||||
* based on results measured from some test devices. |
||||
* |
||||
* No longer used. |
||||
* |
||||
* @property {number} |
||||
* @deprecated |
||||
*/ |
||||
Object.defineProperty(self, 'tooSlowCutoff', { |
||||
get: function() { |
||||
return 0; |
||||
} |
||||
}); |
||||
|
||||
/** |
||||
* 'Slow' devices can play audio and should sorta play our |
||||
* extra-tiny Wikimedia 160p15 transcodes |
||||
* |
||||
* @property {boolean} |
||||
*/ |
||||
Object.defineProperty(self, 'slow', { |
||||
get: function() { |
||||
return (self.speed < self.slowCutoff); |
||||
} |
||||
}); |
||||
|
||||
/** |
||||
* 'Too slow' devices aren't reliable at all |
||||
* |
||||
* @property {boolean} |
||||
*/ |
||||
Object.defineProperty(self, 'tooSlow', { |
||||
get: function() { |
||||
return (self.speed < self.tooSlowCutoff); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
module.exports = BogoSlow; |
||||
|
||||
|
||||
/***/ }) |
||||
/******/ ]); |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {}; |
||||
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) { |
||||
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) |
||||
/******/ return installedModules[moduleId].exports; |
||||
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = { |
||||
/******/ exports: {}, |
||||
/******/ id: moduleId, |
||||
/******/ loaded: false |
||||
/******/ }; |
||||
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); |
||||
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.loaded = true; |
||||
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports; |
||||
/******/ } |
||||
|
||||
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules; |
||||
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules; |
||||
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = ""; |
||||
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(0); |
||||
/******/ }) |
||||
/************************************************************************/ |
||||
/******/ ([ |
||||
/* 0 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
//
|
||||
// -- ogv-support.js
|
||||
// https://github.com/brion/ogv.js
|
||||
// Copyright (c) 2013-2016 Brion Vibber
|
||||
//
|
||||
|
||||
(function() { |
||||
|
||||
var OGVVersion = ("1.4.2-20170425024925-504d7197"); |
||||
|
||||
if (window) { |
||||
// 1.0-compat globals
|
||||
window.OGVVersion = OGVVersion; |
||||
} |
||||
|
||||
module.exports = { |
||||
OGVVersion: OGVVersion |
||||
}; |
||||
|
||||
})(); |
||||
|
||||
|
||||
/***/ }) |
||||
/******/ ]); |
@ -0,0 +1,701 @@
@@ -0,0 +1,701 @@
|
||||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {}; |
||||
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) { |
||||
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) |
||||
/******/ return installedModules[moduleId].exports; |
||||
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = { |
||||
/******/ exports: {}, |
||||
/******/ id: moduleId, |
||||
/******/ loaded: false |
||||
/******/ }; |
||||
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); |
||||
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.loaded = true; |
||||
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports; |
||||
/******/ } |
||||
|
||||
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules; |
||||
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules; |
||||
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = ""; |
||||
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(0); |
||||
/******/ }) |
||||
/************************************************************************/ |
||||
/******/ ([ |
||||
/* 0 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
module.exports = __webpack_require__(1); |
||||
|
||||
|
||||
/***/ }), |
||||
/* 1 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVWorkerSupport = __webpack_require__(2); |
||||
|
||||
var proxy = new OGVWorkerSupport([ |
||||
'loadedMetadata', |
||||
'audioFormat', |
||||
'audioBuffer', |
||||
'cpuTime' |
||||
], { |
||||
init: function(args, callback) { |
||||
this.target.init(callback); |
||||
}, |
||||
|
||||
processHeader: function(args, callback) { |
||||
this.target.processHeader(args[0], function(ok) { |
||||
callback([ok]); |
||||
}); |
||||
}, |
||||
|
||||
processAudio: function(args, callback) { |
||||
this.target.processAudio(args[0], function(ok) { |
||||
callback([ok]); |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
module.exports = proxy; |
||||
|
||||
/***/ }), |
||||
/* 2 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVLoader = __webpack_require__(3); |
||||
|
||||
/** |
||||
* Web Worker wrapper for codec fun |
||||
*/ |
||||
function OGVWorkerSupport(propList, handlers) { |
||||
|
||||
var transferables = (function() { |
||||
var buffer = new ArrayBuffer(1024), |
||||
bytes = new Uint8Array(buffer); |
||||
try { |
||||
postMessage({ |
||||
action: 'transferTest', |
||||
bytes: bytes |
||||
}, [buffer]); |
||||
if (buffer.byteLength) { |
||||
// No transferable support
|
||||
return false; |
||||
} else { |
||||
return true; |
||||
} |
||||
} catch (e) { |
||||
return false; |
||||
} |
||||
})(); |
||||
|
||||
var self = this; |
||||
self.target = null; |
||||
|
||||
var sentProps = {}, |
||||
pendingEvents = []; |
||||
|
||||
function copyObject(obj) { |
||||
var copy = {}; |
||||
for (var prop in obj) { |
||||
if (obj.hasOwnProperty(prop)) { |
||||
copy[prop] = obj[prop]; |
||||
} |
||||
} |
||||
return copy; |
||||
} |
||||
|
||||
function copyAudioBuffer(data) { |
||||
if (data == null) { |
||||
return null; |
||||
} else { |
||||
// Array of Float32Arrays
|
||||
var copy = []; |
||||
for (var i = 0; i < data.length; i++) { |
||||
copy[i] = new Float32Array(data[i]); |
||||
} |
||||
return copy; |
||||
} |
||||
} |
||||
|
||||
function handleEvent(data) { |
||||
handlers[data.action].call(self, data.args, function(args) { |
||||
args = args || []; |
||||
|
||||
// Collect and send any changed properties...
|
||||
var props = {}, |
||||
transfers = []; |
||||
propList.forEach(function(propName) { |
||||
var propVal = self.target[propName]; |
||||
|
||||
if (sentProps[propName] !== propVal) { |
||||
// Save this value for later reference...
|
||||
sentProps[propName] = propVal; |
||||
|
||||
if (propName == 'duration' && isNaN(propVal) && isNaN(sentProps[propName])) { |
||||
// NaN is not === itself. Nice!
|
||||
// no need to update it here.
|
||||
} else if (propName == 'audioBuffer') { |
||||
// Don't send the entire emscripten heap!
|
||||
propVal = copyAudioBuffer(propVal); |
||||
props[propName] = propVal; |
||||
if (propVal) { |
||||
for (var i = 0; i < propVal.length; i++) { |
||||
transfers.push(propVal[i].buffer); |
||||
} |
||||
} |
||||
} else if (propName == 'frameBuffer') { |
||||
// We already extract ahead of time now,
|
||||
// so transfer the small buffers.
|
||||
props[propName] = propVal; |
||||
if (propVal) { |
||||
transfers.push(propVal.y.bytes.buffer); |
||||
transfers.push(propVal.u.bytes.buffer); |
||||
transfers.push(propVal.v.bytes.buffer); |
||||
} |
||||
} else { |
||||
props[propName] = propVal; |
||||
} |
||||
} |
||||
}); |
||||
|
||||
var out = { |
||||
action: 'callback', |
||||
callbackId: data.callbackId, |
||||
args: args, |
||||
props: props |
||||
}; |
||||
if (transferables) { |
||||
postMessage(out, transfers); |
||||
} else { |
||||
postMessage(out); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
handlers.construct = function(args, callback) { |
||||
var className = args[0], |
||||
options = args[1]; |
||||
|
||||
OGVLoader.loadClass(className, function(classObj) { |
||||
self.target = new classObj(options); |
||||
callback(); |
||||
while (pendingEvents.length) { |
||||
handleEvent(pendingEvents.shift()); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
addEventListener('message', function workerOnMessage(event) { |
||||
var data = event.data; |
||||
if (!data || typeof data !== 'object') { |
||||
// invalid
|
||||
return; |
||||
} else if (data.action == 'transferTest') { |
||||
// ignore
|
||||
} else if (typeof data.action !== 'string' || typeof data.callbackId !== 'string' || typeof data.args !== 'object') { |
||||
console.log('invalid message data', data); |
||||
} else if (!(data.action in handlers)) { |
||||
console.log('invalid message action', data.action); |
||||
} else if (data.action == 'construct') { |
||||
// always handle constructor
|
||||
handleEvent(data); |
||||
} else if (!self.target) { |
||||
// queue until constructed
|
||||
pendingEvents.push(data); |
||||
} else { |
||||
handleEvent(data); |
||||
} |
||||
}); |
||||
|
||||
} |
||||
|
||||
module.exports = OGVWorkerSupport; |
||||
|
||||
|
||||
/***/ }), |
||||
/* 3 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVVersion = ("1.4.2-20170425024925-504d7197"); |
||||
|
||||
(function() { |
||||
var global = this; |
||||
|
||||
var scriptMap = { |
||||
OGVDemuxerOgg: 'ogv-demuxer-ogg.js', |
||||
OGVDemuxerOggW: 'ogv-demuxer-ogg-wasm.js', |
||||
OGVDemuxerWebM: 'ogv-demuxer-webm.js', |
||||
OGVDemuxerWebMW: 'ogv-demuxer-webm-wasm.js', |
||||
OGVDecoderAudioOpus: 'ogv-decoder-audio-opus.js', |
||||
OGVDecoderAudioOpusW: 'ogv-decoder-audio-opus-wasm.js', |
||||
OGVDecoderAudioVorbis: 'ogv-decoder-audio-vorbis.js', |
||||
OGVDecoderAudioVorbisW: 'ogv-decoder-audio-vorbis-wasm.js', |
||||
OGVDecoderVideoTheora: 'ogv-decoder-video-theora.js', |
||||
OGVDecoderVideoTheoraW: 'ogv-decoder-video-theora-wasm.js', |
||||
OGVDecoderVideoVP8: 'ogv-decoder-video-vp8.js', |
||||
OGVDecoderVideoVP8W: 'ogv-decoder-video-vp8-wasm.js', |
||||
OGVDecoderVideoVP8MT: 'ogv-decoder-video-vp8-mt.js', |
||||
OGVDecoderVideoVP9: 'ogv-decoder-video-vp9.js', |
||||
OGVDecoderVideoVP9W: 'ogv-decoder-video-vp9-wasm.js', |
||||
OGVDecoderVideoVP9MT: 'ogv-decoder-video-vp9-mt.js' |
||||
}; |
||||
|
||||
// @fixme make this less awful
|
||||
var proxyTypes = { |
||||
OGVDecoderAudioOpus: 'audio', |
||||
OGVDecoderAudioOpusW: 'audio', |
||||
OGVDecoderAudioVorbis: 'audio', |
||||
OGVDecoderAudioVorbisW: 'audio', |
||||
OGVDecoderVideoTheora: 'video', |
||||
OGVDecoderVideoTheoraW: 'video', |
||||
OGVDecoderVideoVP8: 'video', |
||||
OGVDecoderVideoVP8W: 'video', |
||||
OGVDecoderVideoVP9: 'video', |
||||
OGVDecoderVideoVP9W: 'video' |
||||
}; |
||||
var proxyInfo = { |
||||
audio: { |
||||
proxy: __webpack_require__(4), |
||||
worker: 'ogv-worker-audio.js', |
||||
}, |
||||
video: { |
||||
proxy: __webpack_require__(6), |
||||
worker: 'ogv-worker-video.js' |
||||
} |
||||
} |
||||
|
||||
function urlForClass(className) { |
||||
var scriptName = scriptMap[className]; |
||||
if (scriptName) { |
||||
return urlForScript(scriptName); |
||||
} else { |
||||
throw new Error('asked for URL for unknown class ' + className); |
||||
} |
||||
}; |
||||
|
||||
function urlForScript(scriptName) { |
||||
if (scriptName) { |
||||
var base = OGVLoader.base; |
||||
if (base === undefined) { |
||||
base = ''; |
||||
} else { |
||||
base += '/'; |
||||
} |
||||
return base + scriptName + '?version=' + encodeURIComponent(OGVVersion); |
||||
} else { |
||||
throw new Error('asked for URL for unknown script ' + scriptName); |
||||
} |
||||
}; |
||||
|
||||
var scriptStatus = {}, |
||||
scriptCallbacks = {}; |
||||
function loadWebScript(src, callback) { |
||||
if (scriptStatus[src] == 'done') { |
||||
callback(); |
||||
} else if (scriptStatus[src] == 'loading') { |
||||
scriptCallbacks[src].push(callback); |
||||
} else { |
||||
scriptStatus[src] = 'loading'; |
||||
scriptCallbacks[src] = [callback]; |
||||
|
||||
var scriptNode = document.createElement('script'); |
||||
function done(event) { |
||||
var callbacks = scriptCallbacks[src]; |
||||
delete scriptCallbacks[src]; |
||||
scriptStatus[src] = 'done'; |
||||
|
||||
callbacks.forEach(function(cb) { |
||||
cb(); |
||||
}); |
||||
} |
||||
scriptNode.addEventListener('load', done); |
||||
scriptNode.addEventListener('error', done); |
||||
scriptNode.src = src; |
||||
document.querySelector('head').appendChild(scriptNode); |
||||
} |
||||
} |
||||
|
||||
function loadWebAssembly(src, callback) { |
||||
if (!src.match(/-wasm\.js/)) { |
||||
callback(null); |
||||
} else { |
||||
var wasmSrc = src.replace(/-wasm\.js/, '-wasm.wasm'); |
||||
var xhr = new XMLHttpRequest(); |
||||
xhr.responseType = 'arraybuffer'; |
||||
xhr.onload = function() { |
||||
callback(xhr.response); |
||||
}; |
||||
xhr.onerror = function() { |
||||
callback(null); |
||||
}; |
||||
xhr.open('GET', wasmSrc); |
||||
xhr.send(); |
||||
} |
||||
} |
||||
|
||||
function defaultBase() { |
||||
if (typeof global.window === 'object') { |
||||
|
||||
// for browser, try to autodetect
|
||||
var scriptNodes = document.querySelectorAll('script'), |
||||
regex = /^(?:|(.*)\/)ogv(?:-support)?\.js(?:\?|#|$)/, |
||||
path, |
||||
matches; |
||||
for (var i = 0; i < scriptNodes.length; i++) { |
||||
path = scriptNodes[i].getAttribute('src'); |
||||
if (path) { |
||||
matches = path.match(regex); |
||||
if (matches) { |
||||
return matches[1]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return undefined; // current dir
|
||||
|
||||
} else { |
||||
|
||||
// for workers, assume current directory
|
||||
// if not a worker, too bad.
|
||||
return undefined; |
||||
|
||||
} |
||||
} |
||||
|
||||
var OGVLoader = { |
||||
base: defaultBase(), |
||||
|
||||
loadClass: function(className, callback, options) { |
||||
options = options || {}; |
||||
if (options.worker) { |
||||
this.workerProxy(className, callback); |
||||
return; |
||||
} |
||||
var url = urlForClass(className); |
||||
loadWebAssembly(url, function(wasmBinary) { |
||||
function wasmClassWrapper(options) { |
||||
options = options || {}; |
||||
if (wasmBinary !== null) { |
||||
options.wasmBinary = wasmBinary; |
||||
} |
||||
return new global[className](options); |
||||
} |
||||
if (typeof global[className] === 'function') { |
||||
// already loaded!
|
||||
callback(wasmClassWrapper); |
||||
} else if (typeof global.window === 'object') { |
||||
loadWebScript(url, function() { |
||||
callback(wasmClassWrapper); |
||||
}); |
||||
} else if (typeof global.importScripts === 'function') { |
||||
// worker has convenient sync importScripts
|
||||
global.importScripts(url); |
||||
callback(wasmClassWrapper); |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
workerProxy: function(className, callback) { |
||||
var proxyType = proxyTypes[className], |
||||
info = proxyInfo[proxyType]; |
||||
|
||||
if (!info) { |
||||
throw new Error('Requested worker for class with no proxy: ' + className); |
||||
} |
||||
|
||||
var proxyClass = info.proxy, |
||||
workerScript = info.worker, |
||||
codecUrl = urlForScript(scriptMap[className]), |
||||
workerUrl = urlForScript(workerScript), |
||||
worker; |
||||
|
||||
var construct = function(options) { |
||||
return new proxyClass(worker, className, options); |
||||
}; |
||||
|
||||
if (workerUrl.match(/^https?:|\/\//i)) { |
||||
// Can't load workers natively cross-domain, but if CORS
|
||||
// is set up we can fetch the worker stub and the desired
|
||||
// class and load them from a blob.
|
||||
var getCodec, |
||||
getWorker, |
||||
codecResponse, |
||||
workerResponse, |
||||
codecLoaded = false, |
||||
workerLoaded = false, |
||||
blob; |
||||
|
||||
function completionCheck() { |
||||
if ((codecLoaded == true) && (workerLoaded == true)) { |
||||
try { |
||||
blob = new Blob([codecResponse + " " + workerResponse], {type: 'application/javascript'}); |
||||
} catch (e) { // Backwards-compatibility
|
||||
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; |
||||
blob = new BlobBuilder(); |
||||
blob.append(codecResponse + " " + workerResponse); |
||||
blob = blob.getBlob(); |
||||
} |
||||
// Create the web worker
|
||||
worker = new Worker(URL.createObjectURL(blob)); |
||||
callback(construct); |
||||
} |
||||
} |
||||
|
||||
// Load the codec
|
||||
getCodec = new XMLHttpRequest(); |
||||
getCodec.open("GET", codecUrl, true); |
||||
getCodec.onreadystatechange = function() { |
||||
if(getCodec.readyState == 4 && getCodec.status == 200) { |
||||
codecResponse = getCodec.responseText; |
||||
// Update the codec response loaded flag
|
||||
codecLoaded = true; |
||||
completionCheck(); |
||||
} |
||||
}; |
||||
getCodec.send(); |
||||
|
||||
// Load the worker
|
||||
getWorker = new XMLHttpRequest(); |
||||
getWorker.open("GET", workerUrl, true); |
||||
getWorker.onreadystatechange = function() { |
||||
if(getWorker.readyState == 4 && getWorker.status == 200) { |
||||
workerResponse = getWorker.responseText; |
||||
// Update the worker response loaded flag
|
||||
workerLoaded = true; |
||||
completionCheck(); |
||||
} |
||||
}; |
||||
getWorker.send(); |
||||
} else { |
||||
// Local URL; load it directly for simplicity.
|
||||
worker = new Worker(workerUrl); |
||||
callback(construct); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
module.exports = OGVLoader; |
||||
|
||||
})(); |
||||
|
||||
|
||||
/***/ }), |
||||
/* 4 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVProxyClass = __webpack_require__(5); |
||||
|
||||
var OGVDecoderAudioProxy = OGVProxyClass({ |
||||
loadedMetadata: false, |
||||
audioFormat: null, |
||||
audioBuffer: null, |
||||
cpuTime: 0 |
||||
}, { |
||||
init: function(callback) { |
||||
this.proxy('init', [], callback); |
||||
}, |
||||
|
||||
processHeader: function(data, callback) { |
||||
this.proxy('processHeader', [data], callback, [data]); |
||||
}, |
||||
|
||||
processAudio: function(data, callback) { |
||||
this.proxy('processAudio', [data], callback, [data]); |
||||
}, |
||||
|
||||
close: function() { |
||||
this.terminate(); |
||||
} |
||||
}); |
||||
|
||||
module.exports = OGVDecoderAudioProxy; |
||||
|
||||
|
||||
/***/ }), |
||||
/* 5 */ |
||||
/***/ (function(module, exports) { |
||||
|
||||
/** |
||||
* Proxy object for web worker interface for codec classes. |
||||
* |
||||
* Used by the high-level player interface. |
||||
* |
||||
* @author Brion Vibber <brion@pobox.com> |
||||
* @copyright 2015 |
||||
* @license MIT-style |
||||
*/ |
||||
function OGVProxyClass(initialProps, methods) { |
||||
return function(worker, className, options) { |
||||
options = options || {}; |
||||
var self = this; |
||||
|
||||
var transferables = (function() { |
||||
var buffer = new ArrayBuffer(1024), |
||||
bytes = new Uint8Array(buffer); |
||||
try { |
||||
worker.postMessage({ |
||||
action: 'transferTest', |
||||
bytes: bytes |
||||
}, [buffer]); |
||||
if (buffer.byteLength) { |
||||
// No transferable support
|
||||
return false; |
||||
} else { |
||||
return true; |
||||
} |
||||
} catch (e) { |
||||
return false; |
||||
} |
||||
})(); |
||||
|
||||
// Set up proxied property getters
|
||||
var props = {}; |
||||
for (var iPropName in initialProps) { |
||||
if (initialProps.hasOwnProperty(iPropName)) { |
||||
(function(propName) { |
||||
props[propName] = initialProps[propName]; |
||||
Object.defineProperty(self, propName, { |
||||
get: function getProperty() { |
||||
return props[propName]; |
||||
} |
||||
}); |
||||
})(iPropName); |
||||
} |
||||
} |
||||
|
||||
// Current player wants to avoid async confusion.
|
||||
var processingQueue = 0; |
||||
Object.defineProperty(self, 'processing', { |
||||
get: function() { |
||||
return (processingQueue > 0); |
||||
} |
||||
}); |
||||
|
||||
// Set up proxied methods
|
||||
for (var method in methods) { |
||||
if (methods.hasOwnProperty(method)) { |
||||
self[method] = methods[method]; |
||||
} |
||||
} |
||||
|
||||
// And some infrastructure!
|
||||
var messageCount = 0, |
||||
pendingCallbacks = {}; |
||||
this.proxy = function(action, args, callback, transfers) { |
||||
if (!worker) { |
||||
throw 'Tried to call "' + action + '" method on closed proxy object'; |
||||
} |
||||
var callbackId = 'callback-' + (++messageCount) + '-' + action; |
||||
if (callback) { |
||||
pendingCallbacks[callbackId] = callback; |
||||
} |
||||
var out = { |
||||
'action': action, |
||||
'callbackId': callbackId, |
||||
'args': args || [] |
||||
}; |
||||
processingQueue++; |
||||
if (transferables) { |
||||
worker.postMessage(out, transfers || []); |
||||
} else { |
||||
worker.postMessage(out); |
||||
} |
||||
}; |
||||
this.terminate = function() { |
||||
if (worker) { |
||||
worker.terminate(); |
||||
worker = null; |
||||
processingQueue = 0; |
||||
pendingCallbacks = {}; |
||||
} |
||||
}; |
||||
|
||||
worker.addEventListener('message', function proxyOnMessage(event) { |
||||
processingQueue--; |
||||
if (event.data.action !== 'callback') { |
||||
// ignore
|
||||
return; |
||||
} |
||||
|
||||
var data = event.data, |
||||
callbackId = data.callbackId, |
||||
args = data.args, |
||||
callback = pendingCallbacks[callbackId]; |
||||
|
||||
// Save any updated properties returned to us...
|
||||
if (data.props) { |
||||
for (var propName in data.props) { |
||||
if (data.props.hasOwnProperty(propName)) { |
||||
props[propName] = data.props[propName]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (callback) { |
||||
delete pendingCallbacks[callbackId]; |
||||
callback.apply(this, args); |
||||
} |
||||
}); |
||||
|
||||
// Tell the proxy to load and initialize the appropriate class
|
||||
self.proxy('construct', [className, options], function() {}); |
||||
|
||||
return self; |
||||
}; |
||||
} |
||||
|
||||
module.exports = OGVProxyClass; |
||||
|
||||
/***/ }), |
||||
/* 6 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVProxyClass = __webpack_require__(5); |
||||
|
||||
var OGVDecoderVideoProxy = OGVProxyClass({ |
||||
loadedMetadata: false, |
||||
videoFormat: null, |
||||
frameBuffer: null, |
||||
cpuTime: 0 |
||||
}, { |
||||
init: function(callback) { |
||||
this.proxy('init', [], callback); |
||||
}, |
||||
|
||||
processHeader: function(data, callback) { |
||||
this.proxy('processHeader', [data], callback, [data]); |
||||
}, |
||||
|
||||
processFrame: function(data, callback) { |
||||
this.proxy('processFrame', [data], callback, [data]); |
||||
}, |
||||
|
||||
close: function() { |
||||
this.terminate(); |
||||
} |
||||
}); |
||||
|
||||
module.exports = OGVDecoderVideoProxy; |
||||
|
||||
/***/ }) |
||||
/******/ ]); |
@ -0,0 +1,700 @@
@@ -0,0 +1,700 @@
|
||||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {}; |
||||
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) { |
||||
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) |
||||
/******/ return installedModules[moduleId].exports; |
||||
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = { |
||||
/******/ exports: {}, |
||||
/******/ id: moduleId, |
||||
/******/ loaded: false |
||||
/******/ }; |
||||
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); |
||||
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.loaded = true; |
||||
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports; |
||||
/******/ } |
||||
|
||||
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules; |
||||
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules; |
||||
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = ""; |
||||
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(0); |
||||
/******/ }) |
||||
/************************************************************************/ |
||||
/******/ ([ |
||||
/* 0 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
module.exports = __webpack_require__(1); |
||||
|
||||
/***/ }), |
||||
/* 1 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVWorkerSupport = __webpack_require__(2); |
||||
|
||||
var proxy = new OGVWorkerSupport([ |
||||
'loadedMetadata', |
||||
'videoFormat', |
||||
'frameBuffer', |
||||
'cpuTime' |
||||
], { |
||||
init: function(args, callback) { |
||||
this.target.init(callback); |
||||
}, |
||||
|
||||
processHeader: function(args, callback) { |
||||
this.target.processHeader(args[0], function(ok) { |
||||
callback([ok]); |
||||
}); |
||||
}, |
||||
|
||||
processFrame: function(args, callback) { |
||||
this.target.processFrame(args[0], function(ok) { |
||||
callback([ok]); |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
module.exports = proxy; |
||||
|
||||
/***/ }), |
||||
/* 2 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVLoader = __webpack_require__(3); |
||||
|
||||
/** |
||||
* Web Worker wrapper for codec fun |
||||
*/ |
||||
function OGVWorkerSupport(propList, handlers) { |
||||
|
||||
var transferables = (function() { |
||||
var buffer = new ArrayBuffer(1024), |
||||
bytes = new Uint8Array(buffer); |
||||
try { |
||||
postMessage({ |
||||
action: 'transferTest', |
||||
bytes: bytes |
||||
}, [buffer]); |
||||
if (buffer.byteLength) { |
||||
// No transferable support
|
||||
return false; |
||||
} else { |
||||
return true; |
||||
} |
||||
} catch (e) { |
||||
return false; |
||||
} |
||||
})(); |
||||
|
||||
var self = this; |
||||
self.target = null; |
||||
|
||||
var sentProps = {}, |
||||
pendingEvents = []; |
||||
|
||||
function copyObject(obj) { |
||||
var copy = {}; |
||||
for (var prop in obj) { |
||||
if (obj.hasOwnProperty(prop)) { |
||||
copy[prop] = obj[prop]; |
||||
} |
||||
} |
||||
return copy; |
||||
} |
||||
|
||||
function copyAudioBuffer(data) { |
||||
if (data == null) { |
||||
return null; |
||||
} else { |
||||
// Array of Float32Arrays
|
||||
var copy = []; |
||||
for (var i = 0; i < data.length; i++) { |
||||
copy[i] = new Float32Array(data[i]); |
||||
} |
||||
return copy; |
||||
} |
||||
} |
||||
|
||||
function handleEvent(data) { |
||||
handlers[data.action].call(self, data.args, function(args) { |
||||
args = args || []; |
||||
|
||||
// Collect and send any changed properties...
|
||||
var props = {}, |
||||
transfers = []; |
||||
propList.forEach(function(propName) { |
||||
var propVal = self.target[propName]; |
||||
|
||||
if (sentProps[propName] !== propVal) { |
||||
// Save this value for later reference...
|
||||
sentProps[propName] = propVal; |
||||
|
||||
if (propName == 'duration' && isNaN(propVal) && isNaN(sentProps[propName])) { |
||||
// NaN is not === itself. Nice!
|
||||
// no need to update it here.
|
||||
} else if (propName == 'audioBuffer') { |
||||
// Don't send the entire emscripten heap!
|
||||
propVal = copyAudioBuffer(propVal); |
||||
props[propName] = propVal; |
||||
if (propVal) { |
||||
for (var i = 0; i < propVal.length; i++) { |
||||
transfers.push(propVal[i].buffer); |
||||
} |
||||
} |
||||
} else if (propName == 'frameBuffer') { |
||||
// We already extract ahead of time now,
|
||||
// so transfer the small buffers.
|
||||
props[propName] = propVal; |
||||
if (propVal) { |
||||
transfers.push(propVal.y.bytes.buffer); |
||||
transfers.push(propVal.u.bytes.buffer); |
||||
transfers.push(propVal.v.bytes.buffer); |
||||
} |
||||
} else { |
||||
props[propName] = propVal; |
||||
} |
||||
} |
||||
}); |
||||
|
||||
var out = { |
||||
action: 'callback', |
||||
callbackId: data.callbackId, |
||||
args: args, |
||||
props: props |
||||
}; |
||||
if (transferables) { |
||||
postMessage(out, transfers); |
||||
} else { |
||||
postMessage(out); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
handlers.construct = function(args, callback) { |
||||
var className = args[0], |
||||
options = args[1]; |
||||
|
||||
OGVLoader.loadClass(className, function(classObj) { |
||||
self.target = new classObj(options); |
||||
callback(); |
||||
while (pendingEvents.length) { |
||||
handleEvent(pendingEvents.shift()); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
addEventListener('message', function workerOnMessage(event) { |
||||
var data = event.data; |
||||
if (!data || typeof data !== 'object') { |
||||
// invalid
|
||||
return; |
||||
} else if (data.action == 'transferTest') { |
||||
// ignore
|
||||
} else if (typeof data.action !== 'string' || typeof data.callbackId !== 'string' || typeof data.args !== 'object') { |
||||
console.log('invalid message data', data); |
||||
} else if (!(data.action in handlers)) { |
||||
console.log('invalid message action', data.action); |
||||
} else if (data.action == 'construct') { |
||||
// always handle constructor
|
||||
handleEvent(data); |
||||
} else if (!self.target) { |
||||
// queue until constructed
|
||||
pendingEvents.push(data); |
||||
} else { |
||||
handleEvent(data); |
||||
} |
||||
}); |
||||
|
||||
} |
||||
|
||||
module.exports = OGVWorkerSupport; |
||||
|
||||
|
||||
/***/ }), |
||||
/* 3 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVVersion = ("1.4.2-20170425024925-504d7197"); |
||||
|
||||
(function() { |
||||
var global = this; |
||||
|
||||
var scriptMap = { |
||||
OGVDemuxerOgg: 'ogv-demuxer-ogg.js', |
||||
OGVDemuxerOggW: 'ogv-demuxer-ogg-wasm.js', |
||||
OGVDemuxerWebM: 'ogv-demuxer-webm.js', |
||||
OGVDemuxerWebMW: 'ogv-demuxer-webm-wasm.js', |
||||
OGVDecoderAudioOpus: 'ogv-decoder-audio-opus.js', |
||||
OGVDecoderAudioOpusW: 'ogv-decoder-audio-opus-wasm.js', |
||||
OGVDecoderAudioVorbis: 'ogv-decoder-audio-vorbis.js', |
||||
OGVDecoderAudioVorbisW: 'ogv-decoder-audio-vorbis-wasm.js', |
||||
OGVDecoderVideoTheora: 'ogv-decoder-video-theora.js', |
||||
OGVDecoderVideoTheoraW: 'ogv-decoder-video-theora-wasm.js', |
||||
OGVDecoderVideoVP8: 'ogv-decoder-video-vp8.js', |
||||
OGVDecoderVideoVP8W: 'ogv-decoder-video-vp8-wasm.js', |
||||
OGVDecoderVideoVP8MT: 'ogv-decoder-video-vp8-mt.js', |
||||
OGVDecoderVideoVP9: 'ogv-decoder-video-vp9.js', |
||||
OGVDecoderVideoVP9W: 'ogv-decoder-video-vp9-wasm.js', |
||||
OGVDecoderVideoVP9MT: 'ogv-decoder-video-vp9-mt.js' |
||||
}; |
||||
|
||||
// @fixme make this less awful
|
||||
var proxyTypes = { |
||||
OGVDecoderAudioOpus: 'audio', |
||||
OGVDecoderAudioOpusW: 'audio', |
||||
OGVDecoderAudioVorbis: 'audio', |
||||
OGVDecoderAudioVorbisW: 'audio', |
||||
OGVDecoderVideoTheora: 'video', |
||||
OGVDecoderVideoTheoraW: 'video', |
||||
OGVDecoderVideoVP8: 'video', |
||||
OGVDecoderVideoVP8W: 'video', |
||||
OGVDecoderVideoVP9: 'video', |
||||
OGVDecoderVideoVP9W: 'video' |
||||
}; |
||||
var proxyInfo = { |
||||
audio: { |
||||
proxy: __webpack_require__(4), |
||||
worker: 'ogv-worker-audio.js', |
||||
}, |
||||
video: { |
||||
proxy: __webpack_require__(6), |
||||
worker: 'ogv-worker-video.js' |
||||
} |
||||
} |
||||
|
||||
function urlForClass(className) { |
||||
var scriptName = scriptMap[className]; |
||||
if (scriptName) { |
||||
return urlForScript(scriptName); |
||||
} else { |
||||
throw new Error('asked for URL for unknown class ' + className); |
||||
} |
||||
}; |
||||
|
||||
function urlForScript(scriptName) { |
||||
if (scriptName) { |
||||
var base = OGVLoader.base; |
||||
if (base === undefined) { |
||||
base = ''; |
||||
} else { |
||||
base += '/'; |
||||
} |
||||
return base + scriptName + '?version=' + encodeURIComponent(OGVVersion); |
||||
} else { |
||||
throw new Error('asked for URL for unknown script ' + scriptName); |
||||
} |
||||
}; |
||||
|
||||
var scriptStatus = {}, |
||||
scriptCallbacks = {}; |
||||
function loadWebScript(src, callback) { |
||||
if (scriptStatus[src] == 'done') { |
||||
callback(); |
||||
} else if (scriptStatus[src] == 'loading') { |
||||
scriptCallbacks[src].push(callback); |
||||
} else { |
||||
scriptStatus[src] = 'loading'; |
||||
scriptCallbacks[src] = [callback]; |
||||
|
||||
var scriptNode = document.createElement('script'); |
||||
function done(event) { |
||||
var callbacks = scriptCallbacks[src]; |
||||
delete scriptCallbacks[src]; |
||||
scriptStatus[src] = 'done'; |
||||
|
||||
callbacks.forEach(function(cb) { |
||||
cb(); |
||||
}); |
||||
} |
||||
scriptNode.addEventListener('load', done); |
||||
scriptNode.addEventListener('error', done); |
||||
scriptNode.src = src; |
||||
document.querySelector('head').appendChild(scriptNode); |
||||
} |
||||
} |
||||
|
||||
function loadWebAssembly(src, callback) { |
||||
if (!src.match(/-wasm\.js/)) { |
||||
callback(null); |
||||
} else { |
||||
var wasmSrc = src.replace(/-wasm\.js/, '-wasm.wasm'); |
||||
var xhr = new XMLHttpRequest(); |
||||
xhr.responseType = 'arraybuffer'; |
||||
xhr.onload = function() { |
||||
callback(xhr.response); |
||||
}; |
||||
xhr.onerror = function() { |
||||
callback(null); |
||||
}; |
||||
xhr.open('GET', wasmSrc); |
||||
xhr.send(); |
||||
} |
||||
} |
||||
|
||||
function defaultBase() { |
||||
if (typeof global.window === 'object') { |
||||
|
||||
// for browser, try to autodetect
|
||||
var scriptNodes = document.querySelectorAll('script'), |
||||
regex = /^(?:|(.*)\/)ogv(?:-support)?\.js(?:\?|#|$)/, |
||||
path, |
||||
matches; |
||||
for (var i = 0; i < scriptNodes.length; i++) { |
||||
path = scriptNodes[i].getAttribute('src'); |
||||
if (path) { |
||||
matches = path.match(regex); |
||||
if (matches) { |
||||
return matches[1]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return undefined; // current dir
|
||||
|
||||
} else { |
||||
|
||||
// for workers, assume current directory
|
||||
// if not a worker, too bad.
|
||||
return undefined; |
||||
|
||||
} |
||||
} |
||||
|
||||
var OGVLoader = { |
||||
base: defaultBase(), |
||||
|
||||
loadClass: function(className, callback, options) { |
||||
options = options || {}; |
||||
if (options.worker) { |
||||
this.workerProxy(className, callback); |
||||
return; |
||||
} |
||||
var url = urlForClass(className); |
||||
loadWebAssembly(url, function(wasmBinary) { |
||||
function wasmClassWrapper(options) { |
||||
options = options || {}; |
||||
if (wasmBinary !== null) { |
||||
options.wasmBinary = wasmBinary; |
||||
} |
||||
return new global[className](options); |
||||
} |
||||
if (typeof global[className] === 'function') { |
||||
// already loaded!
|
||||
callback(wasmClassWrapper); |
||||
} else if (typeof global.window === 'object') { |
||||
loadWebScript(url, function() { |
||||
callback(wasmClassWrapper); |
||||
}); |
||||
} else if (typeof global.importScripts === 'function') { |
||||
// worker has convenient sync importScripts
|
||||
global.importScripts(url); |
||||
callback(wasmClassWrapper); |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
workerProxy: function(className, callback) { |
||||
var proxyType = proxyTypes[className], |
||||
info = proxyInfo[proxyType]; |
||||
|
||||
if (!info) { |
||||
throw new Error('Requested worker for class with no proxy: ' + className); |
||||
} |
||||
|
||||
var proxyClass = info.proxy, |
||||
workerScript = info.worker, |
||||
codecUrl = urlForScript(scriptMap[className]), |
||||
workerUrl = urlForScript(workerScript), |
||||
worker; |
||||
|
||||
var construct = function(options) { |
||||
return new proxyClass(worker, className, options); |
||||
}; |
||||
|
||||
if (workerUrl.match(/^https?:|\/\//i)) { |
||||
// Can't load workers natively cross-domain, but if CORS
|
||||
// is set up we can fetch the worker stub and the desired
|
||||
// class and load them from a blob.
|
||||
var getCodec, |
||||
getWorker, |
||||
codecResponse, |
||||
workerResponse, |
||||
codecLoaded = false, |
||||
workerLoaded = false, |
||||
blob; |
||||
|
||||
function completionCheck() { |
||||
if ((codecLoaded == true) && (workerLoaded == true)) { |
||||
try { |
||||
blob = new Blob([codecResponse + " " + workerResponse], {type: 'application/javascript'}); |
||||
} catch (e) { // Backwards-compatibility
|
||||
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; |
||||
blob = new BlobBuilder(); |
||||
blob.append(codecResponse + " " + workerResponse); |
||||
blob = blob.getBlob(); |
||||
} |
||||
// Create the web worker
|
||||
worker = new Worker(URL.createObjectURL(blob)); |
||||
callback(construct); |
||||
} |
||||
} |
||||
|
||||
// Load the codec
|
||||
getCodec = new XMLHttpRequest(); |
||||
getCodec.open("GET", codecUrl, true); |
||||
getCodec.onreadystatechange = function() { |
||||
if(getCodec.readyState == 4 && getCodec.status == 200) { |
||||
codecResponse = getCodec.responseText; |
||||
// Update the codec response loaded flag
|
||||
codecLoaded = true; |
||||
completionCheck(); |
||||
} |
||||
}; |
||||
getCodec.send(); |
||||
|
||||
// Load the worker
|
||||
getWorker = new XMLHttpRequest(); |
||||
getWorker.open("GET", workerUrl, true); |
||||
getWorker.onreadystatechange = function() { |
||||
if(getWorker.readyState == 4 && getWorker.status == 200) { |
||||
workerResponse = getWorker.responseText; |
||||
// Update the worker response loaded flag
|
||||
workerLoaded = true; |
||||
completionCheck(); |
||||
} |
||||
}; |
||||
getWorker.send(); |
||||
} else { |
||||
// Local URL; load it directly for simplicity.
|
||||
worker = new Worker(workerUrl); |
||||
callback(construct); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
module.exports = OGVLoader; |
||||
|
||||
})(); |
||||
|
||||
|
||||
/***/ }), |
||||
/* 4 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVProxyClass = __webpack_require__(5); |
||||
|
||||
var OGVDecoderAudioProxy = OGVProxyClass({ |
||||
loadedMetadata: false, |
||||
audioFormat: null, |
||||
audioBuffer: null, |
||||
cpuTime: 0 |
||||
}, { |
||||
init: function(callback) { |
||||
this.proxy('init', [], callback); |
||||
}, |
||||
|
||||
processHeader: function(data, callback) { |
||||
this.proxy('processHeader', [data], callback, [data]); |
||||
}, |
||||
|
||||
processAudio: function(data, callback) { |
||||
this.proxy('processAudio', [data], callback, [data]); |
||||
}, |
||||
|
||||
close: function() { |
||||
this.terminate(); |
||||
} |
||||
}); |
||||
|
||||
module.exports = OGVDecoderAudioProxy; |
||||
|
||||
|
||||
/***/ }), |
||||
/* 5 */ |
||||
/***/ (function(module, exports) { |
||||
|
||||
/** |
||||
* Proxy object for web worker interface for codec classes. |
||||
* |
||||
* Used by the high-level player interface. |
||||
* |
||||
* @author Brion Vibber <brion@pobox.com> |
||||
* @copyright 2015 |
||||
* @license MIT-style |
||||
*/ |
||||
function OGVProxyClass(initialProps, methods) { |
||||
return function(worker, className, options) { |
||||
options = options || {}; |
||||
var self = this; |
||||
|
||||
var transferables = (function() { |
||||
var buffer = new ArrayBuffer(1024), |
||||
bytes = new Uint8Array(buffer); |
||||
try { |
||||
worker.postMessage({ |
||||
action: 'transferTest', |
||||
bytes: bytes |
||||
}, [buffer]); |
||||
if (buffer.byteLength) { |
||||
// No transferable support
|
||||
return false; |
||||
} else { |
||||
return true; |
||||
} |
||||
} catch (e) { |
||||
return false; |
||||
} |
||||
})(); |
||||
|
||||
// Set up proxied property getters
|
||||
var props = {}; |
||||
for (var iPropName in initialProps) { |
||||
if (initialProps.hasOwnProperty(iPropName)) { |
||||
(function(propName) { |
||||
props[propName] = initialProps[propName]; |
||||
Object.defineProperty(self, propName, { |
||||
get: function getProperty() { |
||||
return props[propName]; |
||||
} |
||||
}); |
||||
})(iPropName); |
||||
} |
||||
} |
||||
|
||||
// Current player wants to avoid async confusion.
|
||||
var processingQueue = 0; |
||||
Object.defineProperty(self, 'processing', { |
||||
get: function() { |
||||
return (processingQueue > 0); |
||||
} |
||||
}); |
||||
|
||||
// Set up proxied methods
|
||||
for (var method in methods) { |
||||
if (methods.hasOwnProperty(method)) { |
||||
self[method] = methods[method]; |
||||
} |
||||
} |
||||
|
||||
// And some infrastructure!
|
||||
var messageCount = 0, |
||||
pendingCallbacks = {}; |
||||
this.proxy = function(action, args, callback, transfers) { |
||||
if (!worker) { |
||||
throw 'Tried to call "' + action + '" method on closed proxy object'; |
||||
} |
||||
var callbackId = 'callback-' + (++messageCount) + '-' + action; |
||||
if (callback) { |
||||
pendingCallbacks[callbackId] = callback; |
||||
} |
||||
var out = { |
||||
'action': action, |
||||
'callbackId': callbackId, |
||||
'args': args || [] |
||||
}; |
||||
processingQueue++; |
||||
if (transferables) { |
||||
worker.postMessage(out, transfers || []); |
||||
} else { |
||||
worker.postMessage(out); |
||||
} |
||||
}; |
||||
this.terminate = function() { |
||||
if (worker) { |
||||
worker.terminate(); |
||||
worker = null; |
||||
processingQueue = 0; |
||||
pendingCallbacks = {}; |
||||
} |
||||
}; |
||||
|
||||
worker.addEventListener('message', function proxyOnMessage(event) { |
||||
processingQueue--; |
||||
if (event.data.action !== 'callback') { |
||||
// ignore
|
||||
return; |
||||
} |
||||
|
||||
var data = event.data, |
||||
callbackId = data.callbackId, |
||||
args = data.args, |
||||
callback = pendingCallbacks[callbackId]; |
||||
|
||||
// Save any updated properties returned to us...
|
||||
if (data.props) { |
||||
for (var propName in data.props) { |
||||
if (data.props.hasOwnProperty(propName)) { |
||||
props[propName] = data.props[propName]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (callback) { |
||||
delete pendingCallbacks[callbackId]; |
||||
callback.apply(this, args); |
||||
} |
||||
}); |
||||
|
||||
// Tell the proxy to load and initialize the appropriate class
|
||||
self.proxy('construct', [className, options], function() {}); |
||||
|
||||
return self; |
||||
}; |
||||
} |
||||
|
||||
module.exports = OGVProxyClass; |
||||
|
||||
/***/ }), |
||||
/* 6 */ |
||||
/***/ (function(module, exports, __webpack_require__) { |
||||
|
||||
var OGVProxyClass = __webpack_require__(5); |
||||
|
||||
var OGVDecoderVideoProxy = OGVProxyClass({ |
||||
loadedMetadata: false, |
||||
videoFormat: null, |
||||
frameBuffer: null, |
||||
cpuTime: 0 |
||||
}, { |
||||
init: function(callback) { |
||||
this.proxy('init', [], callback); |
||||
}, |
||||
|
||||
processHeader: function(data, callback) { |
||||
this.proxy('processHeader', [data], callback, [data]); |
||||
}, |
||||
|
||||
processFrame: function(data, callback) { |
||||
this.proxy('processFrame', [data], callback, [data]); |
||||
}, |
||||
|
||||
close: function() { |
||||
this.terminate(); |
||||
} |
||||
}); |
||||
|
||||
module.exports = OGVDecoderVideoProxy; |
||||
|
||||
/***/ }) |
||||
/******/ ]); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,103 @@
@@ -0,0 +1,103 @@
|
||||
// Pthread Web Worker startup routine:
|
||||
// This is the entry point file that is loaded first by each Web Worker
|
||||
// that executes pthreads on the Emscripten application.
|
||||
|
||||
// Thread-local, communicated via globals:
|
||||
var threadInfoStruct = 0; // Info area for this thread in Emscripten HEAP (shared). If zero, this worker is not currently hosting an executing pthread.
|
||||
var selfThreadId = 0; // The ID of this thread. 0 if not hosting a pthread.
|
||||
var parentThreadId = 0; // The ID of the parent pthread that launched this thread.
|
||||
|
||||
// Send the pthreads mode and other params in through Module object settings
|
||||
var Module = { |
||||
ENVIRONMENT: 'PTHREAD' |
||||
}; |
||||
|
||||
// Cannot use console.log or console.error in a web worker, since that would risk a browser deadlock! https://bugzilla.mozilla.org/show_bug.cgi?id=1049091
|
||||
// Therefore implement custom logging facility for threads running in a worker, which queue the messages to main thread to print.
|
||||
function threadPrint() { |
||||
var text = Array.prototype.slice.call(arguments).join(' '); |
||||
console.log(text); |
||||
} |
||||
function threadPrintErr() { |
||||
var text = Array.prototype.slice.call(arguments).join(' '); |
||||
console.error(text); |
||||
} |
||||
function threadAlert() { |
||||
var text = Array.prototype.slice.call(arguments).join(' '); |
||||
postMessage({cmd: 'alert', text: text, threadId: selfThreadId}); |
||||
} |
||||
Module['print'] = threadPrint; |
||||
Module['printErr'] = threadPrintErr; |
||||
this.alert = threadAlert; |
||||
|
||||
// If modularized, we can't reuse the module's assert() function.
|
||||
function assert(condition, text) { |
||||
if (!condition) { |
||||
abort('Assertion failed: ' + text); |
||||
} |
||||
} |
||||
|
||||
this.onmessage = function(e) { |
||||
if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code.
|
||||
// Initialize the thread-local field(s):
|
||||
Module['tempDoublePtr'] = e.data.tempDoublePtr; |
||||
|
||||
// Initialize the global "process"-wide fields:
|
||||
Module['buffer'] = e.data.buffer; |
||||
Module['TOTAL_MEMORY'] = e.data.TOTAL_MEMORY; |
||||
Module['STATICTOP'] = e.data.STATICTOP; |
||||
Module['DYNAMIC_BASE'] = e.data.DYNAMIC_BASE; |
||||
Module['DYNAMICTOP_PTR'] = e.data.DYNAMICTOP_PTR; |
||||
|
||||
Module['pthreadWorkerInit'] = e.data.PthreadWorkerInit; |
||||
importScripts(e.data.url); |
||||
if (e.data.modularize) { |
||||
// Feed input options into the modularized constructor...
|
||||
// 'this' is the Worker, which is also global scope.
|
||||
Module = new this[e.data.moduleExportName](Module); |
||||
} |
||||
if (typeof FS !== 'undefined') FS.createStandardStreams(); |
||||
postMessage({ cmd: 'loaded' }); |
||||
} else if (e.data.cmd === 'objectTransfer') { |
||||
Module.PThread.receiveObjectTransfer(e.data); |
||||
} else if (e.data.cmd === 'run') { // This worker was idle, and now should start executing its pthread entry point.
|
||||
threadInfoStruct = e.data.threadInfoStruct; |
||||
Module.PThread.registerPthreadPtr(threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0); // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out.
|
||||
assert(threadInfoStruct); |
||||
selfThreadId = e.data.selfThreadId; |
||||
parentThreadId = e.data.parentThreadId; |
||||
assert(selfThreadId); |
||||
assert(parentThreadId); |
||||
Module.PThread.setStackSpace(e.data.stackBase, e.data.stackBase + e.data.stackSize); |
||||
var result = 0; |
||||
|
||||
Module.PThread.receiveObjectTransfer(e.data); |
||||
|
||||
Module.PThread.setThreadStatus(threadInfoStruct, 1/*EM_THREAD_STATUS_RUNNING*/); |
||||
|
||||
try { |
||||
Module.PThread.runThreadFunc(e.data.start_routine, e.data.arg); |
||||
} catch(e) { |
||||
if (e === 'Canceled!') { |
||||
Module.PThread.threadCancel(); |
||||
return; |
||||
} else { |
||||
Atomics.store(Module.HEAPU32, (threadInfoStruct + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/); |
||||
Atomics.store(Module.HEAPU32, (threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running.
|
||||
Module.PThread.wakeAllThreads(); |
||||
throw e; |
||||
} |
||||
} |
||||
// The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves.
|
||||
// (This is a no-op if explicit pthread_exit() had been called prior.)
|
||||
if (!Module['noExitRuntime']) Module.PThread.threadExit(result); |
||||
else console.log('pthread noExitRuntime: not quitting.'); |
||||
} else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread.
|
||||
if (threadInfoStruct && Module.PThread.thisThreadCancelState == 0/*PTHREAD_CANCEL_ENABLE*/) { |
||||
Module.PThread.threadCancel(); |
||||
} |
||||
} else { |
||||
// Module['printErr']('pthread-main.js received unknown command ' + e.data.cmd);
|
||||
// console.error(e.data)
|
||||
} |
||||
} |
Loading…
Reference in new issue