remotion-dev / remotion

🎥 Make videos programmatically with React
https://remotion.dev
Other
20.58k stars 1.03k forks source link

@remotion/player problems (gabs between videos, repeated data fetching) #750

Closed PaddyWitzigmann closed 2 years ago

PaddyWitzigmann commented 2 years ago

I want to build a web app where I play multiple videos in a row. As a video source, I use urls / videos in the cloud.

I am facing the following problems and would be super thankful if you could help me with this.

1) The videos are not played smoothly after each other as specified. There is always a gap between the videos. 2) The videos are loaded every time during play. Even when replaying videos that have already been played. 3) Is it possible to preload the video sequence like with a normal video tag? 4) Is there the possibility to have a loading spinner when the data of the currently playing video isn't available yet? Like with a normal video tag?

Here is a sandbox with the code: https://codesandbox.io/s/remotion-player-testing-wccnf?file=/src/App.js

Thank you very much for your support!

JonnyBurger commented 2 years ago

Hi Patrick! Good to see you are working on something!

Preloading the assets is currently the responsibility of the user, although we might add native APIs for it at some point. The basic example of a preload would looks something like this:

const preload = (video) => {
  const link = document.createElement("link");
  link.rel = "preload";
  link.href = video;
  link.as = "video";

  document.head.appendChild(link);
};

preload("https://player.vimeo.com/external/291648067.hd.mp4?s=94998971682c6a3267e4cbd19d16a7b6c720f345&profile_id=175&oauth2_token_id=57447761")

Unfortunately your scenario is a special case, because that URL redirects to another URL and then it doesn't work. So for it to work in your scenario, we need to first resolve the real URL and then preload that and use it as the video source:

import { useEffect, useState } from "react";
import { Video, Series } from "remotion";

const resolveRedirect = async (video) => {
  const res = await fetch(video);
  return res.url;
};

const preload = async (video) => {
  const url = await resolveRedirect(video);
  const link = document.createElement("link");
  link.rel = "preload";
  link.href = url;
  link.as = "video";

  document.head.appendChild(link);
};

export const VideoComposition = () => {
  const [resolvedUrls, setResolvedUrls] = useState(null);

  useEffect(() => {
    Promise.all([
      resolveRedirect(
        "https://player.vimeo.com/external/291648067.hd.mp4?s=94998971682c6a3267e4cbd19d16a7b6c720f345&profile_id=175&oauth2_token_id=57447761"
      ),
      resolveRedirect(
        "https://player.vimeo.com/external/269971860.hd.mp4?s=eae965838585cc8342bb5d5253d06a52b2415570&profile_id=174&oauth2_token_id=57447761"
      )
    ]).then((vids) => {
      vids.forEach((vid) => preload(vid));
      setResolvedUrls(vids);
    });
  }, []);

  if (resolvedUrls === null) {
    return null;
  }

  return (
    <Series>
      <Series.Sequence durationInFrames={60}>
        <Video src={resolvedUrls[0]} />
      </Series.Sequence>
      <Series.Sequence durationInFrames={60}>
        <Video src={resolvedUrls[1]} />
      </Series.Sequence>
    </Series>
  );
};

Note that now all videos are fully predownloaded before the video can be played. You could make the solution "smarter" by only downloading a certain amount of videos in advance.

One thing that is not so nice unfortunately is that even though the video is now preloaaded, there is still a 0.05sec white flicker while the video is loaded from memory. The only way I could find a way to alleviate it is to mount two videos at the same time for a split second:

-      <Series.Sequence durationInFrames={60}>
+      <Series.Sequence durationInFrames={60} offset={-1}>

More research regarding this is needed, but I believe it should get you unstuck.

Updated code here: https://codesandbox.io/s/remotion-player-testing-forked-192ll?file=/src/VideoComposition.js:1208-1266

You can also render a spinner for a video by attaching a onLoad listener to the <Video> element and make the video invisible before it is called.

If the answer solves your problem, please close the issue!

PaddyWitzigmann commented 2 years ago

Thanks for your help Jonny!! :)