I'm making an iOS app to wrap my javascript game. On mobile safari it works fine because after I play a sound in ontouchstart, then I'm allowed to play any audio whenever I want, and I can set their volume too.
Problem 1: In a WKWebView I can only play the specific sounds that I played in ontouchstart, not the rest. So I'm playing every single audio in my game on the first tap. It sounds really bad. Otherwise in the javascript on audio.play() I get
Unhandled Promise Rejection: NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
Problem 2: I also can't lower the volume of sounds in a WKWebView. If I set myAudio.volume=.5 it's instantly 1 again. Which means the user has to actually hear them, in order for them to get to readyState=4
Any good solution or hack? Right now I'm playing every single sound on the first tap, and everything is full volume.
<html>
<body style='bakcground:#DDD;height:333px'>
<div id=debug style='font-size:20px' onclick="playSound(myAudio)">
Debug<br />
Tap here to play the sound.<br />
</div>
<script>
var context = new webkitAudioContext()
var myAudio = new Audio("sound/upgrade.wav")
myAudio.addEventListener('canplaythrough', function(){log('canplaythrough1')}, false)
var myAudio2 = new Audio("sound/dragon.wav")
myAudio2.addEventListener('canplaythrough', function(){log('canplaythrough2')}, false)
function playSound(sound)
{
log("playSound() readyState="+sound.readyState)
try{
sound.play()
context.resume().then(() => {
log("Context resumed")
sound.play()
})
}
catch(e)
{
log(e)
}
}
function log(m)
{
console.log(m)
debug.innerHTML += m+"<br />"
}
window.ontouchstart = function()
{
log("ontouchstart()")
playSound(myAudio)
setTimeout(function() {
playSound(myAudio2, 1111)
})
}
</script>
</body>
</html>
And ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController {
private var myWebView3 : WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.myWebView3 = WKWebView(frame: .zero)
self.myWebView3.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
self.myWebView3.configuration.mediaTypesRequiringUserActionForPlayback = []
self.myWebView3.configuration.allowsInlineMediaPlayback = true
let url = Bundle.main.url(forResource: "index6", withExtension: "html", subdirectory: "/")!
self.myWebView3.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
self.myWebView3.load(request)
self.view.addSubview(self.myWebView3)
}
override func viewDidLayoutSubviews() {
self.myWebView3.frame = self.view.bounds
}
}
I'm using Xcode 10.1
iPhone SE 12.1.1
I have also tried on an iPad with iOS 10 and get the same error.
I have also tried context.decodeAudioData() / context.createBufferSource() and get the same error.
Re: problem 1, you need to create the configuration object before you set the web view's configuration. The line
self.myWebView3.configuration.mediaTypesRequiringUserActionForPlayback = []
will fail silently. Just create a new WKWebViewConfiguration object, set its properties, and then create the web view:
webView = WKWebView(frame: .zero, configuration: webConfiguration)
In the WKWebView's delegate, after webview loading finished, execute javascript that addEventListener to audio and video tag with event play, pause, ended. Then check the playback state for purpose.
Related
I cannot seem to get the AudioContext in Safari 15 to function properly. When you initiate it, it is in a running state, but the AudioContext.currentTime never ticks up and nothing plays. All the previous advice covers old versions of Safari where you needed to call resume inside a click handler to get it out of a suspended state, but as you can see below it is running and calling resume does not make a difference.This is the most basic example I could come up with below:
https://codepen.io/thelamer123/pen/vYeLXOm
<html>
<body>
<button onclick="play()">Test Audio</button>
<div id="output"></div>
<script>
async function play() {
var ac = new window.AudioContext();
ac.resume();
var response = await fetch('https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3');
var buffer = await response.arrayBuffer();
ac.decodeAudioData(buffer, onDecoded);
function onDecoded(buffer){
var bs = ac.createBufferSource();
bs.buffer = buffer;
bs.loop = true;
bs.connect(ac.destination);
bs.start(0);
}
var logloop = setInterval(() => {
document.getElementById('output').innerHTML = '';
document.getElementById('output').innerHTML = 'AudioContext State: ' + ac.state + '<br>Current Time: ' + ac.currentTime;
}, 100);
}
</script>
</body>
</html>
So this was a couple things, this example seems to function hit or miss depending on your OS or platform.
The advice I would give anyone running into this is to wrap your logic in a button click, do not try to use event capture. Initiate the Audio context at a top level on page load then when the button is pressed do this:
await audiocontext.resume();
console.log(audiocontext);
For some reason to have compatibility you need to not only be inside an async clicked on function but also console log the audio context after resuming it. With Safari 15.1 it seems it can be in a state where it is "running" but not really if you do not do these two things.
I would like to set an NDEF message on my website if it is opened from android chrome and read it with an NFC Reader using nfcpy.
According to https://developer.mozilla.org/en-US/docs/Web/API/NDEFMessage/NDEFMessage I think I should be able to do this.
My page looks like this:
<button onclick="set_ndef()">Set NDEF msg</button>
<pre id="log"></pre>
<script>
async function set_ndef() {
if ("NDEFReader" in window) {
try {
const ndefmsg = new NDEFMessage({'records': [{'recordType': 'text', 'data': 'asd'}]});
consoleLog(ndefmsg)
} catch(error) {
consoleLog(error);
}
} else {
consoleLog("Web NFC is not supported.");
}
}
function consoleLog(data) {
var logElement = document.getElementById('log');
logElement.innerHTML += data + '\n';
}
</script>
The website uses HTTPS, and when I press the button an NDEFRecord object is printed into the div.
I have an acr122u NFC reader, which I was able to set up using nfcpy:
import nfc
import time
def on_connect(tag):
print(tag.identifier.hex())
tag_ident = tag.identifier.hex()[:8]
print(tag.ndef)
if tag.ndef:
for record in tag.ndef:
print(record)
return True
while True:
with nfc.ContactlessFrontend('usb') as clf:
tag = clf.connect(rdwr={'on-connect': on_connect, 'beep-on-connect': True})
time.sleep(1)
After I place my phone on the reader (after pressing the button on the page) I am only able to read the UID of the phone, but the tag.ndef is None
How can I do this (if I am even able to)?
https://stackoverflow.com/a/65659726/2373819 should give you some background as why this won't work, Android Chrome NFC is an NFC reader and 2 readers won't work.
Though with the acr122u NFC reader can usually be configure to behave as an NFC Tag, then Android Chrome should be able read and write NDEF messages to it (https://nfcpy.readthedocs.io/en/latest/topics/get-started.html#emulate-a-card)
I have a use case where we donot want the user who is taking a test get sleep timeout while in test which is of 3 hrs. I was looking at nosleep.js but i has cpu overuse problem as it was not working in windows.
I was wondering how test taking apps do it, also how youtube achieves this.
Any help on this is really appreciated
Wake Lock API arrives in Chrome 79. However none of the other browser support this as of now.
https://developers.google.com/web/updates/2019/12/nic79#wake-lock
YouTube handles this by simply playing video. When you play a video with sound in the browser, the browser requests a wake lock automatically so as not to interrupt the user.
It isn't clear from your question what your specific requirements are, but if playing a video is possible, try that.
So after dealing with the pain of this problem for nice afternoon. I'm posting my solution. I'm using modules so delete export statements if you're not. My goal was to minimize effect on battery. So I play empty video file (10 x 10px, 1 frame lenght with empty audio track) every 10 seconds. I play it for the first time when user first clicks so the browser will allow me to play it. There are still side effects though. I.e. in Safari, the audio icon will blink and on iPhone lockscreen, the page will be shown as the last one playing video/audio.
let noSleep = null
let video = null
// --- Public ---
export function activate() {
if (!noSleep) noSleep = new NoSleep()
}
export function deactivate() {
if (noSleep) {
noSleep.stop()
noSleep = null
}
}
// --- Private ---
class NoSleep {
constructor() {
this._noSleep = true
this._keepAwake()
}
stop() {
this._noSleep = false
}
async _keepAwake() {
while(this._noSleep) {
if (video) video.play()
document.body.innerHTML += "<br>Played"
await wait(10000)
}
}
}
window.addEventListener("click", function enable() {
window.removeEventListener("click", enable)
// Initialize video element
video = document.createElement("video")
video.setAttribute("playsinline", "")
// Add mp4 source
let source = document.createElement("source")
source.src = mp4Src
source.type = "video/mp4"
video.append(source)
// Add webm source
source = document.createElement("source")
source.src = webmSrc
source.type = "video/webm"
video.append(source)
// Play it as a result of user interaction
video.play()
document.body.innerHTML = "Activated"
activate()
})
function wait(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
const mp4Src = "data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAyBtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAGwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACSnRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAGwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAABoAAAgAAAEAAAAAAcJtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAEgFXEAAAAAAAxaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAENvcmUgTWVkaWEgQXVkaW8AAAABaW1pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABLXN0YmwAAAB7c3RzZAAAAAAAAAABAAAAa21wNGEAAAAAAAAAAQAAAAAAAAAAAAIAEAAAAACsRAAAAAAAM2VzZHMAAAAAA4CAgCIAAQAEgICAFEAVAAAAAAJ/9wACf/cFgICAAhIQBoCAgAECAAAAFGJ0cnQAAAAAAAJ/9wACf/cAAAAgc3R0cwAAAAAAAAACAAAAAwAABAAAAAABAAAAgAAAABxzdHNjAAAAAAAAAAEAAAABAAAABAAAAAEAAAAkc3RzegAAAAAAAAAAAAAABAAAAXMAAAF0AAABcwAAAXQAAAAUc3RjbwAAAAAAAAABAAADTAAAABpzZ3BkAQAAAHJvbGwAAAACAAAAAf//AAAAHHNiZ3AAAAAAcm9sbAAAAAEAAAAEAAAAAQAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTguNzYuMTAwAAAACGZyZWUAAAXWbWRhdCERRQAUUAFG//EKWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaXemCFLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS8IRFFABRQAUb/8QpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpd6aIUtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS8IRFFABRQAUb/8QpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpd6YIUtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLwhEUUAFFABRv/xClpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWl3pohS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLw="
const webmSrc = "data:video/webm;base64,GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQRChYECGFOAZwEAAAAAAANXEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHYTbuMU6uEElTDZ1OsggE/TbuMU6uEHFO7a1OsggNB7AEAAAAAAABZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmsirXsYMPQkBNgI1MYXZmNTguNzYuMTAwV0GNTGF2ZjU4Ljc2LjEwMESJiEBBAAAAAAAAFlSua+KuAQAAAAAAAFnXgQFzxYjHh4Jmxpm3C5yBACK1nIN1bmSGhkFfT1BVU1aqg2MuoFa7hATEtACDgQLhkZ+BArWIQOdwAAAAAABiZIEYY6KTT3B1c0hlYWQBAjgBgLsAAAAAABJUw2dB03NzAQAAAAAAAQ5jwIBnyAEAAAAAAAAVRaOLTUFKT1JfQlJBTkREh4RxdCAgZ8gBAAAAAAAAFEWjjU1JTk9SX1ZFUlNJT05Eh4EwZ8gBAAAAAAAAG0WjkUNPTVBBVElCTEVfQlJBTkRTRIeEcXQgIGfIAQAAAAAAABlFo4hUSU1FQ09ERUSHizAwOjAwOjAwOjAwZ8gBAAAAAAAAKkWjn0NPTS5BUFBMRS5RVUlDS1RJTUUuRElTUExBWU5BTUVEh4VlbXB0eWfIAQAAAAAAACRFo5lDT00uQVBQTEUuUVVJQ0tUSU1FLlRJVExFRIeFZW1wdHlnyAEAAAAAAAAaRaOHRU5DT0RFUkSHjUxhdmY1OC43Ni4xMDBzcwEAAAAAAACxY8CLY8WIx4eCZsaZtwtnyAEAAAAAAAAiRaOMSEFORExFUl9OQU1FRIeQQ29yZSBNZWRpYSBBdWRpb2fIAQAAAAAAABtFo4lWRU5ET1JfSUREh4xbMF1bMF1bMF1bMF1nyAEAAAAAAAAjRaOHRU5DT0RFUkSHlkxhdmM1OC4xMzQuMTAwIGxpYm9wdXNnyKJFo4hEVVJBVElPTkSHlDAwOjAwOjAwLjAzNDAwMDAwMAAAH0O2daTngQCjh4EAAID8//6gAQAAAAAAAA+hh4EAFQD8//51ooNwiJgcU7trkbuPs4EAt4r3gQHxggMY8IED"
Implementation is made so multiple calls to activate()/deactivate() have no effect (which was beneficial in my use case). I create NoSleep object to make sure that it's gonna deactivate (operation may take up to 10 seconds to deactivate). Without it quick successive activations and deactivations could result in multiple wake cycles to run at the same time without quiting themselves eventually.
Play looped VIDEO or AUDIO on your page
You can use this a quick example to add a Fake looped video to your page and prevent mobile device from sleep:
// Create the root video element
var video = document.createElement('video');
video.setAttribute('loop', '');
// Add some styles if needed
video.setAttribute('style', 'position: fixed;');
// A helper to add sources to video
function addSourceToVideo(element, type, dataURI) {
var source = document.createElement('source');
source.src = dataURI;
source.type = 'video/' + type;
element.appendChild(source);
}
// A helper to concat base64
var base64 = function(mimeType, base64) {
return 'data:' + mimeType + ';base64,' + base64;
};
// Add Fake sourced
addSourceToVideo(video,'webm', base64('video/webm', 'GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA='));
addSourceToVideo(video, 'mp4', base64('video/mp4', 'AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAG21kYXQAAAGzABAHAAABthADAowdbb9/AAAC6W1vb3YAAABsbXZoZAAAAAB8JbCAfCWwgAAAA+gAAAAAAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIVdHJhawAAAFx0a2hkAAAAD3wlsIB8JbCAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAIAAAACAAAAAABsW1kaWEAAAAgbWRoZAAAAAB8JbCAfCWwgAAAA+gAAAAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAAVxtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAEcc3RibAAAALhzdHNkAAAAAAAAAAEAAACobXA0dgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAIAAgASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAFJlc2RzAAAAAANEAAEABDwgEQAAAAADDUAAAAAABS0AAAGwAQAAAbWJEwAAAQAAAAEgAMSNiB9FAEQBFGMAAAGyTGF2YzUyLjg3LjQGAQIAAAAYc3R0cwAAAAAAAAABAAAAAQAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAAEwAAAAEAAAAUc3RjbwAAAAAAAAABAAAALAAAAGB1ZHRhAAAAWG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAK2lsc3QAAAAjqXRvbwAAABtkYXRhAAAAAQAAAABMYXZmNTIuNzguMw=='));
// Append the video to where ever you need
document.body.appendChild(video);
// Start playing video after any user interaction.
// NOTE: Running video.play() handler without a user action may be blocked by browser.
var playFn = function() {
video.play();
document.body.removeEventListener('touchend', playFn);
};
document.body.addEventListener('touchend', playFn);
I am making a web app that can be open for a long time. I don't want to load audio at load time (when the HTML gets downloaded and parsed) to make the first load as fast as possible and to spare precious resources for mobile users. Audio is disabled by default.
Putting the audio in CSS or using preload is not appropriate here because I don't want to load it at load time with the rest.
I am searching for the ideal method to load audio at run time, (after a checkbox has been checked, this can be after 20 minutes after opening the app) given a list of audio elements.
The list is already in a variable allSounds. I have the following audio in a webpage (there are more):
<audio preload="none">
<source src="sound1.mp3">
</audio>
I want to keep the same HTML because after second visit I can easily change it to (this works fine with my server-side HTML generation)
<audio preload="auto">
<source src="sound1.mp3">
</audio>
and it works.
Once the option is turned on, I want to load the sounds, but not play them immediately. I know that .play() loads the sounds. But I want to avoid the delay between pressing a button and the associated feedback sound.
It is better to not play sound than delayed (in my app).
I made this event handler to load sounds (it works) but in the chrome console, it says that download was cancelled, and then restarted I don't know exactly what I am doing wrong.
Is this is the correct way to force load sounds? What are the other ways? If possible without changing the HTML.
let loadSounds = function () {
allSounds.forEach(function (sound) {
sound.preload = "auto";
sound.load();
});
loadSounds = function () {}; // Execute once
};
here is playSound function, but not very important for the questions
const playSound = function (sound) {
// PS
/* only plays ready sound to avoid delays between fetching*/
if (!soundEnabled)) {
return;
}
if (sound.readyState < sound.HAVE_ENOUGH_DATA) {
return;
}
sound.play();
};
Side question: Should there be a preload="full" in the HTML spec?
See also:
Preload mp3 file in queue to avoid any delay in playing the next file in queue
how we can Play Audio with text highlight word by word in angularjs
To cache the audio will need to Base64 encode your MP3 files, and start the Base64 encoded MP3 file with data:audio/mpeg;base64,
Then you can pre-load/cache the file with css using something like:
body::after {
content:url(myfile.mp3);
display:none;
}
I think I would just use the preloading functionality without involving audio tag at all...
Example:
var link = document.createElement('link')
link.rel = 'preload'
link.href = 'sound1.mp3'
link.as = 'audio'
link.onload = function() {
// Done loading the mp3
}
document.head.appendChild(link)
I'm quite sure that I've found a solution for you. As far as I'm concerned, your sounds are additional functionality, and are not required for everybody. In that case I would propose to load the sounds using pure javascript, after user has clicked unmute button.
A simple sketch of solution is:
window.addEventListener('load', function(event) {
var audioloaded = false;
var audioobject = null;
// Load audio on first click
document.getElementById('unmute').addEventListener('click', function(event) {
if (!audioloaded) { // Load audio on first click
audioobject = document.createElement("audio");
audioobject.preload = "auto"; // Load now!!!
var source = document.createElement("source");
source.src = "sound1.mp3"; // Append src
audioobject.appendChild(source);
audioobject.load(); // Just for sure, old browsers fallback
audioloaded = true; // Globally remember that audio is loaded
}
// Other mute / unmute stuff here that you already got... ;)
});
// Play sound on click
document.getElementById('playsound').addEventListener('click', function(event) {
audioobject.play();
});
});
Of course, button should have id="unmute", and for simplicity, body id="body" and play sound button id="playsound. You can modify that of course to suit your needs. After that, when someone will click unmute, audio object will be generated and dynamically loaded.
I didn't try this solution so there may be some little mistakes (I hope not!). But I hope this will get you an idea (sketch) how this can be acomplished using pure javascript.
Don't be afraid that this is pure javascript, without html. This is additional functionality, and javascript is the best way to implement it.
You can use a Blob URL representation of the file
let urls = [
"https://upload.wikimedia.org/wikipedia/commons/b/be/Hidden_Tribe_-_Didgeridoo_1_Live.ogg"
, "https://upload.wikimedia.org/wikipedia/commons/6/6e/Micronesia_National_Anthem.ogg"
];
let audioNodes, mediaBlobs, blobUrls;
const request = url => fetch(url).then(response => response.blob())
.catch(err => {throw err});
const handleResponse = response => {
mediaBlobs = response;
blobUrls = mediaBlobs.map(blob => URL.createObjectURL(blob));
audioNodes = blobUrls.map(blobURL => new Audio(blobURL));
}
const handleMediaSelection = () => {
const select = document.createElement("select");
document.body.appendChild(select);
const label = new Option("Select audio to play");
select.appendChild(label);
select.onchange = () => {
audioNodes.forEach(audio => {
audio.pause();
audio.currentTime = 0;
});
audioNodes[select.value].play();
}
select.onclick = () => {
const media = audioNodes.find(audio => audio.currentTime > 0);
if (media) {
media.pause();
media.currentTime = 0;
select.selectedIndex = 0;
}
}
mediaBlobs.forEach((blob, index) => {
let option = new Option(
new URL(urls[index]).pathname.split("/").pop()
, index
);
option.onclick = () => console.log()
select.appendChild(option);
})
}
const handleError = err => {
console.error(err);
}
Promise.all(urls.map(request))
.then(handleResponse)
.then(handleMediaSelection)
.catch(handleError);
When an iOS 8 device running a Web Application (i.e. launched from a shortcut on the Home Screen) returns from it's Sleep state all asynchronous web requests made fail to trigger the OnUpdateReady callback.
The problem is quite easy to reproduce - simply put the two code files below on any web server and give it a try.
Has anyone else run into this issue? If so is there any workarounds?
I'm posting this to try to attract attention to this bug in iOS 8 that has essentially ruined all of my web applications - we've had to recommend to NOT upgrade beyond iOS 7. And yes, I've posted the problem on Apple Bug Reporter but I think no one is looking at these since it has been a long time.
app.manifest
CACHE MANIFEST
# 2014-09-24 - Test
CACHE:
default.html
default.html
<!DOCTYPE html>
<html manifest="app.manifest">
<head>
<title>Test Harness</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<meta name="HandheldFriendly" content="true" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<script language="javascript" type="text/javascript">
var Test = new function () {
var _GetEnd = function (oResult) {
var sResult = ': ' +
((oResult.Value === true)
? 'Success'
: 'Failure<br>' + oResult.Reason) +
'<br>';
var oLog = document.getElementById('idLog');
oLog.innerHTML = (new Date()) + sResult + oLog.innerHTML
setTimeout(_GetBegin, 1000);
};
var _GetBegin = function () {
var sURL = 'app.manifest';
var hAsyncCallback = _GetEnd;
try {
var oRequest = new XMLHttpRequest();
oRequest.onreadystatechange =
function () {
if (oRequest.readyState != 4) return;
if (oRequest.status != 200) {
hAsyncCallback({ Value: false, Reason: oRequest.responseText });
} else {
hAsyncCallback({ Value: true, Reason: null });
}
};
oRequest.open('GET', sURL, true);
oRequest.send(null);
} catch (e) {
alert('Critical Error: ' + e.message );
}
};
this.Start = function () { _GetBegin(); }
};
</script>
</head>
<body onload="Test.Start();">
<ol>
<li>Put both app.manifest and default.html on a web server.</li>
<li>Make sure this page is being launched from the Home screen as a web application.</li>
<li>Press the sleep button while it is running.</li>
<li>Press the wake button and unlock the phone to get back to this screen.</li>
<li>Under iOS7x the page continues, under iOS8 the onreadystatechange never gets called again.</li>
</ol>
<div id="idLog"></div>
</body>
</html>
Installing iOS 8.1.1 fixes this.
I am also seeing the same issue, though my example is much simpler. Simply have a webclip application with
<script>
window.setInterval(function(){
console.log("Johnny Five Alive! : " + new Date());
},1000);
</script>
on the page. Inspecting the console, after the sleep wakes up, no more console output. This works fine on iOS7 (my actual application is a complicated angularJS thing, I just boiled down the issue to this). Have you had any response on your bug report?
Our workaround (for AJAX) is:
Detect iOS8 (indeed 8.0.2 still has this) (also see this for other workaround: How to resume JavaScript timer on iOS8 web app after screen unlock?)
Remove the normal eventListeners, but keep the onProgress one
...
this.onProgress = function(e)
{
var position = e.position || e.loaded;
var total = e.totalSize || e.total;
var percentage = 0.0;
if(total != 0)
{
percentage = position / total;
}
if(percentage == 1) {
if( this.isIOS8() ) {
recovery_uuid.get(uuid, _.bind(this.ios8ScriptReturn, this));
}
}
}
...
//this gets called when the script with this UUID is injected
this.ios8ScriptReturn = function(uuid, value) {
//then we create a simpler non real one
this.xhr = {};
this.xhr.readyState = 4;
this.xhr.status = 200;
this.xhr.responseText = value;
this.xhr.onreadystatechange = null;
this.xhr.isFake = true;
//fake stateChnage
this.onReadyStateChange();
}
add a UUID to each request
if( this.isIOS8() ) {
ajaxInfo.url += '&recoveryUUID='+ajaxInfo.uuid;
}
Then still perform the XHR Send (that actually works fine, server gets and send back fine).
server Side save the 'result' in database/file with the UUID as index/part of filename
//detect the need for saving the result, and save it till requested
if(isset($_GET['recoveryUUID'])) {
$uuid = $_GET['recoveryUUID'];
RecoveryUUID::post($uuid, $result_json_string);
}
On the client create a little helper global object that listens to the code injects and redirects them to the onProgress handler.
var RecoveryUUID = (function (_root) {
function RecoveryUUID() {
this.callbacks = {};
}
var proto = RecoveryUUID.prototype;
proto.onLoaded = null;
proto.set = function(uuid, value) {
console.log('RECOVERY UUID: Received DATA: '+uuid+' value: '+value);
if(typeof this.callbacks[uuid] != 'undefined') {
this.callbacks[uuid](uuid, value);
delete this.callbacks[uuid]; //auto remove
}
if(this.onLoaded != null) {
this.onLoaded(uuid, value);
}
var script = document.getElementById("recoveryScript_"+uuid);
script.parentElement.removeChild(script);
}
proto.getURL = function(uuid) {
return "http://"+window.location.hostname+":8888/recoveryuuid/index.php?uuid="+uuid;
}
proto.get = function(uuid, callback) {
var script = document.createElement("script");
script.setAttribute("id", "recoveryScript_"+uuid);
script.setAttribute("type", "text/javascript");
script.setAttribute("src", this.getURL(uuid));
if(typeof callback != 'undefined') {
this.callbacks[uuid] = callback;
}
document.getElementsByTagName("head")[0].appendChild(script);
}
return RecoveryUUID;
})();
//global - just so the injected script knows what to call
recovery_uuid = new RecoveryUUID();
The script that is loaded immediately executes (pushes, since setInterval is dead as well).
// this is: http://"+window.location.hostname+":8888/recoveryuuid/index.php?uuid=...."
<?php
header('Cache-Control: no-cache, no-store, must-revalidate, post-check=0, pre-check=0 '); // HTTP 1.1. //iOS force this file to keep fresh
header('Pragma: no-cache'); // HTTP 1.0.
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header('Content-type: application/javascript; charset=UTF-8');
if(isset($_GET['uuid'])) {
$uuid = $_GET['uuid'];
$out = 'recovery_uuid.set('.json_encode($uuid).','.json_encode(RecoveryUUID::get($uuid)).');';
echo $out;
}
?>
Below is a simple filebased results implementation.
<?php
class RecoveryUUID {
static public function getFileName($uuid) {
return SOMESTATIC LOCATION.$uuid.'.json';
}
static public function post($uuid, $stdClassOrString) {
$data = '{ "data": '.json_encode($stdClassOrString).', "uuid": '.json_encode($uuid).' }';
file_put_contents(self::getFileName($uuid), $data);
}
//might not work first time as the script tag might request this file before it was written.
//so we wait just a bit.
static public function getFull($uuid) {
$tries = 10;
$filename = self::getFileName($uuid);
while ($tries > 0) {
if(file_exists($filename)) {
if (is_readable($filename)) {
$data = #file_get_contents($filename);
if($data !== FALSE) {
unlink($filename);
return $data;
}
}
}
$tries = $tries -1;
usleep(250000);//wait 0.25 secs ...
}
$data = new stdClass();
$data->uuid = $uuid;
$data->data = 'ERROR RECOVERYUUID: timeout on reading file';
return $data;
}
static public function get($uuid) {
$decoded = json_decode(self::getFull($uuid));
if( $decoded->uuid == $uuid ) {
return $decoded->data;
}
return null;
}
}
?>
As we do not use JQuery all we needed to do was add the extra logic in our Ajax class, and of course the Saving to Database for all requests..
Downsides:
Nasty
Will keep on adding memory footprint for each call (for us not an issue as the memory is cleared between window.location.href calls (we do not use SPA) so eventually will fall over.
Extra serverside logic.
Upsides:
Works until memory runs out (removing script tags, which we do does not remove the memory associated)
Comments:
You could of course just send everything with the 'call' but we wanted to have minimal server side impact (or not much work for us anyway) + we presume this will be fixed and means our 'user' code has 0 impact.
Weird, Apple just closed my bug and referred to the same bug number. Also a web app but I found css3 transitions to stop working after screen lock see below:
Engineering has determined that your bug report (18556061) is a duplicate of another issue (18042389) and will be closed
My report:
If you add an HTML app to the home screen and open it, all CSS3 transitions work correctly. Without closing the app and pressing screen lock the transitions seem to stop and can cause the ui to appear to freeze. For example if an absolute overlay is triggered (opacity:0 to opacity:1) it remains invisible making the app appear not to work
Ajax requests, Timer functions and WebkitAnimation are broken after a lock-screen on iOS8.
For the Ajax and Timer functions, we are using this solution in our system:
How to resume JavaScript timer on iOS8 web app after screen unlock? (link to the gitHub in the comment).
It is not exactly part of the question but I would like to share our workaround with CSS3 animations and events, since it may help somebody in the future.
For the webkitAnimation, we found that redrawing the element with the animation on, or more drastically the body would restart animations and events applied to them (webkitAnimationEnd for instance, which is used heavily by jquery-mobile).
so our code gives something like:
document.body.style.display='none';
setTimeout( function() { document.body.style.display = 'block'; }, 1);
You may or may not need the setTimeout function on the second statement. Most interesting thing is, once it has been redrawn, it will never go frozen again no matter how many lock screens come up after that...
The webapp environment is so horribly broken when resuming after screen lock I don't see how (a) Apple could ignore this indefinitely and (b) how any webapp can confidently work around the crippled environment.
My solution is to detect resume after sleep using setInterval (which stops working after the resume) and then posting an alert() to the user stating that the app has to be re-launched from the home screen because iOS cannot resume it.
It just so happens that alert() is also broken after the resume--it displays the alert and then the webapp exits to the home screen when the user hits OK! So this forces the user to re-launch.
The only issue when the user re-launches is the handling of apple-mobile-web-app-status-bar-style. I have this set to black-translucent which normally sets the status bar content to black (on light backgrounds) or white (on dark backgrounds). On the first re-launch after resume, the status bar content is always black. On subsequent re-launches (not interrupted by sleep/resume) the behavior returns to normal.
What a mess. If I was responsible for this at Apple I'd be embarrassed, and here we are with 8.1 and it still hasn't been fixed.