├── .babelrc
├── examples
├── main.js
├── basic
│ ├── bbb.mp4
│ └── index.html
├── common.js
├── common.css
├── videosphere
│ └── index.html
└── index.html
├── example.gif
├── .gitignore
├── webpack.config.js
├── lib
├── source-element.js
├── media-element.js
├── mime-types.json
└── inline-video.js
├── LICENSE
├── package.json
├── README.md
├── dist
├── aframe-vid-shader.min.js
└── aframe-vid-shader.js
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | { "presets": ["es2015"] }
2 |
--------------------------------------------------------------------------------
/examples/main.js:
--------------------------------------------------------------------------------
1 | import 'aframe'
2 | import '../'
--------------------------------------------------------------------------------
/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mayognaise/aframe-video-shader/HEAD/example.gif
--------------------------------------------------------------------------------
/examples/basic/bbb.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mayognaise/aframe-video-shader/HEAD/examples/basic/bbb.mp4
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .sw[ponm]
2 | examples/build.js
3 | examples/node_modules/
4 | gh-pages
5 | node_modules/
6 | npm-debug.log
7 | .DS_Store
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | loaders: [
4 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" },
5 | { test: /\.json$/, loader: 'json-loader'}
6 | ]
7 | }
8 | }
--------------------------------------------------------------------------------
/examples/common.js:
--------------------------------------------------------------------------------
1 | var scene = document.querySelector('a-scene')
2 | if (scene.hasLoaded) { ready() }
3 | else { scene.addEventListener('loaded', ready) }
4 |
5 | function ready () {
6 |
7 | /**
8 | * toggle visibility dom element when entering/exiting stereo mode.
9 | */
10 | scene.addEventListener('enter-vr', function () { document.body.setAttribute('data-vr', true) })
11 | scene.addEventListener('exit-vr', function () { document.body.setAttribute('data-vr', false) })
12 |
13 |
14 |
15 | }
--------------------------------------------------------------------------------
/examples/common.css:
--------------------------------------------------------------------------------
1 | html {
2 | position: inherit !important;
3 | }
4 | .buttons {
5 | position: absolute;
6 | z-index: 2;
7 | right: 0;
8 | text-align: right;
9 | height: 0;
10 | }
11 | .buttons a {
12 | display: inline-block;
13 | border: none;
14 | padding: 1em;
15 | margin: 1em 1em 0 0;
16 | background: gray;
17 | color: white;
18 | font: 14px monospace;
19 | text-decoration: none;
20 | }
21 | .buttons a:active {
22 | background: #333;
23 | }
24 | .spacer {
25 | position: relative;
26 | pointer-events: none;
27 | height: 100%;
28 | }
29 | .spacer2 {
30 | position: relative;
31 | pointer-events: none;
32 | height: 1px;
33 | }
34 | body[data-vr="true"] .novr{
35 | display: none;
36 | }
--------------------------------------------------------------------------------
/examples/videosphere/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | A-Frame Video Shader - Videosphere
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/lib/source-element.js:
--------------------------------------------------------------------------------
1 | /**
2 | * original data is by @Jam3
3 | * @see https://github.com/Jam3/ios-video-test
4 | */
5 |
6 |
7 | var extname = require('path').extname
8 |
9 | var mimeTypes = require('./mime-types.json')
10 | var mimeLookup = {}
11 | Object.keys(mimeTypes).forEach(function (key) {
12 | var extensions = mimeTypes[key]
13 | extensions.forEach(function (ext) {
14 | mimeLookup[ext] = key
15 | })
16 | })
17 |
18 | module.exports = createSourceElement
19 | function createSourceElement (src, type) {
20 | var source = document.createElement('source')
21 | source.src = src
22 | if (!type) type = lookup(src)
23 | source.type = type
24 | return source
25 | }
26 |
27 | function lookup (src) {
28 | var ext = extname(src)
29 | if (ext.indexOf('.') === 0) {
30 | ext = mimeLookup[ext.substring(1).toLowerCase()]
31 | }
32 | if (!ext) {
33 | throw new TypeError('could not determine mime-type from source: ' + src)
34 | }
35 | return ext
36 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Mayo Tobita
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | A-Frame Video Shader
6 |
7 |
28 |
29 |
30 | A-Frame Video Shader
31 | Basic
32 |
33 |
38 |
39 |
--------------------------------------------------------------------------------
/examples/basic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | A-Frame Video Shader - Basic
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aframe-video-shader",
3 | "version": "0.1.8",
4 | "description": "A shader to render DOM Element for A-Frame VR.",
5 | "main": "dist/aframe-vid-shader.js",
6 | "scripts": {
7 | "build": "webpack -p examples/main.js examples/build.js",
8 | "dev": "budo examples/main.js:build.js --dir examples --port 8000 --live -- -t babelify",
9 | "dist": "webpack index.js dist/aframe-vid-shader.js && webpack -p index.js dist/aframe-vid-shader.min.js",
10 | "postpublish": "npm run dist",
11 | "preghpages": "npm run build && rm -rf gh-pages && cp -r examples gh-pages",
12 | "ghpages": "npm run preghpages && ghpages -p gh-pages"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/mayognaise/aframe-video-shader.git"
17 | },
18 | "keywords": [
19 | "aframe",
20 | "aframe-shader",
21 | "aframe-vr",
22 | "vr",
23 | "aframe-layout",
24 | "mozvr",
25 | "webvr",
26 | "video",
27 | "canvas",
28 | "shader",
29 | "material"
30 | ],
31 | "author": "Mayo Tobita ",
32 | "license": "MIT",
33 | "bugs": {
34 | "url": "https://github.com/mayognaise/aframe-video-shader/issues"
35 | },
36 | "homepage": "https://github.com/mayognaise/aframe-video-shader#readme",
37 | "devDependencies": {
38 | "aframe": "^0.2.0",
39 | "babel-core": "^6.7.6",
40 | "babel-loader": "^6.2.4",
41 | "babel-preset-es2015": "^6.6.0",
42 | "babelify": "^7.2.0",
43 | "browserify": "^13.0.0",
44 | "budo": "^8.2.1",
45 | "ghpages": "0.0.3",
46 | "json-loader": "^0.5.4",
47 | "run-parallel": "^1.1.6",
48 | "webpack": "^1.12.15"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/media-element.js:
--------------------------------------------------------------------------------
1 | /**
2 | * original data is by @Jam3
3 | * @see https://github.com/Jam3/ios-video-test
4 | */
5 |
6 | function noop () {}
7 |
8 | var createSource = require('./source-element')
9 |
10 | module.exports.video = simpleMediaElement.bind(null, 'video')
11 | module.exports.audio = simpleMediaElement.bind(null, 'audio')
12 |
13 | function simpleMediaElement (elementName, sources, opt, cb) {
14 | if (typeof opt === 'function') {
15 | cb = opt
16 | opt = {}
17 | }
18 | cb = cb || noop
19 | opt = opt || {}
20 |
21 | if (!Array.isArray(sources)) {
22 | sources = [ sources ]
23 | }
24 |
25 | var media = opt.element || document.createElement(elementName)
26 |
27 | if (opt.loop) media.setAttribute('loop', true)
28 | if (opt.muted) media.setAttribute('muted', 'muted')
29 | if (opt.autoplay) media.setAttribute('autoplay', 'autoplay')
30 | if (opt.preload) media.setAttribute('preload', opt.preload)
31 | if (typeof opt.volume !== 'undefined') media.setAttribute('volume', opt.volume)
32 | media.setAttribute('crossorigin', 'anonymous')
33 | media.setAttribute('webkit-playsinline', '')
34 |
35 | sources.forEach(function (source) {
36 | media.appendChild(createSource(source))
37 | })
38 |
39 | process.nextTick(start)
40 | return media
41 |
42 | function start () {
43 | media.addEventListener('canplay', function () {
44 | cb(null, media)
45 | cb = noop
46 | }, false)
47 | media.addEventListener('error', function (err) {
48 | cb(err)
49 | cb = noop
50 | }, false)
51 | media.addEventListener('readystatechange', checkReadyState, false)
52 | media.load()
53 | checkReadyState()
54 | }
55 |
56 | function checkReadyState () {
57 | if (media.readyState > media.HAVE_FUTURE_DATA) {
58 | cb(null, media)
59 | cb = noop
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/mime-types.json:
--------------------------------------------------------------------------------
1 | {
2 | "audio/adpcm": ["adp"],
3 | "audio/basic": ["au", "snd"],
4 | "audio/midi": ["mid", "midi", "kar", "rmi"],
5 | "audio/mp4": ["mp4a", "m4a"],
6 | "audio/mpeg": ["mpga", "mp2", "mp2a", "mp3", "m2a", "m3a"],
7 | "audio/ogg": ["oga", "ogg", "spx"],
8 | "audio/s3m": ["s3m"],
9 | "audio/silk": ["sil"],
10 | "audio/vnd.dece.audio": ["uva", "uvva"],
11 | "audio/vnd.digital-winds": ["eol"],
12 | "audio/vnd.dra": ["dra"],
13 | "audio/vnd.dts": ["dts"],
14 | "audio/vnd.dts.hd": ["dtshd"],
15 | "audio/vnd.lucent.voice": ["lvp"],
16 | "audio/vnd.ms-playready.media.pya": ["pya"],
17 | "audio/vnd.nuera.ecelp4800": ["ecelp4800"],
18 | "audio/vnd.nuera.ecelp7470": ["ecelp7470"],
19 | "audio/vnd.nuera.ecelp9600": ["ecelp9600"],
20 | "audio/vnd.rip": ["rip"],
21 | "audio/webm": ["weba"],
22 | "audio/x-aac": ["aac"],
23 | "audio/x-aiff": ["aif", "aiff", "aifc"],
24 | "audio/x-caf": ["caf"],
25 | "audio/x-flac": ["flac"],
26 | "audio/x-matroska": ["mka"],
27 | "audio/x-mpegurl": ["m3u"],
28 | "audio/x-ms-wax": ["wax"],
29 | "audio/x-ms-wma": ["wma"],
30 | "audio/x-pn-realaudio": ["ram", "ra"],
31 | "audio/x-pn-realaudio-plugin": ["rmp"],
32 | "audio/x-wav": ["wav"],
33 | "audio/xm": ["xm"],
34 | "video/3gpp": ["3gp"],
35 | "video/3gpp2": ["3g2"],
36 | "video/h261": ["h261"],
37 | "video/h263": ["h263"],
38 | "video/h264": ["h264"],
39 | "video/jpeg": ["jpgv"],
40 | "video/jpm": ["jpm", "jpgm"],
41 | "video/mj2": ["mj2", "mjp2"],
42 | "video/mp2t": ["ts"],
43 | "video/mp4": ["mp4", "mp4v", "mpg4"],
44 | "video/mpeg": ["mpeg", "mpg", "mpe", "m1v", "m2v"],
45 | "video/ogg": ["ogv"],
46 | "video/quicktime": ["qt", "mov"],
47 | "video/vnd.dece.hd": ["uvh", "uvvh"],
48 | "video/vnd.dece.mobile": ["uvm", "uvvm"],
49 | "video/vnd.dece.pd": ["uvp", "uvvp"],
50 | "video/vnd.dece.sd": ["uvs", "uvvs"],
51 | "video/vnd.dece.video": ["uvv", "uvvv"],
52 | "video/vnd.dvb.file": ["dvb"],
53 | "video/vnd.fvt": ["fvt"],
54 | "video/vnd.mpegurl": ["mxu", "m4u"],
55 | "video/vnd.ms-playready.media.pyv": ["pyv"],
56 | "video/vnd.uvvu.mp4": ["uvu", "uvvu"],
57 | "video/vnd.vivo": ["viv"],
58 | "video/webm": ["webm"],
59 | "video/x-f4v": ["f4v"],
60 | "video/x-fli": ["fli"],
61 | "video/x-flv": ["flv"],
62 | "video/x-m4v": ["m4v"],
63 | "video/x-matroska": ["mkv", "mk3d", "mks"],
64 | "video/x-mng": ["mng"],
65 | "video/x-ms-asf": ["asf", "asx"],
66 | "video/x-ms-vob": ["vob"],
67 | "video/x-ms-wm": ["wm"],
68 | "video/x-ms-wmv": ["wmv"],
69 | "video/x-ms-wmx": ["wmx"],
70 | "video/x-ms-wvx": ["wvx"],
71 | "video/x-msvideo": ["avi"],
72 | "video/x-sgi-movie": ["movie"],
73 | "video/x-smv": ["smv"]
74 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AFrame Video Shader
2 |
3 | **iOS 10 supports inline video so do not use this and just wait for it 😆**
4 |
5 | A shader to display video for [A-Frame](https://aframe.io) VR. Inspired by [@Jam3](https://github.com/Jam3)'s [ios-video-test](https://github.com/Jam3/ios-video-test)
6 |
7 |
8 | **[DEMO](https://mayognaise.github.io/aframe-video-shader/basic/index.html)**
9 |
10 | 
11 |
12 | ## Notes
13 |
14 | - **This was made for inline video playback for iPhone. If you only support desktop/android, please use [`flat`](https://aframe.io/docs/core/shaders.html#Flat-Shading-Model) instead for better performance.**
15 |
16 |
17 | ## Limitation
18 |
19 | - **Currently only videos under SAME DOMAIN can be played with any browsers on iOS devices and desktop Safari.**
20 | - **Large/long video mostly gets error. More about limitation, please see [here](https://github.com/Jam3/ios-video-test#limitations)**
21 |
22 |
23 |
24 |
25 | ## Properties
26 |
27 | - Basic material's properties are supported.
28 | - The property is pretty much same as `flat` shader besides `repeat`. Will update it soon.
29 | - `autoplay` will be useful when [Method](#method) is ready.
30 | - **`muted` is currently always true.** Will be supported soon.
31 | - **`loop` is currently always true.** Will be supported soon.
32 | - `filter` property will be supported soon.
33 | - `pause` controls the playback.
34 |
35 | | Property | Description | Default Value |
36 | | -------- | ----------- | ------------- |
37 | | src | image url. @see [Textures](https://aframe.io/docs/components/material.html#Textures)| null |
38 | | autoplay | play automatecally once it's ready| true |
39 | | preload | preload video (this works for only desktop)| true |
40 | | muted | mute or unmute| true (**currently always true.**) |
41 | | loop | loop video| true (**currently always true.**) |
42 | | fps | video fps| 60 |
43 | | volume | video volume | undefined |
44 | | pause | video playback | false |
45 |
46 | For refference, please check the following links:
47 | - [Material](https://aframe.io/docs/components/material.html)
48 | - [Textures](https://aframe.io/docs/components/material.html#Textures)
49 | - [Flat Shading Model](https://aframe.io/docs/core/shaders.html#Flat-Shading-Model)
50 |
51 |
52 | [MediaElement properties](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#Properties) will be supported soon.
53 |
54 |
55 | ## Method
56 |
57 | [MediaElement methods](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#Methods) will be supported soon.
58 |
59 |
60 | ## Events
61 |
62 |
63 | [Media events](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events) will be supported soon.
64 |
65 |
66 | ## Usage
67 |
68 | ### Browser Installation
69 |
70 | Install and use by directly including the [browser files](dist):
71 |
72 | ```html
73 |
74 | My A-Frame Scene
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | ```
86 |
87 | ### NPM Installation
88 |
89 | Install via NPM:
90 |
91 | ```bash
92 | npm i -D aframe-video-shader
93 | ```
94 |
95 | Then register and use.
96 |
97 | ```js
98 | import 'aframe'
99 | import 'aframe-video-shader'
100 | ```
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/lib/inline-video.js:
--------------------------------------------------------------------------------
1 | /**
2 | * original data is by @Jam3
3 | * @see https://github.com/Jam3/ios-video-test
4 | */
5 |
6 | import parallel from 'run-parallel'
7 | import media from './media-element'
8 |
9 | /* get util from AFRAME */
10 | const { debug } = AFRAME.utils
11 | // debug.enable('shader:video:*')
12 | debug.enable('shader:video:warn')
13 | const warn = debug('shader:video:warn')
14 | const log = debug('shader:video:debug')
15 |
16 | const fallback = /i(Pad|Phone)/i.test(navigator.userAgent)
17 | // const fallback = true
18 |
19 | const noop = function () {}
20 |
21 | exports.inlineVideo = (source, opt, cb) => {
22 |
23 | const { autoplay, preload, muted, loop, fps, canvas, context, render, element } = opt
24 | const video = element || document.createElement('video')
25 | let lastTime = Date.now()
26 | let elapsed = 0
27 | let duration = Infinity
28 | let audio
29 |
30 | if (fallback) {
31 |
32 | if (!opt.muted) {
33 | audio = document.createElement('audio')
34 | /* disable autoplay for this scenario */
35 | opt.autoplay = false
36 | }
37 |
38 | /* load audio and muted video */
39 | parallel([
40 | next => {
41 | media.video(source, Object.assign({}, opt, {
42 | muted: true,
43 | element: video
44 | }), next)
45 | },
46 | next => {
47 | media.audio(source, Object.assign({}, opt, {
48 | element: audio
49 | }), next)
50 | }
51 | ], ready)
52 | } else {
53 | media.video(source, Object.assign({}, opt, {
54 | element: video
55 | }), ready)
56 | }
57 |
58 | /*=============================
59 | = ready =
60 | =============================*/
61 |
62 |
63 | function ready (err) {
64 | if (err) {
65 | warn(err)
66 | cb('Somehow there is error during loading video')
67 | return
68 | }
69 | canvas.width = THREE.Math.nearestPowerOfTwo(video.videoWidth)
70 | canvas.height = THREE.Math.nearestPowerOfTwo(video.videoHeight)
71 |
72 | if (fallback) {
73 | video.addEventListener('timeupdate', drawFrame, false)
74 | if (audio) {
75 | audio.addEventListener('ended', function () {
76 | if (loop) {
77 | audio.currentTime = 0
78 | } else {
79 | /**
80 | TODO:
81 | - add stop logic
82 | */
83 |
84 | }
85 | }, false)
86 | }
87 | }
88 |
89 | duration = video.duration
90 |
91 | const canvasVideo = {
92 | play: play,
93 | pause: pause,
94 | tick: tick,
95 | canvas: canvas,
96 | video: video,
97 | audio: audio,
98 | fallback: fallback,
99 | }
100 |
101 | cb(null, canvasVideo)
102 |
103 |
104 | }
105 |
106 | /*================================
107 | = playback =
108 | ================================*/
109 |
110 | function play () {
111 | lastTime = Date.now()
112 | if (audio) audio.play()
113 | if (!fallback) video.play()
114 | }
115 |
116 | function pause () {
117 | if (audio) audio.pause()
118 | if (!fallback) video.pause()
119 | }
120 |
121 | function tick () {
122 | /* render immediately in desktop */
123 | if (!fallback) {
124 | return drawFrame()
125 | }
126 |
127 | /*
128 | * in iPhone, we render based on audio (if it exists)
129 | * otherwise we step forward by a target FPS
130 | */
131 | const time = Date.now()
132 | elapsed = (time - lastTime) / 1000
133 | if (elapsed >= ((1000 / fps) / 1000)) {
134 | if (fallback) { /* seek and wait for timeupdate */
135 | if (audio) {
136 | video.currentTime = audio.currentTime
137 | } else {
138 | video.currentTime = video.currentTime + elapsed
139 | }
140 | }
141 | lastTime = time
142 | }
143 |
144 | /**
145 | * in iPhone, when audio is not present we need
146 | * to track duration
147 | */
148 |
149 | if (fallback && !audio) {
150 | if (Math.abs(video.currentTime - duration) < 0.1) {
151 | /* whether to restart or just stop the raf loop */
152 | if (loop) {
153 | video.currentTime = 0
154 | } else {
155 | /**
156 | TODO:
157 | - add stop logic
158 | */
159 | }
160 | }
161 | }
162 | }
163 |
164 | /*============================
165 | = draw =
166 | ============================*/
167 |
168 | function drawFrame () {
169 | render(video)
170 | }
171 |
172 | }
--------------------------------------------------------------------------------
/dist/aframe-vid-shader.min.js:
--------------------------------------------------------------------------------
1 | !function(e){function t(n){if(i[n])return i[n].exports;var a=i[n]={exports:{},id:n,loaded:!1};return e[n].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t,i){"use strict";function n(e,t){return{status:"error",src:t,message:e,timestamp:Date.now()}}var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},r=i(2);if("undefined"==typeof AFRAME)throw"Component attempted to register before AFRAME was available.";var o=AFRAME.utils.srcLoader.parseUrl,u=AFRAME.utils.debug;u.enable("shader:video:warn");var s=u("shader:video:warn"),d=u("shader:video:debug"),c={};AFRAME.registerShader("video",{schema:{color:{type:"color"},fog:{"default":!0},src:{"default":null},autoplay:{"default":!0},preload:{"default":!0},muted:{"default":!0},loop:{"default":!0},fps:{"default":60},volume:{"default":void 0},pause:{"default":!1}},init:function(e){return d("init",e),this.__cnv=document.createElement("canvas"),this.__cnv.width=2,this.__cnv.height=2,this.__ctx=this.__cnv.getContext("2d"),this.__texture=new THREE.Texture(this.__cnv),this.__reset(),this.material=new THREE.MeshBasicMaterial({map:this.__texture}),this.el.sceneEl.addBehavior(this),this.material},update:function(e){return d("update",e),this.__updateMaterial(e),this.__updateTexture(e),this.material},tick:function(e){d("tick"),!this.__paused&&this.__canvasVideo&&this.__canvasVideo.tick()},__updateMaterial:function(e){var t=this.material,i=this.__getMaterialData(e);Object.keys(i).forEach(function(e){t[e]=i[e]})},__getMaterialData:function(e){return{fog:e.fog,color:new THREE.Color(e.color)}},__setTexure:function(e){d("__setTexure",e),"error"===e.status?(s("Error: "+e.message+"\nsrc: "+e.src),this.__reset()):"success"===e.status&&e.src!==this.__textureSrc&&(this.__reset(),this.__ready(e))},__updateTexture:function(e){var t=e.src,i=e.autoplay,n=(e.muted,e.volume,e.loop,e.fps),a=e.pause;"boolean"==typeof i?this.__autoplay=i:"undefined"==typeof i&&(this.__autoplay=this.schema.autoplay["default"]),this.__autoplay&&this.__frames&&this.play(),"boolean"==typeof preload?this.__preload=preload:"undefined"==typeof preload&&(this.__preload=this.schema.preload["default"]),a?this.pause():this.play(),this.__muted=!0,this.__volume=void 0,this.__loop=!0,this.__fps=n||this.schema.fps["default"],t?this.__validateSrc(t,this.__setTexure.bind(this)):this.__reset()},__validateSrc:function(e,t){var i=o(e);if(i)return void this.__getVideoSrc(i,t);var r=void 0,u=this.__validateAndGetQuerySelector(e);if(u&&"object"===("undefined"==typeof u?"undefined":a(u))){if(u.error)r=u.error;else{var s=u.tagName.toLowerCase();"video"===s?(e=u.src,this.__getVideoSrc(e,t,u)):r="img"===s?"For <"+s+"> element, please use `shader:flat`":"For <"+s+"> element, please use `aframe-html-shader`"}r&&!function(){var i=c[e],a=n(r,e);i&&i.callbacks?i.callbacks.forEach(function(e){return e(a)}):t(a),c[e]=a}()}},__getVideoSrc:function(e,t,i){if(e!==this.__textureSrc){var o=c[e];if(o&&o.callbacks){if(o.src)return void t(o);if(o.callbacks)return void o.callbacks.push(t)}else o=c[e]={callbacks:[]},o.callbacks.push(t);var u={autoplay:this.__autoplay,preload:this.__preload,muted:this.__muted,volume:this.__volume,loop:this.__loop,fps:this.__fps,canvas:this.__cnv,context:this.__ctx,render:this.__render.bind(this),element:i};(0,r.inlineVideo)(e,u,function(t,i){if(t){var r=function(){var i=n(t,e);return o.callbacks&&(o.callbacks.forEach(function(e){return e(i)}),c[e]=i),{v:void 0}}();if("object"===("undefined"==typeof r?"undefined":a(r)))return r.v}var u={status:"success",src:e,canvasVideo:i,timestamp:Date.now()};o.callbacks&&(o.callbacks.forEach(function(e){return e(u)}),c[e]=u)})}},__validateAndGetQuerySelector:function(e){try{var t=document.querySelector(e);return t?t:{error:"No element was found matching the selector"}}catch(i){return{error:"no valid selector"}}},__addPublicFunctions:function(){this.el.shader={play:this.play.bind(this),pause:this.pause.bind(this),togglePlayback:this.togglePlayback.bind(this),paused:this.paused.bind(this),nextFrame:this.nextFrame.bind(this)}},pause:function(){d("pause"),this.__paused=!0,this.__canvasVideo&&this.__canvasVideo.pause()},play:function(){d("play"),this.__paused=!1,this.__canvasVideo&&this.__canvasVideo.play()},togglePlayback:function(){this.paused()?this.play():this.pause()},paused:function(){return this.__paused},__clearCanvas:function(){this.__ctx.clearRect(0,0,this.__width,this.__height),this.__texture.needsUpdate=!0},__draw:function(e){this.__ctx.drawImage(e,0,0,this.__width,this.__height),this.__texture.needsUpdate=!0},__render:function(e){this.__clearCanvas(),this.__draw(e)},__ready:function(e){var t=e.src,i=e.canvasVideo;d("__ready"),this.__textureSrc=t,this.__width=this.__cnv.width,this.__height=this.__cnv.height,this.__canvasVideo=i,this.__autoplay?this.play():this.pause()},__reset:function(){this.pause(),this.__clearCanvas(),this.__textureSrc=null}})},function(e,t){function i(){d=!1,o.length?s=o.concat(s):c=-1,s.length&&n()}function n(){if(!d){var e=setTimeout(i);d=!0;for(var t=s.length;t;){for(o=s,s=[];++c1)for(var i=1;i=1e3/f/1e3&&(c&&(x?_.currentTime=x.currentTime:_.currentTime=_.currentTime+y),g=e),c&&!x&&Math.abs(_.currentTime-b)<.1&&v&&(_.currentTime=0)}function l(){p(_)}var v=(t.autoplay,t.preload,t.muted,t.loop),f=t.fps,h=t.canvas,p=(t.context,t.render),m=t.element,_=m||document.createElement("video"),g=Date.now(),y=0,b=1/0,x=void 0;c?(t.muted||(x=document.createElement("audio"),t.autoplay=!1),(0,r["default"])([function(i){u["default"].video(e,Object.assign({},t,{muted:!0,element:_}),i)},function(i){u["default"].audio(e,Object.assign({},t,{element:x}),i)}],n)):u["default"].video(e,Object.assign({},t,{element:_}),n)}},function(e,t,i){(function(t){"use strict";function n(){}function a(e,i,a,o){function u(){d.addEventListener("canplay",function(){o(null,d),o=n},!1),d.addEventListener("error",function(e){o(e),o=n},!1),d.addEventListener("readystatechange",s,!1),d.load(),s()}function s(){d.readyState>d.HAVE_FUTURE_DATA&&(o(null,d),o=n)}"function"==typeof a&&(o=a,a={}),o=o||n,a=a||{},Array.isArray(i)||(i=[i]);var d=a.element||document.createElement(e);return a.loop&&d.setAttribute("loop",!0),a.muted&&d.setAttribute("muted","muted"),a.autoplay&&d.setAttribute("autoplay","autoplay"),a.preload&&d.setAttribute("preload",a.preload),"undefined"!=typeof a.volume&&d.setAttribute("volume",a.volume),d.setAttribute("crossorigin","anonymous"),d.setAttribute("webkit-playsinline",""),i.forEach(function(e){d.appendChild(r(e))}),t.nextTick(u),d}var r=i(4);e.exports.video=a.bind(null,"video"),e.exports.audio=a.bind(null,"audio")}).call(t,i(1))},function(e,t,i){"use strict";function n(e,t){var i=document.createElement("source");return i.src=e,t||(t=a(e)),i.type=t,i}function a(e){var t=r(e);if(0===t.indexOf(".")&&(t=u[t.substring(1).toLowerCase()]),!t)throw new TypeError("could not determine mime-type from source: "+e);return t}var r=i(7).extname,o=i(5),u={};Object.keys(o).forEach(function(e){var t=o[e];t.forEach(function(t){u[t]=e})}),e.exports=n},function(e,t){e.exports={"audio/adpcm":["adp"],"audio/basic":["au","snd"],"audio/midi":["mid","midi","kar","rmi"],"audio/mp4":["mp4a","m4a"],"audio/mpeg":["mpga","mp2","mp2a","mp3","m2a","m3a"],"audio/ogg":["oga","ogg","spx"],"audio/s3m":["s3m"],"audio/silk":["sil"],"audio/vnd.dece.audio":["uva","uvva"],"audio/vnd.digital-winds":["eol"],"audio/vnd.dra":["dra"],"audio/vnd.dts":["dts"],"audio/vnd.dts.hd":["dtshd"],"audio/vnd.lucent.voice":["lvp"],"audio/vnd.ms-playready.media.pya":["pya"],"audio/vnd.nuera.ecelp4800":["ecelp4800"],"audio/vnd.nuera.ecelp7470":["ecelp7470"],"audio/vnd.nuera.ecelp9600":["ecelp9600"],"audio/vnd.rip":["rip"],"audio/webm":["weba"],"audio/x-aac":["aac"],"audio/x-aiff":["aif","aiff","aifc"],"audio/x-caf":["caf"],"audio/x-flac":["flac"],"audio/x-matroska":["mka"],"audio/x-mpegurl":["m3u"],"audio/x-ms-wax":["wax"],"audio/x-ms-wma":["wma"],"audio/x-pn-realaudio":["ram","ra"],"audio/x-pn-realaudio-plugin":["rmp"],"audio/x-wav":["wav"],"audio/xm":["xm"],"video/3gpp":["3gp"],"video/3gpp2":["3g2"],"video/h261":["h261"],"video/h263":["h263"],"video/h264":["h264"],"video/jpeg":["jpgv"],"video/jpm":["jpm","jpgm"],"video/mj2":["mj2","mjp2"],"video/mp2t":["ts"],"video/mp4":["mp4","mp4v","mpg4"],"video/mpeg":["mpeg","mpg","mpe","m1v","m2v"],"video/ogg":["ogv"],"video/quicktime":["qt","mov"],"video/vnd.dece.hd":["uvh","uvvh"],"video/vnd.dece.mobile":["uvm","uvvm"],"video/vnd.dece.pd":["uvp","uvvp"],"video/vnd.dece.sd":["uvs","uvvs"],"video/vnd.dece.video":["uvv","uvvv"],"video/vnd.dvb.file":["dvb"],"video/vnd.fvt":["fvt"],"video/vnd.mpegurl":["mxu","m4u"],"video/vnd.ms-playready.media.pyv":["pyv"],"video/vnd.uvvu.mp4":["uvu","uvvu"],"video/vnd.vivo":["viv"],"video/webm":["webm"],"video/x-f4v":["f4v"],"video/x-fli":["fli"],"video/x-flv":["flv"],"video/x-m4v":["m4v"],"video/x-matroska":["mkv","mk3d","mks"],"video/x-mng":["mng"],"video/x-ms-asf":["asf","asx"],"video/x-ms-vob":["vob"],"video/x-ms-wm":["wm"],"video/x-ms-wmv":["wmv"],"video/x-ms-wmx":["wmx"],"video/x-ms-wvx":["wvx"],"video/x-msvideo":["avi"],"video/x-sgi-movie":["movie"],"video/x-smv":["smv"]}},function(e,t,i){(function(t){e.exports=function(e,i){function n(e){function n(){i&&i(e,r),i=null}s?t.nextTick(n):n()}function a(e,t,i){r[e]=i,(0===--o||t)&&n(t)}var r,o,u,s=!0;Array.isArray(e)?(r=[],o=e.length):(u=Object.keys(e),r={},o=u.length),o?u?u.forEach(function(t){e[t](function(e,i){a(t,e,i)})}):e.forEach(function(e,t){e(function(e,i){a(t,e,i)})}):n(null),s=!1}}).call(t,i(1))},function(e,t,i){(function(e){function i(e,t){for(var i=0,n=e.length-1;n>=0;n--){var a=e[n];"."===a?e.splice(n,1):".."===a?(e.splice(n,1),i++):i&&(e.splice(n,1),i--)}if(t)for(;i--;i)e.unshift("..");return e}function n(e,t){if(e.filter)return e.filter(t);for(var i=[],n=0;n=-1&&!a;r--){var o=r>=0?arguments[r]:e.cwd();if("string"!=typeof o)throw new TypeError("Arguments to path.resolve must be strings");o&&(t=o+"/"+t,a="/"===o.charAt(0))}return t=i(n(t.split("/"),function(e){return!!e}),!a).join("/"),(a?"/":"")+t||"."},t.normalize=function(e){var a=t.isAbsolute(e),r="/"===o(e,-1);return e=i(n(e.split("/"),function(e){return!!e}),!a).join("/"),e||a||(e="."),e&&r&&(e+="/"),(a?"/":"")+e},t.isAbsolute=function(e){return"/"===e.charAt(0)},t.join=function(){var e=Array.prototype.slice.call(arguments,0);return t.normalize(n(e,function(e,t){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e}).join("/"))},t.relative=function(e,i){function n(e){for(var t=0;t=0&&""===e[i];i--);return t>i?[]:e.slice(t,i-t+1)}e=t.resolve(e).substr(1),i=t.resolve(i).substr(1);for(var a=n(e.split("/")),r=n(i.split("/")),o=Math.min(a.length,r.length),u=o,s=0;o>s;s++)if(a[s]!==r[s]){u=s;break}for(var d=[],s=u;st&&(t=e.length+t),e.substr(t,i)}}).call(t,i(1))}]);
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import { inlineVideo } from './lib/inline-video'
2 |
3 | if (typeof AFRAME === 'undefined') {
4 | throw 'Component attempted to register before AFRAME was available.'
5 | }
6 |
7 | /* get util from AFRAME */
8 | const { parseUrl } = AFRAME.utils.srcLoader
9 | const { debug } = AFRAME.utils
10 | // debug.enable('shader:video:*')
11 | debug.enable('shader:video:warn')
12 | const warn = debug('shader:video:warn')
13 | const log = debug('shader:video:debug')
14 |
15 | /* store data so that you won't load same data */
16 | const videoData = {}
17 |
18 | /* create error message */
19 | function createError (err, src) {
20 | return { status: 'error', src: src, message: err, timestamp: Date.now() }
21 | }
22 |
23 | AFRAME.registerShader('video', {
24 |
25 | /**
26 | * For material component:
27 | * @see https://github.com/aframevr/aframe/blob/60d198ef8e2bfbc57a13511ae5fca7b62e01691b/src/components/material.js
28 | * For example of `registerShader`:
29 | * @see https://github.com/aframevr/aframe/blob/41a50cd5ac65e462120ecc2e5091f5daefe3bd1e/src/shaders/flat.js
30 | * For MeshBasicMaterial
31 | * @see http://threejs.org/docs/#Reference/Materials/MeshBasicMaterial
32 | */
33 |
34 | schema: {
35 |
36 | /* For material */
37 | color: { type: 'color' },
38 | fog: { default: true },
39 |
40 | /* For texuture */
41 | src: { default: null },
42 | autoplay: { default: true },
43 | preload: { default: true },
44 | muted: { default: true },
45 | loop: { default: true },
46 | fps: { default: 60 },
47 | volume: { default: undefined },
48 | pause: { default: false },
49 |
50 | },
51 |
52 | /**
53 | * Initialize material. Called once.
54 | * @protected
55 | */
56 | init (data) {
57 |
58 | log('init', data)
59 | this.__cnv = document.createElement('canvas')
60 | this.__cnv.width = 2
61 | this.__cnv.height = 2
62 | this.__ctx = this.__cnv.getContext('2d')
63 | this.__texture = new THREE.Texture(this.__cnv)
64 | this.__reset()
65 | this.material = new THREE.MeshBasicMaterial({ map: this.__texture })
66 | this.el.sceneEl.addBehavior(this)
67 | // this.__addPublicFunctions() /* [TODO] ask how to get shader from `a-entity` element */
68 | return this.material
69 | },
70 |
71 | /**
72 | * Update or create material.
73 | * @param {object|null} oldData
74 | */
75 | update (oldData) {
76 |
77 | /* if desktop, change shader with `flat` */
78 | // if (!AFRAME.utils.isMobile()) {
79 | // const material = Object.assign({}, this.el.getAttribute('material'), { shader: 'flat' })
80 | // this.el.removeAttribute('material')
81 | // setTimeout(() => {
82 | // this.el.setAttribute('material', material)
83 | // }, 0)
84 | // return
85 | // }
86 |
87 | log('update', oldData)
88 | this.__updateMaterial(oldData)
89 | this.__updateTexture(oldData)
90 | return this.material
91 | },
92 |
93 | /**
94 | * Called on each scene tick.
95 | * @protected
96 | */
97 | tick (t) {
98 |
99 | log('tick')
100 |
101 | if (this.__paused || !this.__canvasVideo) { return }
102 | this.__canvasVideo.tick()
103 |
104 | },
105 |
106 | /*================================
107 | = material =
108 | ================================*/
109 |
110 | /**
111 | * Updating existing material.
112 | * @param {object} data - Material component data.
113 | */
114 | __updateMaterial (data) {
115 | const { material } = this
116 | const newData = this.__getMaterialData(data)
117 | Object.keys(newData).forEach(key => {
118 | material[key] = newData[key]
119 | })
120 | },
121 |
122 |
123 | /**
124 | * Builds and normalize material data, normalizing stuff along the way.
125 | * @param {Object} data - Material data.
126 | * @return {Object} data - Processed material data.
127 | */
128 | __getMaterialData (data) {
129 | return {
130 | fog: data.fog,
131 | color: new THREE.Color(data.color),
132 | }
133 | },
134 |
135 |
136 | /*==============================
137 | = texure =
138 | ==============================*/
139 |
140 | /**
141 | * set texure
142 | * @private
143 | * @param {Object} data
144 | * @property {string} status - success / error
145 | * @property {string} src - src url
146 | * @property {Object} canvasVideo - response from inline-video.js
147 | * @property {Date} timestamp - created at the texure
148 | */
149 |
150 | __setTexure (data) {
151 | log('__setTexure', data)
152 | if (data.status === 'error') {
153 | warn(`Error: ${data.message}\nsrc: ${data.src}`)
154 | this.__reset()
155 | }
156 | else if (data.status === 'success' && data.src !== this.__textureSrc) {
157 | this.__reset()
158 | /* Texture added or changed */
159 | this.__ready(data)
160 | }
161 | },
162 |
163 | /**
164 | * Update or create texure.
165 | * @param {Object} data - Material component data.
166 | */
167 | __updateTexture (data) {
168 | const { src, autoplay, muted, volume, loop, fps, pause } = data
169 |
170 | /* autoplay */
171 | if (typeof autoplay === 'boolean') { this.__autoplay = autoplay }
172 | else if (typeof autoplay === 'undefined') { this.__autoplay = this.schema.autoplay.default }
173 | if (this.__autoplay && this.__frames) { this.play() }
174 |
175 | /* preload */
176 | if (typeof preload === 'boolean') { this.__preload = preload }
177 | else if (typeof preload === 'undefined') { this.__preload = this.schema.preload.default }
178 |
179 | /* pause */
180 | if (pause) {
181 | this.pause()
182 | }
183 | else {
184 | this.play()
185 | }
186 |
187 | /* muted */
188 | this.__muted = true
189 | // if (typeof muted === 'boolean') { this.__muted = muted }
190 | // else if (typeof muted === 'undefined') { this.__muted = this.schema.muted.default }
191 |
192 | /* volume */
193 | this.__volume = undefined
194 | // if (typeof volume === 'number') { this.__volume = volume }
195 | // else if (typeof volume === 'undefined') { this.__volume = 0 }
196 |
197 | /* loop */
198 | this.__loop = true
199 | // if (typeof loop === 'boolean') { this.__loop = loop }
200 | // else if (typeof loop === 'undefined') { this.__loop = this.schema.loop.default }
201 |
202 | /* fps */
203 | this.__fps = fps || this.schema.fps.default
204 |
205 | /* src */
206 | if (src) {
207 | this.__validateSrc(src, this.__setTexure.bind(this))
208 | } else {
209 | /* Texture removed */
210 | this.__reset()
211 | }
212 | },
213 |
214 | /*=============================================
215 | = varidation for texure =
216 | =============================================*/
217 |
218 | __validateSrc (src, cb) {
219 |
220 | /* check if src is a url */
221 | const url = parseUrl(src)
222 | if (url) {
223 | this.__getVideoSrc(url, cb)
224 | return
225 | }
226 |
227 | let message
228 |
229 | /* check if src is a query selector */
230 | const el = this.__validateAndGetQuerySelector(src)
231 | if (!el || typeof el !== 'object') { return }
232 | if (el.error) {
233 | message = el.error
234 | }
235 | else {
236 | const tagName = el.tagName.toLowerCase()
237 | if (tagName === 'video') {
238 | src = el.src
239 | this.__getVideoSrc(src, cb, el)
240 | }
241 | else if (tagName === 'img') {
242 | message = `For <${tagName}> element, please use \`shader:flat\``
243 | }
244 | else {
245 | message = `For <${tagName}> element, please use \`aframe-html-shader\``
246 | }
247 | }
248 |
249 | /* if there is message, create error data */
250 | if (message) {
251 | const srcData = videoData[src]
252 | const errData = createError(message, src)
253 | /* callbacks */
254 | if (srcData && srcData.callbacks) {
255 | srcData.callbacks.forEach(cb => cb(errData))
256 | }
257 | else {
258 | cb(errData)
259 | }
260 | /* overwrite */
261 | videoData[src] = errData
262 | }
263 |
264 | },
265 |
266 | /**
267 | * Validate src is a valid image url
268 | * @param {string} src - url that will be tested
269 | * @param {function} cb - callback with the test result
270 | * @param {VIDEO} el - video element
271 | */
272 | __getVideoSrc (src, cb, el) {
273 |
274 | /* if src is same as previous, ignore this */
275 | if (src === this.__textureSrc) { return }
276 |
277 | /* check if we already get the srcData */
278 | let srcData = videoData[src]
279 | if (!srcData || !srcData.callbacks) {
280 | /* create callback */
281 | srcData = videoData[src] = { callbacks: [] }
282 | srcData.callbacks.push(cb)
283 | }
284 | else if (srcData.src) {
285 | cb(srcData)
286 | return
287 | }
288 | else if (srcData.callbacks) {
289 | /* add callback */
290 | srcData.callbacks.push(cb)
291 | return
292 | }
293 |
294 | const options = {
295 | autoplay: this.__autoplay,
296 | preload: this.__preload,
297 | muted: this.__muted,
298 | volume: this.__volume,
299 | loop: this.__loop,
300 | fps: this.__fps,
301 | canvas: this.__cnv,
302 | context: this.__ctx,
303 | render: this.__render.bind(this),
304 | element: el,
305 | }
306 |
307 | inlineVideo(src, options, (err, canvasVideo) => {
308 |
309 | if (err) {
310 |
311 | /* create error data */
312 | const errData = createError(err, src)
313 | /* callbacks */
314 | if (srcData.callbacks) {
315 | srcData.callbacks.forEach(cb => cb(errData))
316 | /* overwrite */
317 | videoData[src] = errData
318 | }
319 | return
320 | }
321 |
322 | /* store data */
323 | const newData = { status: 'success', src: src, canvasVideo: canvasVideo, timestamp: Date.now() }
324 | /* callbacks */
325 | if (srcData.callbacks) {
326 | srcData.callbacks.forEach(cb => cb(newData))
327 | /* overwrite */
328 | videoData[src] = newData
329 | }
330 | })
331 |
332 | },
333 |
334 |
335 | /**
336 | * Query and validate a query selector,
337 | *
338 | * @param {string} selector - DOM selector.
339 | * @return {object} Selected DOM element | error message object.
340 | */
341 | __validateAndGetQuerySelector (selector) {
342 | try {
343 | var el = document.querySelector(selector)
344 | if (!el) {
345 | return { error: 'No element was found matching the selector' }
346 | }
347 | return el
348 | } catch (e) { // Capture exception if it's not a valid selector.
349 | return { error: 'no valid selector' }
350 | }
351 | },
352 |
353 |
354 | /*================================
355 | = playback =
356 | ================================*/
357 |
358 | /**
359 | * add public functions
360 | * @private
361 | */
362 | __addPublicFunctions () {
363 | this.el.shader = {
364 | play: this.play.bind(this),
365 | pause: this.pause.bind(this),
366 | togglePlayback: this.togglePlayback.bind(this),
367 | paused: this.paused.bind(this),
368 | nextFrame: this.nextFrame.bind(this),
369 | }
370 | },
371 |
372 | /**
373 | * Pause video
374 | * @public
375 | */
376 | pause () {
377 | log('pause')
378 | this.__paused = true
379 | this.__canvasVideo && this.__canvasVideo.pause()
380 | },
381 |
382 | /**
383 | * Play video
384 | * @public
385 | */
386 | play () {
387 | log('play')
388 | this.__paused = false
389 | this.__canvasVideo && this.__canvasVideo.play()
390 | },
391 |
392 | /**
393 | * Toggle playback. play if paused and pause if played.
394 | * @public
395 | */
396 |
397 | togglePlayback () {
398 | if (this.paused()) { this.play() }
399 | else { this.pause() }
400 |
401 | },
402 |
403 | /**
404 | * Return if the playback is paused.
405 | * @public
406 | * @return {boolean}
407 | */
408 | paused () {
409 | return this.__paused
410 | },
411 |
412 | /*==============================
413 | = canvas =
414 | ==============================*/
415 |
416 | /**
417 | * clear canvas
418 | * @private
419 | */
420 | __clearCanvas () {
421 | this.__ctx.clearRect(0, 0, this.__width, this.__height)
422 | this.__texture.needsUpdate = true
423 | },
424 |
425 | /**
426 | * draw
427 | * @private
428 | */
429 | __draw (video) {
430 | this.__ctx.drawImage(video, 0, 0, this.__width, this.__height)
431 | this.__texture.needsUpdate = true
432 | },
433 |
434 | /**
435 | * render
436 | * @private
437 | */
438 | __render (video) {
439 | this.__clearCanvas()
440 | this.__draw(video)
441 | },
442 |
443 |
444 | /*============================
445 | = ready =
446 | ============================*/
447 |
448 | /**
449 | * setup video animation and play if autoplay is true
450 | * @private
451 | * @property {string} src - src url
452 | * @property {Object} canvasVideo - response from inline-video.js
453 | */
454 | __ready ({ src, canvasVideo }) {
455 | log('__ready')
456 | this.__textureSrc = src
457 | this.__width = this.__cnv.width
458 | this.__height = this.__cnv.height
459 | this.__canvasVideo = canvasVideo
460 | if (this.__autoplay) {
461 | this.play()
462 | }
463 | else {
464 | this.pause()
465 | }
466 | },
467 |
468 |
469 |
470 | /*=============================
471 | = reset =
472 | =============================*/
473 |
474 | /**
475 | * @private
476 | */
477 |
478 | __reset () {
479 | this.pause()
480 | this.__clearCanvas()
481 | this.__textureSrc = null
482 | },
483 |
484 |
485 |
486 | })
487 |
488 |
--------------------------------------------------------------------------------
/dist/aframe-vid-shader.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 |
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 |
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 |
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 |
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 |
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 |
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 |
29 |
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 |
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 |
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "";
38 |
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ([
44 | /* 0 */
45 | /***/ function(module, exports, __webpack_require__) {
46 |
47 | 'use strict';
48 |
49 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
50 |
51 | var _inlineVideo = __webpack_require__(1);
52 |
53 | if (typeof AFRAME === 'undefined') {
54 | throw 'Component attempted to register before AFRAME was available.';
55 | }
56 |
57 | /* get util from AFRAME */
58 | var parseUrl = AFRAME.utils.srcLoader.parseUrl;
59 | var debug = AFRAME.utils.debug;
60 | // debug.enable('shader:video:*')
61 |
62 | debug.enable('shader:video:warn');
63 | var warn = debug('shader:video:warn');
64 | var log = debug('shader:video:debug');
65 |
66 | /* store data so that you won't load same data */
67 | var videoData = {};
68 |
69 | /* create error message */
70 | function createError(err, src) {
71 | return { status: 'error', src: src, message: err, timestamp: Date.now() };
72 | }
73 |
74 | AFRAME.registerShader('video', {
75 |
76 | /**
77 | * For material component:
78 | * @see https://github.com/aframevr/aframe/blob/60d198ef8e2bfbc57a13511ae5fca7b62e01691b/src/components/material.js
79 | * For example of `registerShader`:
80 | * @see https://github.com/aframevr/aframe/blob/41a50cd5ac65e462120ecc2e5091f5daefe3bd1e/src/shaders/flat.js
81 | * For MeshBasicMaterial
82 | * @see http://threejs.org/docs/#Reference/Materials/MeshBasicMaterial
83 | */
84 |
85 | schema: {
86 |
87 | /* For material */
88 | color: { type: 'color' },
89 | fog: { default: true },
90 |
91 | /* For texuture */
92 | src: { default: null },
93 | autoplay: { default: true },
94 | preload: { default: true },
95 | muted: { default: true },
96 | loop: { default: true },
97 | fps: { default: 60 },
98 | volume: { default: undefined },
99 | pause: { default: false }
100 |
101 | },
102 |
103 | /**
104 | * Initialize material. Called once.
105 | * @protected
106 | */
107 | init: function init(data) {
108 |
109 | log('init', data);
110 | this.__cnv = document.createElement('canvas');
111 | this.__cnv.width = 2;
112 | this.__cnv.height = 2;
113 | this.__ctx = this.__cnv.getContext('2d');
114 | this.__texture = new THREE.Texture(this.__cnv);
115 | this.__reset();
116 | this.material = new THREE.MeshBasicMaterial({ map: this.__texture });
117 | this.el.sceneEl.addBehavior(this);
118 | // this.__addPublicFunctions() /* [TODO] ask how to get shader from `a-entity` element */
119 | return this.material;
120 | },
121 |
122 |
123 | /**
124 | * Update or create material.
125 | * @param {object|null} oldData
126 | */
127 | update: function update(oldData) {
128 |
129 | /* if desktop, change shader with `flat` */
130 | // if (!AFRAME.utils.isMobile()) {
131 | // const material = Object.assign({}, this.el.getAttribute('material'), { shader: 'flat' })
132 | // this.el.removeAttribute('material')
133 | // setTimeout(() => {
134 | // this.el.setAttribute('material', material)
135 | // }, 0)
136 | // return
137 | // }
138 |
139 | log('update', oldData);
140 | this.__updateMaterial(oldData);
141 | this.__updateTexture(oldData);
142 | return this.material;
143 | },
144 |
145 |
146 | /**
147 | * Called on each scene tick.
148 | * @protected
149 | */
150 | tick: function tick(t) {
151 |
152 | log('tick');
153 |
154 | if (this.__paused || !this.__canvasVideo) {
155 | return;
156 | }
157 | this.__canvasVideo.tick();
158 | },
159 |
160 |
161 | /*================================
162 | = material =
163 | ================================*/
164 |
165 | /**
166 | * Updating existing material.
167 | * @param {object} data - Material component data.
168 | */
169 | __updateMaterial: function __updateMaterial(data) {
170 | var material = this.material;
171 |
172 | var newData = this.__getMaterialData(data);
173 | Object.keys(newData).forEach(function (key) {
174 | material[key] = newData[key];
175 | });
176 | },
177 |
178 |
179 | /**
180 | * Builds and normalize material data, normalizing stuff along the way.
181 | * @param {Object} data - Material data.
182 | * @return {Object} data - Processed material data.
183 | */
184 | __getMaterialData: function __getMaterialData(data) {
185 | return {
186 | fog: data.fog,
187 | color: new THREE.Color(data.color)
188 | };
189 | },
190 |
191 |
192 | /*==============================
193 | = texure =
194 | ==============================*/
195 |
196 | /**
197 | * set texure
198 | * @private
199 | * @param {Object} data
200 | * @property {string} status - success / error
201 | * @property {string} src - src url
202 | * @property {Object} canvasVideo - response from inline-video.js
203 | * @property {Date} timestamp - created at the texure
204 | */
205 |
206 | __setTexure: function __setTexure(data) {
207 | log('__setTexure', data);
208 | if (data.status === 'error') {
209 | warn('Error: ' + data.message + '\nsrc: ' + data.src);
210 | this.__reset();
211 | } else if (data.status === 'success' && data.src !== this.__textureSrc) {
212 | this.__reset();
213 | /* Texture added or changed */
214 | this.__ready(data);
215 | }
216 | },
217 |
218 |
219 | /**
220 | * Update or create texure.
221 | * @param {Object} data - Material component data.
222 | */
223 | __updateTexture: function __updateTexture(data) {
224 | var src = data.src;
225 | var autoplay = data.autoplay;
226 | var muted = data.muted;
227 | var volume = data.volume;
228 | var loop = data.loop;
229 | var fps = data.fps;
230 | var pause = data.pause;
231 |
232 | /* autoplay */
233 |
234 | if (typeof autoplay === 'boolean') {
235 | this.__autoplay = autoplay;
236 | } else if (typeof autoplay === 'undefined') {
237 | this.__autoplay = this.schema.autoplay.default;
238 | }
239 | if (this.__autoplay && this.__frames) {
240 | this.play();
241 | }
242 |
243 | /* preload */
244 | if (typeof preload === 'boolean') {
245 | this.__preload = preload;
246 | } else if (typeof preload === 'undefined') {
247 | this.__preload = this.schema.preload.default;
248 | }
249 |
250 | /* pause */
251 | if (pause) {
252 | this.pause();
253 | } else {
254 | this.play();
255 | }
256 |
257 | /* muted */
258 | this.__muted = true;
259 | // if (typeof muted === 'boolean') { this.__muted = muted }
260 | // else if (typeof muted === 'undefined') { this.__muted = this.schema.muted.default }
261 |
262 | /* volume */
263 | this.__volume = undefined;
264 | // if (typeof volume === 'number') { this.__volume = volume }
265 | // else if (typeof volume === 'undefined') { this.__volume = 0 }
266 |
267 | /* loop */
268 | this.__loop = true;
269 | // if (typeof loop === 'boolean') { this.__loop = loop }
270 | // else if (typeof loop === 'undefined') { this.__loop = this.schema.loop.default }
271 |
272 | /* fps */
273 | this.__fps = fps || this.schema.fps.default;
274 |
275 | /* src */
276 | if (src) {
277 | this.__validateSrc(src, this.__setTexure.bind(this));
278 | } else {
279 | /* Texture removed */
280 | this.__reset();
281 | }
282 | },
283 |
284 |
285 | /*=============================================
286 | = varidation for texure =
287 | =============================================*/
288 |
289 | __validateSrc: function __validateSrc(src, cb) {
290 |
291 | /* check if src is a url */
292 | var url = parseUrl(src);
293 | if (url) {
294 | this.__getVideoSrc(url, cb);
295 | return;
296 | }
297 |
298 | var message = void 0;
299 |
300 | /* check if src is a query selector */
301 | var el = this.__validateAndGetQuerySelector(src);
302 | if (!el || (typeof el === 'undefined' ? 'undefined' : _typeof(el)) !== 'object') {
303 | return;
304 | }
305 | if (el.error) {
306 | message = el.error;
307 | } else {
308 | var tagName = el.tagName.toLowerCase();
309 | if (tagName === 'video') {
310 | src = el.src;
311 | this.__getVideoSrc(src, cb, el);
312 | } else if (tagName === 'img') {
313 | message = 'For <' + tagName + '> element, please use `shader:flat`';
314 | } else {
315 | message = 'For <' + tagName + '> element, please use `aframe-html-shader`';
316 | }
317 | }
318 |
319 | /* if there is message, create error data */
320 | if (message) {
321 | (function () {
322 | var srcData = videoData[src];
323 | var errData = createError(message, src);
324 | /* callbacks */
325 | if (srcData && srcData.callbacks) {
326 | srcData.callbacks.forEach(function (cb) {
327 | return cb(errData);
328 | });
329 | } else {
330 | cb(errData);
331 | }
332 | /* overwrite */
333 | videoData[src] = errData;
334 | })();
335 | }
336 | },
337 |
338 |
339 | /**
340 | * Validate src is a valid image url
341 | * @param {string} src - url that will be tested
342 | * @param {function} cb - callback with the test result
343 | * @param {VIDEO} el - video element
344 | */
345 | __getVideoSrc: function __getVideoSrc(src, cb, el) {
346 |
347 | /* if src is same as previous, ignore this */
348 | if (src === this.__textureSrc) {
349 | return;
350 | }
351 |
352 | /* check if we already get the srcData */
353 | var srcData = videoData[src];
354 | if (!srcData || !srcData.callbacks) {
355 | /* create callback */
356 | srcData = videoData[src] = { callbacks: [] };
357 | srcData.callbacks.push(cb);
358 | } else if (srcData.src) {
359 | cb(srcData);
360 | return;
361 | } else if (srcData.callbacks) {
362 | /* add callback */
363 | srcData.callbacks.push(cb);
364 | return;
365 | }
366 |
367 | var options = {
368 | autoplay: this.__autoplay,
369 | preload: this.__preload,
370 | muted: this.__muted,
371 | volume: this.__volume,
372 | loop: this.__loop,
373 | fps: this.__fps,
374 | canvas: this.__cnv,
375 | context: this.__ctx,
376 | render: this.__render.bind(this),
377 | element: el
378 | };
379 |
380 | (0, _inlineVideo.inlineVideo)(src, options, function (err, canvasVideo) {
381 |
382 | if (err) {
383 | var _ret2 = function () {
384 |
385 | /* create error data */
386 | var errData = createError(err, src);
387 | /* callbacks */
388 | if (srcData.callbacks) {
389 | srcData.callbacks.forEach(function (cb) {
390 | return cb(errData);
391 | });
392 | /* overwrite */
393 | videoData[src] = errData;
394 | }
395 | return {
396 | v: void 0
397 | };
398 | }();
399 |
400 | if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v;
401 | }
402 |
403 | /* store data */
404 | var newData = { status: 'success', src: src, canvasVideo: canvasVideo, timestamp: Date.now() };
405 | /* callbacks */
406 | if (srcData.callbacks) {
407 | srcData.callbacks.forEach(function (cb) {
408 | return cb(newData);
409 | });
410 | /* overwrite */
411 | videoData[src] = newData;
412 | }
413 | });
414 | },
415 |
416 |
417 | /**
418 | * Query and validate a query selector,
419 | *
420 | * @param {string} selector - DOM selector.
421 | * @return {object} Selected DOM element | error message object.
422 | */
423 | __validateAndGetQuerySelector: function __validateAndGetQuerySelector(selector) {
424 | try {
425 | var el = document.querySelector(selector);
426 | if (!el) {
427 | return { error: 'No element was found matching the selector' };
428 | }
429 | return el;
430 | } catch (e) {
431 | // Capture exception if it's not a valid selector.
432 | return { error: 'no valid selector' };
433 | }
434 | },
435 |
436 |
437 | /*================================
438 | = playback =
439 | ================================*/
440 |
441 | /**
442 | * add public functions
443 | * @private
444 | */
445 | __addPublicFunctions: function __addPublicFunctions() {
446 | this.el.shader = {
447 | play: this.play.bind(this),
448 | pause: this.pause.bind(this),
449 | togglePlayback: this.togglePlayback.bind(this),
450 | paused: this.paused.bind(this),
451 | nextFrame: this.nextFrame.bind(this)
452 | };
453 | },
454 |
455 |
456 | /**
457 | * Pause video
458 | * @public
459 | */
460 | pause: function pause() {
461 | log('pause');
462 | this.__paused = true;
463 | this.__canvasVideo && this.__canvasVideo.pause();
464 | },
465 |
466 |
467 | /**
468 | * Play video
469 | * @public
470 | */
471 | play: function play() {
472 | log('play');
473 | this.__paused = false;
474 | this.__canvasVideo && this.__canvasVideo.play();
475 | },
476 |
477 |
478 | /**
479 | * Toggle playback. play if paused and pause if played.
480 | * @public
481 | */
482 |
483 | togglePlayback: function togglePlayback() {
484 | if (this.paused()) {
485 | this.play();
486 | } else {
487 | this.pause();
488 | }
489 | },
490 |
491 |
492 | /**
493 | * Return if the playback is paused.
494 | * @public
495 | * @return {boolean}
496 | */
497 | paused: function paused() {
498 | return this.__paused;
499 | },
500 |
501 |
502 | /*==============================
503 | = canvas =
504 | ==============================*/
505 |
506 | /**
507 | * clear canvas
508 | * @private
509 | */
510 | __clearCanvas: function __clearCanvas() {
511 | this.__ctx.clearRect(0, 0, this.__width, this.__height);
512 | this.__texture.needsUpdate = true;
513 | },
514 |
515 |
516 | /**
517 | * draw
518 | * @private
519 | */
520 | __draw: function __draw(video) {
521 | this.__ctx.drawImage(video, 0, 0, this.__width, this.__height);
522 | this.__texture.needsUpdate = true;
523 | },
524 |
525 |
526 | /**
527 | * render
528 | * @private
529 | */
530 | __render: function __render(video) {
531 | this.__clearCanvas();
532 | this.__draw(video);
533 | },
534 |
535 |
536 | /*============================
537 | = ready =
538 | ============================*/
539 |
540 | /**
541 | * setup video animation and play if autoplay is true
542 | * @private
543 | * @property {string} src - src url
544 | * @property {Object} canvasVideo - response from inline-video.js
545 | */
546 | __ready: function __ready(_ref) {
547 | var src = _ref.src;
548 | var canvasVideo = _ref.canvasVideo;
549 |
550 | log('__ready');
551 | this.__textureSrc = src;
552 | this.__width = this.__cnv.width;
553 | this.__height = this.__cnv.height;
554 | this.__canvasVideo = canvasVideo;
555 | if (this.__autoplay) {
556 | this.play();
557 | } else {
558 | this.pause();
559 | }
560 | },
561 |
562 |
563 | /*=============================
564 | = reset =
565 | =============================*/
566 |
567 | /**
568 | * @private
569 | */
570 |
571 | __reset: function __reset() {
572 | this.pause();
573 | this.__clearCanvas();
574 | this.__textureSrc = null;
575 | }
576 | });
577 |
578 | /***/ },
579 | /* 1 */
580 | /***/ function(module, exports, __webpack_require__) {
581 |
582 | 'use strict';
583 |
584 | var _runParallel = __webpack_require__(2);
585 |
586 | var _runParallel2 = _interopRequireDefault(_runParallel);
587 |
588 | var _mediaElement = __webpack_require__(4);
589 |
590 | var _mediaElement2 = _interopRequireDefault(_mediaElement);
591 |
592 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
593 |
594 | /* get util from AFRAME */
595 | /**
596 | * original data is by @Jam3
597 | * @see https://github.com/Jam3/ios-video-test
598 | */
599 |
600 | var debug = AFRAME.utils.debug;
601 | // debug.enable('shader:video:*')
602 |
603 | debug.enable('shader:video:warn');
604 | var warn = debug('shader:video:warn');
605 | var log = debug('shader:video:debug');
606 |
607 | var fallback = /i(Pad|Phone)/i.test(navigator.userAgent);
608 | // const fallback = true
609 |
610 | var noop = function noop() {};
611 |
612 | exports.inlineVideo = function (source, opt, cb) {
613 | var autoplay = opt.autoplay;
614 | var preload = opt.preload;
615 | var muted = opt.muted;
616 | var loop = opt.loop;
617 | var fps = opt.fps;
618 | var canvas = opt.canvas;
619 | var context = opt.context;
620 | var render = opt.render;
621 | var element = opt.element;
622 |
623 | var video = element || document.createElement('video');
624 | var lastTime = Date.now();
625 | var elapsed = 0;
626 | var duration = Infinity;
627 | var audio = void 0;
628 |
629 | if (fallback) {
630 |
631 | if (!opt.muted) {
632 | audio = document.createElement('audio');
633 | /* disable autoplay for this scenario */
634 | opt.autoplay = false;
635 | }
636 |
637 | /* load audio and muted video */
638 | (0, _runParallel2.default)([function (next) {
639 | _mediaElement2.default.video(source, Object.assign({}, opt, {
640 | muted: true,
641 | element: video
642 | }), next);
643 | }, function (next) {
644 | _mediaElement2.default.audio(source, Object.assign({}, opt, {
645 | element: audio
646 | }), next);
647 | }], ready);
648 | } else {
649 | _mediaElement2.default.video(source, Object.assign({}, opt, {
650 | element: video
651 | }), ready);
652 | }
653 |
654 | /*=============================
655 | = ready =
656 | =============================*/
657 |
658 | function ready(err) {
659 | if (err) {
660 | warn(err);
661 | cb('Somehow there is error during loading video');
662 | return;
663 | }
664 | canvas.width = THREE.Math.nearestPowerOfTwo(video.videoWidth);
665 | canvas.height = THREE.Math.nearestPowerOfTwo(video.videoHeight);
666 |
667 | if (fallback) {
668 | video.addEventListener('timeupdate', drawFrame, false);
669 | if (audio) {
670 | audio.addEventListener('ended', function () {
671 | if (loop) {
672 | audio.currentTime = 0;
673 | } else {
674 | /**
675 | TODO:
676 | - add stop logic
677 | */
678 |
679 | }
680 | }, false);
681 | }
682 | }
683 |
684 | duration = video.duration;
685 |
686 | var canvasVideo = {
687 | play: play,
688 | pause: pause,
689 | tick: tick,
690 | canvas: canvas,
691 | video: video,
692 | audio: audio,
693 | fallback: fallback
694 | };
695 |
696 | cb(null, canvasVideo);
697 | }
698 |
699 | /*================================
700 | = playback =
701 | ================================*/
702 |
703 | function play() {
704 | lastTime = Date.now();
705 | if (audio) audio.play();
706 | if (!fallback) video.play();
707 | }
708 |
709 | function pause() {
710 | if (audio) audio.pause();
711 | if (!fallback) video.pause();
712 | }
713 |
714 | function tick() {
715 | /* render immediately in desktop */
716 | if (!fallback) {
717 | return drawFrame();
718 | }
719 |
720 | /*
721 | * in iPhone, we render based on audio (if it exists)
722 | * otherwise we step forward by a target FPS
723 | */
724 | var time = Date.now();
725 | elapsed = (time - lastTime) / 1000;
726 | if (elapsed >= 1000 / fps / 1000) {
727 | if (fallback) {
728 | /* seek and wait for timeupdate */
729 | if (audio) {
730 | video.currentTime = audio.currentTime;
731 | } else {
732 | video.currentTime = video.currentTime + elapsed;
733 | }
734 | }
735 | lastTime = time;
736 | }
737 |
738 | /**
739 | * in iPhone, when audio is not present we need
740 | * to track duration
741 | */
742 |
743 | if (fallback && !audio) {
744 | if (Math.abs(video.currentTime - duration) < 0.1) {
745 | /* whether to restart or just stop the raf loop */
746 | if (loop) {
747 | video.currentTime = 0;
748 | } else {
749 | /**
750 | TODO:
751 | - add stop logic
752 | */
753 | }
754 | }
755 | }
756 | }
757 |
758 | /*============================
759 | = draw =
760 | ============================*/
761 |
762 | function drawFrame() {
763 | render(video);
764 | }
765 | };
766 |
767 | /***/ },
768 | /* 2 */
769 | /***/ function(module, exports, __webpack_require__) {
770 |
771 | /* WEBPACK VAR INJECTION */(function(process) {module.exports = function (tasks, cb) {
772 | var results, pending, keys
773 | var isSync = true
774 |
775 | if (Array.isArray(tasks)) {
776 | results = []
777 | pending = tasks.length
778 | } else {
779 | keys = Object.keys(tasks)
780 | results = {}
781 | pending = keys.length
782 | }
783 |
784 | function done (err) {
785 | function end () {
786 | if (cb) cb(err, results)
787 | cb = null
788 | }
789 | if (isSync) process.nextTick(end)
790 | else end()
791 | }
792 |
793 | function each (i, err, result) {
794 | results[i] = result
795 | if (--pending === 0 || err) {
796 | done(err)
797 | }
798 | }
799 |
800 | if (!pending) {
801 | // empty
802 | done(null)
803 | } else if (keys) {
804 | // object
805 | keys.forEach(function (key) {
806 | tasks[key](function (err, result) { each(key, err, result) })
807 | })
808 | } else {
809 | // array
810 | tasks.forEach(function (task, i) {
811 | task(function (err, result) { each(i, err, result) })
812 | })
813 | }
814 |
815 | isSync = false
816 | }
817 |
818 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))
819 |
820 | /***/ },
821 | /* 3 */
822 | /***/ function(module, exports) {
823 |
824 | // shim for using process in browser
825 |
826 | var process = module.exports = {};
827 | var queue = [];
828 | var draining = false;
829 | var currentQueue;
830 | var queueIndex = -1;
831 |
832 | function cleanUpNextTick() {
833 | draining = false;
834 | if (currentQueue.length) {
835 | queue = currentQueue.concat(queue);
836 | } else {
837 | queueIndex = -1;
838 | }
839 | if (queue.length) {
840 | drainQueue();
841 | }
842 | }
843 |
844 | function drainQueue() {
845 | if (draining) {
846 | return;
847 | }
848 | var timeout = setTimeout(cleanUpNextTick);
849 | draining = true;
850 |
851 | var len = queue.length;
852 | while(len) {
853 | currentQueue = queue;
854 | queue = [];
855 | while (++queueIndex < len) {
856 | if (currentQueue) {
857 | currentQueue[queueIndex].run();
858 | }
859 | }
860 | queueIndex = -1;
861 | len = queue.length;
862 | }
863 | currentQueue = null;
864 | draining = false;
865 | clearTimeout(timeout);
866 | }
867 |
868 | process.nextTick = function (fun) {
869 | var args = new Array(arguments.length - 1);
870 | if (arguments.length > 1) {
871 | for (var i = 1; i < arguments.length; i++) {
872 | args[i - 1] = arguments[i];
873 | }
874 | }
875 | queue.push(new Item(fun, args));
876 | if (queue.length === 1 && !draining) {
877 | setTimeout(drainQueue, 0);
878 | }
879 | };
880 |
881 | // v8 likes predictible objects
882 | function Item(fun, array) {
883 | this.fun = fun;
884 | this.array = array;
885 | }
886 | Item.prototype.run = function () {
887 | this.fun.apply(null, this.array);
888 | };
889 | process.title = 'browser';
890 | process.browser = true;
891 | process.env = {};
892 | process.argv = [];
893 | process.version = ''; // empty string to avoid regexp issues
894 | process.versions = {};
895 |
896 | function noop() {}
897 |
898 | process.on = noop;
899 | process.addListener = noop;
900 | process.once = noop;
901 | process.off = noop;
902 | process.removeListener = noop;
903 | process.removeAllListeners = noop;
904 | process.emit = noop;
905 |
906 | process.binding = function (name) {
907 | throw new Error('process.binding is not supported');
908 | };
909 |
910 | process.cwd = function () { return '/' };
911 | process.chdir = function (dir) {
912 | throw new Error('process.chdir is not supported');
913 | };
914 | process.umask = function() { return 0; };
915 |
916 |
917 | /***/ },
918 | /* 4 */
919 | /***/ function(module, exports, __webpack_require__) {
920 |
921 | /* WEBPACK VAR INJECTION */(function(process) {'use strict';
922 |
923 | /**
924 | * original data is by @Jam3
925 | * @see https://github.com/Jam3/ios-video-test
926 | */
927 |
928 | function noop() {}
929 |
930 | var createSource = __webpack_require__(5);
931 |
932 | module.exports.video = simpleMediaElement.bind(null, 'video');
933 | module.exports.audio = simpleMediaElement.bind(null, 'audio');
934 |
935 | function simpleMediaElement(elementName, sources, opt, cb) {
936 | if (typeof opt === 'function') {
937 | cb = opt;
938 | opt = {};
939 | }
940 | cb = cb || noop;
941 | opt = opt || {};
942 |
943 | if (!Array.isArray(sources)) {
944 | sources = [sources];
945 | }
946 |
947 | var media = opt.element || document.createElement(elementName);
948 |
949 | if (opt.loop) media.setAttribute('loop', true);
950 | if (opt.muted) media.setAttribute('muted', 'muted');
951 | if (opt.autoplay) media.setAttribute('autoplay', 'autoplay');
952 | if (opt.preload) media.setAttribute('preload', opt.preload);
953 | if (typeof opt.volume !== 'undefined') media.setAttribute('volume', opt.volume);
954 | media.setAttribute('crossorigin', 'anonymous');
955 | media.setAttribute('webkit-playsinline', '');
956 |
957 | sources.forEach(function (source) {
958 | media.appendChild(createSource(source));
959 | });
960 |
961 | process.nextTick(start);
962 | return media;
963 |
964 | function start() {
965 | media.addEventListener('canplay', function () {
966 | cb(null, media);
967 | cb = noop;
968 | }, false);
969 | media.addEventListener('error', function (err) {
970 | cb(err);
971 | cb = noop;
972 | }, false);
973 | media.addEventListener('readystatechange', checkReadyState, false);
974 | media.load();
975 | checkReadyState();
976 | }
977 |
978 | function checkReadyState() {
979 | if (media.readyState > media.HAVE_FUTURE_DATA) {
980 | cb(null, media);
981 | cb = noop;
982 | }
983 | }
984 | }
985 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))
986 |
987 | /***/ },
988 | /* 5 */
989 | /***/ function(module, exports, __webpack_require__) {
990 |
991 | 'use strict';
992 |
993 | /**
994 | * original data is by @Jam3
995 | * @see https://github.com/Jam3/ios-video-test
996 | */
997 |
998 | var extname = __webpack_require__(6).extname;
999 |
1000 | var mimeTypes = __webpack_require__(7);
1001 | var mimeLookup = {};
1002 | Object.keys(mimeTypes).forEach(function (key) {
1003 | var extensions = mimeTypes[key];
1004 | extensions.forEach(function (ext) {
1005 | mimeLookup[ext] = key;
1006 | });
1007 | });
1008 |
1009 | module.exports = createSourceElement;
1010 | function createSourceElement(src, type) {
1011 | var source = document.createElement('source');
1012 | source.src = src;
1013 | if (!type) type = lookup(src);
1014 | source.type = type;
1015 | return source;
1016 | }
1017 |
1018 | function lookup(src) {
1019 | var ext = extname(src);
1020 | if (ext.indexOf('.') === 0) {
1021 | ext = mimeLookup[ext.substring(1).toLowerCase()];
1022 | }
1023 | if (!ext) {
1024 | throw new TypeError('could not determine mime-type from source: ' + src);
1025 | }
1026 | return ext;
1027 | }
1028 |
1029 | /***/ },
1030 | /* 6 */
1031 | /***/ function(module, exports, __webpack_require__) {
1032 |
1033 | /* WEBPACK VAR INJECTION */(function(process) {// Copyright Joyent, Inc. and other Node contributors.
1034 | //
1035 | // Permission is hereby granted, free of charge, to any person obtaining a
1036 | // copy of this software and associated documentation files (the
1037 | // "Software"), to deal in the Software without restriction, including
1038 | // without limitation the rights to use, copy, modify, merge, publish,
1039 | // distribute, sublicense, and/or sell copies of the Software, and to permit
1040 | // persons to whom the Software is furnished to do so, subject to the
1041 | // following conditions:
1042 | //
1043 | // The above copyright notice and this permission notice shall be included
1044 | // in all copies or substantial portions of the Software.
1045 | //
1046 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1047 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1048 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
1049 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
1050 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
1051 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
1052 | // USE OR OTHER DEALINGS IN THE SOFTWARE.
1053 |
1054 | // resolves . and .. elements in a path array with directory names there
1055 | // must be no slashes, empty elements, or device names (c:\) in the array
1056 | // (so also no leading and trailing slashes - it does not distinguish
1057 | // relative and absolute paths)
1058 | function normalizeArray(parts, allowAboveRoot) {
1059 | // if the path tries to go above the root, `up` ends up > 0
1060 | var up = 0;
1061 | for (var i = parts.length - 1; i >= 0; i--) {
1062 | var last = parts[i];
1063 | if (last === '.') {
1064 | parts.splice(i, 1);
1065 | } else if (last === '..') {
1066 | parts.splice(i, 1);
1067 | up++;
1068 | } else if (up) {
1069 | parts.splice(i, 1);
1070 | up--;
1071 | }
1072 | }
1073 |
1074 | // if the path is allowed to go above the root, restore leading ..s
1075 | if (allowAboveRoot) {
1076 | for (; up--; up) {
1077 | parts.unshift('..');
1078 | }
1079 | }
1080 |
1081 | return parts;
1082 | }
1083 |
1084 | // Split a filename into [root, dir, basename, ext], unix version
1085 | // 'root' is just a slash, or nothing.
1086 | var splitPathRe =
1087 | /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
1088 | var splitPath = function(filename) {
1089 | return splitPathRe.exec(filename).slice(1);
1090 | };
1091 |
1092 | // path.resolve([from ...], to)
1093 | // posix version
1094 | exports.resolve = function() {
1095 | var resolvedPath = '',
1096 | resolvedAbsolute = false;
1097 |
1098 | for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
1099 | var path = (i >= 0) ? arguments[i] : process.cwd();
1100 |
1101 | // Skip empty and invalid entries
1102 | if (typeof path !== 'string') {
1103 | throw new TypeError('Arguments to path.resolve must be strings');
1104 | } else if (!path) {
1105 | continue;
1106 | }
1107 |
1108 | resolvedPath = path + '/' + resolvedPath;
1109 | resolvedAbsolute = path.charAt(0) === '/';
1110 | }
1111 |
1112 | // At this point the path should be resolved to a full absolute path, but
1113 | // handle relative paths to be safe (might happen when process.cwd() fails)
1114 |
1115 | // Normalize the path
1116 | resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
1117 | return !!p;
1118 | }), !resolvedAbsolute).join('/');
1119 |
1120 | return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
1121 | };
1122 |
1123 | // path.normalize(path)
1124 | // posix version
1125 | exports.normalize = function(path) {
1126 | var isAbsolute = exports.isAbsolute(path),
1127 | trailingSlash = substr(path, -1) === '/';
1128 |
1129 | // Normalize the path
1130 | path = normalizeArray(filter(path.split('/'), function(p) {
1131 | return !!p;
1132 | }), !isAbsolute).join('/');
1133 |
1134 | if (!path && !isAbsolute) {
1135 | path = '.';
1136 | }
1137 | if (path && trailingSlash) {
1138 | path += '/';
1139 | }
1140 |
1141 | return (isAbsolute ? '/' : '') + path;
1142 | };
1143 |
1144 | // posix version
1145 | exports.isAbsolute = function(path) {
1146 | return path.charAt(0) === '/';
1147 | };
1148 |
1149 | // posix version
1150 | exports.join = function() {
1151 | var paths = Array.prototype.slice.call(arguments, 0);
1152 | return exports.normalize(filter(paths, function(p, index) {
1153 | if (typeof p !== 'string') {
1154 | throw new TypeError('Arguments to path.join must be strings');
1155 | }
1156 | return p;
1157 | }).join('/'));
1158 | };
1159 |
1160 |
1161 | // path.relative(from, to)
1162 | // posix version
1163 | exports.relative = function(from, to) {
1164 | from = exports.resolve(from).substr(1);
1165 | to = exports.resolve(to).substr(1);
1166 |
1167 | function trim(arr) {
1168 | var start = 0;
1169 | for (; start < arr.length; start++) {
1170 | if (arr[start] !== '') break;
1171 | }
1172 |
1173 | var end = arr.length - 1;
1174 | for (; end >= 0; end--) {
1175 | if (arr[end] !== '') break;
1176 | }
1177 |
1178 | if (start > end) return [];
1179 | return arr.slice(start, end - start + 1);
1180 | }
1181 |
1182 | var fromParts = trim(from.split('/'));
1183 | var toParts = trim(to.split('/'));
1184 |
1185 | var length = Math.min(fromParts.length, toParts.length);
1186 | var samePartsLength = length;
1187 | for (var i = 0; i < length; i++) {
1188 | if (fromParts[i] !== toParts[i]) {
1189 | samePartsLength = i;
1190 | break;
1191 | }
1192 | }
1193 |
1194 | var outputParts = [];
1195 | for (var i = samePartsLength; i < fromParts.length; i++) {
1196 | outputParts.push('..');
1197 | }
1198 |
1199 | outputParts = outputParts.concat(toParts.slice(samePartsLength));
1200 |
1201 | return outputParts.join('/');
1202 | };
1203 |
1204 | exports.sep = '/';
1205 | exports.delimiter = ':';
1206 |
1207 | exports.dirname = function(path) {
1208 | var result = splitPath(path),
1209 | root = result[0],
1210 | dir = result[1];
1211 |
1212 | if (!root && !dir) {
1213 | // No dirname whatsoever
1214 | return '.';
1215 | }
1216 |
1217 | if (dir) {
1218 | // It has a dirname, strip trailing slash
1219 | dir = dir.substr(0, dir.length - 1);
1220 | }
1221 |
1222 | return root + dir;
1223 | };
1224 |
1225 |
1226 | exports.basename = function(path, ext) {
1227 | var f = splitPath(path)[2];
1228 | // TODO: make this comparison case-insensitive on windows?
1229 | if (ext && f.substr(-1 * ext.length) === ext) {
1230 | f = f.substr(0, f.length - ext.length);
1231 | }
1232 | return f;
1233 | };
1234 |
1235 |
1236 | exports.extname = function(path) {
1237 | return splitPath(path)[3];
1238 | };
1239 |
1240 | function filter (xs, f) {
1241 | if (xs.filter) return xs.filter(f);
1242 | var res = [];
1243 | for (var i = 0; i < xs.length; i++) {
1244 | if (f(xs[i], i, xs)) res.push(xs[i]);
1245 | }
1246 | return res;
1247 | }
1248 |
1249 | // String.prototype.substr - negative index don't work in IE8
1250 | var substr = 'ab'.substr(-1) === 'b'
1251 | ? function (str, start, len) { return str.substr(start, len) }
1252 | : function (str, start, len) {
1253 | if (start < 0) start = str.length + start;
1254 | return str.substr(start, len);
1255 | }
1256 | ;
1257 |
1258 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))
1259 |
1260 | /***/ },
1261 | /* 7 */
1262 | /***/ function(module, exports) {
1263 |
1264 | module.exports = {
1265 | "audio/adpcm": [
1266 | "adp"
1267 | ],
1268 | "audio/basic": [
1269 | "au",
1270 | "snd"
1271 | ],
1272 | "audio/midi": [
1273 | "mid",
1274 | "midi",
1275 | "kar",
1276 | "rmi"
1277 | ],
1278 | "audio/mp4": [
1279 | "mp4a",
1280 | "m4a"
1281 | ],
1282 | "audio/mpeg": [
1283 | "mpga",
1284 | "mp2",
1285 | "mp2a",
1286 | "mp3",
1287 | "m2a",
1288 | "m3a"
1289 | ],
1290 | "audio/ogg": [
1291 | "oga",
1292 | "ogg",
1293 | "spx"
1294 | ],
1295 | "audio/s3m": [
1296 | "s3m"
1297 | ],
1298 | "audio/silk": [
1299 | "sil"
1300 | ],
1301 | "audio/vnd.dece.audio": [
1302 | "uva",
1303 | "uvva"
1304 | ],
1305 | "audio/vnd.digital-winds": [
1306 | "eol"
1307 | ],
1308 | "audio/vnd.dra": [
1309 | "dra"
1310 | ],
1311 | "audio/vnd.dts": [
1312 | "dts"
1313 | ],
1314 | "audio/vnd.dts.hd": [
1315 | "dtshd"
1316 | ],
1317 | "audio/vnd.lucent.voice": [
1318 | "lvp"
1319 | ],
1320 | "audio/vnd.ms-playready.media.pya": [
1321 | "pya"
1322 | ],
1323 | "audio/vnd.nuera.ecelp4800": [
1324 | "ecelp4800"
1325 | ],
1326 | "audio/vnd.nuera.ecelp7470": [
1327 | "ecelp7470"
1328 | ],
1329 | "audio/vnd.nuera.ecelp9600": [
1330 | "ecelp9600"
1331 | ],
1332 | "audio/vnd.rip": [
1333 | "rip"
1334 | ],
1335 | "audio/webm": [
1336 | "weba"
1337 | ],
1338 | "audio/x-aac": [
1339 | "aac"
1340 | ],
1341 | "audio/x-aiff": [
1342 | "aif",
1343 | "aiff",
1344 | "aifc"
1345 | ],
1346 | "audio/x-caf": [
1347 | "caf"
1348 | ],
1349 | "audio/x-flac": [
1350 | "flac"
1351 | ],
1352 | "audio/x-matroska": [
1353 | "mka"
1354 | ],
1355 | "audio/x-mpegurl": [
1356 | "m3u"
1357 | ],
1358 | "audio/x-ms-wax": [
1359 | "wax"
1360 | ],
1361 | "audio/x-ms-wma": [
1362 | "wma"
1363 | ],
1364 | "audio/x-pn-realaudio": [
1365 | "ram",
1366 | "ra"
1367 | ],
1368 | "audio/x-pn-realaudio-plugin": [
1369 | "rmp"
1370 | ],
1371 | "audio/x-wav": [
1372 | "wav"
1373 | ],
1374 | "audio/xm": [
1375 | "xm"
1376 | ],
1377 | "video/3gpp": [
1378 | "3gp"
1379 | ],
1380 | "video/3gpp2": [
1381 | "3g2"
1382 | ],
1383 | "video/h261": [
1384 | "h261"
1385 | ],
1386 | "video/h263": [
1387 | "h263"
1388 | ],
1389 | "video/h264": [
1390 | "h264"
1391 | ],
1392 | "video/jpeg": [
1393 | "jpgv"
1394 | ],
1395 | "video/jpm": [
1396 | "jpm",
1397 | "jpgm"
1398 | ],
1399 | "video/mj2": [
1400 | "mj2",
1401 | "mjp2"
1402 | ],
1403 | "video/mp2t": [
1404 | "ts"
1405 | ],
1406 | "video/mp4": [
1407 | "mp4",
1408 | "mp4v",
1409 | "mpg4"
1410 | ],
1411 | "video/mpeg": [
1412 | "mpeg",
1413 | "mpg",
1414 | "mpe",
1415 | "m1v",
1416 | "m2v"
1417 | ],
1418 | "video/ogg": [
1419 | "ogv"
1420 | ],
1421 | "video/quicktime": [
1422 | "qt",
1423 | "mov"
1424 | ],
1425 | "video/vnd.dece.hd": [
1426 | "uvh",
1427 | "uvvh"
1428 | ],
1429 | "video/vnd.dece.mobile": [
1430 | "uvm",
1431 | "uvvm"
1432 | ],
1433 | "video/vnd.dece.pd": [
1434 | "uvp",
1435 | "uvvp"
1436 | ],
1437 | "video/vnd.dece.sd": [
1438 | "uvs",
1439 | "uvvs"
1440 | ],
1441 | "video/vnd.dece.video": [
1442 | "uvv",
1443 | "uvvv"
1444 | ],
1445 | "video/vnd.dvb.file": [
1446 | "dvb"
1447 | ],
1448 | "video/vnd.fvt": [
1449 | "fvt"
1450 | ],
1451 | "video/vnd.mpegurl": [
1452 | "mxu",
1453 | "m4u"
1454 | ],
1455 | "video/vnd.ms-playready.media.pyv": [
1456 | "pyv"
1457 | ],
1458 | "video/vnd.uvvu.mp4": [
1459 | "uvu",
1460 | "uvvu"
1461 | ],
1462 | "video/vnd.vivo": [
1463 | "viv"
1464 | ],
1465 | "video/webm": [
1466 | "webm"
1467 | ],
1468 | "video/x-f4v": [
1469 | "f4v"
1470 | ],
1471 | "video/x-fli": [
1472 | "fli"
1473 | ],
1474 | "video/x-flv": [
1475 | "flv"
1476 | ],
1477 | "video/x-m4v": [
1478 | "m4v"
1479 | ],
1480 | "video/x-matroska": [
1481 | "mkv",
1482 | "mk3d",
1483 | "mks"
1484 | ],
1485 | "video/x-mng": [
1486 | "mng"
1487 | ],
1488 | "video/x-ms-asf": [
1489 | "asf",
1490 | "asx"
1491 | ],
1492 | "video/x-ms-vob": [
1493 | "vob"
1494 | ],
1495 | "video/x-ms-wm": [
1496 | "wm"
1497 | ],
1498 | "video/x-ms-wmv": [
1499 | "wmv"
1500 | ],
1501 | "video/x-ms-wmx": [
1502 | "wmx"
1503 | ],
1504 | "video/x-ms-wvx": [
1505 | "wvx"
1506 | ],
1507 | "video/x-msvideo": [
1508 | "avi"
1509 | ],
1510 | "video/x-sgi-movie": [
1511 | "movie"
1512 | ],
1513 | "video/x-smv": [
1514 | "smv"
1515 | ]
1516 | };
1517 |
1518 | /***/ }
1519 | /******/ ]);
--------------------------------------------------------------------------------