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

<video /> attribute needed but not guaranteed by React #10389

Open
elrumordelaluz opened this issue Aug 4, 2017 · 91 comments
Open

<video /> attribute needed but not guaranteed by React #10389

elrumordelaluz opened this issue Aug 4, 2017 · 91 comments

Comments

@elrumordelaluz
Copy link

elrumordelaluz commented Aug 4, 2017

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

@syranide
Copy link
Contributor

syranide commented Aug 6, 2017

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.

@montogeek
Copy link
Contributor

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

@syranide
Copy link
Contributor

syranide commented Aug 8, 2017

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

@aweary
Copy link
Contributor

aweary commented Sep 15, 2017

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.

@kiurchv
Copy link

kiurchv commented Nov 28, 2017

@aweary Any progress on this?

@NicolasPoirierB
Copy link

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

@jquense
Copy link
Contributor

jquense commented Apr 4, 2018

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

@syranide
Copy link
Contributor

syranide commented Apr 4, 2018

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

@aweary
Copy link
Contributor

aweary commented Apr 4, 2018

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.

@jquense
Copy link
Contributor

jquense commented Apr 4, 2018

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

@aweary
Copy link
Contributor

aweary commented Apr 4, 2018

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.

@jquense
Copy link
Contributor

jquense commented Apr 4, 2018

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

@aweary
Copy link
Contributor

aweary commented Apr 4, 2018

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 😅

@aweary
Copy link
Contributor

aweary commented Apr 4, 2018

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

@vinay72
Copy link

vinay72 commented Oct 9, 2018

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

ctate commented Dec 27, 2018

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.

@tomasdev
Copy link

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

@aweary aweary removed their assignment Jan 24, 2019
@Zemnmez
Copy link

Zemnmez commented Feb 22, 2019

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

@ml242
Copy link

ml242 commented Mar 6, 2019

And still this issue persisted

@richardnadj
Copy link

richardnadj commented Mar 15, 2019

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

@adjohu
Copy link

adjohu commented Apr 2, 2019

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.

@keithpickering
Copy link

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.

@eps1lon
Copy link
Collaborator

eps1lon commented Jul 3, 2022

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

nickhsine added a commit to nickhsine/readr-react that referenced this issue Jul 12, 2022
…manually

We set `audio.currentTime` and `audio.muted` manually for the following situation:
The default behavior is to autoplay the audio and quote shadow animation.

However, the modern browser will block autoplay if the audio
is not set to `muted`. (For more information, please see
https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability)
(aftermath: `audio.play()` will return promise rejection)

And the unlucky part is that we cannot set `muted` in the `audio` tag
due to this known issue (facebook/react#10389).
(aftermath: we have to change `muted` attribute by manipulating DOM
element)

Because of the above issues, we cannot play the audio with sound only if
users click the audio button. At the same time, the quote animation works
well.
(aftermath: audio and shadow animation are not synced)

When the users click the audio button, we have to play the audio at the
right `currentTime` to catch up the shadow animation.
(counting the `currentTime` by ourselves)

And this patch handles the above situation.
@aghArdeshir
Copy link

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

@eps1lon
Copy link
Collaborator

eps1lon commented Jul 14, 2022

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

@ljharb
Copy link
Contributor

ljharb commented Jul 14, 2022

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

@aghArdeshir
Copy link

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

@eps1lon
Copy link
Collaborator

eps1lon commented Jul 18, 2022

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.

nickhsine added a commit to nickhsine/orangutan that referenced this issue Jul 27, 2022
This patch removes `muted` attribute in `<video>` tag since React
does not support it yet. The related issue is
facebook/react#10389.
@johiny
Copy link

johiny commented Sep 28, 2022

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

@eps1lon
Copy link
Collaborator

eps1lon commented Sep 29, 2022

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)

@johiny
Copy link

johiny commented Sep 29, 2022

@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
Copy link
Collaborator

eps1lon commented Sep 29, 2022

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

@johiny
Copy link

johiny commented Sep 29, 2022

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

@eps1lon
Copy link
Collaborator

eps1lon commented Sep 29, 2022

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

@johiny
Copy link

johiny commented Sep 29, 2022

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

@Deeppjp116
Copy link

Deeppjp116 commented May 29, 2023

This can be helpful

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

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

@Betterhear Betterhear mentioned this issue May 29, 2023
@marcwieland95
Copy link

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.

@keithpickering
Copy link

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

@idoliki
Copy link

idoliki commented Jun 21, 2023

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.

@marcwieland95
Copy link

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

@rsvijaytiwari
Copy link

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

@BrendanBerkley
Copy link

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

Grawl commented Aug 5, 2024

lol

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