49

I want to update the source tag in a HTML5 video element so that when I click a button, whatever's playing switches to a new video.

I have a Clip component that returns an HTML5 video element, with the source URL provided via props.

function Clip(props) {
  return (
    <video width="320" height="240" controls autoPlay>
      <source src={props.url} />
    </video>
  )
}

I also have a Movie component, which contains multiple URLs, each corresponding to a section of the movie. It also contains a position attribute, which acts like an iterator by remembering which section is currently playing.

class Movie extends Component {
  constructor() {
    super();
    this.state = {
      sections: [
        'https://my-bucket.s3.amazonaws.com/vid1.mp4',
        'https://my-bucket.s3.amazonaws.com/vid2.mp4'
      ],
      position: 0
    };
  }
  render() {
    const sections = this.state.sections
    const position = this.state.position

    return (
      <div>
        {position}
        <Clip url={sections[position]} />
        <button onClick={() => this.updatePosition()}>Next</button>
      </div>
    )
  }
  updatePosition() {
    const position = this.state.position + 1;
    this.setState({ position: position });
  }
}

The Movie component renders a Clip component and a "Next" button. When the Next button gets clicked, I want to update the position attribute and re-render the HTML5 video element using the next URL from sections.

As of now, when I hit Next the HTML5 video source tag updates, but the element continues playing the video from the previous URL. Can anyone help me figure out how to reset the video element?

Update: I created a JSFiddle, which you can see here.

Update 2: Updating the src attribute on the video element works!

Sherwood Callaway
  • 1,428
  • 1
  • 14
  • 18

5 Answers5

181

Quick and dirty awnser: Add a key prop to <Clip> or <video>, e.g.:

function Clip({ url }) {
  return (
    <video key={url}>
      <source src={url} />
    </video>
  );
}

Recommended awnser: Trigger a load event for the video element whenever there's a new URL, e.g.:

function Clip({ url }) {
  const videoRef = useRef();

  useEffect(() => {    
    videoRef.current?.load();
  }, [url]);

  return (
    <video ref={videoRef}>
      <source src={url} />
    </video>
  );
}

Explanation: The video initially doesn't change because in essence you're only modifying the <source> element and React understands that <video> should remain unchanged, so it does not update it on the DOM and doesn't trigger a new load event for that source. A new load event should be triggered for <video>.

The dirty answer works because when you change the key of a React element, React understands that's a whole new element. It then creates a new element in the DOM which naturally will have its own load event triggered on mount.

Ramon Balthazar
  • 3,907
  • 3
  • 25
  • 34
  • I needed to use `createRef` instead of `useRef` – Daniel Lizik Oct 17 '21 at 04:41
  • @DanielLizik, that's most certainly because you have a class component, correct? – Ramon Balthazar Oct 18 '21 at 09:43
  • no, because I'm storing the video element ref in some 3rd party state lib. with `useRef`, the ref never changes, so even though `source` is updated correctly react continues to use the old ref; for me it created a strange bug where the video went black, but the audio from the previous video continued playing – Daniel Lizik Oct 19 '21 at 05:04
  • 2
    Use `const videoRef = useRef(null);` for typescript. – DollarAkshay Oct 26 '22 at 22:27
  • @RamonBalthazar Do you know if there is any performance/other benefits of using the second version compared to the first ? – DollarAkshay Oct 26 '22 at 22:28
  • @DollarAkshay, I assume in terms of performance they are the same. The second method is recommended if you've already made changes to the `video` element that you want to keep while changing sources. – Ramon Balthazar Oct 27 '22 at 10:08
  • I think both do pretty much the same(re-render video tag) but to make code clean and understandable go with option 2. – Akash Kumar Seth Feb 03 '23 at 17:38
22

Changing the src of <source /> doesn't seem to switch the video for some reason. This isn't a react issue I don't think. Probably just how <source /> works. Maybe you have to call .load() on the element again. But it's just easier to set it directly on the element itself.

See the comment to the top answer: Can I use javascript to dynamically change a video's source?

You can set src directly on element instead:

function Clip(props) {
  return (
    <video width="320" height="240" src={props.url} controls autoPlay>
    </video>
  )
}
Community
  • 1
  • 1
Martin Dawson
  • 7,455
  • 6
  • 49
  • 92
0

Bind the URL in the src property of Video tag and not Source Tag

<video autoplay loop muted playsinline="true" webkit-playsinline="true" [src]="videoSrc" type="video/mp4">

starforce
  • 21
  • 4
0

Add a parent div to second video
My code:

{isMobile ? (
            <video
              autoPlay={true}
              loop
              className="moviles"
              muted
              playsInline
              poster="/totto/kids.png"
            >
              <source src="/totto/losmoviles.webm"></source>
              <source src="/totto/losmoviles.mp4"></source>
            </video>
          ) : (
            <div>
              <video
                autoPlay={true}
                loop
                className="moviles2"
                muted
                playsInline
                poster="/totto/portada.jpg"
              >
                <source src="/totto/moviles.webm"></source>
                <source src="/totto/moviles.mp4"></source>
              </video>
            </div>
          )}
Jose
  • 23
  • 3
0

You can use the key attribute :

<video key={videoUrl} autoPlay width="100%">
  <source src={videoUrl} type="video/mp4"></source>
</video>

If the video's key attribute change, the video component will be re-mounted