Browse Source

Added JS OPUS decoder via ogv.js

Fixed playback of voice messages from iOS app in Chrome
Added playback ability to Safari
Closes #1396
Closes #1464
Closes #1465
master
Igor Zhukov 7 years ago
parent
commit
98de12e860
  1. 5
      app/index.html
  2. 68
      app/js/directives.js
  3. 1
      app/js/services.js
  4. 9
      app/less/app.less
  5. 12
      app/partials/desktop/audio_player.html
  6. 25
      app/partials/mobile/audio_player.html
  7. 15
      app/vendor/angular-media-player/angular-media-player.js
  8. 21
      app/vendor/ogv.js/COPYING
  9. 28
      app/vendor/ogv.js/COPYING-ogg.txt
  10. 44
      app/vendor/ogv.js/COPYING-opus.txt
  11. 28
      app/vendor/ogv.js/COPYING-theora.txt
  12. 28
      app/vendor/ogv.js/COPYING-vorbis.txt
  13. 13
      app/vendor/ogv.js/LICENSE-nestegg.txt
  14. 31
      app/vendor/ogv.js/LICENSE-vpx.txt
  15. 23
      app/vendor/ogv.js/PATENTS-vpx.txt
  16. 371
      app/vendor/ogv.js/README.md
  17. BIN
      app/vendor/ogv.js/dynamicaudio.swf
  18. 17
      app/vendor/ogv.js/ogv-decoder-audio-opus-wasm.js
  19. BIN
      app/vendor/ogv.js/ogv-decoder-audio-opus-wasm.wasm
  20. 32
      app/vendor/ogv.js/ogv-decoder-audio-opus.js
  21. 17
      app/vendor/ogv.js/ogv-decoder-audio-vorbis-wasm.js
  22. BIN
      app/vendor/ogv.js/ogv-decoder-audio-vorbis-wasm.wasm
  23. 30
      app/vendor/ogv.js/ogv-decoder-audio-vorbis.js
  24. 17
      app/vendor/ogv.js/ogv-decoder-video-theora-wasm.js
  25. BIN
      app/vendor/ogv.js/ogv-decoder-video-theora-wasm.wasm
  26. 30
      app/vendor/ogv.js/ogv-decoder-video-theora.js
  27. 31
      app/vendor/ogv.js/ogv-decoder-video-vp8-mt.js
  28. 17
      app/vendor/ogv.js/ogv-decoder-video-vp8-wasm.js
  29. BIN
      app/vendor/ogv.js/ogv-decoder-video-vp8-wasm.wasm
  30. 31
      app/vendor/ogv.js/ogv-decoder-video-vp8.js
  31. 33
      app/vendor/ogv.js/ogv-decoder-video-vp9-mt.js
  32. 17
      app/vendor/ogv.js/ogv-decoder-video-vp9-wasm.js
  33. BIN
      app/vendor/ogv.js/ogv-decoder-video-vp9-wasm.wasm
  34. 33
      app/vendor/ogv.js/ogv-decoder-video-vp9.js
  35. 17
      app/vendor/ogv.js/ogv-demuxer-ogg-wasm.js
  36. BIN
      app/vendor/ogv.js/ogv-demuxer-ogg-wasm.wasm
  37. 30
      app/vendor/ogv.js/ogv-demuxer-ogg.js
  38. 17
      app/vendor/ogv.js/ogv-demuxer-webm-wasm.js
  39. BIN
      app/vendor/ogv.js/ogv-demuxer-webm-wasm.wasm
  40. 30
      app/vendor/ogv.js/ogv-demuxer-webm.js
  41. 272
      app/vendor/ogv.js/ogv-support.js
  42. 70
      app/vendor/ogv.js/ogv-version.js
  43. 701
      app/vendor/ogv.js/ogv-worker-audio.js
  44. 700
      app/vendor/ogv.js/ogv-worker-video.js
  45. 9663
      app/vendor/ogv.js/ogv.js
  46. 103
      app/vendor/ogv.js/pthread-main.js
  47. 2
      app/webogram.appcache

5
app/index.html

@ -72,6 +72,11 @@ @@ -72,6 +72,11 @@
<script type="text/javascript" src="vendor/angularjs-toaster/toaster.js"></script>
<script type="text/javascript" src="vendor/clipboard/clipboard.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv-demuxer-ogg.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv-decoder-audio-opus.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv-decoder-audio-vorbis.js"></script>
<script type="text/javascript" src="vendor/ogv.js/ogv-support.js"></script>
<script type="text/javascript" src="js/lib/utils.js"></script>
<script type="text/javascript" src="js/lib/bin_utils.js"></script>

68
app/js/directives.js

@ -3120,8 +3120,31 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3120,8 +3120,31 @@ angular.module('myApp.directives', ['myApp.filters'])
}
})
.directive('myOgvPlayer', function ($compile) {
return {
link: function ($scope, $element, $attrs) {
var audio = $scope.audio
var playerEl
if (audio.mime_type == 'audio/ogg' &&
// false &&
OGVCompat.hasWebAudio() && // we don't want to use Flash
OGVCompat.supported('OGVPlayer')) {
playerEl = new OGVPlayer({debug: false, worker: false})
} else {
playerEl = document.createElement('audio')
}
$(playerEl).attr('media-player', $attrs.myOgvPlayer)
$(playerEl).attr('src', '{{::' + $attrs.src + '}}')
$compile(playerEl)($scope)
$($element).append(playerEl)
}
}
})
.directive('myAudioPlayer', function ($timeout, $q, Storage, AppDocsManager, AppMessagesManager, ErrorService) {
var currentPlayer = false
var currentPlayerScope = false
var audioVolume = 0.5
Storage.get('audio_volume').then(function (newAudioVolume) {
@ -3147,20 +3170,23 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3147,20 +3170,23 @@ angular.module('myApp.directives', ['myApp.filters'])
return {
link: link,
scope: {
audio: '=',
message: '='
audio: '='
},
templateUrl: templateUrl('audio_player')
}
function checkPlayer (newPlayer) {
if (newPlayer === currentPlayer) {
function checkAudioPlayer (newPlayerScope) {
if (newPlayerScope === currentPlayerScope) {
return false
}
if (currentPlayer) {
currentPlayer.pause()
if (currentPlayerScope) {
;(function ($scope) {
setZeroTimeout(function () {
$scope.mediaPlayer.player.pause()
})
})(currentPlayerScope)
}
currentPlayer = newPlayer
currentPlayerScope = newPlayerScope
}
function link ($scope, element, attrs) {
@ -3168,20 +3194,34 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3168,20 +3194,34 @@ angular.module('myApp.directives', ['myApp.filters'])
$scope.volume = audioVolume
$scope.mediaPlayer = {}
if ($scope.$parent.messageId) {
$scope.message = AppMessagesManager.wrapForHistory($scope.$parent.messageId)
}
$scope.download = function () {
AppDocsManager.saveDocFile($scope.audio.id)
}
$scope.duration = function () {
if ($scope.mediaPlayer.player &&
$scope.mediaPlayer.player.duration > 0 &&
$scope.mediaPlayer.player.duration < Infinity) {
return $scope.mediaPlayer.player.duration
}
return $scope.audio && $scope.audio.duration || 0
}
$scope.togglePlay = function () {
if ($scope.audio.url) {
checkPlayer($scope.mediaPlayer.player)
$scope.mediaPlayer.player.playPause()
checkAudioPlayer($scope)
setZeroTimeout(function () {
$scope.mediaPlayer.player.playPause()
})
} else if ($scope.audio.progress && $scope.audio.progress.enabled) {
} else {
AppDocsManager.downloadDoc($scope.audio.id).then(function () {
onContentLoaded(function () {
var errorListenerEl = $('audio', element)[0] || element[0]
var errorListenerEl = $('audio, ogvjs', element)[0] || element[0]
if (errorListenerEl) {
var errorAlready = false
var onAudioError = function (event) {
@ -3209,13 +3249,13 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -3209,13 +3249,13 @@ angular.module('myApp.directives', ['myApp.filters'])
})
}
setTimeout(function () {
checkPlayer($scope.mediaPlayer.player)
checkAudioPlayer($scope)
$scope.mediaPlayer.player.setVolume(audioVolume)
$scope.mediaPlayer.player.play()
if ($scope.message &&
!$scope.message.pFlags.out &&
$scope.message.pFlags.media_unread) {
!$scope.message.pFlags.out &&
$scope.message.pFlags.media_unread) {
AppMessagesManager.readMessages([$scope.message.mid])
}
}, 300)

1
app/js/services.js

@ -4869,7 +4869,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4869,7 +4869,6 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
// console.warn(dT(), 'server', draft)
} else {
// console.warn(dT(), 'local', draft)
console.warn(dT(), 'local', draft)
}
var replyToMsgID = draft && draft.replyToMsgID
if (replyToMsgID) {

9
app/less/app.less

@ -2042,6 +2042,15 @@ img.im_message_document_thumb { @@ -2042,6 +2042,15 @@ img.im_message_document_thumb {
}
}
.audio_player_media {
position: absolute;
visibility: hidden;
canvas {
display: none;
}
}
.im_message_upload_progress_wrap,
.im_message_download_progress_wrap {
margin-top: 5px;

12
app/partials/desktop/audio_player.html

@ -4,8 +4,8 @@ @@ -4,8 +4,8 @@
</a>
<div class="audio_player_title_wrap">
<div class="audio_player_meta pull-right" ng-if="audio.downloaded &amp;&amp; (mediaPlayer.player.duration || audio.duration)" ng-switch="mediaPlayer.player.playing || mediaPlayer.player.currentTime > 0">
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span>
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : duration()"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="duration() | duration"></span>
</div>
<span class="copyonly">[ </span>
<a ng-attr-title="{{audio.file_name}}" ng-click="download()" class="audio_player_title" ng-switch="::audio.audioTitle.length > 0 ? 2 : (audio.file_name.length > 0 ? 1 : 0)">
@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
</a>
<span class="copyonly">]</span>
<i ng-if="::message.pFlags.media_unread || false" ng-show="message.pFlags.media_unread" class="icon icon-audio-unread"></i>
<div class="audio_player_meta" ng-if="!audio.downloaded || !(mediaPlayer.player.duration || audio.duration)" ng-switch="audio.progress.enabled">
<div class="audio_player_meta" ng-if="!audio.downloaded || !duration()" ng-switch="audio.progress.enabled">
<span ng-switch-when="true" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span>
<span ng-switch-default class="audio_player_size" ng-bind="audio.size | formatSize"></span>
</div>
@ -37,11 +37,9 @@ @@ -37,11 +37,9 @@
</div>
</div>
<div ng-switch-default class="im_message_playback_progress_wrap">
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="mediaPlayer.player.duration || audio.duration" slider-onchange="seek(value)"></div>
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="duration()" slider-onchange="seek(value)"></div>
<div class="audio_player_volume_slider" my-slider slider-model="mediaPlayer.player.volume" slider-min="0" slider-max="1" slider-onchange="setVolume(value)"></div>
</div>
</div>
<audio ng-if="audio.url" media-player="mediaPlayer.player">
<source ng-src="{{::audio.url}}" type="{{audio.mime_type || 'audio/ogg'}}" volume="{{::volume}}" />
</audio>
<div class="audio_player_media" ng-if="audio.url" my-ogv-player="mediaPlayer.player" src="audio.url" volume="{{::volume}}"></div>
</div>

25
app/partials/mobile/audio_player.html

@ -4,10 +4,11 @@ @@ -4,10 +4,11 @@
</a>
<div class="audio_player_title_wrap">
<div class="audio_player_meta pull-right" ng-if="audio.downloaded &amp;&amp; (mediaPlayer.player.duration || audio.duration)" ng-switch="mediaPlayer.player.playing || mediaPlayer.player.currentTime > 0">
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : (mediaPlayer.player.duration || audio.duration)"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="mediaPlayer.player.duration || audio.duration | duration"></span>
<span ng-switch-when="true" class="audio_player_duration" ng-bind="mediaPlayer.player.currentTime | durationRemains : duration()"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="duration() | duration"></span>
</div>
<a ng-click="download()" class="audio_player_title" ng-switch="::audio.audioTitle.length > 0 ? 2 : (audio.file_name.length > 0 ? 1 : 0)">
<span class="copyonly">[ </span>
<a ng-attr-title="{{audio.file_name}}" ng-click="download()" class="audio_player_title" ng-switch="::audio.audioTitle.length > 0 ? 2 : (audio.file_name.length > 0 ? 1 : 0)">
<span ng-switch-when="2">
<strong ng-bind="::audio.audioPerformer"></strong>
<span ng-bind="::(audio.audioPerformer ? '– ' : '') + audio.audioTitle"></span>
@ -15,19 +16,20 @@ @@ -15,19 +16,20 @@
<span ng-switch-when="1" ng-bind="::audio.file_name"></span>
<span ng-switch-default my-i18n="message_attach_audio_message"></span>
</a>
<span class="copyonly">]</span>
<i ng-if="::message.pFlags.media_unread || false" ng-show="message.pFlags.media_unread" class="icon icon-audio-unread"></i>
<div class="audio_player_meta" ng-if="!audio.downloaded || !(mediaPlayer.player.duration || audio.duration)" ng-switch="audio.progress.enabled">
<div class="audio_player_meta" ng-if="!audio.downloaded || !duration()" ng-switch="audio.progress.enabled">
<span ng-switch-when="true" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span>
<span ng-switch-default class="audio_player_size" ng-bind="audio.size | formatSize"></span>
</div>
</div>
<div class="audio_player_actions" ng-if="!audio.progress.enabled &amp;&amp; !audio.downloaded">
<a ng-if="audio._ == 'document'" ng-click="download()" my-i18n="message_attach_document_download"></a>
<a ng-click="togglePlay()" my-i18n="message_attach_audio_play"></a>
<div class="audio_player_actions noselect" ng-if="!audio.progress.enabled &amp;&amp; !audio.downloaded">
<a class="nocopy" ng-if="audio._ == 'document'" ng-click="download()" my-i18n="message_attach_document_download"></a>
<a class="nocopy" ng-click="togglePlay()" my-i18n="message_attach_audio_play"></a>
</div>
<div class="audio_player_progress_wrap" ng-if="audio.progress.enabled || audio.downloaded" ng-switch="audio.progress.enabled">
<div ng-switch-when="true" class="clearfix im_message_cancelable_progress_wrap">
<a class="im_message_media_progress_cancel pull-right" ng-click="audio.progress.cancel()" my-i18n="modal_cancel"></a>
<a class="im_message_media_progress_cancel pull-right nocopy" ng-click="audio.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: audio.progress.percent + '%'}"></div>
@ -35,10 +37,9 @@ @@ -35,10 +37,9 @@
</div>
</div>
<div ng-switch-default class="im_message_playback_progress_wrap">
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="mediaPlayer.player.duration || audio.duration" slider-onchange="seek(value)"></div>
<div class="audio_player_seek_slider" my-slider slider-model="mediaPlayer.player.currentTime" slider-max="duration()" slider-onchange="seek(value)"></div>
<div class="audio_player_volume_slider" my-slider slider-model="mediaPlayer.player.volume" slider-min="0" slider-max="1" slider-onchange="setVolume(value)"></div>
</div>
</div>
<audio ng-if="audio.url" media-player="mediaPlayer.player">
<source ng-src="{{::audio.url}}" type="{{audio.mime_type || 'audio/ogg'}}" volume="{{::volume}}" />
</audio>
<div class="audio_player_media" ng-if="audio.url" my-ogv-player="mediaPlayer.player" src="audio.url" volume="{{::volume}}"></div>
</div>

15
app/vendor/angular-media-player/angular-media-player.js vendored

@ -50,7 +50,9 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -50,7 +50,9 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
this.$domEl.load();
this.ended = undefined;
if (autoplay) {
this.$element.one('canplay', this.play.bind(this));
// ogv.js doesn't have support for canplay event yet
var canPlayEvent = this.$domEl.tagName == 'OGVJS' ? 'loadeddata' : 'canplay'
this.$element.one(canPlayEvent, this.play.bind(this));
}
},
/**
@ -242,6 +244,10 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -242,6 +244,10 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
* Returns an unbinding function
*/
var bindListeners = function (au, al, element) {
var updateTime = function (scope) {
scope.currentTime = al.currentTime;
scope.formatTime = scope.$formatTime(scope.currentTime);
}
var listeners = {
playing: function () {
au.$apply(function (scope) {
@ -261,13 +267,13 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -261,13 +267,13 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
au.$apply(function (scope) {
scope.ended = true;
scope.playing = false; // IE9 does not throw 'pause' when file ends
updateTime(scope)
});
}
},
timeupdate: throttle(1000, false, function () {
au.$apply(function (scope) {
scope.currentTime = al.currentTime;
scope.formatTime = scope.$formatTime(scope.currentTime);
updateTime(scope)
});
}),
loadedmetadata: function () {
@ -278,6 +284,7 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -278,6 +284,7 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
if (al.buffered.length) {
scope.loadPercent = Math.round((al.buffered.end(al.buffered.length - 1) / scope.duration) * 100);
}
updateTime(scope)
});
},
progress: function () {
@ -434,7 +441,7 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers']) @@ -434,7 +441,7 @@ angular.module('mediaPlayer', ['mediaPlayer.helpers'])
scope.$eval(mediaName + ' = player', {player: player});
}
if (element[0].tagName !== 'AUDIO' && element[0].tagName !== 'VIDEO') {
if (element[0].tagName !== 'AUDIO' && element[0].tagName !== 'VIDEO' && element[0].tagName !== 'OGVJS') {
return new Error('player directive works only when attached to an <audio>/<video> type tag');
}
var mediaElement = [],

21
app/vendor/ogv.js/COPYING vendored

@ -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.

28
app/vendor/ogv.js/COPYING-ogg.txt vendored

@ -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.

44
app/vendor/ogv.js/COPYING-opus.txt vendored

@ -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/

28
app/vendor/ogv.js/COPYING-theora.txt vendored

@ -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.

28
app/vendor/ogv.js/COPYING-vorbis.txt vendored

@ -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.

13
app/vendor/ogv.js/LICENSE-nestegg.txt vendored

@ -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.

31
app/vendor/ogv.js/LICENSE-vpx.txt vendored

@ -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.

23
app/vendor/ogv.js/PATENTS-vpx.txt vendored

@ -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.

371
app/vendor/ogv.js/README.md vendored

@ -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.

BIN
app/vendor/ogv.js/dynamicaudio.swf vendored

Binary file not shown.

17
app/vendor/ogv.js/ogv-decoder-audio-opus-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-audio-opus-wasm.wasm vendored

Binary file not shown.

32
app/vendor/ogv.js/ogv-decoder-audio-opus.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-decoder-audio-vorbis-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-audio-vorbis-wasm.wasm vendored

Binary file not shown.

30
app/vendor/ogv.js/ogv-decoder-audio-vorbis.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-decoder-video-theora-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-video-theora-wasm.wasm vendored

Binary file not shown.

30
app/vendor/ogv.js/ogv-decoder-video-theora.js vendored

File diff suppressed because one or more lines are too long

31
app/vendor/ogv.js/ogv-decoder-video-vp8-mt.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-decoder-video-vp8-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-video-vp8-wasm.wasm vendored

Binary file not shown.

31
app/vendor/ogv.js/ogv-decoder-video-vp8.js vendored

File diff suppressed because one or more lines are too long

33
app/vendor/ogv.js/ogv-decoder-video-vp9-mt.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-decoder-video-vp9-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-decoder-video-vp9-wasm.wasm vendored

Binary file not shown.

33
app/vendor/ogv.js/ogv-decoder-video-vp9.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-demuxer-ogg-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-demuxer-ogg-wasm.wasm vendored

Binary file not shown.

30
app/vendor/ogv.js/ogv-demuxer-ogg.js vendored

File diff suppressed because one or more lines are too long

17
app/vendor/ogv.js/ogv-demuxer-webm-wasm.js vendored

File diff suppressed because one or more lines are too long

BIN
app/vendor/ogv.js/ogv-demuxer-webm-wasm.wasm vendored

Binary file not shown.

30
app/vendor/ogv.js/ogv-demuxer-webm.js vendored

File diff suppressed because one or more lines are too long

272
app/vendor/ogv.js/ogv-support.js vendored

@ -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;
/***/ })
/******/ ]);

70
app/vendor/ogv.js/ogv-version.js vendored

@ -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
};
})();
/***/ })
/******/ ]);

701
app/vendor/ogv.js/ogv-worker-audio.js vendored

@ -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;
/***/ })
/******/ ]);

700
app/vendor/ogv.js/ogv-worker-video.js vendored

@ -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;
/***/ })
/******/ ]);

9663
app/vendor/ogv.js/ogv.js vendored

File diff suppressed because it is too large Load Diff

103
app/vendor/ogv.js/pthread-main.js vendored

@ -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)
}
}

2
app/webogram.appcache

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
CACHE MANIFEST
# 66
# 75
NETWORK:
*

Loading…
Cancel
Save