If I have HTML5 video and audio elements, is there a clean way to keep them in sync? They should act like a video file that contains an audio track, so advancing one track manually should bring the other one along with it. Let's say the two tracks have the same duration.
I'd like a solution that works across browsers, but I don't particularly care if it works on older browsers. It would also be nice to avoid the use of JavaScript if possible. Otherwise, a dead-simple JavaScript library would be best -- something that only asks for which tracks to synchronize and takes care of the rest.
I've looked into mediagroup... but it looks like it only works in Safari. I've looked into audioTracks... but the user has to enable the feature in Firefox.
I've looked into Popcorn.js, which is a JavaScript framework that seems designed for this task... but it looks like there hasn't been any activity in over a year. Besides that, the only demonstrations I can find are of synchronizing things like text or slides to video, not audio to video.
You can use Promise.all(), fetch() to retrieve media resource as a Blob, URL.createObjectURL() to create a Blob URL of resource; canplaythrough event and Array.prototype.every() to check if each resource can play; call .play() on each resource when both resources can play
I didn't make it clear in the question. I meant that the two tracks
should stay in sync as though playing a regular video file with audio.
One approach could be to create a timestamp variable, utilize seeked event to update .currentTime of elements when set variable is greater than a minimum delay to prevent .currentTime being called recursively.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<button>load media</button>
<br>
<div id="loading" style="display:none;">loading media...</div>
<script>
var mediaBlobs = [];
var mediaTypes = [];
var mediaLoaded = [];
var button = document.querySelector("button");
var loading = document.getElementById("loading");
var curr = void 0;
var loadMedia = () => {
loading.style.display = "block";
button.setAttribute("disabled", "disabled");
return Promise.all([
// `RETURN` by smiling cynic are licensed under a Creative Commons Attribution 3.0 Unported License
fetch("https://ia600305.us.archive.org/30/items/return_201605/return.mp3")
, fetch("http://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4")
])
.then(responses => responses.map(media => ({
[media.headers.get("Content-Type").split("/")[0]]: media.blob()
})))
.then(data => {
for (var currentMedia = 0; currentMedia < data.length; currentMedia++) {
(function(i) {
var type = Object.keys(data[i])[0];
mediaTypes.push(type);
console.log(data[i]);
var mediaElement = document.createElement(type);
mediaElement.id = type;
var label = document.createElement("label");
mediaElement.setAttribute("controls", "controls");
mediaElement.oncanplaythrough = () => {
mediaLoaded.push(true);
if (mediaLoaded.length === data.length
&& mediaLoaded.every(Boolean)) {
loading.style.display = "none";
for (var track = 0; track < mediaTypes.length; track++) {
document.getElementById(mediaTypes[track]).play();
console.log(document.getElementById(mediaTypes[track]));
}
}
}
var seek = (e) => {
if (!curr || new Date().getTime() - curr > 20) {
document.getElementById(
mediaTypes.filter(id => id !== e.target.id)[0]
).currentTime = e.target.currentTime;
curr = new Date().getTime();
}
}
mediaElement.onseeked = seek;
mediaElement.onended = () => {
for (var track = 0; track < mediaTypes.length; track++) {
document.getElementById(mediaTypes[track]).pause()
}
}
mediaElement.ontimeupdate = (e) => {
e.target.previousElementSibling
.innerHTML = `${mediaTypes[i]} currentTime: ${Math.round(e.target.currentTime)}<br>`;
}
data[i][type].then(blob => {
mediaBlobs.push(URL.createObjectURL(blob));
mediaElement.src = mediaBlobs[mediaBlobs.length - 1];
document.body.appendChild(mediaElement);
document.body.insertBefore(label, mediaElement);
})
}(currentMedia));
}
})
};
button.addEventListener("click", loadMedia);
</script>
</body>
</html>
plnkr http://plnkr.co/edit/r51oh7KsZv8DEYhpRJlL?p=preview
To my knowledge, this is impossible with pure HTML.
You can use the currentTime property of both <video> and <audio> elements to synchronize the time.
var video = document.getElementById("your-video");
var audio = document.getElementByid("your-audio");
audio.currentTime = video.currentTime;
If necessary, you could also use the timeupdate event to continuously re-sync the video and audio.
Related
I have a webpage where we generate PDFs based upon the user selection of on-page items. This causes a postback (it's an ASP.NET WebForms page) which creates the PDFs server-side. An <a class="documentDownload"> tag is then added to the page for each item.
When the page reloads in the browser the following jQuery script is executed to automatically download the files (if the user had chosen a auto-download option):
var divHost = document.createElement("div");
divHost.id = "elmntDnldLinks";
divHost.style.display = "none";
document.body.appendChild(divHost);
setTimeout(function() {
$(".documentDownload").each(function(idx, val) {
var lnkDownload = $(val),
save = document.createElement("a");
save.href = lnkDownload.attr("href");
save.download = lnkDownload.attr("download");
save.target = "_blank";
divHost.appendChild(save);
save.click();
});
}, 1000);
This script has a delay of 1 second, then for each .documentDownload element it creates a new <a> element with the same href attribute of the original element, appends it to a newly-added hidden element, then programmatically clicks it.
[This strategy of creating new links and clicking those instead of clicking the original DOM elements gets around a browser security measure.]
This works perfectly well in Firefox but Chrome never downloads more than 10 files. Why? I can see, for example, 15 links on the page and in the hidden element, but only 10 files are downloaded.
If you pause for a second between each 10 downloads, all of them will work in Chrome.
I used async timeout function for this workaround:
function pause(msec) {
return new Promise(
(resolve, reject) => {
setTimeout(resolve, msec || 1000);
}
);
}
async function downloadAll(elements) {
var count = 0;
for (var e in elements) {
download(elements[e]); // your custom download code here, click or whatever
if (++count >= 10) {
await pause(1000);
count = 0;
}
}
}
Simple timeouts multiplied by element counter could probably work too, but I did not test it.
You can do a pause in your downloads like that
function sleep(milliseconds) {
let timeStart = new Date().getTime();
while (true) {
let elapsedTime = new Date().getTime() - timeStart;
if (elapsedTime > milliseconds) {
break;
}
}
}
$("#aDescarga")[0].click();
$("#aDescarga").attr("href","");
if (i > 9) {
sleep(2000);
i = 0;
} else {
i = i + 1;
}
Setting a 200ms delay between each download solves the problem.
Tested on Google Chrome 100.0.4896.127 (Windows, macOS)
Demo in CodePen - 'Download multiple files' should be allowed.
const downloadFileWithAnchor = () => {
const anchor = document.createElement("a");
anchor.href = "data:text/plain;charset=utf-8,";
anchor.download = 'blank.txt';
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
};
const repeatCount = 20;
for (let i = 0; i < repeatCount; i += 1) {
setTimeout(
() => {
downloadFileWithAnchor();
},
i * 200 // Delay download every 200ms
);
}
I have urls of several audio files like this:
example.net/{num}.mp3
And I would like to play them one after the other without a delay between them.
I tried having two audio elements with preload=true and switching between them, but I still have a delay.
Any ideas ?
I'd like to see the actual code of what you have so far, because I'm not 100% sure what "switching between them" means, but I wrote the following example which starts the next file playing before the first one finishes. You specify a set amount of "overlap", and it starts playing the next file JSFiddle: http://jsfiddle.net/76xqhupw/4/
const OVERLAP_TIME = 1.0; //seconds
const song1 = document.getElementById('audio1');
const song2 = document.getElementById('audio2');
song1.addEventListener("timeupdate", function() {
if (song1.duration - song1.currentTime <= OVERLAP_TIME) {
song2.play();
}
});
song1.play();
This is just relevant to playing the next audio element instantly, since you said you already had two audio elements, but if I'm missing something let me know.
<html>
<head>
<title>Subway Maps</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body onload="onload();">
<video id="idle_video" width="1280" height="720" onended="onVideoEnded();"></video>
<script>
var video_list = ["Skydiving Video - Hotel Room Reservation while in FreeFall - Hotels.com.mp4",
"Experience Conrad Hotels & Resorts.mp4",
"Mount Airy Casino Resort- Summer TV Ad 2.mp4"
];
var video_index = 0;
var video_player = null;
function onload(){
console.log("body loaded");
video_player = document.getElementById("idle_video");
video_player.setAttribute("src", video_list[video_index]);
video_player.play();
}
function onVideoEnded(){
console.log("video ended");
if(video_index < video_list.length - 1){
video_index++;
}
else{
video_index = 0;
}
video_player.setAttribute("src", video_list[video_index]);
video_player.play();
}
</script>
</body>
for refrence how to display multiple videos one by one dynamically in loop using HTML5 and javascript
You can use ended event, Array.prototype.shift() to set src of element to next item within array, if array .length is greater than 0, call .load(), .play()
var url = "example.net/"
var type = ".mp3";
var n = [0,1,2,3,4,5];
var audio = document.querySelector("audio");
audio.onended = function() {
if (n.length) {
this.src = url + n.shift() + type;
this.load();
this.play();
}
}
audio.src = url + n[0] + type;
audio.load();
audio.play();
I have 15 videos I need to show. Instead of creating 15 pages with each html5 video embed targeting a different video, I rather have just one page and via the URL tell the player what to load. This way I can just have 15 custom links but just one player and one html page. Need this to be supported by all browsers, iOS and Android.
Example:
www.mysite.com/videoplayer.html?v=mymovie
www.mysite.com/videoplayer.html?v=mymovie&t=13.6 - this should jump to the player playhead to a point in time.
videoplayer.html
<script>
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
alert('Query Variable ' + variable + ' not found');
}
var videoFile = getQueryVariable("v");
var videoElement = document.getElementById("mp4source");
videoElement.setAttribute("source", videoFile + ".mp4");
</script>
<video id="mp4" controls="controls">
<source id="mp4source" src="../videos/jude.mp4" type="video/mp4" />
</video>
Main.html
<div id="menubar1">
Play Movie
Chapter 2
</div>
I'm a beginner to javascript, please be specific in your answers.
Thanks.
IMHO DOM Manipulation on the main page would be a better solution, but here it is, at least for modern browsers, from your example code.
I changed your getQueryVariable in order to be able to use it as a
boolean.
To change the current playback time, you will have to wait for the
video's metadata to be loaded, then you can set the currentTime
property (in seconds).
In order to comply the "all browsers" support you will have
to transcode your videos to ogg vorbis format, then add a source pointing to
this video file. This will do for major modern browsers.
For older browsers, you will have to add a fallback (e.g. flash player or java applet).
For the "jumping playhead" in ios, you have some tricks to do : look at this question , personally I used this code which seems to work on my ipad ios 8. Note that it will lack of autoplay if you decide to add it in the video tag.
Now, you can't get video for all browsers (e.g text-based browsers).
Live Example
Play Movie Chapter 2
Commented videoplayer.html
<video id="video" controls="controls">
<source id="mp4source" src="" type="video/mp4" />
<source id="oggSource" src="" type="video/ogg" />
</video>
<script>
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
//returning false will help knowing if that variable exists
return false;
}
function loadVideo() {
var videoFile = getQueryVariable("v");
//if v is not set
if (!videoFile) {
alert('please choose a video file, \n maybe you came here by accident?');
//no need to go further
return;
}
//Select the sources for the mp4 and the ogg version
var mp4source = document.getElementById("mp4source");
mp4source.setAttribute("src", videoFile + ".mp4");
var oggSource = document.getElementById("oggSource");
oggSource.setAttribute("src", videoFile + ".ogv");
//if t is set
if (getQueryVariable("t")) {
//userAgent can be overridden but it may be the best way to detect ios devices
var iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/) !== null;
if (iOS) {
iOSLoadSeek();
} else {
//wait for the video meta data to be loaded
document.getElementById('video').addEventListener('loadedmetadata', function() {
//then change the time position
this.currentTime = getQueryVariable("t");
})
}
}
}
//ios load seek workaround, edited from https://gist.github.com/millermedeiros/891886
function iOSLoadSeek() {
var vid = document.getElementById('video');
if (vid.readyState !== 4) { //HAVE_ENOUGH_DATA
vid.addEventListener('canplaythrough', iosCanPlay, false);
vid.addEventListener('load', iosCanPlay, false); //add load event as well to avoid errors, sometimes 'canplaythrough' won't dispatch.
vid.addEventListener('play', iosCanPlay, false); //Actually play event seems to be faster
vid.play();
setTimeout(function() {
vid.pause(); //block play so it buffers before playing
}, 10); //it needs to be after a delay otherwise it doesn't work properly.
}
}
//called when one of the three events fires
function iosCanPlay() {
//remove all the event listeners
this.removeEventListener('canplaythrough', iosCanPlay, false);
this.removeEventListener('load', iosCanPlay, false);
this.removeEventListener('play', iosCanPlay, false);
//finally seek the desired position
this.currentTime = getQueryVariable("t");
this.play();
}
//When the page is loaded, execute
window.onload = loadVideo();
</script>
My GIF image freezes after 5 seconds while my JavaScript code is doing some work. It unfreezes when procesing is finished.
Is there a solution to animate my GIF while the code is executing?
Here's my JavaScript code:
var play = function() {
var image = document.getElementById('img');
image.style.display="block";
window.setTimeout(function(){
for ( var i =0; i < 2000000000; i++ )
{
//Do nothing, in example
}
}, 5000);
};
var stop = function() {
var image = document.getElementById('img');
image.style.display="none";
};
HTML code:
<img id="img" src="loader.gif" style="display: none;"/>
<button id="Start" onclick="play()">Play</button>
<button id="Stop" onclick="stop()">Stop</button>
I think that in this case you should use webworker elements. They allow you to do some asynchronous operation in the background of the UI elements.
One example
<p>Value passed by the worker: <output id="result"></output></p>
<script>
var worker = new Worker('worker.js');
worker.onmessage = function (event) {
document.getElementById('result').textContent = event.data;
};
</script>
In a separate file (worker.js):
var n = 1;
search: while (true) {
n += 1;
for (var i = 2; i <= Math.sqrt(n); i += 1)
if (n % i == 0)
continue search;
// Found a prime!
postMessage(n);
}
Every time that you call postMessage you send some data from the background (that fires onmessage event of the worker element ) to the main thread.
Have a look at Using jQuery UI progress bar with MVVM, Knockout and web workers for your case:
In this post I would like to explore:
How to use the jQuery UI progress bar with KnockoutJS and MVVM pattern, as a simple example of reusing existing JavaScript UI components.
How to use web workers to execute long running task asynchronously and notify view model about the results and progress.
Use yield as in this working example with minor mod's to your own code...
var play = function() {
//'-----------------------------------Insert Handler..
var obj = myfuncGen.next(); //'start it
if (obj.done == false) {
setTimeout(play, 150); //'adjust for the amount of time you wish to yield (depends how much screen drawing is required or etc)
}
}
var myfuncGen = myfuncSurrogate(); //'creates a "Generator" out of next.
function* myfuncSurrogate() { //'This the original function repackaged! Note asterisk.
//'-------------------------------------End Insert
var image = document.getElementById('img');
image.style.display="block";
//window.setTimeout(function(){
console.log("start");
for ( var i =0; i < 20000000; i++ )
{
if ((i % 1000000) == 0) { //a yield set to go off every 1,000,000 loops (if the yield is called too often it will needlessly slowe down yor loop)
console.log("yielding ", i / 1000000);
yield;
}
}
console.log("done");
//}, 500);
};
play()
<img id='img' src='http://bestanimations.com/Earth&Space/Earth/earth-spinning-rotating-animation-52.gif' />
..I had to edit out window.setTimeout() as it created a detached function which causes an error with Yield. To restore your original intent, I would call play() from such a timeout.
If the link becomes broken to the random gif I chose, I am sure you could substitute another :-).
I have a client who has made a 'Visual Diary' of photographs that he took once a day for 365 days, for his portfolio site. He wants me to put these together into a time-lapse effect piece. I thought about using Flash but in the end opted for JavaScript.
What needs to happen is this: The images cycle really really quickly with no transitions or anything, just image-image-image etc. About 30/fps or something like that. When you click the flashing images, it stops on the image you have selected, so the user can take a look. When you click again, the slideshow starts playing again.
My problem is I'm a PHP/XHTML/CSS bloke who hasn't really the foggiest about JavaScript. I can happily integrate it into any page, but it's just coding the JavaScript that's troubling me.
On his homepage, I have this code used to display a basic slideshow - with transition effects etc. It's in the HTML but you can fathom out the code I'm sure:
<!-- Code for slideshow -->
<!-- Found on http://www.webdeveloper.com/forum/showthread.php?t=81441 -->
<SCRIPT LANGUAGE="JavaScript">
<!-- Begin
// Set slideShowSpeed (milliseconds)
var slideShowSpeed = 3000;
// Duration of crossfade (seconds)
var crossFadeDuration = 3;
// Specify the image files
var Pic = new Array();
// to add more images, just continue
// the pattern, adding to the array below
Pic[0] = '1.jpg'
Pic[1] = '2.jpg'
Pic[2] = '3.jpg'
Pic[3] = '4.jpg'
Pic[4] = '5.jpg'
Pic[5] = '6.jpg'
Pic[6] = '7.jpg'
Pic[7] = '8.jpg'
Pic[8] = '9.jpg'
Pic[9] = '10.jpg'
Pic[10] = '11.jpg'
Pic[11] = '12.jpg'
Pic[12] = '13.jpg'
Pic[13] = '14.jpg'
Pic[14] = '15.jpg'
Pic[15] = '16.jpg'
Pic[16] = '17.jpg'
Pic[17] = '18.jpg'
Pic[18] = '19.jpg'
Pic[19] = '20.jpg'
// do not edit anything below this line
var t;
var j = 0;
var p = Pic.length;
var preLoad = new Array();
for (i = 0; i < p; i++) {
preLoad[i] = new Image();
preLoad[i].src = Pic[i];
}
function runSlideShow() {
if (document.all) {
document.images.SlideShow.style.filter="blendTrans(duration=2)";
document.images.SlideShow.style.filter="blendTrans(duration=crossFadeDuration)";
document.images.SlideShow.filters.blendTrans.Apply();
}
document.images.SlideShow.src = preLoad[j].src;
if (document.all) {
document.images.SlideShow.filters.blendTrans.Play();
}
j = j + 1;
if (j > (p - 1)) j = 0;
t = setTimeout('runSlideShow()', slideShowSpeed);
}
// End -->
</script>
Is there any way to modify this code to turn off all transitional effects, and make it stop playing when you click it/start it again? Otherwise, a reference to some different code would be helpful.
Thank you everybody!
Jack
You seem to be using IE-specific code. I would recommend using the various effects modules in a dedicated JavaScript library such as MooTools (my personal favourite), jQuery, or Prototype + script.aculo.us.
To stop the slideshow, you should be able to simply clear timeout t:
clearTimeout(t);
Also, you shouldn't quote the first parameter of setTimeout. Pass it a function reference:
setTimeout(runSlideShow, slideShowSpeed);