why vimeo HTML player interrupt my app audio session? - ios

In my app(audio base app), i'm playing remote audio.
before start playing i set following category
AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
so while i'm playing audio, i also play vimeo video in WKWebView which cause audio interrupt. how to avoid this behaviour?
<!DOCTYPE html>
<html>
<body>
<style type="text/css">
body, html {width: 100%; height: 100%; margin: 0; padding: 0;background:black}
.main {position: absolute;top: 0; left: 0; right: 0; background-color: #101010;height:100%;}
.main iframe {display: block; width: 100%; height: 100%; border: none;}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<script src="https://player.vimeo.com/api/player.js"></script>
<div class="main">
<iframe src=\(url)></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<script>
var iframe = document.querySelector('iframe');
var player = new Vimeo.Player(iframe);
player.on('play', function() {
window.webkit.messageHandlers.play.postMessage("play")
});
player.on('pause', function() {
window.webkit.messageHandlers.pause.postMessage("pause")
});
player.on('ended', function(data) {
window.webkit.messageHandlers.ended.postMessage("ended")
});
player.on('bufferstart', function(data) {
window.webkit.messageHandlers.bufferstart.postMessage("bufferstart")
});
player.on('bufferend', function(data) {
window.webkit.messageHandlers.bufferend.postMessage("bufferend")
});
Promise.all([player.getVideoWidth(), player.getVideoHeight()]).then(function(dimensions) {
window.webkit.messageHandlers.dimensions.postMessage(dimensions)
});
player.getDuration().then(function(duration) {
window.webkit.messageHandlers.duration.postMessage(duration);
});
player.ready().then(function () {
player.setVolume(1)
window.webkit.messageHandlers.ready.postMessage("ready");
});
function play() {
player.play();
}
function pause() {
player.pause();
}
function destroy() {
player.destroy();
}
</script>
</body>
</html>

The interruption happens because WKWebview sound is handled by different process and a separate AVAudioSession. The WKWebview AVAudioSessionCategory category will switch to .playback while the sounds is playing either through <audio> or <video> tags (or its javascript counterparts). This must be the underlying mechanism that your vimeo playback uses (otherwise the interrupts would not occur).
There are 2 main routes you can go:
1) If you're ok with your main app sounds(not the WKWebView ones) mixing with any other iOS sounds (like the iPod music playback) then go with #Rog 's suggestion from the comment to use .ambient for your main app audiosession category. (This will also cause your app main audio session not to emit sound while the hardware silent switch is in off position).
2) A more flexible but requiring careful timing handling is to additionally set .mixWithOthers options for your main app audiosession .playback category. You would remove .mixWithOthers once you detect WKWebView is done playing it's own sound. From user perspective it would appear as your app is using .playback all the time.
I covered somewhat related topic in my answer here

Related

Django App Implementing Auth0 won't render on iOS devices after logging in

I have a nice Django app that implements Auth0. It works on all browsers on pcs and on browsers on Android. When testing on iOS devices however, after the user logs in through Auth0, the device asks to download a file and then downloads it and does nothing. If I try to redirect to my english page, it downloads a file called "en", if I try to redirect to my french version of the page, it downloads a file called "fr". Not sure why - it is at the end of the url myurl.something.org/myForm/en for English for example.
At first I thought the issue had to do with Apple not allowing Same-Site cookies, so I added the CSRF_COOKIE_SAMESITE = None setting. But I see now that after logging in, in the address bar there is the url that I want the user to be redirected to. When I tried using the Web Inspector for Safari on Iphone, I see that there are no same-site cookies, so it seems that this is not the problem.
I see the document "en" in the list of resources on the Web Inspector when on the login page. It is type "document" and shows that inside of it is the following:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Sign In with Auth0</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.bgimg {
background-image: url('pic.jpg');
}
#myVideo {
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
min-height: 100%;
z-index: 1;
}
.auth0-lock.auth0-lock.auth0-lock-opened .auth0-lock-overlay {
opacity: 0.0 !important;
-webkit-transition: opacity 0.3s ease-in 0s;
transition: opacity 0.3s ease-in 0s;
}
.video{
position:absolute;
z-index:-5 !important;
}
.overlay{
background:#333;
color: white;
position:fixed;
top: 50px;
left: 0;
width: 400px;
height: 100px;
z-index:1000;
visibility:hidden;
/*
* if you want to make it none clickable but make the
* clicks go to the video
*/
pointer-events: none;
}
</style>
</head>
<body background="https://www.nbn.org.il/wp-content/uploads/2019/04/auth0_bkg.jpg">
<div class="login-container"></div>
<!--
<div class ="video"> <video autoplay muted loop id="myVideo">
<source src="https://www.nbn.org.il/source_files/login/nbnommunitywaiting.mp4" type="video/mp4">
Your browser does not support HTML5 video.
</video>
</div>
-->
<!--[if IE 8]>
<script src="//cdnjs.cloudflare.com/ajax/libs/ie8/0.2.5/ie8.js"></script>
<![endif]-->
<!--[if lte IE 9]>
<script src="https://cdn.auth0.com/js/base64.js"></script>
<script src="https://cdn.auth0.com/js/es5-shim.min.js"></script>
<![endif]-->
<script src="https://cdn.auth0.com/js/lock/11.3/lock.min.js"></script>
<script>
// Decode utf8 characters properly
var config = JSON.parse(decodeURIComponent(escape(window.atob('long_token'))));
config.extraParams = config.extraParams || {};
var connection = config.connection;
var prompt = config.prompt;
var languageDictionary;
var language;
if (config.dict && config.dict.signin && config.dict.signin.title) {
languageDictionary = { title: config.dict.signin.title };
} else if (typeof config.dict === 'string') {
language = config.dict;
}
var loginHint = config.extraParams.login_hint;
var lock = new Auth0LockPasswordless(config.clientID, config.auth0Domain, {
auth: {
redirectUrl: config.callbackURL,
responseType: (config.internalOptions || {}).response_type ||
(config.callbackOnLocationHash ? 'token' : 'code'),
params: config.internalOptions
},
/* additional config needed to use custom domains
configurationBaseUrl: config.clientConfigurationBaseUrl,
overrides: {
__tenant: config.auth0Tenant,
__token_issuer: config.auth0Domain
}, */
assetsUrl: config.assetsUrl,
allowedConnections: connection ? [connection] : null,
rememberLastLogin: !prompt,
language: language,
languageDictionary: languageDictionary,
prompt: 'consent',
theme: {
logo:'pic.png',
primaryColor: "#fbaa40",
},
closable: false,
// uncomment if you want small buttons for social providers
// socialButtonStyle: 'small'
});
lock.show();
</script>
<div class="overlay">i'm a cool overlayed html block</div>
</body>
</html>
and then when I log in and go to the next page, under the Elements, section I get just a blank html body called about:blank.
Here is another image from the Web Inspector of the login page before getting to the first page of my app which may or may not be helpful:
Any insight into what might be wrong would be much appreciated!
My original thoughts about the issue posted above were completely not the problem.
At some point I realized this:
If I required login through auth0 and passed a context in the view the application downloaded html instead of rendering on iOS devices. If I didn’t require login, passing a context was no problem. If I didn’t pass in a context, requiring login through auth0 was no problem.
Even passing an empty context caused a problem.
Eventually I figured out that the problem was this:
In the Auth0 Django SDK that I was following, it shows that you should pass in what I thought was an extra dictionary parameter for auth0:
{'auth0User': auth0user,'userdata': json.dumps(userdata, indent=4) }
Since I didn't consider the other parameter to be a context, I also added my own context in the return statement of the view like this:
return render(request, 'mypage.html', context, { 'auth0User': auth0user, 'userdata': json.dumps(userdata, indent=4)} )
So for some reason this worked on pcs and androids to pass in two contexts, but iOS didn't like it.
Once I combined the two contexts
return render(request, 'mypage.html', {'contextvar1': 'data', 'contextvar2': 'moredata', 'auth0User': auth0user, 'userdata': json.dumps(userdata, indent=4)} )
everything works.
If anyone has understanding as to why this worked on other operating systems besides Apple and why this caused iOS devices to download the html, I would love to understand.

Cannot pause CSS animation in mobile Safari

I try to pause a CSS animation with JavaScript.
This works in all tested browsers but not in Safari on iOS, where the animation continues until the end.
How to fix or workaround this bug?
(function () {
function setAnimationPlayState (state) {
document.getElementById ('debug').innerHTML += state + '/';
document.getElementById ('animation').style.animationPlayState = state;
document.getElementById ('animation').style.webkitAnimationPlayState = state;
}
window.addEventListener ('load', function () {
setAnimationPlayState ('running');
setTimeout (function () {
setAnimationPlayState ('paused');
}, 1000);
});
} ());
#animation {
animation: test linear 4s;
animation-fill-mode: both;
}
#keyframes test {
to {
/* 3D never pause */
transform: rotateY(90deg);
/* 2D may pause or not */
/* transform: translateX(100vw); */
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="debug" style="color: red;">/</div>
<div id="animation">Hello<br>this text<br>should not<br>disappear<br>completely</div>
</body>
</html>
My animation is 3D so I also tested with a 2D animation just in case, and the behavior is even weirder. Sometimes it works, then I reload the page and it doesn't work. This thing is driving me crazy, your help is most welcome.

Redirect in iframe with ios browser freezes Phaser canvas

I use Phaser to create a game but I found an problem for using it in for example facebook. When an redirect is done within an iframe the canvas is not responding after a click.
Example:
I have a IFrame and within the iframe I redirect to the game.html. When I click in the game.html everything freezes.
Everything works fine when using a computer (any browser), windows phone or android, but with an iphone or ipad it won't work.
Below are the example files to replay the problem...
index.html:
<html>
<body>
<iframe src="click.html" height="900" width="800"/>
</body>
</html>
click.html
<!DOCTYPE html>
<html>
<body>
CLICK HERE
</body>
</html>
Game.html
<!DOCTYPE html>
<html>
<head>
<style>
body{overflow:hidden;}
#game_div {
width: 760px;
height: 1100px;
margin: auto;
}
</style>
<script type="text/javascript" src="./Game/phaser.min.js"></script>
<script type="text/javascript" src="./Game/main.js"></script>
</head>
<body>
<div id="game_div"> </div>
</body>
</html>
main.js
var game = new Phaser.Game(760, 1100, Phaser.AUTO, 'game_div');
var overlay, countdownText;
var counter = 0;
var main_state = {
preload: function() {
},
create: function () {
//game overlay
overlay = game.add.graphics(0, 0);
overlay.beginFill(0x00A54F, 0.8);
overlay.drawRect(0, 0, game.width, game.height);
countdownText = game.add.text((game.width / 2), (game.height / 2), counter, { font: "65px Arial", fill: "#ffffff", align: "center" });
countdownText.anchor.set(0.5,0.5);
},
update: function() {
countdownText.setText(counter++);
}
}
game.state.add('main', main_state);
game.state.start('main');
TNX
Found the solution, with thanks to Rich Davey.
When I add the following code it works:
game.stage.disableVisibilityChange = true;

YouTube embed crashes Chromecast

I am currently building a Custom Receiver for our planned Chromecast app.
I need to display YouTube videos on the TV, alongside other photos and info that our app includes.
However, whenever a YouTube iframe embed is played on screen, after a few minutes (min 2, max 15) the Chromecast completely freezes up (including the Dev Tools debugger), and eventually just resets itself.
The issue can be reproduced every time, on multiple Chromecasts, on various videos, using this super simple test page:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Youtube Test</title>
</head>
<body>
<iframe width="1280" height="720" src="//www.youtube.com/embed/YE7VzlLtp-4?autoplay=1" allowfullscreen></iframe>
</body>
</html>
Has anyone encountered a similar issue, or a solution to this?
The included timeline [1] shows that the iframe stops fetching frames via AJAX and shortly after the whole thing crashes.
[1] Recorded dev tools timeline: https://drive.google.com/file/d/0B_bgLIi2Uw5WcDBHSlpwVlJ5eFE/edit?usp=sharing
I am using this code and it is working.
...
<div id="player" style="text-align: center"></div>
<script type="text/javascript">
var vid = ...;
var tag = document.createElement('script');
tag.src = "http://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('player', {
playerVars: {
videoId: vid,
'enablejsapi': 1,
'autoplay': 1,
'rel': 0,
'showinfo': 0,
'controls': 0,
'egm': 0,
'showsearch': 0
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange,
'onError': onError
}
});
}
</script>
...
You can check the the iframe api reference here: https://developers.google.com/youtube/iframe_api_reference
Regards.

iOS 7 UIWebView memory leak with Google Maps Javascript API

I have an iOS application which displays a map using the Google Maps Javascript API in a UIWebView. It seems that in iOS 7.0, something has changed in the UIWebView implementation that is causing map objects to not be released when there are no longer any references.
Using the XCode memory profiler, I see a 7-20 MB increase in RAM each time a map is created. Removing the map, and clearing references to it does not decrease the memory usage. Memory warnings do not seem to trigger Javascript garbage collection. When running the code on a physical device, the application logs memory warnings and inevitably will crash.
Here is some simplified Javascript which demonstrates the problem:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title></title>
<style type="text/css" media="screen">
body { padding-top: 20px;}
#container { width:100%; height:200px; }
#container > div { height: 100%; width: 100%; }
</style>
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" charset="utf-8">
var domEl;
function createMap() {
var container = document.getElementById( 'container' );
// Create fresh dom element
//
if( domEl ) {
container.removeChild( domEl );
console.log('removed old dom element');
}
domEl = document.createElement( 'div' );
container.appendChild( domEl );
// Create map
//
var mapOptions = {
center: new google.maps.LatLng( -34.397, 150.644 ),
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
new google.maps.Map( domEl, mapOptions );
return false;
}
</script>
</head>
<body>
Create map
<div id="container"></div>
</body>
</html>
I've also posted a complete XCode project on GitHub.
Has anyone seen this before? Can someone suggest a fix or workaround? In this case I have to use the Google Maps API; a native solution isn't an option.

Resources