Flutter audio_service implement skipToNext without queue - flutter

I have a long audio file that has different sections.
I have a Map containing where those sections start.
I want to implement skipToNext() and skipToPrevious methods for it.
I have access to the map inside BackgroundAudioTask.
I have implemented onSkipToNext like this:
#override
Future<void> onSkipToNext() async => () {
print('next pressed');
final currentIndex = _getCurrentTrackIndex(_player.position);
print(currentIndex);
if (currentIndex < _tracks.length - 1) {
_player.seek(
Duration(milliseconds: _tracks[currentIndex + 1]['start']),
);
}
};
The _tracks looks like this:
[{id: 1, title: INTRODUCTION, start: 0}, {id: 2, title: Next track, start: 68347},]
But when I press on MediaControl.skipToNext from the notification or
AudioService.skipToNext(); from Flutter UI.
It is not working i.e next pressed is not showing in the console and the audio is not working.
Am I doing something wrong?
Can't I press MediaControl.skipToNext if there is no queue?
If I cannot do so, how can I implement such a feature?
Note: I am using just_audio for playing audio
Edit: Here's my systemActions
systemActions: [
MediaAction.seekTo,
MediaAction.seekForward,
MediaAction.seekBackward,
MediaAction.skipToNext,
MediaAction.skipToPrevious,
],
Solved: Incorrect implementation of onSkipToNext() was the case of the problem as the accepted answer suggests.

Assuming everything else works and it's just these two skip actions that don't work, the first thing you will need to do is include skipToNext and skipToPrevious in the controls parameter of AudioServiceBackground.setState rather than systemActions. systemActions is only for things that are not clickable buttons (e.g. seekTo).
You must also pass androidEnableQueue: true to AudioService.start if you want these queue-skipping actions to work on Android.
For completeness, I'll also just mention that none of the notification buttons (or headset buttons) will work on Android unless you have declared the broadcast receiver in your AndroidManifest.xml file:
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver" >
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
But aside from all of those things, I notice your code contains a subtle programming error that is unrelated to audio_service. Your implementation of onSkipToNext simply returns a closure () { ... } so even if this callback is being called, the code inside the closure isn't being called. So you need to replace => () { ... } by { ... }.

Related

Flutter how to add a delay when input is focused?

My problem:
I need to add a delay for about 100 milliseconds because the inherited animation of the TextFormField when it gets focused shows the keyboard at almost the same time the building animation of itself is running (and also because I want to apply some animations in between and/or then chain the keyboard to it).
I would like to add a small delay there.
What did I have tried so far:
I have been doing a lot things to reproduce this behavior but with not much success, I tried several strategies from flutter community since adding delays, listen to the keyboard using several packages or listening the context bottom viewPadding, I have attached listeners to the onTap of the TextFormField class and it is not the best outcome.
I also have tried with this strategy, but is not good either (sometimes there's a race between the native and the instruction that flutter has internally to show it):
// flutter hook
useEffect(() {
void focusListener() {
// FOCUS:
if (focusNode.hasFocus) {
// #1. Using SystemChannels.textInput:
// The problem with this approach is that flutter ignores it,
// it gets never hidden.
// ... skipped by brevity ...
SystemChannels.textInput.invokeMethod('TextInput.hide')
// ... skipped by brevity ...
// #2. Delay in hide & show events:
// This approach hide the keyboard but its always visible at the beginning
// for a really short amount of time, that's like a blink
// and it doesn't look nice, but this kind of
// solve the issue.
// ... skipped by brevity ...
final channel = SystemChannels.textInput;
Future.delayed(Duration(seconds: 0), () {
channel.invokeMethod('TextInput.hide').whenComplete(() {
Future.delayed(Duration(milliseconds: 400), () {
channel.invokeMethod('TextInput.show');
});
});
});
// ... skipped by brevity ...
// #3. Delay in show event:
// This approach is similar to the previous but the order
// from the channel 'TextInput.hide' is skipped
// because of the same reason of the first approach.
// ... skipped by brevity ...
channel.invokeMethod('TextInput.hide').whenComplete(() {
Future.delayed(Duration(milliseconds: 400), () {
channel.invokeMethod('TextInput.show');
});
});
// ... skipped by brevity ...
}
// VALIDATION:
autovalidate.value = focusNode.hasFocus;
}
focusNode.addListener(focusListener);
return () => focusNode.removeListener(focusListener);
}, [focusNode.hasFocus]);
Any idea?

Material UI IconButton onClick doesn't let to handle event

I installed "#material-ui/core": "^4.9.2" and "#material-ui/icons": "^4.9.1".
In my form i have several rows, each row has an add button and a remove button. I want the remove button to remove the row from it was clicked. It works fine with regular Button with a "-" character in it. But i want it fancy, so i replaced my Button from an IconButton, and imported the icons to use
import {AddCircleOutline,RemoveCircleOutlineOutlined} from "#material-ui/icons";
And my IconButton looks like this:
<IconButton
onClick={props.onRemoveClick}
className="align-self-center"
color="info"
size="sm"
disabled={props.index > 0 ? false : true}
<RemoveCircleOutlineOutlined/>
</IconButton>
When the IconButton is hit, the onClick method is called (i know because of logs in my console) but i can't handle the event because it is now undefined.
The funny thing is that if i click on the button area that doesn't correspond to the icon, it works. But obviously i need it to work in the whole area of the button.
It is not a binding issue because i already tested it.
Any ideas?
Props that are not cited in the documentation are inherited to their internal <EnhancedButton />, so you need to use a wrapper.
<IconButton
onClick={(e) => props.onRemoveClick(e)}
className="align-self-center"
color="info"
size="sm"
disabled={props.index > 0 ? false : true}
<RemoveCircleOutlineOutlined/>
</IconButton>
Well you gave an idea. Since i needed an index to identify the row's button, i sended the index through a paramater on the onClick method, like this:
onClick={e => props.onRemoveClick(props.index)}
In this way i didn't need to handle the event. I also had to bind my method on the constructor:
constructor(props) {
super(props);
this.handleRemoveClick = this.handleRemoveClick.bind(this);
}
Now i got the behaviour wanted
You can see the github ussue here. There is some problem with typescript definition files but we can work around it.
Solution
I tried to solve it like in the github issue but didn't work. So this works for me.
const onClick = (e: any) => {
// e is of type any so the compiler won't yell at you
}
<IconButton onClick={(e) => onClick(e)}>
I don't know the reason but using e.currentTarget helped me to get the button that I wanted and not the material icon inside it.
onClick={(e) => {
return console.log(e.currentTarget)
}}

Ionic 4 intercept android back button for navigation

so in ionic 3 there was registerBackButton() but in ionic 4 this option is no longer there and has been sitting on the shelf for quite some time now.
I have read the post here that tries to solve the solution I am looking for, however, the back button still performs as it wants to.
this SO answer shows another way but it is the same idea of intercepting and navigating, however, I am just, for now, trying to dismiss the top modal in the stack.
scenario: users open a search modal(modal1) which then they click on a users profile modal(modal2). The person wants to go back to the search modal(modal1) but instead of clicking the nice button that allows them to do that, they use the hardware back button.
result: all modals(modal1 and modal2) are closed.
desired effect: using the hardware back button will allow for custom navigation based on logic in place.
attempted code:
this.platform.backButton.subscribeWithPriority(0, (): void => {
this.modalCtrl.getTop().then(
async (value: HTMLIonModalElement): Promise<void> => {
if (!!value) {
await this.modalCtrl.dismiss();
} else {
this.navCtrl.navigateRoot('/home');
}
},
);
});
also have tried :
// registering back, if there is a view on top, close it, don't go to home.
this.platform.backButton.subscribeWithPriority(0, async (): Promise<void>=> {
try {
console.log('try');
const element = await this.modalCtrl.getTop();
if (element) {
console.log('in true');
await element.dismiss();
}
} catch (error) {
console.log('error closing modal', error);
}
});
note when pressing the back button I never see ANY of the console logs... maybe things have changed a lot more? since the previous Stack overflow questions.
UPDATE:
If you are having this same issue then know you are not alone!
This, and many others are well known, see here for a list they are tracking the issues. Nothing else to do... but wait... I guess...
I will update this when there is a change

With HTML5 SoundCloud player widget how do you programmatically skip to a different track without causing second unwanted track from playing?

I am using the HTML5 Soundcloud widget API to skip to another song on the sound finish event.
http://jsbin.com/axuzoj/4/edit
Unfortunately there appears to be a race condition bug in the finish event. The Soundcloud player ends up playing two songs simultaneously: the next song in the list and the song that was skipped to in the finish event handler.
var widget = null;
$(function() {
var iframe = document.querySelector('#soundcloud_player iframe');
widget = SC.Widget(iframe);
widget.bind(SC.Widget.Events.READY, function() {
widget.bind(SC.Widget.Events.FINISH, function() {
widget.skip(3);
});
});
});
Is this a known bug?
Is there a better way to skip to a different track after a sound finishes?
Is there a way to turn off continuous play?
Adding a short wait before skipping in the finish event handler, gets around the problem. But doesn't seem like a good method.
window.setTimeout(function() { widget.skip(3); }, 300);
Another work around is to skip to a song just before the previous song finishes, using PLAY_PROGRESS event instead of on FINISH event.
widget.bind(SC.Widget.Events.READY, function() {
widget.bind(SC.Widget.Events.PLAY_PROGRESS, function(obj) {
if (obj.relativePosition > 0.999) {
widget.pause();
widget.skip(3);
}
});
});
Yes, there was a race-condition bug which is fixed now.

Mobile Safari: Disable scrolling pages "out of screen"

I want to block scrolling page "out of the iPhone screen" (when gray Safari's background behind the page border is visible). To do this, I'm cancelling touchmove event:
// Disables scrolling the page out of the screen.
function DisableTouchScrolling()
{
document.addEventListener("touchmove", function TouchHandler(e) { e.preventDefault(); }, true);
}
Unfortunately, this also disables mousemove event: when I tap on a button then move my finger out of it, then release the screen, the button's onclick event is triggered anyway.
I've tried mapping touch events on mouse events, as desribed here: http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/, but to no avail (the same behavior).
Any ideas?
From what I understand of your question, you've attempted to combine the code you've presented above with the code snippet provided by Ross Boucher on Posterous. Attempting to combine these two snippets back-to-back won't work, because in disabling touchmove, you've also disabled the shim that allows mousemove to work via his sample.
This question and its answers sketch out a workable solution to your problem. You should try these two snippets to see if they resolve your issue:
This snippet, which disables the old scrolling behavior:
elementYouWantToScroll.ontouchmove = function(e) {
e.stopPropagation();
};
Or this one, from the same:
document.ontouchmove = function(e) {
var target = e.currentTarget;
while(target) {
if(checkIfElementShouldScroll(target))
return;
target = target.parentNode;
}
e.preventDefault();
};
Then, drop in the code on Posterous:
function touchHandler(event)
{
var touches = event.changedTouches,
first = touches[0],
type = "";
switch(event.type)
{
case "touchstart": type = "mousedown"; break;
case "touchmove": type="mousemove"; break;
case "touchend": type="mouseup"; break;
default: return;
}
//initMouseEvent(type, canBubble, cancelable, view, clickCount,
// screenX, screenY, clientX, clientY, ctrlKey,
// altKey, shiftKey, metaKey, button, relatedTarget);
var simulatedEvent = document.createEvent("MouseEvent");
simulatedEvent.initMouseEvent(type, true, true, window, 1,
first.screenX, first.screenY,
first.clientX, first.clientY, false,
false, false, false, 0/*left*/, null);
first.target.dispatchEvent(simulatedEvent);
event.preventDefault();
}
And that should do it for you. If it doesn't, something else isn't working with Mobile Safari.
Unfortunately I haven't had the time to check out to above yet but was working on an identical problem and found that the nesting of elements in the DOM and which relation you apply it to affects the handler a lot (guess the above solves that, too - 'var target = e.currentTarget').
I used a slightly different approach (I'd love feedback on) by basically using a class "locked" that I assign to every element which (including all its children) i don't want the site to scroll when someone touchmoves on it.
E.g. in HTML:
<header class="locked">...</header>
<div id="content">...</div>
<footer class="locked"></div>
Then I have an event-listener running on that class (excuse my lazy jquery-selector):
$('.ubq_locked').on('touchmove', function(e) {
e.preventDefault();
});
This works pretty well for me on iOs and Android and at least gives me the control to not attach the listener to an element which I know causes problems. You do need to watch your z-index values by the way.
Plus I only attach the listener if it is a touch-device, e.g. like this:
function has_touch() {
var isTouchPad = (/hp-tablet/gi).test(navigator.appVersion);
return 'ontouchstart' in window && !isTouchPad;
}
This way non-touch devices will not be affected.
If you don't want to spam your HTML you could of course just write the selectors into an array and run through those ontouchmove, but I would expect that to be more costly in terms of performance (my knowledge there is limited though). Hope this can help.