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
andpause
event listeners rather than trying to do with asetTimeout
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
3 Answers
Reset to default 9 +50Disclaimer
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:
setTimeout
does not care about network delays and other time related issues, so most probably your video sequence will not play seamlessly.- Because of the first flow and because I doubt that
duration
values are exact you should usepause()
. - As @IslamElshobokshy noted in ments to your question using
play/pause
is not so simple. 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