New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support sources in video (fix #3173) #3176
Conversation
…and src attributes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is looking good. thanks for taking this on - I appreciate it.
all the examples work on
- Chrome on all platforms
- Firefox on desktop, Windows and macOS
- all but
/examples/test/videosphere
work in Safari on desktop and iOS (error:WebGL: INVALID_VALUE: texImage2D: no video
)
let me know when you're ready for me to test this again. thanks!
src/systems/material.js
Outdated
if (!src.hasAttribute('src') && !src.hasAttribute('srcObject')) { | ||
warn('Video element was defined without `src` nor `srcObject` attributes.'); | ||
if (!src.src && !src.srcObject) { | ||
if (!src.querySelector('source[src],source[srcObject]')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feel free to add a comma between the selectors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is one?
src/systems/material.js
Outdated
if (!src.hasAttribute('src') && !src.hasAttribute('srcObject')) { | ||
warn('Video element was defined without `src` nor `srcObject` attributes.'); | ||
if (!src.src && !src.srcObject) { | ||
if (!src.querySelector('source[src],source[srcObject]')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feel free to remove this level of nesting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that necessary? The outer level is the check for traditional src / srcObject attribute, the inner is a check for child source elements with them; there is no need to do querySelector in the more common case of src/srcObject.
src/systems/material.js
Outdated
data.src = data.src.src; | ||
if (!data.src && data.src.tagName === 'VIDEO') { | ||
// Video elements can have source elements as well | ||
data.sources = Array.prototype.slice.call( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could remove one function call like this:
data.sources = Array.prototype.map.call(data.src.querySelectorAll('source'), function (vid) {
return video.src || video.srcObject;
}).join(', ');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I specifically did not do this because on some browsers, querySelectorAll results are not usable as an Array, but need to be converted with Array.prototype.slice.call first.
src/systems/material.js
Outdated
@@ -270,6 +278,10 @@ function calculateVideoCacheHash (data, videoEl) { | |||
for (i = 0; i < videoEl.attributes.length; i++) { | |||
videoAttributes[videoEl.attributes[i].name] = videoEl.attributes[i].value; | |||
} | |||
// Video elements can have source elements as well | |||
videoAttributes.sources = Array.prototype.slice.call( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
src/systems/material.js
Outdated
data.src = data.src.getAttribute('src'); | ||
data.src = data.src.src; | ||
if (!data.src && data.src.tagName === 'VIDEO') { | ||
// Video elements can have source elements as well |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you end this comment with a period?
@@ -180,7 +182,13 @@ module.exports.System = registerSystem('material', { | |||
if (data.src.tagName) { | |||
// Since `data.src` can be an element, parse out the string if necessary for the hash. | |||
data = utils.extendDeep({}, data); | |||
data.src = data.src.getAttribute('src'); | |||
data.src = data.src.src; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do these calls in src/core/a-assets.js
need to be adjusted as well?
- https://github.com/chenzlabs/aframe/blob/9f8ae0c/src/core/a-assets.js#L45
- https://github.com/chenzlabs/aframe/blob/9f8ae0c/src/core/a-assets.js#L81
- https://github.com/chenzlabs/aframe/blob/9f8ae0c/src/core/a-assets.js#L103
- https://github.com/chenzlabs/aframe/blob/9f8ae0c/src/core/a-assets.js#L162
- https://github.com/chenzlabs/aframe/blob/9f8ae0c/src/core/a-assets.js#L205
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L45 is for images so no.
L81 maybe, but see below... so at present I would argue no.
L103 is problematic because we don't know which source the browser would select, and I'm not in favor of trying to duplicate browser selection logic here as that is a losing battle longer term.
L162 is problematic as I'm not sure whether we want to rely on THREE level caching rather than browser level caching (or instructions from the sources not to cache)... so at present I would argue no.
L205 maybe, but is it harmful to add crossorigin? We don't know which source the browser would select, and I think we would be better served to ask authors to add crossorigin themselves, as they would have to do outside A-Frame -- just adding the attribute doesn't make the video delivery and browser honor it, and I think that's a contributor to the number of "why doesn't my video play" questions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L81 maybe, but see below... so at present I would argue no.
I'm pretty sure this should be.
L162 is problematic as I'm not sure whether we want to rely on THREE level caching rather than browser level caching (or instructions from the sources not to cache)... so at present I would argue no.
This seems like a larger issue that probably warrants its own GitHub issue. What are your thoughts?
L205 maybe, but is it harmful to add crossorigin?
Personally, I think requiring developers to add crossorigin="anonymous"
is probably security theatre. If it's in the examples or added by A-Frame automatically, whatever developers see or have to do to make it work, they'll probably just do it blindly. I'm not certain it helps.
examples/test/videosphere/index.html
Outdated
<script> | ||
// Call onceSceneLoaded() once the scene is loaded. | ||
var scene = document.querySelector('a-scene'); | ||
if (scene.hasLoaded) { onceSceneLoaded(); } else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can break this out onto separate lines
examples/test/videosphere/index.html
Outdated
src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-197/walking_the_dog_360_video_short.webm" | ||
<!-- Test anonymous video with source element. --> | ||
<!-- Normally, one would give the video an id, e.g. id="lego" --> | ||
<video preload="true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be preload="auto"
(per #3175)
assert.equal(texture.image, videoEl); | ||
system.textureCache[hash].then(function (result) { | ||
assert.equal(texture, result.texture); | ||
assert.equal(texture.image, result.videoEl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 awesome!
examples/test/videosphere/index.html
Outdated
width="360" height="360" autoplay loop="true" | ||
crossOrigin="anonymous"></video> | ||
crossOrigin="anonymous"> | ||
<source src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-197/walking_the_dog_360_video_short.webm"></source> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does Safari support webm format? I assumed that was why this example fails there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(That failure should be present with or without this PR.)
Thanks. Is it awkward since while videos can be loaded, the For multi-asset support, I might consider this which sets the <a-asset-item id="aSound" srcs="a.wav, a.mp3, a.ogg"></a-asset-item> |
Well, given that the source element for video is often used when the browser should pick from given alternatives, I'm not sure how a-assets should work, which one should it load and wait for, and what if it doesn't match the browser? I think a note that video using source elements can't be preloaded via a-assets would be fine. Perhaps that warning should still fire if no src/srcObject attributes (i.e. eliminate that querySelector addition). For media assets of considerable size, I don't think that preloading every possible variant because we don't know which will be used is a good idea. Also, streaming assets may not be preloadable, since they may not terminate. But having a generic way for |
Yeah, it's awkward to officially "support" it since it doesn't play with Streaming, yeah, we wouldn't use |
Streaming plays like any other video texture. You don't need to use a-assets unless you want to block the entire UI to load (large) video files, which IMO is actually a bad thing to do in general even with existing src-attribute MP4 files. (Or giant model files, etc.) |
Yeah, for large videos or streaming videos, often prefer to use inline URLs (either in material or other components) versus |
IMO, using inline URL tends to be part of the problem, because no one can figure out where the actual media element is to be controlled (which is a FAQ) -- better to append a child video element with whatever you want to scene (outside a-assets) and reference by id/selector |
We can document grabbing a handle to the video element if three.js creates it (I believe |
it's A-Frame that creates it |
Ah, right. Still can get the video handle that way above. |
True, but that's kinda gross. (Maybe an accessor function on the material component would be nicer.) Are you requesting more changes here (e.g. reinstate a-assets warning) ? I think the value of being able to play videos that use source elements, even though they won't be preloaded, is worth the small disconnect (especially if the warning is reinstated and/or tweaked to explain) |
The disconnect may or may not be small. It's another edge case to consider and document. Not comfortable supporting I'm thinking again, is there a common need to support multiple video formats? A-Frame is a future-facing project, most people will just use one of the formats that are supported in all modern browsers, versus going through the work of converting to multiple formats. For the people that somehow need it, it'd still be possible to do with a bit of JS. I haven't come across projects that have attempted or asked for multiple formats (@cvan seemed to list A-Blast and Puzzle Rain, but they just use one format). |
my memory was wrong - it was A Saturday Night |
Sadly, yes at the moment, as there is not quite a common set of codecs and formats that are supported by all browsers. For example, no one seemed to notice that examples/test/videosphere is using webm which apparently fails in Safari. H.264 and MP4 is probably closest at the moment for video, but even then there are outliers like experimental Chromium which do not support. |
Sound is a slightly different problem. For video, I don't know if A-Frame should bend for Chromium. I'd prioritize mainline and VR-supported browsers. |
So A-Frame and THREE are perfectly capable of supporting video that does not appear in a-assets, and as we have discussed a significant number of video use cases really don't want to incur preload for hundreds of megabytes or more of video, especially inadvertently on mobile. My suggestion is to continue to document how to use video with src attribute as has been common A-Frame practice for a while, but also to point out that non-preloaded video elements also work (once we merge this PR) with examples including WebRTC and videos with source elements. I would also suggest we think about whether we want to continue encouraging use of a-video and a-videosphere components, or would rather discourage in favor of something else like a third party component. |
@cvan brought up audio, i agree that is different, as preload mistakes are in the single to double digits of megabytes which is less fatal. Multiple video source support is useful and often needed, it's not about Chromium specifically. Question, do we find browser caching (often on mass storage and not purely RAM) to be unreliable so we want to use THREE caching (which is in RAM by necessity and thus bloats footprint) ? |
Is it possible to remove all the query selector logic? For hashing, just use the |
There is no video.src provided when you use source elements. That being said, we can require use of element id in those cases |
In A-Frame, I guess they'd always have IDs? So I'm understanding the fix more, is just to get past A-Frame warnings/checks/hashing so we can pass the video to three.js uninterrupted. Then the fix is mostly making the |
Chromium is used by the official Oculus Browser and Samsung Internet Browser. Those are browsers that A-Frame should handle as first-class citizens, yes? |
@cvan Yes we should support those. Even though Chromium does not ship with proprietary codecs, Oculus Browser and Samsung Internet both support H.264 and MP4 as far as I know, they're not just vanilla Chromium. The only affected one would be Chromium WebVR experimental builds. |
@ngokevin yup. |
The problem with MP4 is that it doesn't do live. (But that's the other discussion) |
|
… weak check for child source elements
Latest version correctly gives a-assets warning for examples/test/videosphere (which still works anyway if you're not using Safari), and also correctly gives no warnings for examples/test/video. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a few minor things - thanks 👍
scale="-1 1 1"> | ||
</a-entity> | ||
</a-scene> | ||
|
||
<!-- Normally, one would use video id, e.g. material="src:#lego" above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a repeat of the comment on line 20. I'd remove this sentence.
scale="-1 1 1"> | ||
</a-entity> | ||
</a-scene> | ||
|
||
<!-- Normally, one would use video id, e.g. material="src:#lego" above. | ||
For this example however, we want to show that an anonymous video |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
anonymous video
? I'm not sure I'd expect people to know what that means
if (!src.hasAttribute('src') && !src.hasAttribute('srcObject')) { | ||
warn('Video element was defined without `src` nor `srcObject` attributes.'); | ||
if (!src.src && !src.srcObject && !src.childElementCount) { | ||
warn('Video element was defined with neither `source` elements nor `src` / `srcObject` attributes.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should
Video
be
`video`
?
@@ -180,7 +182,13 @@ module.exports.System = registerSystem('material', { | |||
if (data.src.tagName) { | |||
// Since `data.src` can be an element, parse out the string if necessary for the hash. | |||
data = utils.extendDeep({}, data); | |||
data.src = data.src.getAttribute('src'); | |||
data.src = data.src.src; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L81 maybe, but see below... so at present I would argue no.
I'm pretty sure this should be.
L162 is problematic as I'm not sure whether we want to rely on THREE level caching rather than browser level caching (or instructions from the sources not to cache)... so at present I would argue no.
This seems like a larger issue that probably warrants its own GitHub issue. What are your thoughts?
L205 maybe, but is it harmful to add crossorigin?
Personally, I think requiring developers to add crossorigin="anonymous"
is probably security theatre. If it's in the examples or added by A-Frame automatically, whatever developers see or have to do to make it work, they'll probably just do it blindly. I'm not certain it helps.
width="360" height="360" autoplay loop="true" | ||
crossOrigin="anonymous"></video> | ||
crossOrigin="anonymous"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I know you didn't modify it, but can you lowercase the O
in Origin
?
@@ -9,15 +9,41 @@ | |||
<body> | |||
<a-scene stats> | |||
<a-assets> | |||
<video id="lego" preload="true" | |||
src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-197/walking_the_dog_360_video_short.webm" | |||
<!-- Test anonymous video with source element. --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
anonymous
-> cross-origin (anonymous)
perhaps? even as someone familiar with browsers' cross-origin policies, this comment doesn't help me a whole lot. perhaps adding a link to MDN might be helpful?
<video id="lego" preload="true" | ||
src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-197/walking_the_dog_360_video_short.webm" | ||
<!-- Test anonymous video with source element. --> | ||
<!-- Normally, one would give the video an id, e.g. id="lego" --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment's a bit confusing, without seeing the JavaScript <script>
below. It seems like this example is being modified to test <source>
. Could we just have a separate example that tests <source>
s?
Let's make sure the approach is alright before going full into nits/comments/typos, just to reduce noise. |
Thanks! That one took a lot of energy 😅 |
Agreed, thanks for helping with this. In the future, would you like me to mark reviews as approved but with Comments? Beyond my admittedly noisy comments here, the important line I wanted to double check on was this: #3176 (comment) Doesn’t regress anything to my knowledge, but I think the loading of the videos are being timed out as a result. Am uncaught Promise, but assets and scene still load. So ok 👌🏽! Thanks for this - I appreciate it, both of you. |
The preloading abilities |
Yeah, perhaps it might time out if you use |
Can not play VR video on ios#3192 |
Fixes #3173. Added test and converted 360 video examples to ensure visibility of correct behavior.