Updating source URL on HTML5 video with React

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!

18 thoughts on “Updating source URL on HTML5 video with React”

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

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

    function Clip({ url }) {
      const videoRef = useRef();
      const previousUrl = useRef(url);
    
      useEffect(() => {
        if (previousUrl.current !== url) {
          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.

    Reply
  2. 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>
      )
    }
    
    Reply

Leave a Comment