facebook/react

<video /> attribute needed but not guaranteed by React

elrumordelaluz opened this issue Β· 91 comments

As @gaearon mentioned, React does not guarantee an attribute will be set, so probably this is not a bug.

If I understand well, react will ensure the property is set anyway.

Current behaviour
React renders the html <video /> element without the attribute muted when explicitly passed.

Demo time
In this pen I made a simple example setting muted to the element and obtaining the result below:
pen-screen-shot

Actually the property is set well, since the original medial file has an audio track and in the pen result is muted.

The point
I think is most a specific need than the expected behaviour.
From the functionality POV, it is absolutely ok, my Component renders a <video /> muted as requested and so on.

But there are browsers and policies, more specifically related to this issue, Webkit and the New updated one year ago, with some interesting changes for the <video /> element.
The part interested is

<video muted> elements will also be allowed to autoplay without a user gesture.

So, the specific need is to have the explicit attribute to tell the browser that this video could be autoPlayed.

There's a similar issue

The muted property is live just like value for inputs and reflects the current value. But the muted attribute reflects the initial state of the component.

So I think the current behavior is wrong, because <video muted /> should force it to always be muted then, just like <input value="foo" /> forces it to always be "foo". So one way to resolve this is to start from scratch; introduce defaultMuted property which maps to the attribute only. Then there should be a muted property which is controlled (just like input value) and there needs to be a callback to go with that e.g. onMutedChange. However! Mute is just equal to volume being zero, so the DOM only exposes the volumechange event.

So perhaps a more DOM-truthful and minimal implementation would be to have muted be the attribute and reflect only the initial mute state.

Then React can take it further and also support a special-property volume and its corresponding event onVolumeChange. But this is a slippery slope and there are tons of such properties that should be added then (and increasingly mixing both behaviors in React is a much larger discussion). Elsewhere we recommend that people simply manually set properties via JS to control elements and I think the same general recommendation works best here too, manually set the properties and expose your fancy video player as a React component if you want to reuse it.

tl;dr IMHO yes, muted should set the attribute only, so that it only sets the initial state of the element.

I am facing the same issue with Android 4.4, according to new (from a year ago), muted attribute needs to be present.

@gaearon This should be an easy fix if you're OK with the direction (making muted set the attribute instead), although there is potential to break some existing code if someone decided to implement their own mute button and rely on this prop.

Since defaultMuted is already a spec-defined property reflecting the muted attribute, it would make sense to let users utilize this API. The React API is already property-focused, so it's not a big leap. This would be the least complex change, and shouldn't be breaking.

@aweary Any progress on this?

@aweary Same question here, we need the muted attribute to be passed to the video tag to allow autoplaying on mobile devices. Has there been any progress in this direction?

Thank you!

folks progress on an Issue is generally visible and takes the form of a PR. If you don't see a linked PR or updates on an issue assume there is no more progress.

It's unclear what the path forward is for this to me. We don't really have way of saying "sync the attribute once and then use the property" and I don't know that adding said logic is even a good idea...

It's unclear what the path forward is for this to me. We don't really have way of saying "sync the attribute once and then use the property" and I don't know that adding said logic is even a good idea...

@jquense I think I understand your point, but inputs have both value and defaultValue. This is exactly the same thing, there should probably be a defaultMuted and muted. It's a "live" value and needs to be special-cased in the same way, that or otherwise just have muted prop which sets the attribute but that means users need to manually access the muted property if they want to control it precisely (just like you can with defaultValue for inputs), which is also fine.

Personally, just having muted set the attribute and leaving everything else up to the user is perfectly fine (and IMHO how input should have been implemented too).

I'm hesitant to change muted to act like value; we want to get away from that special-cased behavior, since it's caused a number of problems. Of course, muted would likely be much less problematic since it's just a boolean value.

We don't really have way of saying "sync the attribute once and then use the property" and I don't know that adding said logic is even a good idea...

defaultMuted is the spec'd solution for this. It sets the initial muted value by reflecting the muted attribute, which can then be controlled using the muted property.

We don't support many attributes that aren't actually DOM attributes, but we do have defaultValue so I think there's a good argument for mirroring that API here.

we want to get away from that special-cased behavior, since it's caused a number of problems.

yeah the controlling behavior ends up causing so many headaches i wouldn't want to do it again, though i agree that it's the right behavior generally.

We don't support many attributes that aren't actually DOM attributes, but we do have defaultValue so I think there's a good argument for mirroring that API here.

I like that idea, tho It's always a slippery slope, what would the SSR behavior if someone specifies defaultMuted but no muted, ? I suppose we already handle this with value and checked...

I like that idea, tho It's always a slippery slope, what would the SSR behavior if someone specifies defaultMuted but no muted, ? I suppose we already handle this with value and checked...

Yeah, I imagine it would behavior just like defaultValue does for SSR, by rendering the value attribute with the provided value.

ya my concern is just that most of the value/defaultValue is special cased through the codebase sort of assuming the pattern will be used just for inputs. I should probably go see how it works tho before worrying about that...

My assumption is that the one use case for defaultMuted is to handle the situation where the muted attribute should be present when the video is first rendered. Since it has no dynamic behavior, per the spec, I think just defining it as a boolean attribute that must set the property (easy to do with PropertyInfoRecord) would be good enough? I hope at least πŸ˜…

It could be just as easy as adding it to this list:

// These are the few React props that we set as DOM properties
// rather than attributes. These are all booleans.
[
'checked',
// Note: `option.selected` is not updated if `select.multiple` is
// disabled with `removeAttribute`. We have special logic for handling this.
'multiple',
'muted',
'selected',
].forEach(name => {
properties[name] = new PropertyInfoRecord(
name,
BOOLEAN,
true, // mustUseProperty
name.toLowerCase(), // attributeName
null, // attributeNamespace
);
});

`const { Component } = React
const { render } = ReactDOM

class App extends Component {
render () {
return (
<video style={{ width: '100%' }}
src="http://clips.vorwaerts-gmbh.de/VfE_html5.mp4"
autoPlay
video />
)
}
}

render(, document.querySelector("#main"))`
If you run this code there is a video playing automatically with audio.
I have used video instead of muted attribute.

ctate commented

muted is an extremely important attribute for background videos. Please expose it to the browser. πŸ™

In the meantime, if you need to autoplay background videos, try this:

import * as React from 'react';
import { Component } from 'react';

export class VideoComponent extends Component {
  videoContainer: HTMLDivElement;
  componentDidMount() {
    const video = document.createElement('video');
    video.autoplay = true;
    video.loop = true;
    video.muted = true; // fixes autoplay in chrome
    video.setAttribute('playsinline', 'true'); // fixes autoplay in webkit (ie. mobile safari)

    const source = document.createElement('source');
    source.src = '/path/to/your/video.mp4';
    source.type = 'video/mp4';
    video.appendChild(source);

    this.videoContainer.appendChild(video);
  }
  render() {
    return (
      <div ref={(ref) => { this.videoContainer = ref; }} />
    );
  }
}

Thank you for your consideration.

Indeed, ended up doing dangerouslySetHTML because React doesn't accept muted.

https://www.npmjs.com/package/react-html5video this component also seems to expose muted

ml242 commented

And still this issue persisted

While muted still doesn't show up as an attribute on the video element. Simply adding the playsInline makes the video play on iOS/Safari and Chrome.

<video className={s.video} src={video.url} autoPlay={video.autoplay} playsInline muted={video.mute ? 1 : null} loop={video.loop} />

As commented in 6544 this supposedly doesn't work in Chrome Android

I feel like this is a source of bugs and therefore not really a feature request. Is there any plan to work on this? None of the workarounds feel nice to me.

Agree with @adjohu, this is a bug, not a feature request. Video elements will not autoplay on in mobile Safari without the muted attribute. How hard can it be to just guarantee the attribute? I can use dangerouslySetInnerHTML for the video element, but I also need a ref to it, so that doesn't work.

Almost two years and there still is no good way of doing this :/

@joshverd is there ANY way of doing it? Haha. I can't find anything about a video element in react, with a muted attribute, and also with a ref. It's like I'm on Mars, I can't believe this is such an uncommon requirement 😬

@keithpickering
Actually, I got something working. It's really hack-ish but it works.

Basically, you want to save the element's 'ref'
<video ref={ref => this.videoRef = ref} src="link/to/video.mp4" />

Then you have to set the element's muted property to true and play the video all from within JS.

startVideoPlayback() {
  // Muting the video
  this.videoRef.muted = true;

  // Playing the video
  this.videoRef.play();
}

This is the only way I can get Chrome to "autoplay" video, but you still have to call the 'startVideoPlayback' function. Anything else and it gets paused by Chrome's autoplay detection.

Wow, that .play() works on mobile? I can never keep track of this dumb spec lol. That's an interesting approach, thanks!

Still experiencing this issue in React 16.11.0, muted attribute not showing up on the video element even though explicitly defined. Did I miss something?

stale commented

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

Still a valid issue

ml242 commented

updated a fiddle to reflect React 16 since the OP was still using 15.

still valid.

demo: https://jsfiddle.net/matt1234/vqnrc83a/1/

If you would like to have muted attribute present in HTML you can use this component (hook version) as well:

import { useRef, useEffect } from 'react'

const Player = () => {
  const videoRef = useRef(null)

  useEffect(() => {
    const { current: videoElement } = videoRef
    videoElement.setAttribute('muted', '')
  }, [])

  return (
    <video
      src="http://clips.vorwaerts-gmbh.de/VfE_html5.mp4"
      ref={videoRef}
      autoPlay
      playsInline
      muted
    />
  )
}

export default Player

Regarding autoplay:
Isn't it related with that: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes

When you are using e.g. jsfiddle.net to test your code snippet, everything is rendered inside iframe there and the default behavior for e.g. Chrome is to block autoplay of such videos. If you would like the autoplay to work, you can add something like this to your local application:
<iframe src="http://clips.vorwaerts-gmbh.de/VfE_html5.mp4" allow="autoplay" />

ml242 commented

@mar753 - that's an elegant solution using hooks but it doesn't answer the question of why this prop isn't being passed, despite being a known bug for years.

Any update on this? Seems like a simple fix.

@gaearon sorry to borther you, but someone should handle this problem since it's really annoying.

People who are just trying to autoplay muted

Thank you for your blog @boltcoder, but to me using dangerouslySetInnerHtml was not the solution I was expecting. I am expecting my muted attribute to be reflected inside the dom, I think browsers also expect that.

From Google documentation about Autoplay policy changes (2017-09):

One cool way to engage users is about using muted autoplay and let them chose to unmute (see code snippet below).
Some websites already do this effectively, including Facebook, Instagram, Twitter, and YouTube.

<video id="video" muted autoplay>
<button id="unmuteButton"></button>

<script>
  unmuteButton.addEventListener('click', function() {
    video.muted = false;
  });
</script>

The example speaks by itself: chrome is expecting the dom to contain the muted attribute, just like it's expecting it to contain the autoplay attribute.

Ofcourse it's not the right solution. But that's what I had to do to make it work in React. I'm just helping people out on tight deadlines to circumvent the limitations while react fixes (or doesn't fix the attribute to dom guarantee issue).

3 years later and facing the same issue here.

<video muted is stripped out from the HTML.

Quite surprised as this feature was not considered in 3 years given that it is really necessary for who is developing a website with a lot of background/autoplay videos.

See: https://webkit.org/blog/6784/new-video-policies-for-ios/

elements will also be allowed to autoplay without a user gesture.
If a element gains an audio track or becomes un-muted without a user gesture, playback will pause.

There is any specific reason to not implement this? Would a PR be welcomed?

Still a bug, can't believe I was just forced to use dangerouslySetHTML to get around this…

Still a bug, can't believe I was just forced to use dangerouslySetHTML to get around this…

you dont need to use dangerouslySetHTML, try this: #6544 (comment)

What is the latest proposal in this thread? I think the issue has stalled because there was no single proposal that people could get behind. It would help to flesh out what exactly the behavior should be with regards to whether it should be setting an attribute or a property, and whether it should be controlled or uncontrolled. Once we have a proposal, it's easier for someone to make a PR.

ml242 commented

proposal: the muted attribute when passed as a prop to a video component with a boolean value will be added to the dom.

What is your plan for defaultMuted vs muted, controlled vs uncontrolled, server rendering?

ml242 commented

incremental progress :)

Sorry, I appreciate your intent but this is not helpful and does not move the thread forward. This thread has stalled because it doesn't have a concrete proposal. If you don't have a concrete technical proposal (what you posted is vague), let's wait for somebody to post theirs. Concretely, there were concerns in #10389 (comment) that need to be addressed.

ml242 commented

From the React16 intro:

In the past, React used to ignore unknown DOM attributes. If you wrote JSX with an attribute that React doesn’t recognize, React would just skip it. For example, this:

// Your code:
<div mycustomattribute="something" />
would render an empty div to the DOM with React 15:

// React 15 output:
<div />
In React 16, we are making a change. Now, any unknown attributes will end up in the DOM:

// React 16 output:
<div mycustomattribute="something" />

So does React omit 'muted' because it is known? Why does React needs to take into account each scenario including SSR / defaultMuted when we have an existing convention that developers should be able to follow?

React, in general, needs to take every scenario into account. :-) It doesn't do that for arbitrary attributes that don't have special behavior because... they don't have special behavior. In this case, there are two different backing properties, and we need make sure that the behavior makes sense (i.e. it follows React's controlled/uncontrolled model like checked and value do).

In React, generally saying, JSX attributes specify the current value rather than the initial one.

<input value={myState} />

means that if myState gets set by some other part of the code, the input value will reflect it.

So if muted={true} meant "initially muted" it would not be consistent with how other similar JSX attributes work.

Instead, to specify the initial behavior, we use properties starting with default: defaultValue, defaultChecked. This is because DOM properties that mirror the attribute are called with this convention.

So it seems like what we really want here is support for defaultMuted?

ml242 commented

Works for me and helps devs conform to mdn's recomendations here.

I created a PR that fixes this issue:
#20087

note, previous PR won't add defaultMuted, instead it put into the DOM the muted property and fixes the unit testing failing with

@gaearon My understanding here is that the agreed solution for this is to introduce defaultMuted to be consistent.
Do you think this could be a "good first issue" to tackle by looking at other default* props or there is more than that?
Is somebody already working in that direction?

This issue is still under consideration?

I'm working on audio/video autoplay and I face the problem with muted attr.
https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability
It would be easy to pass the policy when doing autoplay with muted=true.

5 years now. Hopefully this will be fixed soon.

stale commented

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

bump

bump

Bumps are only helpful when the stalebot comments. Other than that it’s just noise.

Let's hope for a fix for 2030.

Would someone like to submit a PR for the proposal in #10389 (comment)? I'm not saying the team will necessarily accept it but it would help to get this rolling. I agree we need some fix for this.

Also, the audio element has the same issue.

Bump. Can get it constantly using Safari (somehow in Chrome it is happening)

This is really ridiculous. So many years, and still no solution for mobile backdrop video?

still no solution

There very much is a solution. As always, when some DOM feature is not exposed via React, you need to use a ref. For example, like in #10389 (comment) or #10389 (comment), depending on what you're trying to do.

The issue is not about that, but about a built-in solution. That's separate and does not block you from the workaround.

I want to work on this issue . Can anyone explain me in detail what is the issue?

This works for me on chrome.

<video
...
onCanPlay={ (e) => { e.target.muted=true; e.target.play() } }
/>

I had to do some god awful manual DOM manipulation to work around this issue that has been outstanding now for 5 years. Nearly gave me an aneurysm...

It seems to me that this issue is concerned with two problems:

  1. fear of <video autoplay muted /> not auto-playing because the muted attribute isn't set
  2. add support of defaultMuted React prop

The implementation of defaultMuted wouldn't necessarily fix 1. as far as I can tell. There would still be no muted attribute if I want to control "mutedness". Unless the proposal is to always use <video defaultMuted muted /> which is confusing considering how other default* props behave.

However, I don't think there is an issue with <video autoplay muted /> in modern browsers using React 18. I created a codesandbox that tests different scenarios: https://codesandbox.io/s/video-autoplay-muted-7b65ib. The following browsers autoplay <video autoPlay muted />:

  • Chrome Version 103.0.5060.53 (Official Build) (64-bit) (Linux)
  • Chrome Version 103.0.5060.66 (Official Build) (64-bit) (Windows)
  • Chrome Version 103.0.5060.70 (Android 12; Pixel 4a)
  • Edge Version 95.0.1020.40 (Official build) (64-bit) (Windows)
  • Firefox 97.0.1 (64-Bit) (64-bit) (Windows)
  • Firefox 101.0.1 (64-bit) (Linux)

The following browsers do not autoplay this video (even if the muted attribute is present):

  • Chrome (iOS 15.5)
  • Safari (iOS 15.5)

So it would help to focus this issue if somebody could explain what concrete issue (browser + version + os) there is with not setting the muted attribute if we render <video muted />.

If there is none then we can re-focus this issue to controlled vs uncontrolled muted on <video />.

This absence of muted property by default is more important due to:

In some browsers (e.g. Chrome 70.0) autoplay doesn't work if no muted attribute is present.

Considering Chrome is an evergreen browser and version 70 is over 3 years old, it's probably not worth it to work around this quirk. Current versions are working as expected (see #10389 (comment))

@eps1lon no browser is evergreen, despite its marketing claims. Most companies' analytics still have a swath of old Chrome usage.

@eps1lon you can't for sure consider it as "not worth it". My scenario is I have a website builder (take Ucraft, WordPress, Wix, Webflow, etc...) in which you, as a developer, don't fully control the generated dom (structure).

Website designers can drop a video widget/element on the page, and turn on/off some controls/tweaks, like AutoPlay, Mute, Loop, Controls, etc...

The website designer turns on AutoPlay (which by the way must turn on the Mute too).

To ensure your customer's website works well on all of their customers' devices, we need to have this dom property explicitly there. As I personally faced this issue in real life, in which on an iOS device autoPlay was not working.

My workaround was:

  useEffect(() => {
    // autoplay does not work on some browsers if video does not have `muted`
    // attribute:
    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-autoplay

    // and reacts ignores muted attribute:
    // https://github.com/facebook/react/issues/10389

    if (videoRef.current) {
      if (mute) {
        videoRef.current.setAttribute('muted', '');
      } else {
        videoRef.current.removeAttribute('muted');
      }
    }
  }, [mute]);

you can't for sure consider it as "not worth it".

Sure because that's a subjective statement. But just like we don't work around browser quirks for legacy browsers like IE 11 we might not want to work around browser quirks for unmaintained Chrome versions. Especially if the issue is fixed in later versions.

Otherwise everybody has to pay for this workaround even though they don't need to. I think it's completely fine to use your workaround if you need to support older browser versions. Arguing that usage implies the need for a fix is simply not practical considering that there are far fewer browser versions with bug fixes than browser versions without these bug fixes. So this has to be done on a case-by-case basis.

The only data point we have so far is #10389 (comment). So if you want to contribute to this issue you might want to start collecting a browser matrix where React needs to explicitly set the muted attribute and where it doesn't. Then we can make an informed decision whether to fix the quirk in React.

I got to this issue today sure is old, I have been reading the whole day things from 2016 until now
has anyone got autoplay work on mobile?
I have injected the muted attribute to the video tag like previous answers but it simply didn't play on mobile

I got to this issue today sure is old, I have been reading the whole day things from 2016 until now has anyone got autoplay work on mobile? I have injected the muted attribute to the video tag like previous answers but it simply didn't play on mobile

@johiny Assuming you tested this on iOS, it's a Safari issue not a React issue. This would match my testing as well:

The following browsers do not autoplay this video (even if the muted attribute is present):

  • Chrome (iOS 15.5)
  • Safari (iOS 15.5)

@eps1lon I tested it on Chrome 105 and Brave 1.43 both on (Android 10) maybe it has something to do with the manufacturer layer in this case MIUI?

@eps1lon I tested it on Chrome 105 and Brave 1.43 both on (Android 10) maybe it has something to do with the manufacturer layer in this case MIUI?

Might be. When I tested it for Android 12 on my Pixel 4a it was working. But that was with Chrome 103.

Can you check https://7b65ib.csb.app/ and tell me which video is auto-playing?

@eps1lon every single one worked both on Chrome and Brave, I will have to keep testing

@eps1lon every single one worked both on Chrome and Brave, I will have to keep testing

Then there's something off with your specific setup. This demo highlights that this issue is no longer relevant to current React versions.

@eps1lon every single one worked both on Chrome and Brave, I will have to keep testing

Then there's something off with your specific setup. This demo highlights that this issue is no longer relevant to current React versions.

yeah, I keep testing and find that something was happening with my video when it tried to start I was getting a "PIPELINE_ERROR_DECODE: video decoder reinitialization failed" error that looks like was a conflict between my video and android codecs, so if anyone is facing similar I recommend put an onError on the video can be really useful to see if something weird is happening and re-encode your video.
but thx for the help @eps1lon!

This can be helpful

function VideoComponent({ muted }) {
  const videoProps = { ...(muted && { muted: true }) };

  return <video {...videoProps} />;
}

Is there a proper solution to this problem? Also, I don't understand what the issue is with stripping out this attribute at all.

Is somebody more up-to-date on the topic to tell me what the actual issue is? A ticket since 2016, which many people seem to have a problem with, but got never addressed.

The solution is to take control of the video element in the dom since React is refusing to handle it properly. Assign a ref and call play() or pause() as needed

I've just made a ref
const isMuted = useRef(false);
then set the video component as:
<video muted={isMuted.current} ... />
and it shows correctly into the dom and autoplay works on iOS/macOS.

I came up with another possible issue. The muted attribute sometimes doesn't get rendered. So I set it like this:

const videoRef = useRef(null);
useEffect(() => {
    videoRef.current.defaultMuted = true;
})

We are come with this solution and it is working for us. But it requires dangerouslySetInnerHTML.

<div dangerouslySetInnerHTML={{
      __html: function () {
          let videoContainer = document.createElement("video");
          let source = document.createElement("source");
          videoContainer.setAttribute("autoplay", "");
          videoContainer.setAttribute("muted", "");
          videoContainer.setAttribute("loop", "");
          source.src = "VIDEO_URL";
          videoContainer.appendChild(source);
          return videoContainer.outerHTML;
      }()
}}/>

@eps1lon's example doesn't work in iOS because of the lack of playsinline, https://33v428.csb.app/ is an updated example that has it.

The updated example seems to work fine without any trickery.

Grawl commented

lol