Use javascript to detect if an MP4 video has a sound track - javascript

I am creating a custom controller for MP4 video on a web page. The controller includes a volume slider. Some of the videos that are to be played have no sound track. It would be good to disable the volume slider for these videos, so that the user is not confused when changing the position of the volume slider has no effect.
Is there a property or a trick for checking if an MP4 file has an audio track? (jQuery is an option).
Edit: using #dandavis's suggestion, I now have this solution for Chrome (and .ogg on Opera):
var video = document.getElementById("video")
var volume = document.getElementById("volume-slider")
function initializeVolume() {
var enableVolume = true
var delay = 1
if (video.webkitAudioDecodedByteCount !== undefined) {
// On Chrome, we can check if there is audio. Disable the volume
// control by default, and reenable it as soon as a non-zero value
// for webkitAudioDecodedByteCount is detected.
enableVolume = false
startTimeout()
function startTimeout () {
if (!!video.webkitAudioDecodedByteCount) {
enableVolume = true
toggleVolumeEnabled(enableVolume)
} else {
// Keep trying for 2 seconds
if (delay < 2048) {
setTimeout(startTimeout, delay)
delay = delay * 2
}
}
}
}
toggleVolumeEnabled(enableVolume)
}
function toggleVolumeEnabled(enableVolume) {
volume.disabled = !enableVolume
}
The video.webkitAudioDecodedByteCount value is initially 0. In my tests, it may take up to 256ms to get populated with a non-zero value, so I have included a timeout to keep checking (for a while).

There might be a better way of doing this, although it's fairly simple just using regular javascript for webkit or mozilla enabled browsers. webkit utilizes this.audioTracks and mozilla uses this.mozHasAudio respectively:
document.getElementById("video").addEventListener("loadeddata", function() {
if ('WebkitAppearance' in document.documentElement.style)
var hasAudioTrack = this.audioTracks.length;
else if (this.mozHasAudio)
var hasAudioTrack = 1;
if (hasAudioTrack > 0)
alert("audio track detected");
else
alert("audio track not detected");
});
<video id="video" width="320" height="240" controls>
<source src="http://media.w3.org/2010/05/video/movie_300.mp4" type="video/mp4">
</video>
There's also a function this.webkitAudioDecodedByteCount, however, I've never had any luck making it work.

There are different ways to check whether a video file has audio or not, one of which is to use mozHasAudio, video.webkitAudioDecodedByteCount and video.audioTracks?.length properties of video, clean and simple...

Related

Autoplay doesn't work with my music playlist [duplicate]

This question already has answers here:
How to make audio autoplay on chrome
(23 answers)
Closed 2 years ago.
I want to play a music playlist on my web-page in the background, I'm using chrome I don't want the console and I want autoplay but, though I write autoplay, the audio is starting only with the play button of the console. That's what I write in the HTML:
<div id="music_list">
<audio controls autoplay></audio>
</div>
And in the Javascript
(function () {
// Playlist array
var files = [
"ms4/14_1.30.mp3",
"ms4/20_20.mp3",
"ms4/21_34.mp3"
];
// Current index of the files array
var i = 0;
// Get the audio element
var music_player = document.querySelector("#music_list audio");
// function for moving to next audio file
function next() {
// Check for last audio file in the playlist
if (i === files.length - 1) {
i = 0;
} else {
i++;
}
// Change the audio element source
music_player.src = files[i];
}
// Check if the player is slected
if (music_player === null) {
throw "Playlist Player does not exists ...";
} else {
// Start the player
music_player.src = files[i];
// Listen for the music ended event, to play the next audio file
music_player.addEventListener('ended', next, false)
}
})();
How can I fix that? I'm really new in HTML and JS and I'm stuck in this problem.
I got this problem 1 year ago. The problem is of the Internet Browser you using at, for example, in Google Chrome controls autoplay doesn't work, the problem is about the permission, security and privacity of the users.
I think there is some way to make it work in any Internet Browser but, "controls autoplay" doesn't work, just in some Internet Explorers.
Bug players consider autoplay a bad thing because ads. At the end you will be forced to click to play. Nowadays you can try adding muted property to the audio element alongside with autoplay, and remove it after ading the first src
This depends upon your browsers
Run your code in different browsers
and also try this javascript function
var mp3 = document.getElementByTagName("audio");
mp3.autoplay = true;
mp3.load();

Create Seamless Loop of Audio - Web

I want to create a seamless loop of an audio file. But in all approaches I used so far, there was a noticeable gap between end & start.
This is what I tried so far:
First approach was to use the audio in the HTML and it loops but there is still a noticeable delay when going from the end of the track to the beginning.
<audio loop autoplay>
<source src="audio.mp3" type="audio/mpeg">
<audio>
Then I tried it from JavaScript with the same result:
let myAudio = new Audio(file);
myAudio.loop = true;
myAudio.play();
After that I tried this (according to this answer)
myAudio.addEventListener(
'timeupdate',
function() {
var buffer = .44;
if (this.currentTime > this.duration - buffer) {
this.currentTime = 0;
this.play();
}
},
false
);
I played around with the buffer but I only got it to reduce the gap but not leave it out entirely.
I turned to the library SeamlessLoop (GitHub) and got it to work to loop seamlessly in Chromium browsers (but not in the latest Safari. Didn't test in other browsers). Code I used for that:
let loop = new SeamlessLoop();
// My File is 58 Seconds long. Btw there aren't any gaps in the file.
loop.addUri(file, 58000, 'sound1');
loop.callback(soundsLoaded);
function soundsLoaded() {
let n = 1;
loop.start('sound' + n);
}
EDIT: I tried another approach: Looping it trough two different audio elements:
var current_player = "a";
var player_a = document.createElement("audio");
var player_b = document.createElement("audio");
player_a.src = "sounds/back_music.ogg";
player_b.src = player_a.src;
function loopIt(){
var player = null;
if(current_player == "a"){
player = player_b;
current_player = "b";
}
else{
player = player_a;
current_player = "a";
}
player.play();
/*
3104.897 is the length of the audio clip in milliseconds.
Received from player.duration.
This is a different file than the first one
*/
setTimeout(loopIt, 3104.897);
}
loopIt();
But as milliseconds in browsers are not consistent or granular enough this doesn't work too well but it does work much better than the normal "loop" property of the audio.
Can anyone guide me into the right direction to loop the audio seamlessly?
You can use the Web Audio API instead. There are a couple of caveats with this, but it will allow you to loop accurately down to the single sample level.
The caveats are that you have to load the entire file into memory. This may not be practical with large files. If the files are only a few seconds it should however not be any problem.
The second is that you have to write control buttons manually (if needed) as the API has a low-level approach. This means play, pause/stop, mute, volume etc. Scanning and possibly pausing can be a challenge of their own.
And lastly, not all browsers support Web Audio API - in this case you will have to fallback to the regular Audio API or even Flash, but if your target is modern browsers this should not be a major problem nowadays.
Example
This will load a 4 bar drum-loop and play without any gap when looped. The main steps are:
It loads the audio from a CORS enabled source (this is important, either use the same domain as your page or set up the external server to allow for cross-origin usage as Dropbox does for us in this example).
AudioContext then decodes the loaded file
The decoded file is used for the source node
The source node is connected to an output
Looping is enabled and the buffer is played from memory.
var actx = new (AudioContext || webkitAudioContext)(),
src = "https://dl.dropboxusercontent.com/s/fdcf2lwsa748qav/drum44.wav",
audioData, srcNode; // global so we can access them from handlers
// Load some audio (CORS need to be allowed or we won't be able to decode the data)
fetch(src, {mode: "cors"}).then(function(resp) {return resp.arrayBuffer()}).then(decode);
// Decode the audio file, then start the show
function decode(buffer) {
actx.decodeAudioData(buffer, playLoop);
}
// Sets up a new source node as needed as stopping will render current invalid
function playLoop(abuffer) {
if (!audioData) audioData = abuffer; // create a reference for control buttons
srcNode = actx.createBufferSource(); // create audio source
srcNode.buffer = abuffer; // use decoded buffer
srcNode.connect(actx.destination); // create output
srcNode.loop = true; // takes care of perfect looping
srcNode.start(); // play...
}
// Simple example control
document.querySelector("button").onclick = function() {
if (srcNode) {
srcNode.stop();
srcNode = null;
this.innerText = "Play";
} else {
playLoop(audioData);
this.innerText = "Stop";
}
};
<button>Stop</button>
There is a very simple solution for that, just use loopify it makes use of the html5 web audio api and works perfectly well with many formats, not only wav as the dev says.
<script src="loopify.js" type="text/javascript"></script>
<script>
loopify("yourfile.mp3|ogg|webm|flac",ready);
function ready(err,loop){
if (err) {
console.warn(err);
}
loop.play();
}
</script>
This will automatically play the file, if you want to have start and stop buttons for example take a look at his demo

HTML 5 audio .play() delay on mobile

I just built a real-time app using socket.io where a "master" user can trigger sounds on receiving devices (desktop browsers, mobile browsers). That master user sees a list of sound files, and can click "Play" on a sound file.
The audio playback is instant on browsers. On mobiles however, there is a 0.5-2 seconds delay (my Nexus 4 and iPhone 5 about 1 second and iPhone 3GS 1-2 seconds).
I've tried several things to optimize the audio playback to make it faster on mobiles. Right now (at the best "phase" of its optimization I'd say), I combine all the mp3's together in one audio file (it creates .mp3, .ogg, and .mp4 files). I need ideas on how I can further fix / improve this issue. The bottleneck really seems to be in the hmtl 5 audio methods such as .play().
On the receivers I use as such:
<audio id="audioFile" preload="auto">
<source src="/output.m4a" type="audio/mp4"/>
<source src="/output.mp3" type="audio/mpeg"/>
<source src="/output.ogg" type="audio/ogg"/>
<p>Your browser does not support HTML5 audio.</p>
</audio>
In my JS:
var audioFile = document.getElementById('audioFile');
// Little hack for mobile, as only a user generated click will enable us to play the sounds
$('#prepareAudioBtn').on('click', function () {
$(this).hide();
audioFile.play();
audioFile.pause();
audioFile.currentTime = 0;
});
// Master user triggered a sound sprite to play
socket.on('playAudio', function (audioClip) {
if (audioFile.paused)
audioFile.play();
audioFile.currentTime = audioClip.startTime;
// checks every 750ms to pause the clip if the endTime has been reached.
// There is a second of "silence" between each sound sprite so the pause is sure to happen at a correct time.
timeListener(audioClip.endTime);
});
function timeListener(clipEndTime) {
this.clear = function () {
clearInterval(interval);
interval = null;
};
if (interval !== null) {
this.clear();
}
interval = setInterval(function () {
if (audioFile.currentTime >= clipEndTime) {
audioFile.pause();
this.clear();
}
}, 750);
}
Also considered blob for each sound but some sounds can go for minutes so that's why I resorted to combining all sounds together for 1 big audio file (better than several audio tags on the page for each clip)
Instead of pausing / playing, I simply set the volume to 0 when it shouldn't be playing, and back to 1 when it should be playing. The Audio methods currentTime and volume don't slow the audio playback at all even on an iPhone 3GS.
I also added the 'loop' attribute to the audio element so it never has to be .play()'ed again.
It was fruitful to combine all mp3 sounds together because this solutions can work because of that.
Edit: audioElement.muted = true or audioElement.muted = false makes more sense.
Edit2: Can't control volume on user's behalf on iOS so I must pause() and play() the audio element as opposed to just muting and unmuting it.
Your setup is working well on desktop because of the preload attribute.
Unfortunately, here's Apple on the subject of preload:
Safari on iOS never preloads.
And here's MDN:
Note: This value is often ignored on mobile platforms.
The mobile platforms are making a tradeoff to save battery and data usage to only load media when it's actually interacted with by the user or programmatically played (autoplay generally doesn't work for similar reasons).
I think the best you're going to do is combining your tracks together, as you said you've done, so you don't have to pay the initial load-up "cost" as much.
I was having the same delay issue when testing in mobile. I found out what some HTML 5 games are using for audio since games demand very low latencies. Some are using SoundJS. I recommend you try that library out.
You can find a speed comparison between using the HTML Audio tag vs using SoundJS here:
http://www.nickfrazier.com/javascript/audio/ui/2016/08/14/js-sound-libraries.html
(test in mobile to hear the difference)
From my tests SoundJS is much faster.
In fact, it's Good enough to be used in a game, or for sound feedback in a user interface.
Old question but here is my solution using one of the answer above:
const el = document.createElement("audio");
el.muted = true;
el.loop = true;
const source = document.createElement("source");
source.src = lineSe;
source.type = "audio/mpeg";
el.appendChild(source);
// need to call this function after user first interaction, or safari won't do it.
function firstPlay() {
el.play();
}
let timeout = null;
function play() {
// In case user press the button too fast, cancel last timeout
if (lineSeTimeout) {
clearTimeout(timeout);
}
// Back to beginning
el.currentTime = 0;
// unmute
el.muted = false;
// set to mute after the audio finish. In my case 500ms later
// onended event won't work because loop=tue
timeout = setTimeout(() => {
// mute audio again
el.muted = true;
}, 500);
}

javascript - video play not working in setInterval

I am trying to play video using .play().
This is for in app with mraid, only native javascript.
I'm trying to play a video with .play(), it works only when the video is loaded.
var video = document.getElementById('video');
video.play();
the above simple 2 lines works like a charm, only when the video is loaded.
Therefore i have to make sure the video is loaded by using setInterval
var autoplay = setInterval(function(){
var video = document.getElementById('extVideo-tab1');
if(video !== null)
{
video.play();
clearInterval(autoplay);
}
},500);
Im very certain that it went into the if, but the video is not played.
Can't figure out why it is not working.
Please help, THANK YOU!
as an alternative, you could try using readyState, as:
var video = document.getElementById('video');
if( video.readyState === 4) {
// 4 -> HAVE_ENOUGH_DATA
//Enough data is available, the media can be played through to the end without interruption.
video.play();
}
Depending on your use case I suggest one of the two following potential solutions:
If you always want it to play right away, do:
video.autoplay = true;
or you could use readyState:
var autoplay = setInterval(function(){
var video = document.getElementById('extVideo-tab1');
if(video.readyState === 4)
{
video.play();
clearInterval(autoplay);
}
},500);
Is there a reason you cant use the autoplay attribute?
https://developer.mozilla.org/en/docs/Web/HTML/Element/video
Regardless you should use the onloadedmetadata event or similar that way you know when you can start playing or seek to a point (unless im missing something?)
document.getElementById('extVideo-tab1').addEventListener('loadedmetadata', function() {
this.play();
}, false)

<video> element with looping does not loop videos seamlessly in Chrome or Firefox

<video width="640" height="360" src="http://jakelauer.com/fireplace.mp4" autoplay loop muted/>
Fiddle here: http://jsfiddle.net/bWqVf/
IE9 does a decent job of it. Is there any recommendation for ways to overcome this? It is very obvious in videos like this one that SHOULD seamlessly loop, but have an annoying skip/pause.
EDIT:
As you can see, if I use javascript to simulate the loop, there's a measurable lag: http://jsfiddle.net/bWqVf/13/
The problems seem to be related to how both Chrome and FF fills the pre-load buffers. In both cases they seem to ignore the loop flag and "reset" the buffers from start meaning in that case that at the end the buffers are emptied and pre-loaded again when video starts causing a slight delay/jump.
IE seem to consider the loop flag and continue to fill also towards the end.
This means it's gonna be very hard to make this look seamless. I tried several techniques over a few hours including pre-caching the first frames to 15 frames off-screen canvases. The closest I could get to seamless was modifying the video to have two segments in it (I do not (no longer) have capable hardware so I needed to reduce the dimension as well to test - see fiddle).
However, there are drawbacks here as well:
The video is double length
You need to play two instances at the same time
Two downloads of the same video happens
Lag compensation will vary from computer to computer
Browser updates in the future can influence good/bad how the result will end up to be.
In other words - there is no stable solution to get around the problem with these browsers.
I would recommend an extension to what I mention above, to pre-loop some segments. This way you can reduce the glitch.
However, to share what I did here goes.
First I extended the video with an extra segment (and reduced the dimension to run it on my computer):
Then I used the following code to do an overlapping loop. That is:
I start the videos at the same time, but one video from the middle.
The video that is currently => middle is shown
I use a canvas element to draw the video onto
When at end the current video is switched so that the new video is still the one being played from the middle
The theory here is that this will mask the glitch you get at the start as the video playing is always in the middle (starting on the second segment).
The code looks like this:
As the videos are loaded async we need to count the loads as this technique uses two video instances and the browser seem to be unable to share the download.
We also set a new position for video 1 to be at the middle. An event is raised for this when video is moved and ready, so we start everything from that point:
v1.addEventListener('canplay', init, false);
v2.addEventListener('canplay', init, false);
v1.addEventListener('timeupdate', go, false);
Handlers:
function init() {
count--; /// = 2
/// both videos are loaded, prep:
if (count === 0) {
length = v1.duration;
mid = length * 0.5;
current = mid;
/// set first video's start to middle
v1.currentTime = mid + lag;
}
}
function go() {
/// remove listener or this will be called for each "frame"
v1.removeEventListener('timeupdate', go, false);
v1.play();
v2.play();
draw();
}
The lag value is an attempt to compensate for the difference between the two videos starting as they don't start at the exact same time.
The main code draw simply switches between the videos depending on the position of the main video (v1) - the frame rate is also reduce to 30 fps to reduce overhead of drawImage as requestAnimationFrame runs optimally at 60 fps (the video here is 30 fps so we only need to draw a frame every other time):
function draw() {
/// reduce frame-rate from 60 to 30
if (reduce === true) {
reduce = false;
requestAnimationFrame(draw);
return;
} else {
reduce = true;
}
/// use video that is >= middle time
var v = v1.currentTime >= mid ? v1 : v2;
/// draw video frame onto canvas
ctx.drawImage(v, 0, 0);
requestAnimationFrame(draw);
}
Now, using canvas opens up other possibilities as well such as making for example a cross-fade between the two videos to smooth the transition further. I didn't implement this as it is outside the scope (in size/broadness), but worth to mention as that could be a solution in itself.
In any case - as mentioned, this is a solution with many drawbacks but it is the closest I could get to reduce the glitch (using Chrome).
The only solution that can work properly is an internal browser driven one as you would need access to the buffers to be able to do this fully seamlessly.
My "solution" is in essence saying: forget it! It won't work in these browsers, use an repeated looped video instead. :-)
I think the problem is related to browser-specific-video-handling.
As a quirk, you can achieve less latency converting the video to webm, but you should place it before mp4 source, ie:
<video width="640" height="360" autoplay loop muted>
<source src="http://jakelauer.com/fireplace.webm" type="video/webm" />
<source src="http://jakelauer.com/fireplace.mp4" type="video/mp4" />
</video>
Heureka!
We've found the actual, real, work-around-free solution to this problem over at where I work. It explains the inconsistent behavior through multiple developers as well.
The tl;dr version is: Bitrates. Who would've guessed? What I suppose is that many people use standard values for this that usually are around 10 Mbit/s for HD videos if you use the Adobe Media Encoder. This is not sufficient. The correct value would be 18 Mbit/s or maybe even higher. 16 is still a bit janky. I cannot express how well this works. I've, by now, tried the messiest workarounds for about five hours until I found this together with our video editor.
I hope this helps everyone and saves you tons of time!
I also hope it's okay that I posted this in another thread as well, but there are a bunch of questions of the same type about this and I wanted to reach a lot of people.
I don't think your problem is "code-related". It has more to do with the actual video itself. It would be much better if you edit your video for a seamless looping.
Have a look HERE as it will give you some guidance on how to do so.
Hope this helps you.
EDIT: You can try breaking the video up into two sections: the intro and the looping part. Make a <video> element for each one and position them in the same place, with the second video hidden. Set an "ended" event on the intro to swap out the display and start the second video. Then, you can set the loop attribute on the second video element.
You shouldn't have a problem getting the two videos to play seamlessly together as long as you have the preload attribute on at least the looping video.
If that doesn't work, try making two video elements with the same looping video. While one is playing, you can hide the other and set its currentTime back to zero, so any seeking delay will happen when nobody is looking.
If none of the above works for you, then you can try an other way with javascript. Note that i haven't tested the below code. What it does is starting the video from the 2nd second and when the video reaches the 4th second it will start it again (from the 2nd second).
function playVideo() {
var starttime = 2; // start at 2 seconds
var endtime = 4; // stop at 4 seconds
var video = document.getElementById('player1');
//handler should be bound first
video.addEventListener("timeupdate", function() {
if (this.currentTime >= endtime) {
this.play();
}
}, false);
//suppose that video src has been already set properly
video.load();
video.play(); //must call this otherwise can't seek on some browsers, e.g. Firefox 4
try {
video.currentTime = starttime;
} catch (ex) {
//handle exceptions here
}
}
The solution that worked for me (and doesn't require a huge amount of JavaScript) is something like:
var video = document.getElementById('background-video');
var loopPoint = 15; // s
function resetVideo() {
if (video.currentTime >= loopPoint) {
video.currentTime = 0;
}
}
video.addEventListener('timeupdate', resetVideo);
Unfortunately I guess this is quite expensive because it will use a callback every time the time of the video/audio updates.
This issue happens to me using the Chromium wrapper with Electron. Regardless of that, I got closer to solving the issue ( not close enough ). Here's a list of things that improved the looping to near seamless jumping back from cuepoint A to B:
A mp4 video with keyframes only was key (increases video size a bit)
Get a framerate-sensitive loop. This little tool helps a lot when using keyframes and timecodes: http://x3technologygroup.github.io/VideoFrameDocs/#!/documentation/FrameRates
( 3. The last thing is only needed if things in 1 & 2 do not help. I've loaded the whole video with an XmlHTTPrequest to fill the buffer completely. )
var xhr = new XMLHttpRequest();
xhr.open('GET', '../assets/video/Comp1.mp4', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 0) { // I used chromium and electron, usually status == 200 !
var myBlob = this.response;
var vid = URL.createObjectURL(myBlob);
// myBlob is now the blob that the object URL pointed to.
var v = document.getElementById("video");
v.src = vid;
// not needed if autoplay is set for the video element
v.play();
// This requires the VideoFrame-tool (see Nr. 2.)
var videoFrame = new VideoFrame({
id: 'v',
frameRate: 25, // ! must match your video frame rate
callback: function(response) {
// I jump from fram 146 to 72
if (videoFrame.get() === 146) {
// now, jump! Dealbreaker is that the seek is stopping the video
// and the few ms to play it again bugger up the experience.
// Any improvements welcome!
videoFrame.seekBackward(71, function() {
v.play();
});
}
}
});
videoFrame.listen('frame', 25);
v1.play();
}
}
xhr.send(null);
The only issue I encounter with this code is that the seeking stops the video and play() needs to be triggered again. This causes a glitch which I solved by going 3 frames back before the actual cuepoint I want to jump to.
This is still inaccurate if used on different hardware with different videos, but maybe it gets you closer to a solution -- an me too! :)
The problem is nothing.
The starting slide and ending slide is different. If both the slides are same, the looping will looks fine. Because of mismatch in these slides only, it looks like pausing at some seconds. Avoid those things and try out.
check below jsFiddle URL carefully i add console.log and trace video tag event like play, pause, ended etc, i check in window chrome version 28 (working loop for me without fire pause event )
http://jsfiddle.net/bWqVf/6/
Ok... after much trial and error, this is what finally worked for me. It seemed to me that the video is not updating after it's ended, so I just remind it all of its properties again when it finishes playing.
myVid.setAttribute('src', "videos/clip1.mp4");
myVid.autoplay = true;
myVid.addEventListener('ended', vidEnded);
function vidEnded()
{
myVid.setAttribute('src', "videos/clip1.mp4");
myVid.autoplay = true;
}

Categories

Resources