Web Audio Session API to trigger method calls in app rather than playing media - progressive-web-apps

I'm having a hard time getting the Web Audio API to do what I want. My goal is to eventually trigger voice recognition in a PWA on mobile, but first I decided to do a test case of incrementing a counter whenever play or pause was pressed. I am using the Vue framework.
Here is an abbreviated version of my code along with the GUI is creates on the screen.
<template>
<v-app>
<v-btn #click="activateMediaButton">Start</v-btn>
<div>{{counter}}</div>
<v-btn #click="increment">Increment</v-btn>
</v-app>
</template>
<script>
export default {
name: 'App',
data(){
return {
counter: 0
};
},
methods: {
increment(){
console.log('ssedfr');
this.counter++;
},
activateMediaButton(){
navigator.mediaSession.metadata = new window.MediaMetadata({})
navigator.mediaSession.playbackState = 'playing';
try{
navigator.mediaSession.setActionHandler('play', this.increment)
navigator.mediaSession.setActionHandler('pause',this.increment)
}
catch(error){console.log(error);}
}
}
}
</script>
As you can see, the action handlers are registered when I click the start button on the GUI, and the counter value is supposed to go up whenever I press the media buttons. I have tested this counter using the Increment button element on the GUI and it works fine, but the calls to increment aren't even being fired when the media keys are pressed as confirmed by console.log statements. No errors are thrown when the try block is executed to register the actions.
I am testing this functionality by using the Media keys on my laptop, which otherwise do play and pause media available in any open tabs. I have closed all other media tabs so that nothing is intercepting the keystrokes. I do notice that whenever I press the media button after the actions are registered, the focus goes to the Start button element that triggered the registration in such a way that it looks like a click is being held on it.
I have also tested this on my phone through a Netlify hosted PWA and bluetooth earphones and it doesn't work there either.

To work with Web Media Session API, the page must have to be playing a media file (video or audio).
Here is a working example:
<template>
<div>
<button #click="activateMediaButton">Start</button>
<div>{{ counter }}</div>
<button #click="increment">Increment</button>
<audio
src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
controls
></audio>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
counter: 0,
};
},
mounted() {
navigator.mediaSession.metadata = new window.MediaMetadata({
title: "Unforgettable",
artist: "Nat King Cole",
album: "The Ultimate Collection (Remastered)",
artwork: [
{
src: "https://dummyimage.com/512x512",
sizes: "512x512",
type: "image/png",
},
],
});
navigator.mediaSession.setActionHandler("play", this.increment);
navigator.mediaSession.setActionHandler("pause", this.increment);
},
methods: {
increment() {
this.counter++;
// handle action from here
},
activateMediaButton() {
if (navigator.mediaSession.playbackState === "playing") {
navigator.mediaSession.playbackState = "pause";
document.querySelector("audio").pause();
} else {
navigator.mediaSession.playbackState = "playing";
document.querySelector("audio").play();
}
},
},
};
</script>
To understand how it works try pausing the audio manually from the page and then try using the media keys.

Related

Leaflet - How to add click event to button inside marker pop up in ionic app?

I am trying to add a click listener to a button in a leaftlet popup in my ionic app.
Here I am creating the map & displaying markers, also the method I want called when the header tag is clicked is also below:
makeCapitalMarkers(map: L.map): void {
let eventHandlerAssigned = false;
this.http.get(this.capitals).subscribe((res: any) => {
for (const c of res.features) {
const lat = c.geometry.coordinates[0];
const lon = c.geometry.coordinates[1];
let marker = L.marker([lon, lat]).bindPopup(`
<h4 class="link">Click me!</h4>
`);
marker.addTo(map);
}
});
map.on('popupopen', function () {
console.log('Popup Open')
if (!eventHandlerAssigned && document.querySelector('.link')) {
console.log('Inside if')
const link = document.querySelector('.link')
link.addEventListener('click', this.buttonClicked())
eventHandlerAssigned = true
}
})
}
buttonClicked(event) {
console.log('EXECUTED');
}
When I click this header, Popup Open & Inside if are printed in the console, so I know I'm getting inside the If statement, but for some reason the buttonClicked() function isn't being executed.
Can someone please tell me why this is the current behaviour?
I just ran into this issue like 2 hours ago. I'm not familiar with ionic, but hopefully this will help.
Create a variable that keeps track of whether or not the content of your popup has an event handler attached to it already. Then you can add an event listener to the map to listen for a popup to open with map.on('popupopen', function(){}). When that happens, the DOM content in the popup is rendered and available to grab with a querySelector or getElementById. So you can target that, and add an event listener to it. You'll have to also create an event for map.on('popupclose', () => {}), and inside that, remove the event listener from the dom node that you had attached it to.
You'd need to do this for every unique popup you create whose content you want to add an event listener to. But perhaps you can build a function that will do that for you. Here's an example:
const someMarker = L.marker(map.getCenter()).bindPopup(`
<h4 class="norwayLink">To Norway!</h4>
`)
someMarker.addTo(map)
function flyToNorway(){
map.flyTo([
47.57652571374621,
-27.333984375
],3,{animate: true, duration: 5})
someMarker.closePopup()
}
let eventHandlerAssigned = false
map.on('popupopen', function(){
if (!eventHandlerAssigned && document.querySelector('.norwayLink')){
const link = document.querySelector('.norwayLink')
link.addEventListener('click', flyToNorway)
eventHandlerAssigned = true
}
})
map.on('popupclose', function(){
document.querySelector('.norwayLink').removeEventListener('click', flyToNorway)
eventHandlerAssigned = false
})
This is how I targeted the popup content and added a link to it in the demo for my plugin.
So yes you can't do (click) event binding by just adding static HTML. One way to achieve what you want can be by adding listeners after this new dom element is added, see pseudo-code below:
makeCapitalMarkers(map: L.map): void {
marker.bindPopup(this.popUpService.makeCapitalPopup(c));
marker.addTo(map);
addListener();
}
makeCapitalPopup(data: any): string {
return `` +
`<div>Name: John</div>` +
`<div>Address: 5 ....</div>` +
`<br/><button id="myButton" type="button" class="btn btn-primary" >Click me!</button>`
}
addListener() {
document.getElementById('myButton').addEventListener('click', onClickMethod
}
Ideally with Angular, we should not directly be working with DOM, so if this approach above works you can refactor adding event listener via Renderer.
Also I am not familiar with Leaflet library - but for the above approach to work you need to account for any async methods (if any), so that you were calling getElementById only after such DOM element was successfully added to the DOM.

Unable to use debounceTime on ion-button click event

Currently I am having an ion-button with click event which calls a method.
<ion-button expand="full" color="primary" (click)="sendMsg()">Tap</ion-button>
sendMsg method contains the statements to push the objects to an array and opens modal on some condition.
sendMsg = () =>{
// statements to push an objects to an array(this is an array displays on chat page);
this.openModal();
}
async openModal() {
const myModal = await this.modalController.create({
component: ModalPage,
componentProps: {
firstAction: this.firstAction,
secondAction: this.secondAction,
thirdAction: this.thirdAction
},
cssClass: 'modal-css',
backdropDismiss: false
});
It's a chat page where we get the messages on click of TAP button and while tapping in between we show an ion modal . The issue here is when we tap super fast and modal comes up in one of the click event and since we are clicking fast I could see the messages displaying which are suppose to display after the modal comes up..
To avoid this , I thought of adding debounceTime which can have some time delay and considers the latest click event and this was working in normal angular world.
I have followed https://coryrylan.com/blog/creating-a-custom-debounce-click-directive-in-angular but it didn't work under ionic..
Any thoughts are really appreciated..
use a subject as event emitting source and control the click rate from there
const openModalAction=new Subject()
sendMsg = () =>{
// statements to push an objects to an array(this is an array displays on chat page);
openModalAction.next()
}
const openModal=defer(()=>from(this.modalController.create({
component: ModalPage,
componentProps: {
firstAction: this.firstAction,
secondAction: this.secondAction,
thirdAction: this.thirdAction
},
cssClass: 'modal-css',
backdropDismiss: false
})))
const openModalAction.pipe(dounceTime(1000),switchMap(_=>openModal)

IonicFramework - Stop video auto play in fullscreen mode on iOS

In short, I have embedded a youtube video in my app using iframe and set it to auto play when the player’s ready after a user has entered that specific page.
-Problem I’m facing: the video player behaves as I expected on Android devices. The user enters the page, the video is played automatically without pressing the ‘play’ button, when he/she wants to go fullscreen, just hit ‘fullscreen’. Sames go for when that user wants to pause the video.
However, on iOS: the user enters the page --> the video plays automatically in full screen mode which definitely not what I want --> the video is paused when the user exits full screen and if he/she hits ‘play’, it goes fullscreen again. Basically, it’s only playable in full screen.
-The question is: is there any way I can achieve the same behaviors of the video player as on Android for iOS? Thank you so much, any help would be really appreciated.
-This is the html:
<iframe id="iframeTube" width="100%" height="250" src="https://www.youtube.com/embed/b5cv7ihxBeY?enablejsapi=1&fs=1" frameborder="0"
style="border: solid 4px #37474F" allowfullscreen></iframe>
-this is the video being processed in .ts file:
ionViewWillEnter() {
this.onYouTubeIframeAPIReady();
}
ionViewDidEnter() {
this.fullscreenProcess(this);
}
onYouTubeIframeAPIReady() {
this.player = new YT.Player('iframeTube', {
events: {
'onReady': this.onPlayerReady,
'onStateChange': this.onPlayerStateChange
}
});
}
onPlayerReady(event) {
console.log('ready')
var embedCode = event.target.getVideoEmbedCode();
event.target.playVideo();
var self = this;
}
fullscreenProcess(self)
{
document.addEventListener("webkitfullscreenchange", function (event) {
if (document.webkitIsFullScreen) {
console.log("full")
self.screenOrientation.lock(self.screenOrientation.ORIENTATIONS.LANDSCAPE);
}
else {
self.screenOrientation.unlock();
console.log("not full")
}
});
}

Soundcloud Streaming on Safari Mobile

I tried to use this snippet from the Soundcloud API:
<script src="http://connect.soundcloud.com/sdk.js">
<script>
SC.initialize({
client_id: 'YOUR_CLIENT_ID'
});
# stream track id 293
SC.stream("/tracks/293", function(sound){
sound.play();
});
</script>
It works in any browsers aside from Safari mobile, both on iPhone and iPad, where the music stream does not play at all.
What am I doing wrong? (I replaced track id and client id with my own details)
Thanks
Here is work around for this issue. What you need to add extra html element.
<div class="player-helper"></div>
JavaScript
SC.stream('/tracks/' + id, {
useHTML5Audio: true,
debugMode: true
}, function(sound) {
let $eventEmitter = $('.player-helper');
$eventEmitter.on('click', function () {
sound.play();
});
$eventEmitter.trigger('click');
$eventEmitter.off('click');
});
In mobile safari the play has the restriction that audio playback has to be triggered by a user action. So you'll have to add some sort of button or link which when clicked will call the sound.play() function.

jquery live not firing on first click on a checkbox

I have a strange problem. This code works fine in chrome and firefox, but in IE 8 the live event will not fire the first time I uncheck a box. If I check it and then uncheck again it works every time after that.
My serverside code in the view
<%: Html.CheckBox("select-invoice-" + invoice.InvoiceNumber,
true,
new { title = "choose to not pay anything on this invoice by unchecking this box" }) %>
renders to this
<input checked="checked" id="select-invoice-TST-1001"
name="select-invoice-TST-1001"
title="choose to not pay anything on this invoice by unchecking this box"
type="checkbox" value="true" />
Here is my javascript live event wireup, simplified
$(function () {
$("[id^='select-invoice-']").live('change', function () {
var invoiceId = $(this).attr('id').substr('select-invoice-'.length);
ComputeTotalPayment();
if ($(this).is(':checked')) {
//save invoice data
} else {
//remove invoice data
}
});
});
There are no errors in the javascript on any browser. If I switch IE to compatibility mode the live event never works. Other live events for clicks on links work just fine.
The change event doesn't fire correctly in IE until the checkbox loses focus.
Bug: http://webbugtrack.blogspot.com/2007/11/bug-193-onchange-does-not-fire-properly.html
You'll need to map to the "click" event instead.
I have found that change causes some problems in IE. Try using the click event instead. This appears to fix the problem.
I had a similar problem and solved it by calling .change() once on page load.
$(function () {
$("[id^='select-invoice-']").live('change', function () {
var invoiceId = $(this).attr('id').substr('select-invoice-'.length);
ComputeTotalPayment();
if ($(this).is(':checked')) {
//save invoice data
} else {
//remove invoice data
}
}).change();
});