What would be the best way to play multiple YouTube at once? I want them to be synced at the millisecond and therefore not to be affected by buffering issues, or advertisements of different lengths.
Update 1:
I think my question would be answered sufficiently if I would get an answer to the following question:
How can you detect using the javascript API of YouTube when the video is able to play (video has buffered sufficiently to be able to play / advertisement is not playing / video is not halted for any other reason)?
update 2:
The basic idea of YouTube syncing has been done by SwigView. The only thing missing is is for the video's to be synced more precisely at which SwigView didn't do a great job implementing.
I am starting to doubt it is even possible with the current API and I am looking at alternative approaches.
It's possible to sync two YouTube iFrame API players within a tolerance of 0.2s or better by measuring and adjusting the time difference between the two players in regular intervals. For example, the adjustment for a time difference of X milliseconds can be done pretty accurately by doubling or halving the playback speed of one player and setting it back to normal speed after X/2 milliseconds. Helpers for user interaction (stop, play, pause) can be added from the regular API repertoire. They also cover advertisements, since they pause the players.
Code to clarify:
script.js
// the players
var player1;
var player2;
// the rules
var syncThreshold=0.2; // seconds, threshold for an acceptable time difference to prevent non-stop syncing
var jumpThreshold=2; // seconds, threshold for a time difference that should be corrected by a rough jump
var jumpDeadTime=500; // milliseconds, a dead time in which we don't sync after a jump
// timeouts and intervals
var timeSyncInterval;
var syncActionTimeout=undefined;
// The YouTube API calls this once it's ready
function onYouTubeIframeAPIReady() {
player1 = new YT.Player('somediv1', {
videoId: 'zkv-_LqTeQA',
events: {
onReady: syncTime,
onStateChange: syncStateChange
}
});
player2 = new YT.Player('somediv2', {
videoId: 'zkv-_LqTeQA'
});
}
// the syncing magic
function syncTime(){
// making sure the syncing interval has not been set already for some reason
clearInterval(timeSyncInterval);
// setting a 1s interval in which we check it the players are in sync and correct in necessary
timeSyncInterval = setInterval(function () {
// if the timeout is already set, we are already trying to sync the players, so we don't have to do it again
if(syncActionTimeout==undefined){
// measure the time difference and calculate the duration of the sync-action
var time1=player1.getCurrentTime();
var time2=player2.getCurrentTime();
var timeDifference=time2-time1;
var timeDifferenceAmount=Math.abs(timeDifference);
var syncActionDuration=1000*timeDifferenceAmount/2;
if(timeDifferenceAmount>jumpThreshold){
// the players are too far apart, we have to jump
console.log("Players are "+timeDifferenceAmount+" apart, Jumping.");
player2.seekTo(player1.getCurrentTime());
// we give the player a short moment to start the playback after the jump
syncActionTimeout=setTimeout(function () {
syncActionTimeout=undefined;
},jumpDeadTime);
}else if(timeDifference>syncThreshold){
// player 2 is a bit ahead of player 1, slowing player 2 down
console.log("Player 2 is "+timeDifference+"s ahead of player 1. Syncing.");
player2.setPlaybackRate(0.5);
// setting a timeout that fires precisely when both players are sync
syncActionTimeout=setTimeout(function () {
// the players should be sync now, so we can go back to normal speed
player2.setPlaybackRate(1);
syncActionTimeout=undefined;
},syncActionDuration);
}else if(timeDifference<-syncThreshold){
console.log("Player 1 is "+(-timeDifference)+"s ahead of player 2. Syncing.");
// player 1 is bit ahead of player 2, slowing player 2 down
player2.setPlaybackRate(2);
// setting a timeout that fires precisely when both players are sync
syncActionTimeout=setTimeout(function () {
// the players should be sync now, so we can go back to normal speed
player2.setPlaybackRate(1);
// undefining the timeout to indicate that we're done syncing
syncActionTimeout=undefined;
},syncActionDuration);
}
}
},1000);
}
// a little helper to deal with the user
function syncStateChange(e){
if(e.data==YT.PlayerState.PLAYING){
player2.seekTo(player1.getCurrentTime());
player2.playVideo();
}else if(e.data==YT.PlayerState.PAUSED){
player2.seekTo(player1.getCurrentTime());
player2.pauseVideo();
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<title>Sync Two Youtube Videos</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- CDN -->
<script type="text/javascript" src="//www.google.com/jsapi"></script>
<script type="text/javascript" src="https://apis.google.com/js/client.js?onload=onJSClientLoad"></script>
<script src="https://www.youtube.com/iframe_api"></script>
<!-- HOSTED -->
<script src="script.js"></script>
</head>
<body>
<div id="somediv1"></div>
<div id="somediv2"></div>
</body>
</html>
Related
I'm working on a kind of stopwatch/alarm, that's supposed to be running on a website using a modified wordpress plugin. It's function will later be, to start the stopwatch, and once it reaches 15 minutes, there will be an alarm sound. If the watch then doesn't get reset within the next 3 minutes, there will be another action (notification or something, not important right now, that part I already figured out), and if it's reset, it starts the whole thing again (will run for multiple hours).
I've set up the infrastructure via wordpress, and now my only problem is the stopwatch itself.
My problem right now is, that it "kind of" stops running, whenever someone moves to another tab or program. With "kind of", I mean: If the alarm sound was played ONCE, and I then reset it, it keeps counting even when switching tab after that. But on the first page refresh, it doesn't; except sometimes it does, but very slowly (1 "second" takes 3 real seconds). This is what my code looks like right now:
var silence = new Audio('exampleSilence.mp3'); //Audio-file with no content (so just silence)
var audio = new Audio('exampleAlarm.mp3'); //Audio-file with random song/alarm sound
var time=0;
var running=0;
function strtpause () { //Function responsible for start/pause button
if(running==0){
running=1;
increment();
document.getElementById("strtpause").innerHTML="Pause"
}
else{
running=0;
document.getElementById("strtpause").innerHTML="Resume"
return;
}
}
function reset(){ //Function responsible for resetting the stopwatch timer. At first also stopped
//the counting, but I disabled that part as I want it to keep going
//running=0;
time=0; //Sets time back to 0, so timer can start from 0 again
//document.getElementById("strtpause").innerHTML="Start"
document.getElementById("output").innerHTML="00:00"
audio.pause(); //Will also stop the alarm
audio.currentTime = 0; //And set the alarm time back to 0
}
function increment(){ //The actual stopwatch timer function
if(running==1){
setTimeout(function(){
time++;
var mins=Math.floor(time/60);
var secs=Math.floor((time)-(mins*60));
if(mins<10){
mins="0"+mins;
}
if(secs<10){
secs="0"+secs;
}
document.getElementById("output").innerHTML=mins+":"+secs;
if(1<time<9){ //I attempted to play a silent audio file in the background
silence.play(); //to keep the process running, but didn't have an effect
}
if(1<time<9){ //Also tried playing the audio file but very very silent.
audio.volume = 0.001; //It did work, but made timer go slower than regular seconds
audio.play();
}
if (time>10){ //Once the timer reaches a certain time, the alarm is played
audio.volume = 0.5; //Adjusts sound volume
audio.play();
}
increment();
},1000);
}
}
My solution ideas until now were:
-Change the setTimeout increments -> No effect, except with smaller increments it seemed to go slower
-Let it play a "silent" audio file with no actual sound content, so it "has something to do" (no idea if that makes sense, but audio files keep playing in background)
-Let it play an audio file with content while waiting, but very quietly (volume = 0.001). Did work, but made the stopwatch go way too slow, with 1 "second" = 3 actual seconds.
Ideas on how to keep the code running on any OS/Browser are appreciated! I'd prefer not to write/setup a different file or language, as my webdevelopment skills are very very basic, and I don't have rights to edit everything on the website.
Time delay for setTimeout() / setInterval() methods executing on inactive browser tabs are set to one second regardless of their value defined in code.
More about that here: https://usefulangle.com/post/280/settimeout-setinterval-on-inactive-tab
I have two HTML video elements that begin autoplaying on page load and continue looping with no end until the user leaves the page. Both videos are 8 seconds long and I need them to always be in sync with one another, but of course they do get out-of-sync. For the audio, Video 1 plays a full song arrangement and video 2 plays just one instrument in the same song so, to show that they are related, they have to be in sync.
<video id="first-video" autoplay="" muted="" loop="" playsinline="" loading="lazy" src="videos/video-1.mp4"></video>
<video id="second-video" autoplay="" muted="" loop="" playsinline="" loading="lazy" src="videos/video-2.mp4"></video>
There are several solutions in this SO question:
Is there a way to keep two videos synchronized in a webpage without plugins?
but when I try to implement, I see no change in quality of sync, they get out of sync just as easily. I am new to JS, but my guess is that there is some method of detecting the first and last frame of a video and then one could use those to start/stop a second video and with the video on "loop" this could go on indefinitely. If anybody can point me in the right direction for that or any other solution, that would be very helpful!
Thank you.
Simple answer
Assuming both tracks start at the same current time (0) and are playing at the same rate you can constantly change the second video's position to the first video's position. This isn't exact or frame accurate, but should roughly get the job done:
function syncVideos() {
const first = document.getElementById('first-video');
const second = document.getElementById('second-video');
// keep track of if video's seeking to avoid constant changes to position
// don't know if this is really necessary
let isSeeking = false;
second.addEventListener('seeking', () => isSeeking = true);
second.addEventListener('seeked', () => isSeeking = false);
const thresholdMilliseconds = 50; // some ms threshold of difference to avoid excessive seeking
const nudgeOffsetMilliseconds = 5; // just a guess that you may need to assume that seeking won't be instantaneous. I don't know if this is necessary or helpful
// listen for time updates on the first video's position
first.addEventListener('timeupdate', () => {
const deltaMilliseconds = (second.currentTime - first.currentTime) * 1000;
if (Math.abs(delta) >= thresholdMilliseconds) {
if (isSeeking) {
console.warn('not in sync, but currently seeking');
return;
}
console.log(`out of sync by ${deltaMilliseconds}ms. Seeking to ${first.currentTime}`);
// adding a bit of nudge b/c syncing may not be instant. Not an exact science...for that use MSE
second.currentTime = first.currentTime + nudgeOffsetMilliseconds;
}
});
}
syncVideos();
The "right way"
Use MSE (Media Source Extensions), which would probably allow for more accurate seeking and sync. This may help: Playing one of multiple audio tracks in sync with a video
You may want to look for libraries on github. I Haven't looked but searching for "audio mixer" or something may point you somewhere...
I also faced the challenge of synchronizing multiple videos and found the process to be very complicated. As a result, I created a package called sync-video-player that can synchronize two or more videos. If you're interested in the implementation, feel free to take a look at the code on GitHub:
sync-video-player
It's worth noting that this package is relatively new, but I use it actively in my own projects and am committed to maintaining it. If you encounter any issues, please don't hesitate to report them.
The situation is like this - I have two videos, positioned on top of each other. These videos are almost the same (content wise), when you enter the page, video 1 starts playing. There is also a button, which when pressed should sync the second video to the first one and slowly fadeIn (it's hidden at first).
The problem I am having is with the syncing, when I try to set the currentTime of the second video, the player obviously first buffers some frames and after that starts playing, causing desync. I also tried some syncing timeouts with setting currentTime each 10ms until the second video's readyState turns to 4, but that causes pretty big delay.
The code I am using so far:
function switchVideo(first, second) {
var currentTime = first.currentTime;
second.pause();
second.currentTime = currentTime;
second.play();
syncAllowed = true;
var videoInterval = setInterval(function() {
if(next.readyState >= 4) {
second.addClass('show');
syncAllowed = false;
setTimeout(function() {
first.pause();
}, 500);
clearInterval(videoInterval);
}
}, 100);
}
var syncVideos = setInterval(function() {
if(syncAllowed) {
second.currentTime = first.currentTime;
}
}, 10);
So the question is, is there any way to sync one video to another one that is already playing, without having too much delay for the first video to get enought data for playing after setting it's current time? Also I noticed that there is a little bit of lag going backwards in time, which is weird, because that portion of video should already be buffered.
Thanks for any tips!
Nothing specific, E.g:
I have a video [with controls(Pause, play, forward... ) in a tag] How would you do to: when the video is in the second 30, make a div appear, then, in the second 32, make it disappear.
Thanks :)
If you're using the HTML5 <video> element you can use the ontimeupdate event to track where the playback has got to, as in this example:
// display the current and remaining times
video.addEventListener("timeupdate", function () {
// Current time
var vTime = video.currentTime;
document.getElementById("curTime").textContent = vTime.toFixed(1);
document.getElementById("vRemaining").textContent = (vLength - vTime).toFixed(1);
}, false);
Thanks to Microsoft for their reference
Here's a working example:
<!DOCTYPE html>
<html>
<head>
<title>Video demo</title>
<script>
// ontimeupdate event handler
function update(e) {
// get the video element id so that we can retrieve the current time.
el = document.getElementById('myVideo');
// set the current time in the page
document.getElementById('timer').innerHTML = el.currentTime;
// If the current time is between 10 and 15 seconds pop-up the
// second div
if (el.currentTime > 20 && el.currentTime <=25) {
document.getElementById('popup').style.display='block';
} else {
document.getElementById('popup').style.display='none';
}
}
</script>
<style>
video {width:300px;}
</style>
</head>
<body>
<video id=myVideo ontimeupdate="update()" src="http://archive.org/download/HardDriveSpinning/HardDriveWebm.webm" autoplay>Sorry - format unsupported</video>
<div id="timer"></div>
<div id="popup" style="display:none">Boo!</div>
</body>
</html>
See it at this fiddle (Works for Firefox and Chrome. IE doesn't like the WebM video format)
You could use window.setTimeout to run a function after a specified amount of time. This won't be enough by itself if the user is able to pause the video. You'd have to tell us more about how you're displaying the video in order to get a more in-depth solution.
But perhaps more importantly, you might want to go back to square one and think again about what you're really trying to accomplish. Because it seems to me that if it seems like you need to trigger DOM manipulations based on the time index of a video, there's probably a better way to do what you really want that doesn't involve that.
We have a video (13 minutes long) which we would like to control using HTML5. We want to be able to let our users control and select the parts of the video they want to play. Preferably this control would be through 2 input fields. They would input start time (in seconds) in first box and input duration to play (in seconds) in second box. For example, they might want to start the video 10 seconds in and play for 15 seconds. Any suggestions or guidance on the Javascript needed to do this?
Note: I have found the following:
Start HTML5 video at a particular position when loading?
But it addresses only starting at a particular time, and nothing with playing the video for a specified length of time.
You could use the timeupdate event listener.
Save the start time and duration time to variable after loadedmetadata event.
// Set video element to variable
var video = document.getElementById('player1');
var videoStartTime = 0;
var durationTime = 0;
video.addEventListener('loadedmetadata', function() {
videoStartTime = 2;
durationTime = 4;
this.currentTime = videoStartTime;
}, false);
If current time is greater than start time plus duration, pauses the video.
video.addEventListener('timeupdate', function() {
if(this.currentTime > videoStartTime + durationTime){
this.pause();
}
});
If you are able to set start time and end time of video while setting the video url.
you can specify the start and end time in the url itself like
src="future technology_n.mp4#t=20,50"
it will play from 20th second to 50th second.
There are a lot of nuances to using the javascript solution proposed by Paul Sham. A much easier course of action is to use the Media Fragment URI Spec. It will allow you to specify a small segment of a larger audio or video file to play. To use it simply alter the source for the file you are streaming and add #t=start,end where start is the start time in seconds and end is the end time in seconds.
For example:
var start = document.getElementById('startInput').value;
var end = document.getElementById('endInput').value;
document.getElementById('videoPlayer').src = 'http://www.example.com/example.ogv#t='+start+','+end;
This will update the player to start the source video at the specified time and end at the specified time. Browser support for media fragments is also pretty good so it should work in any browser that supports HTML5.
Extend to michael hanon comments:
IE returns buffered.length = 0 and seekable.length = 0. Video doesn't play. So solution:
src="video.mp4#t=10,30"
will not works in IE. If you would like to support IE only way is to use javascript to seek video just after start from 0 second.