How to determine if playing HTML5 audio is actually making sound? - javascript

For the purposes of a project I am working, I want to find out via the JS console if the <audio> tag found on facebook.com is playing audio or not. (I can then move this code into a Chrome extension and also apply it against other websites such as silent HTML5 ads.)
You can find the element by typing:
$$("audio")[0]
I have used the following code (adapted from this jsfiddle which I found during a search) to show when different event listeners are called:
var audio = $$("audio")[0];
var events = 'abort,canplay,canplaythrough,durationchange,emptied,ended,error,loadeddata,loadedmetadata,loadstart,pause,play,playing,progress,ratechange,seeked,seeking,stalled,suspend,timeupdate,volumechange,waiting'.split(',');
// event handler
var onEvent = function(e) {
console.log(e.type);
};
// add event listener to audio for all events
for (var i = 0, len = events.length; i < len; i++) {
audio.addEventListener(events[i], onEvent, false);
}
I would then play the audio by typing:
$$("audio")[0].play();
If I check $$("audio")[0].paused, it will show false, but I haven't found any properties or events that would indicate whether it played sound or not. (I wouldn't expect it to here since I don't think it has audio data to play, but if there was a way to check, I could compare that with an audio element that does play sound.)
Also, I have tried sending myself a message from an incognito tab and don't see a trace of this happening. (I assume the audio element gets used for that.)
Help is appreciated.

To check if an HTMLMediaElement (either video or audio) has ever been played on the page, the best solution is to check for its played attribute.
This will return a TimeRanges object that you can as well use to get how much of the media has been played.
If it has never been played, then the length property of this TimeRanges object will be set to 0 :
if(audioElement.played.length){
// This media has already been played
}else{
// Never
}

Related

HTML5 Audio Player not moving playhead to the end [duplicate]

A notable issue that's appearing as I'm building a simple audio streaming element in HTML5 is that the <audio> tag doesn't behave as one would expect in regards to playing and pausing a live audio stream.
I'm using the most basic HTML5 code for streaming the audio, an <audio> tag with controls, the source of which is a live stream.
Current outcome: When the stream is first played, it plays whatever is streaming as expected. When it's paused and played again, however, the audio resumes exactly where it left off when the stream was previously paused. The user is now listening to a delayed version of the stream. This occurrence isn't browser-specific.
Desired outcome: When the stream is paused, I want the stream to stop. When it is played again, I want it resume where the stream is currently at, not where it was when the user paused the stream.
Does anyone know of a way to make this audio stream resume properly after it's been paused?
Some failed attempts I've made to fix this issue:
Altering the currentTime of the audio element does nothing to streaming audio.
I've removed the audio element from the DOM when the user stops stream playback and added it back in when user resumes playback. The stream still continues where the user left off and worse yet downloads another copy of the stream behind the scenes.
I've added a random GET variable to the end of the stream URL every time the stream is played in an attempt to fool the browser into believing that it's playing a new stream. Playback still resumes where the user paused the stream.
Best way to stop a stream, and then start it again seems to be removing the source and then calling load:
var sourceElement = document.querySelector("source");
var originalSourceUrl = sourceElement.getAttribute("src");
var audioElement = document.querySelector("audio");
function pause() {
sourceElement.setAttribute("src", "");
audioElement.pause();
// settimeout, otherwise pause event is not raised normally
setTimeout(function () {
audioElement.load(); // This stops the stream from downloading
});
}
function play() {
if (!sourceElement.getAttribute("src")) {
sourceElement.setAttribute("src", originalSourceUrl);
audioElement.load(); // This restarts the stream download
}
audioElement.play();
}
Resetting the audio source and calling the load() method seems to be the simplest solution when you want to stop downloading from the stream.
Since it's a stream, the browser will stop downloading only when the user gets offline. Resetting is necessary to protect your users from burning through their cellular data or to avoid serving outdated content that the browser downloaded when they paused the audio.
Keep in mind though that when the source attribute is set to an empty string, like so audio.src = "", the audio source will instead be set to the page's hostname. If you use a random word, that word will be appended as a path.
So as seen below, setting audio.src ="", means that audio.src === "https://stacksnippets.net/js". Setting audio.src="meow" will make the source be audio.src === "https://stacksnippets.net/js/meow" instead. Thus the 3d paragraph is not visible.
const audio1 = document.getElementById('audio1');
const audio2 = document.getElementById('audio2');
document.getElementById('p1').innerHTML = `First audio source: ${audio1.src}`;
document.getElementById('p2').innerHTML = `Second audio source: ${audio2.src}`;
if (audio1.src === "") {
document.getElementById('p3').innerHTML = "You can see me because the audio source is set to an empty string";
}
<audio id="audio1" src=""></audio>
<audio id="audio2" src="meow"></audio>
<p id="p1"></p>
<p id="p2"></p>
<p id="p3"></p>
Be aware of that behavior if you do rely on the audio's source at a given moment. Using the about URI scheme seems to trick it into behaving in a more reliable way. So using "about:" or "about:about", "about:blank", etc. will work fine.
const resetAudioSource = "about:"
const audio = document.getElementById('audio');
audio.src = resetAudioSource;
document.getElementById('p1').innerHTML = `Audio source: -- "${audio.src}"`;
// Somewhere else in your code...
if (audio.src === resetAudioSource){
document.getElementById('p2').innerHTML = "You can see me because you reset the audio source."
}
<audio id="audio"></audio>
<p id="p1"></p>
<p id="p2"></p>
Resetting the audio.src and calling the .load() method will make the audio to try to load the new source. The above comes in handy if you want to show a spinner component while the audio is loading, but don't want to also show that component when you reset your audio source.
A working example can be found here: https://jsfiddle.net/v2xuczrq/
If the source is reset using a random word, then you might end up with the loader showing up when you also pause the audio, or until the onError event handler catches it. https://jsfiddle.net/jcwvue0s/
UPDATE: The strings "javascript:;" and "javascript:void(0)" can be used instead of the "about:" URI and this seems to work even better as it will also stop the console warnings caused by "about:".
Note: I'm leaving this answer for the sake of posterity, since it was the best solution I or anyone could come up with at the time for my issue. But I've since marked Ciantic's later idea as the best solution because it actually stops the stream downloading and playback like I originally wanted. Consider that solution instead of this one.
One solution I came up with while troubleshooting this issue was to ignore the play and pause functions on the audio element entirely and just set the volume property of the audio element to 0 when user wishes to stop playback and then set the volume property back to 1 when the user wishes to resume playback.
The JavaScript code for such a function would look much like this if you're using jQuery (also demonstrated in this fiddle):
/*
* Play/Stop Live Audio Streams
* "audioElement" should be a jQuery object
*/
function streamPlayStop(audioElement) {
if (audioElement[0].paused) {
audioElement[0].play();
} else if (!audioElement[0].volume) {
audioElement[0].volume = 1;
} else {
audioElement[0].volume = 0;
}
}
I should caution that even though this achieves the desired functionality for stopping and resuming live audio streams, it isn't ideal because the stream, when stopped, is actually still playing and being downloaded in the background, using up bandwidth in the process.
However, this solution doesn't necessarily take up more bandwidth than just using .play() and .pause() on a streaming audio element. Simply using the audio tag with streaming audio uses up a great deal of bandwidth anyway, because once streaming audio is played, it continues to download the contents of the stream in the background when it is paused.
It should be noted that this method won't work in iOS because of purposefully built-in limitations for iPhones and iPads:
On iOS devices, the audio level is always under the user’s physical control. The volume property is not settable in JavaScript. Reading the volume property always returns 1.
If you choose to use the workaround in this answer, you'll need to create a fallback for iOS devices that uses the play() and pause() functions normally, or your interface will be unable to pause the stream.
Tested #Ciantics code and it worked with some modifications, if you want to use multiple sources.
As the source is getting removed, the HTML audio player becomes inactive, so the source (URL) needs to be added directly after again to become active.
Also added an event listener at the end to connect the function when pausing:
var audioElement = document.querySelector("audio");
var sources = document.querySelector("audio").children;
var sourceList = [];
for(i=0;i<sources.length;i++){
sourceList[i] = sources[i].getAttribute("src");
}
function pause() {
for(i=0;i<sources.length;i++){
sources[i].setAttribute("src", "");
}
audioElement.pause();
// settimeout, otherwise pause event is not raised normally
setTimeout(function () {
audioElement.load(); // This stops the stream from downloading
});
for(i=0;i<sources.length;i++){
if (!sources[i].getAttribute("src")) {
sources[i].setAttribute("src", sourceList[i]);
audioElement.load(); // This restarts the stream download
}
}
}
audioElement.addEventListener("pause", pause);

Video length of playlist items Webchimera.js Player

Environment
NW.js v0.12.3
io.js v1.2.0
32 bits
Windows 8
Webchimera.js (player)
The following code works but I'm left wondering if it's the best approach. The requirement is to get the length of each video that's in the playlist.
To do that I use the events onPlaying and onPaused.
wjs = require('wcjs-player');
...
chimera_container = new wjs("#chimera_container");
chimera_player = chimera_container.addPlayer({ mute: true, autoplay: false, titleBar: "none" });
chimera_player.onPlaying( OnPlaying ); // Pauses player
chimera_player.onPaused( OnPaused ); // Extracts length information
var OnPlaying = function(){
chimera_player.pause();
};
var OnPaused = function() {
console.log( chimera_player.itemDesc(chimera_player.currentItem()).mrl , chimera_player.length());
if(!chimera_player.next())
chimera_player.clearPlaylist();
};
At first I tried doing all the code in the event onPlaying but the app always crashed with no error. After checking chimera_player.state() it seemed that even after doing chimera_player.pause() the state didn't change while inside the onPlaying event. I figure having state Playing and trying to do chimera_player.next() causes the exception.
This way seems a bit hacky, but I can't think of another one.
My approach was definitely not the best. #RSATom kindly exposed the function libvlc_media_get_duration in the WebChimera.js API.
In order to get the duration all that is needed is:
... after adding playlist...
var vlcPlaylist = chimera_player.vlc.playlist;
for(var i=0, limit=chimera_player.itemCount(); i<limit; ++i ){
var vlcMedia = vlcPlaylist.items[i];
vlcMedia.parse(); // Metadata is not available if not parsed
if(vlcMedia.parsed)
// Access duration via --> vlcMedia.duration
else
logger("ERROR -- parsePlaylist -- " + vlcMedia.mrl );
}
If you are going to try to get duration from files with MPEG format then you are in for a headache. In order to have VLC return duration of MPEG file before playing the video, it is necessary that its Demuxer is set to Avformat demuxer. Problem is you can't do that via the libvlc api. If demuxer is not set, then vlcMedia.duration will always return 0.
There's two options here:
Use ffprobe to access video metadata and forget doing it via libvlc
Play with this posts' original way of getting duration via a combination of play() pause() events.
I've tried find any function in libvlc API allowed get media length without playing it - with no success. So it's very possible there are no way to get media length without playing it.
I was wrong, it's possible (with libvlc_media_get_duration), but it's not exposed in WebChimera.js API yet. I'll add it if you will create issue for it on GitHub.
P.S.: And it will be great if you will create issues on GitHub for discovered crashes...
upd: required API implemented

How to check whether a sound file is playing in JavaScript

var soundObject = null;
function PlaySound() {
var textvalue = document.getElementById('<%= TextBox7.ClientID %>').value;
if (soundObject != null) {
document.body.removeChild(soundObject);
soundObject.removed = true;
soundObject = null;
}
soundObject = document.createElement("embed");
soundObject.setAttribute("src", textvalue);
soundObject.setAttribute("hidden", false);
soundObject.setAttribute("autostart", true);
document.getElementById("div1").appendChild(soundObject);
Above is my javascript code, TextBox7.text="c:\temp\abc.wav" in c#. I have two questions:
1. Is there anyway to tell whether the file c:\temp\abc.wax exists before playing the audio file?
2. How to check if the file start playing or not (since I put autostart)?
My intention is to alert user if the file does not exist or not playing?
PS: Actually, the program is working, it will play the audio when file exists and show a blackscreen if the audio file not found. I just want to make it better so that the user know what is going on.
The <embed> element does not give you that level of control over the audio file being played. If you are able to, I would strongly encourage using the HTML5 <audio> element instead. It inherits from the HTMLMediaElement Dom interface which gives you access to a lot more media specific data. The <embed> element only inherits from the HTMLElement which does not give the same level of control.
You can change your above code to be:
soundObject = new Audio();
soundObject.src = textValue;
soundObject.hidden = true;
soundObject.autoplay = true;
document.getElementById("div1").appendChild(soundObject);
soundObject.play();
Once you do that if you get an error on playback you can use soundObject.error to access the error message. This will give you an error if the file doesn't exist or any other error during playback.
To tell if the file is actively playing you can monitor the soundObject.played to see what part of the file has already been played.
I hope this solves your problem for you!
Edit:
I am attaching a JSFiddle that will hopefully give you a better idea of how to use this feature here.

Why isn't my audio rewinding?

I'm having a bit of trouble rewinding audio in Javascript. I basically have a countdown that beeps each second as it gets towards the end of the countdown.
I tried using;
var bip = new Audio("http://www.soundjay.com/button/beep-7.wav");
bip.play();
but it didn't beep every second which I'm guessing has something to do withit having to load a new sound every second. I then tried loading the sound externally and triggering it with the following code.
bip.pause();
bip.currentTime = 0;
console.log(bip.currentTime);
bip.play();
but this only plays the sound once then completely fails to rewind it (this is shown by the console logging a time of 0.19 seconds after the first playthrough).
Is there something I'm missing here?
In google chrome I noticed that it only works if you have the audio file in same domain. I have no problems if the audio file is in same domain. Event setting .currentTime works.
Cross-domain:
var bip = new Audio("http://www.soundjay.com/button/beep-7.wav");
bip.play();
bip.currentTime; //0.10950099676847458
bip.currentTime = 0;
bip.currentTime; //0.10950099676847458
Same-domain:
var bip = new Audio("beep-7.wav");
bip.play();
bip.currentTime; //0.10950099676847458
bip.currentTime = 0;
bip.currentTime; //0
I tried googling for a while and could find nothing in specs about this or even any discussion.
when I want rewind I simply load the audio again:
my_audio.load()
*btw, I also use a eventlistener for 'canplay' to trigger the my_audio.play(). It seems that this is necessary in android, and maybe other devices also*
To further dsdsdsdsd's answer with a bit of paint-by-numbers for the "whole shebang"
NOTE: In my app, loc1 is a dummy that refers to the song's stored location
// ... code before ...
tune = new Audio(loc1); // First, create audio event using js
// set up "song's over' event listener & action
tune.addEventListener('ended', function(){
tune.load(); //reload audio event (and reset currentTime!)
tune.play(); //play audio event for subsequent 'ended' events
},false);
tune.play(); // plays it the first time thru
// ... code after ...
I spent days and days trying to figure out what I was doing wrong, but it all works fine now... at least on the desktop browsers...
As of Chrome version 37.0.2062.120 m, the behaviour described by #Esailija has not changed.
I workaround this issue by encoding the audio data in base64 encoding and feed the data to Audio() using data: URL.
Test code:
snd = new Audio('data:audio/ogg;base64,[...base64 encoded data...]');
snd.onloadeddata = function() {
snd.currentTime = 0;
snd.play();
setTimeout(function() {
snd.currentTime = 0;
snd.play();
}, 200);
};
(I am surprised that there are no bug reports or references on this matter... or maybe my Google-fu is not strong enough.)

JS .play() on iPad plays wrong file...suggestions?

So, I am building a web app that has a div with text that changes on various user actions (it's stepping through an array of pieces of text). I'm trying to add audio to it, so I made another array with the sound files in the appropriate positions:
var phrases=['Please hand me the awl.','etc1','etc2','etc3'];
var phrasesAudio=['awl.mp3','etc1.mp3','etc2.mp3','etc3.mp3'];
And on each action completion, a 'counter' variable in incremented, and each array looks for the object at that counter
var audio = document.createElement("audio"),
canPlayMP3 = (typeof audio.canPlayType === "function" &&
audio.canPlayType("audio/mpeg") !== "");
function onAction(){
correct++;
document.getElementById('speech').innerHTML=phrases[correct];
if(canPlayMP3){
snd = new Audio(phrasesAudio[correct]);
}
else{
snd = new Audio(phrasesAudioOgg[correct]);
}
snd.play();
}
(the text replaces a div's HTML and I use .play() for the audio object)...usually works okay (and ALWAYS does in a 'real' browser), but on the iPad (the actual target device) after a few successful iterations, the TEXT continues to progress accurately, but the AUDIO will hiccup and repeat a sound file one or more times. I added some logging and looking there it reports that it's playing screw.mp3 (just an example, no particular file is more or less error prone), but in actuality it plays screwdriver.mp3 (the prior file, if there is an error, the audio always LAGS, never LEADS)...because of this I am thinking that the problem is with my use of .play()...so I tried setting snd=null; between each sound, but nothing changed...Any suggestions for how to proceed? This is my first use of audio elements so any advice is appreciated. Thanks.
edit: I've also tried setting the files with snd.src (based on https://wiki.mozilla.org/Audio_Data_API) and this caused no audio to play
for iPad you need to call snd.load() before snd.play() - otherwise you get "odd" behaviour...
see for some insight:
http://jnjnjn.com/187/playing-audio-on-the-ipad-with-html5-and-javascript/
Autoplay audio files on an iPad with HTML5
EDIT - as per comment from the OP:
Here https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox you can find a tip on halting a currently playing piece of media with
var mediaElement = document.getElementById("myMediaElementID");
mediaElement.pause();
mediaElement.src = "";
and, following that with setting the correct src, load(), play() works great

Categories

Resources