最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - play video in loop using setTimeout - Stack Overflow

programmeradmin2浏览0评论

I want to play videos in a loop. For some reason I don't want change video src on ended event. So I created video elements for each video in a loop. Also I have video src and durations in array.

here is my idea: Only current playing video tag can be visible. Others will be hided. Instead of using ended event, I want to use setTimeout function. Video's duration will be delay parameter.

But they all play together. I couldn't make them play in order.

Here is what I done so far:

videoArray = [
    {"video":video1.mp4, "duration": 5},
    {"video":video2.mp4, "duration": 7},
    {"video":video3.mp4, "duration": 9},
    {"video":video4.mp4, "duration": 10},
]

for (var j = 0; j < videoArray.length; j++){
    var video = document.createElement("video");
    video.src=videoArray[j];
    video.id="video-"+j;
    video.preload = "metadata";
    video.type="video/mp4";
    video.autoplay = true; 
    video.style.display="none";
    document.body.appendChild(video); 
}

for (var count = 0; count < videoArray.length; count++) {
    (function(num){
        setTimeout(function() {
            videoArray[num].video.style.display="block";
            videoArray[num].video.play();
        }, 1000 * videoArray[num].duration);
        videoArray[num].video.style.display="none";
    })(count);
}

I want to play videos in a loop. For some reason I don't want change video src on ended event. So I created video elements for each video in a loop. Also I have video src and durations in array.

here is my idea: Only current playing video tag can be visible. Others will be hided. Instead of using ended event, I want to use setTimeout function. Video's duration will be delay parameter.

But they all play together. I couldn't make them play in order.

Here is what I done so far:

videoArray = [
    {"video":video1.mp4, "duration": 5},
    {"video":video2.mp4, "duration": 7},
    {"video":video3.mp4, "duration": 9},
    {"video":video4.mp4, "duration": 10},
]

for (var j = 0; j < videoArray.length; j++){
    var video = document.createElement("video");
    video.src=videoArray[j];
    video.id="video-"+j;
    video.preload = "metadata";
    video.type="video/mp4";
    video.autoplay = true; 
    video.style.display="none";
    document.body.appendChild(video); 
}

for (var count = 0; count < videoArray.length; count++) {
    (function(num){
        setTimeout(function() {
            videoArray[num].video.style.display="block";
            videoArray[num].video.play();
        }, 1000 * videoArray[num].duration);
        videoArray[num].video.style.display="none";
    })(count);
}
Share Improve this question asked Mar 11, 2020 at 12:03 linepisodelinepisode 6511 bronze badges 4
  • 1 You need to make use of timeupdate, play and pause event listeners rather than trying to do with a setTimeout and keep this in mind : developers.google./web/updates/2017/06/… – Alexandre Elshobokshy Commented Mar 13, 2020 at 14:35
  • Why do you want to do it using setTimeout – Mufaddal Hamid Commented Mar 13, 2020 at 17:40
  • Keep in mind, that the user can change the video speed in firefox in the context menu. Not sure about other browsers. – artanik Commented Mar 14, 2020 at 6:47
  • I've added a second answer that utilizes setTimeout. Again, I do not remend going with that approach, but it is feasible. – Shahar Commented Mar 21, 2020 at 14:52
Add a ment  | 

3 Answers 3

Reset to default 9 +50

Disclaimer

I know that the question was asked without the ended event, but I do not think that set time out is the way to go.
Think of the scenario where you have video buffering, or slowing down for any reason, your setTimeout will be out of sync.

At the bottom I've added another solution that answers the requirement of not using the ended event, but again, I do not remend using it.

Solution

The idea is to have an event listener to the end of the video, in that case, even if you run the video on a different speed you are still going to run the next video regardless of the duration.

Another benefit is that you do not need to know the duration of the videos in the first place.

PS. the event listener that you need to listen to is video.addEventListener("ended", callback);

You are more than wele to run the code or to have a look at a working example I've created for you

Working Example

    const videoUrls = [
        'https://videos-play-looplify./video1.mp4',
        'https://videos-play-looplify.//video2.mp4',
        'https://videos-play-looplify.//video3.mp4',
    ];

    const createVideo = ({id, src, width, cls = 'video', display = 'block', playbackRate = 1, muted = true, type = 'video/mp4', autoplay = false, controls = true}) => {
        const videoElement = document.createElement("video");
        videoElement.id = id;
        videoElement.src = src;
        videoElement.classList.add(src);
        videoElement.type = type;
        videoElement.autoplay = autoplay;
        videoElement.controls = controls;
        videoElement.style.display = display;
        videoElement.muted = muted;
        videoElement.playbackRate = playbackRate;
        return videoElement;
    };


    const addVideos = (container, videoUrls) => {
        const videos = videoUrls.map((url, index) => {
            const first = index === 0;
            const display = first ? 'block' : 'none';
            return createVideo({id: `video-${index}`, src: url,display, width: 640, autoplay: first, playbackRate: 3});
        });
        videos.forEach((video, index) => {
            const last = index === videos.length - 1;
            const playNext = (element) => {
                element.target.style.display = "none";
                const nextElementIndex = last ? 0 : index + 1;
                const nextElement = videos[nextElementIndex];
                nextElement.autoplay = true;
                nextElement.style.display="block";
                nextElement.load();
            };
            video.addEventListener("ended", playNext);
            container.appendChild(video)
        });
    };
    const videoWrapper = document.getElementById('video-wrapper');
    addVideos(videoWrapper, videoUrls);
#video-wrapper video {
    max-width: 600px;
}
<div id="video-wrapper"></div>

Working solution with setTimeout (please use the solution above)

const videoUrls = [{
    url: `https://videos-play-looplify./video3.mp4`,
    duration: 3,
  },
  {
    url: `https://videos-play-looplify./video2.mp4`,
    duration: 4
  },
  {
    url: `https://videos-play-looplify./video1.mp4`,
    duration: 5
  }
];


const createVideo = ({
  id,
  src,
  width,
  cls = 'video',
  display = 'block',
  duration,
  playbackRate = 1,
  muted = true,
  type = 'video/mp4',
  autoplay = false,
  controls = true
}) => {
  const videoElement = document.createElement("video");
  videoElement.id = id;
  videoElement.src = src;
  videoElement.classList.add(src);
  videoElement.type = type;
  videoElement.autoplay = autoplay;
  videoElement.controls = controls;
  videoElement.style.display = display;
  videoElement.muted = muted;
  videoElement.playbackRate = playbackRate;
  videoElement.setAttribute('data-duration', duration);
  return videoElement;
};

const playNext = (videos, index) => {
  const current = videos[index];
  const activeVideoDuration = parseInt(current.dataset.duration) * 1000;
  setTimeout(() => {
    const last = index === videos.length - 1;
    current.style.display = "none";
    current.pause();
    const activeVideoIndex = last ? 0 : index + 1;
    const next = videos[activeVideoIndex];
    next.autoplay = true;
    next.style.display = "block";
    next.load();
    next.play();
    playNext(videos, activeVideoIndex);
  }, activeVideoDuration);
};


const addVideos = (container, videoUrls) => {
  const videos = videoUrls.map((video, index) => {
    const {
      url,
      duration
    } = video;
    const first = index === 0;
    const display = first ? 'block' : 'none';
    return createVideo({
      id: `video-${index}`,
      src: url,
      duration,
      display,
      width: 640,
      autoplay: first,
    });
  });

  videos.forEach(video => container.appendChild(video));
  playNext(videos, 0);
};

const videoWrapper = document.getElementById('video-wrapper');
addVideos(videoWrapper, videoUrls);
#video-wrapper video {
  max-width: 600px;
}
<div id="video-wrapper"></div>

You could hold the duration of the videos in a variable and the accumulate this variable with the previous video duration and set this a the setTimeOut duration.

Note that the time of the videos is in seconds. And for the first video to play user has to interact otherwise the video will not play.

Working Example:

function startVideos(event) {
 event.target.style.display= "none";
        (function() {
          videoArray = [
            {
              video:
                "http://techslides./demos/sample-videos/small.mp4",
              duration: 5
            },
            {
              video:
                "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
              duration: 60
            },
            {
              video:
                "https://mobamotion.mobatek/samples/sample-mp4-video.mp4",
              duration: 120
            },
            {
              video:
                "http://mondatastorage.googleapis./gtv-videos-bucket/sample/BigBuckBunny.mp4",
              duration: 600
            }
          ];

          let videArrayElem = [];

          for (var j = 0; j < videoArray.length; j++) {
            var video = document.createElement("video");
            video.src = videoArray[j].video;
            video.id = "video-" + j;
            video.preload = "metadata";
            video.type = "video/mp4";
            video.autoplay = false;
            video.controls= true;
            video.style.display = "none";
            videArrayElem.push(video);
            document.body.appendChild(video);
          }

          let prviousVideoDuration = 0;
          for (var count = 0; count < videoArray.length; count++) {
            (function(num) {
              setTimeout(function() {
                videArrayElem[num].style.display = "block";
                videArrayElem[num].play();
              }, prviousVideoDuration);
              prviousVideoDuration += 1000 * videoArray[num].duration;
              videArrayElem[num].style.display = "none";
            })(count);
          }
        })();
      }
video {
        height: 100px;
        width: 100px;
        display: inline-block;
        margin: 4px;
        float: left;
      }
<button type="button" onclick="startVideos(event)">Start Video Demo</button>

Aside from the facts that you are trying to achieve something strange, and that your code example will not pile. As I see it, you've got it. The only missing thing is pause() and display = "none". With minimum edits you have what you need.

const videoArray = [
    {"video":"video1.mp4", "duration": 5}, // of cause you need "" 
    {"video":"video2.mp4", "duration": 7},
    {"video":"video3.mp4", "duration": 9},
    {"video":"video4.mp4", "duration": 10},
]

for (let j = 0; j < videoArray.length; j++){
    const video = document.createElement("video");
    // you need to save DOM nodes somewhere to use them in the second loop
    videoArray[j].video_el = video
    video.src=videoArray[j].video; // `.video` is added
    video.id="video-"+j;
    video.preload = "metadata";
    video.type="video/mp4";
    video.autoplay = true; 
    video.style.display="none";
    document.body.appendChild(video); 
}

for (var count = 0; count < videoArray.length; count++) {
    (function(num){
        setTimeout(function() {
            // add this to hide previous video node and stop video from playing 
            if( num ) {
                videoArray[num-1].video_el.style.display="none";
                videoArray[num-1].video_el.pause()
            }
            // videoArray[num].video - is a string, not a DOM node
            // so, you need to change this:
            // videoArray[num].video.style.display="block";
            // for this:
            videoArray[num].video_el.style.display="block";
            // no need. `autoplay` is set to `true` in the first loop
            // videoArray[num].video_el.play();
        }, 1000 * videoArray[num].duration);
        // no need. already done in the first loop
        // videoArray[num].video_el.style.display="none";
    })(count);
}

But there is a lot of flaws:

  1. setTimeout does not care about network delays and other time related issues, so most probably your video sequence will not play seamlessly.
  2. Because of the first flow and because I doubt that duration values are exact you should use pause().
  3. As @IslamElshobokshy noted in ments to your question using play/pause is not so simple.
  4. If user opens the page and does nothing more, you'll get:

    Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.

    in Chrome. And

    Autoplay is only allowed when approved by the user, the site is activated by the user, or media is muted.
    NotAllowedError: The play method is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.

    in Firefox.

So you'd probably be better off with the ended event after all, and with muted attribute (to mitigate the last issue):

for (let j = 0; j < videoArray.length; j++){
    const video = document.createElement("video")
    videoArray[j].video_el = video

    video.src      = videoArray[j].video
    video.id       = "video-"+j
    video.preload  = "metadata"
    video.type     = "video/mp4"
    video.autoplay = true

    // show the first video right away
    if( j !== 0 ) video.style.display = "none"
    // set muted attribute
    video.muted = "muted"
    // play next video after the previous had ended
    video.addEventListener('ended', (num => function() {
        this.style.display = "none";
        if( num !== videoArray.length-1 ) {
            videoArray[num+1].video_el.style.display="block";
            // only needed if `muted` is not set 
            videoArray[num+1].video_el.play();
        }
    })(j), false);

    document.body.appendChild(video); 
}

If muted attribute is not the option, you can add some event listeners on the body to call play() on the first video as soon as the user starts interaction with the page. And maybe you would like to read more about autoplay here: https://developer.mozilla/en-US/docs/Web/Media/Autoplay_guide

发布评论

评论列表(0)

  1. 暂无评论