Skip to content
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

Merged
merged 8 commits into from Oct 19, 2017

Conversation

machenmusik
Copy link
Contributor

Fixes #3173. Added test and converted 360 video examples to ensure visibility of correct behavior.

Copy link
Contributor

@cvan cvan left a 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!

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]')) {
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is one?

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]')) {
Copy link
Contributor

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

Copy link
Contributor Author

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.

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(
Copy link
Contributor

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(', ');

Copy link
Contributor Author

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.

@@ -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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

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
Copy link
Contributor

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

<script>
// Call onceSceneLoaded() once the scene is loaded.
var scene = document.querySelector('a-scene');
if (scene.hasLoaded) { onceSceneLoaded(); } else {
Copy link
Contributor

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

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"
Copy link
Contributor

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 awesome!

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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting this error in macOS desktop Safari:

image

(all the other video examples seem to work in Safari when I have Develop > Cross-Origin Restrictions enabled.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same on iOS Safari too:

image

Copy link
Contributor Author

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

Copy link
Contributor Author

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

@ngokevin
Copy link
Member

Thanks. Is it awkward since while videos can be loaded, the a-assets doesn't match the support? It also doesn't seem like just a simpler .src replace, that seemed fine to me since it wouldn't add anything. But need to do query selectors and walk through the sources when we consider a video element. And later for audio as well.

For multi-asset support, I might consider this which sets the src based on an available list of srcs. This normalizes the API so each asset is an element in a-assets with a src attribute, and the rest of the codebase doesn't need to do checks. Importantly, this also lets us reliably preload files through FileLoader since that's what THREE.AudioLoader will use, not audio elements.

<a-asset-item id="aSound" srcs="a.wav, a.mp3, a.ogg"></a-asset-item>

@machenmusik
Copy link
Contributor Author

Is it awkward since while videos can be loaded, the a-assets doesn't match the support?

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 a-asset-item to specify multiple explicit URLs to load would be OK in my book, and might also help the gilt plus associated files problem, if a component could be used on a-asset-item to generate the srcs list.

@ngokevin
Copy link
Member

Yeah, it's awkward to officially "support" it since it doesn't play with <a-assets> as expected, and they can't preload well so that weakens the point in using <a-assets> at all. <a-asset-item> supporting multiple would fit better with A-Frame and it enables efficient audio preloading. Wouldn't really be a component, but a feature to <a-asset-item> which is open for expansion.

Streaming, yeah, we wouldn't use <a-assets> at all since <a-assets> is there purely for preloading. Still think it's a better fit in a component. I wonder how that plays well with uploading to the GPU, does it have to decode and upload each frame?

@machenmusik
Copy link
Contributor Author

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

@ngokevin
Copy link
Member

Yeah, for large videos or streaming videos, often prefer to use inline URLs (either in material or other components) versus <a-assets>, only used for preloading files you want to preload. Hopefully, network fetching does not affect rendering too much?

@machenmusik
Copy link
Contributor Author

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

@ngokevin
Copy link
Member

We can document grabbing a handle to the video element if three.js creates it (I believe el.getObject3D('mesh').material.map.image).

@machenmusik
Copy link
Contributor Author

@ngokevin
Copy link
Member

Ah, right. Still can get the video handle that way above.

@machenmusik
Copy link
Contributor Author

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)

@ngokevin
Copy link
Member

The disconnect may or may not be small. It's another edge case to consider and document. Not comfortable supporting <source> in <a-assets> when we can't preload, yet that's the purpose of <a-assets>.

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

@cvan
Copy link
Contributor

cvan commented Oct 18, 2017

my memory was wrong - it was A Saturday Night

@machenmusik
Copy link
Contributor Author

machenmusik commented Oct 18, 2017

is there a common need to support multiple video formats?

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.

@ngokevin
Copy link
Member

ngokevin commented Oct 18, 2017

Sound is a slightly different problem. <source> would be less effective there as three.js AudioLoader does not even use the <audio> element, but rather the FileLoader. Unprescribing <audio> and prescribing something like <a-asset-item srcs> would be effective there.

For video, I don't know if A-Frame should bend for Chromium. I'd prioritize mainline and VR-supported browsers.

@machenmusik
Copy link
Contributor Author

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.

@machenmusik
Copy link
Contributor Author

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

@ngokevin
Copy link
Member

Is it possible to remove all the query selector logic? For hashing, just use the video.src provided as is. Then the PR is neutral and video <source> works (for now) at no cost. Just to make the PR a pure getAttribute('src') -> .src change? Then a warning in <a-assets> for videos with sources maybe.

@machenmusik
Copy link
Contributor Author

There is no video.src provided when you use source elements. That being said, we can require use of element id in those cases

@ngokevin
Copy link
Member

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 hash functions not mess it up (don't think they're too important anyways for video).

@cvan
Copy link
Contributor

cvan commented Oct 18, 2017

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?

@dmarcos
Copy link
Member

dmarcos commented Oct 18, 2017

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

@machenmusik
Copy link
Contributor Author

@ngokevin yup.

@machenmusik
Copy link
Contributor Author

The problem with MP4 is that it doesn't do live. (But that's the other discussion)

@ngokevin
Copy link
Member

needschanges to remove the querySelector logic, use IDs for the hash.

@machenmusik
Copy link
Contributor Author

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.

Copy link
Contributor

@cvan cvan left a 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.
Copy link
Contributor

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
Copy link
Contributor

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.');
Copy link
Contributor

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;
Copy link
Contributor

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">
Copy link
Contributor

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. -->
Copy link
Contributor

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" -->
Copy link
Contributor

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?

@ngokevin
Copy link
Member

Let's make sure the approach is alright before going full into nits/comments/typos, just to reduce noise.

@ngokevin ngokevin merged commit 187f520 into aframevr:master Oct 19, 2017
@ngokevin
Copy link
Member

Thanks! That one took a lot of energy 😅

@cvan
Copy link
Contributor

cvan commented Oct 19, 2017

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.

@ngokevin
Copy link
Member

The preloading abilities <source> video will be limited (it's not great to preload large videos anyways). The PR will enable at least the use of <source> without weird warnings (by passing an ID or the video element directly thru setAttribute).

@ngokevin
Copy link
Member

Yeah, perhaps it might time out if you use <source>. Could be a separate fix to skip media elements that don't have a src attribute defined directly.

@NameIsSoDiao
Copy link

Can not play VR video on ios#3192

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants