I am trying to play a sound in Safari on a special event.
This is what I am doing
<html>
<head>
<in a js file>
if(event)
document.getElementById('sound').play();
</in a js file>
</head>
<body>
<audio id="sound" src="pling.mp3"></audio>
</body>
</html>
The sound pling.mp3 is in the the same folder as the html file.
The result in Safari developer console is:
TypeError: Result of expression 'document.getElementById('sound').play' [undefined] is not a function.
What am I doing wrong?
Isn't this the way it is supposed to work?
Try the following:
document.getElementById('sound')[0].play();
I would use the Audio API, as explained here, to play sound effects after certain events. In my opinion the audio tag is more suited for songs can be played, stopped and controlled via the UI. If you do use audio tags, you must pause and rewind before it can be played again. You can use this script which does that automatically.
If you want to play audio for example on hover events, do something like this in jQuery:
// iterate through the elements that must have audio applied on hover
$('.audio_effect').each(function() {
$(this).on('mouseover', function() {
new Audio('path-to-audiofile.mp3').play();
});
});
Since you can't play an Audio element again without resetting its internal pointer, the easiest course of action is to just create the element fresh on every hover, like I am doing above.
Related
We have a Cordova app that renders an HTML page that uses audio and video HTML tags for streaming audio and video.
Setting the muted attribute on these tags has no effect (always hear the sound) when rendering this page in Cordova.
If I render the same page in Safari (on the same iOS device) the muted attribute works as expected.
Anyone have any insight as to why muted doesn't work in Cordova? Known issue? Maybe a Webkit issue?
Turns out iosrtc was "getting in way". I ended up disabling the audio track instead of muting the html tag.
The iosrtc plugin render the video as a "mock" html element. Not real html element. So every time changes made on that element, you need to refresh it natively using cordova.plugins.iosrtc.refreshVideos() function. This function will send command to native codes to refresh new attribute or changes on the UIView in native.
Here's some example I used before:
$(".muted-btn").on("click", function(){
$("video").prop("muted", true);
cordova.plugins.iosrtc.refreshVideos();
});
Even if you navigate to another page, the video element will stick to your view if you are not refreshing the UIView using the refreshVideos() function.
With the release of OSX High-Sierra*, one of the new features in Safari is that videos on websites will not auto play anymore and scripts can't start it either, just like on iOS. As a user, I like the feature, but as a developer it puts a problem before me: I have an in-browser HTML5 game that contains video. The videos do not get automatically played anymore unless the user changes their settings. This messes up the game flow.
My question is, can I somehow use the players' interaction with the game as a trigger for the video to start playing automatically, even if said activity is not directly linked to the video element?
I cannot use jQuery or other frameworks, because of a restraint that my employer has put on our development. The one exception is pixi.js which - among all other animations - we are also using to play our videos inside a pixi container.
*The same restriction also applies on Mobile Chrome.
Yes, you can bind on event that are not directly ones triggered on the video element:
btn.onclick = e => vid.play();
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
So you can replace this button with any other splash screen requesting an user click, and you'll be granted access to play the video.
But to keep this ability, you must call at least once the video's play method inside the event handler itself.
Not working:
btn.onclick = e => {
// won't work, we're not in the event handler anymore
setTimeout(()=> vid.play().catch(console.error), 5000);
}
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
Proper fix:
btn.onclick = e => {
vid.play().then(()=>vid.pause()); // grants full access to the video
setTimeout(()=> vid.play().catch(console.error), 5000);
}
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
Ps: here is the list of trusted events as defined by the specs, I'm not sure if Safari limits itself to these, nor if it includes all of these.
Important note regarding Chrome and preparing multiple MediaElements
Chrome has a long-standing bug caused by the maximum simultaneous requests per host which does affect MediaElement playing in the page, limiting their number to 6.
This means that you can not use the method above to prepare more than 6 different MediaElements in your page.
At least two workarounds exist though:
It seems that once a MediaElement has been marked as user-approved, it will keep this state, even though you change its src. So you could prepare a maximum of MediaElements and then change their src when needed.
The Web Audio API, while also concerned by this user-gesture requirement can play any number of audio sources once allowed. So, thanks to the decodeAudioData() method, one could load all their audio resources as AudioBuffers, and even audio resources from videos medias, which images stream could just be displayed in a muted <video> element in parallel of the AudioBuffer.
In my case i was combining transparent video (with audio) with GSAP animation. The solution from Kaiido works perfectly!
First, on user interaction, start and pause the video:
videoPlayer.play().then(() => videoPlayer.pause());
After that you can play it whenever you want. Like this:
const tl = gsap.timeline();
tl.from('.element', {scale: 0, duration: 5);
tl.add(() => videoPlayer.play());
Video will play after the scale animation :).
Tested in Chrome, Safari on iPhone
With the release of OSX High-Sierra*, one of the new features in Safari is that videos on websites will not auto play anymore and scripts can't start it either, just like on iOS. As a user, I like the feature, but as a developer it puts a problem before me: I have an in-browser HTML5 game that contains video. The videos do not get automatically played anymore unless the user changes their settings. This messes up the game flow.
My question is, can I somehow use the players' interaction with the game as a trigger for the video to start playing automatically, even if said activity is not directly linked to the video element?
I cannot use jQuery or other frameworks, because of a restraint that my employer has put on our development. The one exception is pixi.js which - among all other animations - we are also using to play our videos inside a pixi container.
*The same restriction also applies on Mobile Chrome.
Yes, you can bind on event that are not directly ones triggered on the video element:
btn.onclick = e => vid.play();
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
So you can replace this button with any other splash screen requesting an user click, and you'll be granted access to play the video.
But to keep this ability, you must call at least once the video's play method inside the event handler itself.
Not working:
btn.onclick = e => {
// won't work, we're not in the event handler anymore
setTimeout(()=> vid.play().catch(console.error), 5000);
}
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
Proper fix:
btn.onclick = e => {
vid.play().then(()=>vid.pause()); // grants full access to the video
setTimeout(()=> vid.play().catch(console.error), 5000);
}
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
Ps: here is the list of trusted events as defined by the specs, I'm not sure if Safari limits itself to these, nor if it includes all of these.
Important note regarding Chrome and preparing multiple MediaElements
Chrome has a long-standing bug caused by the maximum simultaneous requests per host which does affect MediaElement playing in the page, limiting their number to 6.
This means that you can not use the method above to prepare more than 6 different MediaElements in your page.
At least two workarounds exist though:
It seems that once a MediaElement has been marked as user-approved, it will keep this state, even though you change its src. So you could prepare a maximum of MediaElements and then change their src when needed.
The Web Audio API, while also concerned by this user-gesture requirement can play any number of audio sources once allowed. So, thanks to the decodeAudioData() method, one could load all their audio resources as AudioBuffers, and even audio resources from videos medias, which images stream could just be displayed in a muted <video> element in parallel of the AudioBuffer.
In my case i was combining transparent video (with audio) with GSAP animation. The solution from Kaiido works perfectly!
First, on user interaction, start and pause the video:
videoPlayer.play().then(() => videoPlayer.pause());
After that you can play it whenever you want. Like this:
const tl = gsap.timeline();
tl.from('.element', {scale: 0, duration: 5);
tl.add(() => videoPlayer.play());
Video will play after the scale animation :).
Tested in Chrome, Safari on iPhone
I have modified this script to accept videos instead of images:
http://www.catchmyfame.com/catchmyfame-jquery-plugins/jquery-beforeafter-plugin/
Locally, it works fine, but when I upload to a server, the videos are not synced. The first video starts earlier than the other one. Check it out:
http://amarsyla.com/sandbox/beforeafter/
HTML:
<div id="container">
<div>
<video alt="before" autoplay="true" loop="true" width="600" height="366">
<source src="before.mp4" type="video/mp4;">
</video>
</div>
<div>
<video alt="after" autoplay="true" loop="true" width="600" height="366">
<source src="after.mp4" type="video/mp4;">
</video>
</div>
</div>
I initialize the plugin using this code:
$(window).load(function() {
$('#container').beforeAfter();
});
Obviously, the window.load doesn't do the job. I need a JavaScript event or something similar which will be triggered that both of the videos have been loaded and they can start playing simultaneously with each other. I want the videos to be in perfect sync with each other, so each of them starts at the same time, and I thought this would be possible by initializing the plugin after both videos have been fully loaded. I've tried this:
var vid = document.getElementById("myVideo");
vid.oncanplaythrough = function() {
alert("Can play through video without stopping");
};
That doesn't work as I expected. It doesn't always fire. Any help would be appreciated.
The code in your question is different from the code at the link, so I'll go by the latter since that's the one actually running.
It looks like what's happening is that even once the videos are playing, they're losing synchronization a few seconds later when they have to wait for more data from the network. One might think that "oncanplaythrough" would be enough to assume that the videos are sufficiently buffered to play all the way through without pausing, it's not always the case.
In theory, "canplaythrough" fires when the browser guesses that data is coming in faster than you're playing it, as opposed to "canplay" which fires when there is just enough data to show one or two frames from the current time. But Chrome fires "canplaythrough" immediately after "canplay" so you can't count on it. Even on other better-behaved browsers, it's still possible that the data transfer starts fast and then slows down after the event fires.
So that means that you have to continuously watch for any "waiting" events on either video and pause them both until they catch up again.
Here's an example you can use as a reference to get you started:
http://code.chirls.com/whiteknuckles/
It's very old code and not my best work. I would do it differently if I wrote it today, but it seems to work reasonably well.
Before you relink me, I've read questions like How do I stop a page from unloading (navigating away) in JS?, but they deal with HTML4 and below. I want an answer that utilizes the HTML5 event attribute onunload. I also want this to be as pure as possible, that is without external libraries or plugins like jQuery or Flash and only using standardized code (PURE HTML, CSS, and JavaScript, nothing platform-dependant).
Before you tell me to use onbeforeunload, note that it is NON-STANDARD, and therefore does not fit my criteria as described above. It does NOT work on all browsers, especially those made before IE implemented it.
Question
On my site, I have several music players, and if any one of them is playing when the user attempts to navigate away from the page, I want to popup a dialog box ensuring that they intended this and that they understand that this will stop the audio stream, and if they choose "yes" or "OK", it proceeds with the navigation, but if they click "no" or "Cancel", it stays on the same page and keeps playing the audio without a break. How do I use JavaScript to cancel the page unloading WITHOUT USING ONBEFOREUNLOAD?
Code so far
I have the following code so far, but no idea how to stop unloading:
<!DOCTYPE HTML><html>
<head>
<script><!-- Loading Scripts -->
function unloadTasks(){
window.alert(":" + playing + ":");
if (playing && !window.confirm("A podcast is playing, and navigating away from this page will stop that. Are you sure you want to go?"))
window.alert("Here is where I will stop the page from unloading... somehow");
}
</script>
<script><!-- Player Scripts -->
var playing = false;
function logPlay(){
playing = isPlaying("e1audPlayer");
}
function isPlaying(player){
return document.getElementById(player).currentTime > 0 && !document.getElementById(player).paused && !document.getElementById(player).ended;
}
</script>
</head>
<body onunload="unloadTasks()">
<audio id="e1audPlayer" style="width:100%;" controls="controls" preload="auto" onplaying="logPlay()" onpause="logPlay()">
<source src="http://s.supuhstar.operaunite.com/s/content/pod/ADHD Episode 001.mp3" type="audio/mpeg"/>
<source src="http://s.supuhstar.operaunite.com/s/content/pod/ADHD Episode 001.ogg" type="audio/ogg"/>
Your browser does not support embedded audio players. Try Opera or Chrome
</audio>
Link away
</body>
</html>
Working example
You should be using onbeforeunload to prompt the user with the message.
You can not prevent this entirely, however can notify the user via a popup that he should not reload. This is done with the onbeforeunload window event, best documented at MDN.
This will behave exactly like here on Stackoverflow, if you add some text to the answer textarea and than try to leave the page.