I'm working on an app that will play sound files. If I open apple music app, the slider let me moving between the song where I am.
Other apps like spotify or overcast does not allow this behaviour.
Until now, I have been able to change all parameters of the control center with that exception. Is there any way of making this slider useful?
I'm using something like the following code:
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
NSArray *commands = #[commandCenter.playCommand, commandCenter.pauseCommand, commandCenter.nextTrackCommand, commandCenter.previousTrackCommand, commandCenter.bookmarkCommand, commandCenter.changePlaybackPositionCommand, commandCenter.changePlaybackRateCommand, commandCenter.dislikeCommand, commandCenter.enableLanguageOptionCommand, commandCenter.likeCommand, commandCenter.ratingCommand, commandCenter.seekBackwardCommand, commandCenter.seekForwardCommand, commandCenter.skipBackwardCommand, commandCenter.skipForwardCommand, commandCenter.stopCommand, commandCenter.togglePlayPauseCommand];
for (MPRemoteCommand *command in commands) {
[command removeTarget:nil];
[command setEnabled:NO];
}
[commandCenter.playCommand addTarget:self action:#selector(playTrack)];
[commandCenter.pauseCommand addTarget:self action:#selector(pauseTrack)];
[commandCenter.playCommand setEnabled:YES];
[commandCenter.pauseCommand setEnabled:YES];
The New Way for iOS 12+ and Swift 4+
Following is an improved way of handling scrubbing in Remote Play Center:
// Handle remote events
func setupRemoteTransportControls() {
let commandCenter = MPRemoteCommandCenter.shared()
// Scrubber
commandCenter.changePlaybackPositionCommand.addTarget { [weak self](remoteEvent) -> MPRemoteCommandHandlerStatus in
guard let self = self else {return .commandFailed}
if let player = self.player {
let playerRate = player.rate
if let event = remoteEvent as? MPChangePlaybackPositionCommandEvent {
player.seek(to: CMTime(seconds: event.positionTime, preferredTimescale: CMTimeScale(1000)), completionHandler: { [weak self](success) in
guard let self = self else {return}
if success {
self.player?.rate = playerRate
}
})
return .success
}
}
return .commandFailed
}
// Register to receive events
UIApplication.shared.beginReceivingRemoteControlEvents()
}
There is the changePlaybackPositionCommand API with the associated Event MPChangePlaybackPositionCommandEvent.positionTime (see https://developer.apple.com/library/ios/releasenotes/General/iOS91APIDiffs/Objective-C/MediaPlayer.html)
I tried
[commandCenter.changePlaybackPositionCommand
addTarget: self
action: #selector( onChangePlaybackPositionCommand: )]
with the associated method
- (MPRemoteCommandHandlerStatus) onChangePlaybackPositionCommand:
(MPChangePlaybackPositionCommandEvent *) event
{
NSLog(#"changePlaybackPosition to %f", event.positionTime);
return MPRemoteCommandHandlerStatusSuccess;
}
but the cursor is still not movable and the method is not called. I guess I still miss something
In addition to implementing the callback, it seems that you would have to set the canBeControlledByScrubbing property to true. Unfortunately, there is no public accessor to set it, so you would have to do it as follows (and won't be able to submit your app to the AppStore):
NSNumber *shouldScrub = [NSNumber numberWithBool:YES];
[[[MPRemoteCommandCenter sharedCommandCenter] changePlaybackPositionCommand]
performSelector:#selector(setCanBeControlledByScrubbing:) withObject:shouldScrub];
[[[MPRemoteCommandCenter sharedCommandCenter] changePlaybackPositionCommand]
addTarget:self
action:#selector(handleChangePlaybackPositionCommand:)];
If you do it like this, you will get the callback on your handleChangePlaybackPositionCommand: method, taking an MPChangePlaybackPositionCommandEvent as its only parameter.
If you do want to support older versions of iOS than 9.1, I'd suggest to check for the iOS version before executing the above code to prevent crashes (You can either do it using the new API introduced with iOS 8, or if you want to support iOS 7 and earlier as well, using something like
[[[UIDevice currentDevice] systemVersion] compare:#"9.1" options:NSNumericSearch] != NSOrderedAscending
I hope this helps :-)
As of 2020 with Swift 5, if we only use commandCenter.changePlaybackPositionCommand it won't work, we also need to set metadata with MPMediaItemPropertyPlaybackDuration for the player then we can use the slider.
Check this article from Apple:
https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/creating_a_basic_video_player_ios_and_tvos/controlling_background_audio
Look at the section: Provide Display Metadata
There’s no API to support that—as you’ve noticed, the built-in Music app is the only one that gets to use it. If you’d like an addition to the API, your best option is to file an enhancement request.
update: looks like as of iOS 9.1 this may no longer be the case—see PatrickV’s answer.
I have answered this here command center Scrubber on lock screen swift
Basically you just need the nowPlaying metadata then for each function you want you enable it and add a handler for it.
I realised that comments about this only being for Apple were wrong when I saw the Librivox app had a working scrubber.
I have an application which requires to use the microphone for recording user voice. I'm trying to make a speech to text.
I'm work with SpeechKit.framework and below is my code used:
-(void)starRecording{
self.voiceSearch = [[SKRecognizer alloc] initWithType:SKSearchRecognizerType
detection:SKShortEndOfSpeechDetection
language:[[USER_DEFAULT valueForKey:LANGUAGE_SPEECH_DIC] valueForKey:#"record"]
delegate:self];
}
- (void)recognizer:(SKRecognizer *)recognizer didFinishWithResults:(SKRecognition *)results {
long numOfResults = [results.results count];
if (numOfResults > 0) {
// update the text of text field with best result from SpeechKit
self.recordString = [results firstResult];
[self sendChatWithMediaType:#"messageCall" MediaUrl:#"" ContactDetail:#"{}" LocationDetail:#"{}"];
[self.voiceSearch stopRecording];
}
if (self.voiceSearch) {
[self.voiceSearch cancel];
}
[self starRecording];
}
That makes the SKRecognizer to be always open and that thing reduce the application performance.
I want to start the SKRecognizer when the microphone is detecting input audio.
I have a method for that? A method which is called when the microphone have input sound for me or a method which is always returning the level of audio detected?
Thank you!
You need to use the SpeechKit class to set up the audio.
Look here for details;
http://www.raywenderlich.com/60870/building-ios-app-like-siri
This project shows how to detect audio threshold;
github.com/picciano/iOS-Audio-Recoginzer
Is there any way to access the last time an ABAddressBook contact was accessed, interacted with, called, miss-called, etc.
I'm aware of the ABPerson properties, specifically kABPersonModificationDateProperty. But I was wondering if there any way of knowing more about the users interaction with that contact.
No apple does not allow access to the Call list. Since a call information is stored in the call and not in the addressbook there is no way to get the information you want from the addressbook.
I don't think you can access called history in iOS, especially after iOS 4. You can however know that a phone call was dialled using CoreTelephony framework.
I do it in applicationDidBecomeActive of my AppDelegate.m
...
typeof(self) __weak weakSelf = self;
self.center = [[CTCallCenter alloc]init];
self.center.callEventHandler = ^(CTCall *call) {
if(call.callState == CTCallStateDialing) {
weakSelf.callWasMade = YES;
}
};
...
I used CoreTelephony framework introduced in iOS SDK 4.0 to know about Incoming call & its dropped state.
CTTelephonyNetworkInfo *tni = [[CTTelephonyNetworkInfo alloc] init];
callCenter = [[CTCallCenter alloc] init];
crtCarrierName = tni.subscriberCellularProvider.carrierName;
[callCenter setCallEventHandler:^(CTCall *call) {
if ([[call callState] isEqual:CTCallStateConnected]) {
//this call has just connected
} else if ([[call callState] isEqual:CTCallStateDisconnected]) {
//this call has just ended (dropped/hung up/etc)
}
}];
Can i use this event handler to track call state when my app is in background?
Can i also fetch incoming call phone number from CTCall object? or there is any other way around.
I dont want to use Private API.Is there way available from Apple iOS SDK?
No there is no way to do this in the official SDK, you can not use it in the background since it does not fall on of the background running categories unless you app does something else in the background then just monitoring the call.
You will never be able to get the phone number of the current call since this is private data Apple will not allow you to acces the data.
I'm trying to create an app that transfers data between 2+ phones using GKSession. Thing is there are two options:
First: using the GKPeerPicker.. However here I get stuck at the point where I have to implement my own WIFI interface.. apple provides no instructions on how to do that:
- (void)peerPickerController:(GKPeerPickerController *)picker didSelectConnectionType: (GKPeerPickerConnectionType)type {
if (type == GKPeerPickerConnectionTypeOnline) {
picker.delegate = nil;
[picker dismiss];
[picker autorelease];
// Implement your own internet user interface here.
}
}
Second: Skipping GKPeerPicker and doing the whole thing my self, like in this example. However the app dev documentation doesn't provide any instructions on how to send/receive data without using GKPeerPicker.. (nor could I find any example of that on thew web)
I just figured out how to connect devices without the peerpicker. It was a bit of a guessing game because the documentation is pretty unclear and I've looked for so long on the internet for any info about this. I'll try to explain everything here to clear up any questions anyone in the future might have.
From the documentation:
A GKSession object provides the ability to discover and connect to
nearby iOS devices using Bluetooth or Wi-fi.
This was the first step to understand it for me. I thought the GKPeerPickerController was responsible of the advertising and connecting but GKSession actually does all that.
The second thing to understand is that what is referred to as peers are not necessarily connected to you. They can just be nearby waiting to be discovered and connected to. All peers have a state
GKPeerStateAvailable (this is what's useful!)
GKPeerStateUnavailable
GKPeerStateConnected
GKPeerStateDisconnected
GKPeerStateConnecting
So how do we actually connect? Well first we have to create a GKSession object to be able to find peers around us and see when they become available:
// nil will become the device name
GKSession *gkSession = [[GKSession alloc] initWithSessionID:#"something.unique.i.use.my.bundle.name" displayName:nil sessionMode:GKSessionModePeer];
[gkSession setDataReceiveHandler:self withContext:nil];
gkSession.delegate = self;
gkSession.available = YES; // I'm not sure this if this is the default value, this might not be needed
Now we have some delegate calls to respond to. session:didReceiveConnectionRequestFromPeer: and session:peer:didChangeState (you should also handle the calls of GKSessionDelegate for disconnection and failure appropriately)
-(void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
{
if(state == GKPeerStateDisconnected)
{
// A peer disconnected
}
else if(state == GKPeerStateConnected)
{
// You can now send messages to the connected peer(s)
int number = 1337;
[session sendDataToAllPeers:[NSData dataWithBytes:&number length:4] withDataMode:GKSendDataReliable error:nil];
}
else if (state == GKPeerStateAvailable)
{
// A device became available, meaning we can connect to it. Lets do it! (or at least try and make a request)
/*
Notice: This will connect to every iphone that's nearby you directly.
You would maybe want to make an interface similar to peerpicker instead
In that case, you should just save this peer in a availablePeers array and
call this method later on. For your UI, the name of the peer can be
retrived with [session displayNameForPeer:peerId]
*/
[session connectToPeer:peerID withTimeout:10];
}
}
The other peer now received a request that he should respond to.
-(void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID
{
// We can now decide to deny or accept
bool shouldAccept = YES;
if(shouldAccept)
{
[session acceptConnectionFromPeer:peerID error:nil];
}
else
{
[session denyConnectionFromPeer:peerID];
}
}
Finally to receive our little 1337 message
-(void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession*)session context:(void *)context
{
int number = 1337;
if([data isEqualToData:[NSData dataWithBytes:&number length:4]])
{
NSLog(#"Yey!");
}
}