NotificationCenter stops working when the screen is locked - ios

I'm having trouble with an app I'm building, the app objective is to play audio files, it works by requesting an audio file from a public API, playing it and wait until it ends, after it ends it requests another audio and starts over.
Here's a shortened version of the code that does this, I omitted the error checking part for simplicity
func requestEnded(audioSource: String) {
let url = URL(string: "https://example.com/audios/" + audioSource)
audio = AVPlayer(url: url!)
NotificationCenter.default.addObserver(self,selector: #selector(MediaItem.finishedPlaying(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: audio?.currentItem)
audio?.play()
}
#objc func finishedPlaying(_ notification: NSNotification) {
print("Audio ended")
callAPI()
}
func callAPI() {
// do all the http request stuff
requestEnded(audioSource: "x.m4a")
}
// callAPI() is called when the app is initialized
It works well when the screen is unlocked. When I lock the phone the current audio keeps playing but when it ends finishedPlaying() never gets called (the print statement is not shown on the console).
How can I make it so the app would know the audio ended and trigger another one all while locked?, In the android version I got around the screenlock problem by using a partial wakelock which made it run normally even with the screen off.
It has to be done this way because the API decides the audio on realtime and it's all done on the backend so no way to buffer more than one audio without breaking the requirements of the app
What are my options here?, any help is appreciated.

Related

Programmatically trigger the action that a headphone pause button would do

I am trying to find a way to pause any playing media on the device, so I was thinking of triggering the same logic that is fired when a user press the headphone "middle button"
I managed to prevent music from resuming (after I pause it within my app, which basically start an AVAudioSession for recording) by NOT setting the AVAudioSession active property to false and leave it hanging, but I am pretty sure thats a bad way to do it. If I deactivate it the music resumes. The other option I am thinking of is playing some kind of silent loop that would "imitate" the silence I need to do. But I think if what I am seeking is doable, it would be the best approach as I understood from this question it cannot be done using the normal means
func stopAudioSession() {
let audioSession = AVAudioSession.sharedInstance(
do {
if audioSession.secondaryAudioShouldBeSilencedHint{
print("someone is playing....")
}
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
isSessionActive = false
} catch let error as NSError {
print("Unable to deactivate audio session: \(error.localizedDescription)")
print("retying.......")
}
}
In this code snippet as the function name implies I set active to false, tried to find other options but I could not find another way of stopping my recording session and prevent resume of the other app that was already playing
If someone can guide me to which library I should look into, if for example I can tap into the H/W part and trigger it OR if I can find out which library is listening to this button press event and handling the pause/play functionality
A friend of mine who is more experienced in IOS development suggested the following workaround and it worked - I am posting it here as it might help someone trying to achieve a similar behaviour.
In order to stop/pause what is currently being played on a user device, you will need to add a music player into your app. then at the point where you need to pause/stop the current media, you just initiate the player, play and then pause/stop it - simple :)
like so:
let musicPlayer = MPMusicPlayerApplicationController.applicationQueuePlayer
func stopMedia(){
MPMediaLibrary.requestAuthorization({(newPermissionStatus: MPMediaLibraryAuthorizationStatus) in
self.musicPlayer.setQueue(with: .songs())
self.musicPlayer.play()
print("Stopping music player")
self.musicPlayer.pause()
print("Stopped music player")
})
}
the part with MPMediaLibrary.requestAuthorization is needed to avoid an authorisation error when accessing user's media library.
and of course you will need to add the Privacy - Media Library Usage Description
key into your Info.plist file

Process the text once voice input is stopped from SFSpeechRecognizer

I am developing a Voice to Text application using iOS SFSpeechRecognizer API.
Found a great tutorial here: and it worked fine.
I wanted to process the text and perform some action as soon as the voice input is stopped. So, was curious whether there is a delegate method available for SFSpeechRecognizer which can recognise when the voice input is stopped so that I can capture the input and process further?
So, was curious whether there is a delegate method available for SFSpeechRecognizer which can recognise when the voice input is stopped so that I can capture the input and process further?
Not built into the SFSpeechRecognizer API, no. On the contrary, that is exactly why you must provide interface that allows the user to tell the recognizer that the input is finished (e.g. a Done button of some sort). Your app will be rejected if you omit that interface.
A possible solution maybe to use a third party library like FDSoundActivatedRecorder which start recording when sound is detected and
stops recording when the user is done talking.
Then you can use the recorded audio as in this link to convert it to text in a go.
func transcribeAudio(url: URL) {
// create a new recognizer and point it at our audio
let recognizer = SFSpeechRecognizer()
let request = SFSpeechURLRecognitionRequest(url: url)
// start recognition!
recognizer?.recognitionTask(with: request) { [unowned self] (result, error) in
// abort if we didn't get any transcription back
guard let result = result else {
print("There was an error: \(error!)")
return
}
// if we got the final transcription back, print it
if result.isFinal {
// pull out the best transcription...
print(result.bestTranscription.formattedString)
}
}
}

CallKit can reactivate sound after swapping call

I'm developing CallKit application, I have a problem, Call Holding is failing to restart audio when "swapping" calls on the CallKit screen until user returns to in-app call screen. I can bypass this by updating:
supportsHolding = false
but I can I solve this problem, whatsapp for example can do this correctly!
p.s. I'm using webrtc to make a call!
thanks!
EDIT:
This is code of provider:
public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
guard let call = conductor!.callWithUUID(uuid: action.callUUID) else {
WebRtcConductor.debug("\(self.TAG) 🔴 failed to perform HeldAction: uuid: \(action.uuid), calluiid: \(action.callUUID)")
action.fail()
return
}
setIsHeld(call: call, isHeld: action.isOnHold)
action.fulfill()
}
the setIsHeld function simply do:
audioTrack.isEnabled = enabled
If I use "mute" button of callkit screen, all works fine, but if I have 2 active calls, when I swipe from webrtc call to normal call, CXSetHeldCallAction is called and audio track did disabled, If I swipe again to webrtc call, audio track is enabled but i do not hear nothing, if I return to main app screen, audio works fine again!
Actually, there is a limitation in the Google WebRTC Library which leads to the described problem when implementing a CallKit integration which supports swapping calls.
The WebRTC Issue 8126 is known for over a year now, but not yet integrated into the WebRTC master branch. However, you can find the necessary code changes to fix this problem in the original ticket.
However, as a workarround, you can trigger the system notification which is subscribed by WebRTC internally.
Post a AVAudioSessionInterruptionType.ended Notification in the "didActivate audioSession" method of the CallKit Provider:
var userInfo = Dictionary<AnyHashable, Any>()
let interrupttioEndedRaw = AVAudioSessionInterruptionType.ended.rawValue
userInfo[AVAudioSessionInterruptionTypeKey] = interrupttioEndedRaw
NotificationCenter.default.post(name: NSNotification.Name.AVAudioSessionInterruption, object: self, userInfo: userInfo)
PS: Stare the ticket to improve chances of a merge ;-)
Had the same issue. If I have 1 active call, then new calls is incoming, I tap hold&accept. New call works, but after using Swap in CallKit audio stops working.
Found that provider:performSetHeldCallAction: method from CXProviderDelegate protocol is the spot where you can actually deactivate/activate audio for Swap calls via CallKit native interface.
In my case I used the audioController.deactivateAudioSession() method for the call was putting in OnHold.
But found that the same method provider:performSetHeldCallAction: was fired for other call that is being put active (from OnHold state), when tap Swap button via CallKit.
So you just need to deactivate/activate audio respectively to call's state (either hold or not).
In common way it should look this way:
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
// Retrieve the Call instance corresponding to the action's call UUID
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
// Update the Call's underlying hold state.
call.isOnHold = action.isOnHold
// Stop or start audio in response to holding or unholding the call.
if call.isOnHold {
stopAudio()
} else {
startAudio()
}
// Signal to the system that the action has been successfully performed.
action.fulfill()
}
P.S. It looks like you should have some class that responds for Audio session. It should implement kind of activate audio session / deactivate audio session.

Get notified of HLS segment requests in Swift

I am writing an iOS app in Swift for HLS live streaming. I want my app logic to be notified when each HLS segment request is initiated during playback (and what the respective URL is). I have tried to observe changes to various properties of AVPlayer and AVPlayerItem using KVO. Though, it only informs me of when the playback is initiated. For example, adding the following observer triggers a invocation of observeValue method when playback starts, but I have not found a way to be continuously notified of each segment request.
playerItem.addObserver(self, forKeyPath: "status", options:NSKeyValueObservingOptions(), context: nil)
Is there a method with KVO that should allow me to be notified of each segment request? Are there other objects/API:s not related to AVFoundation that I should consider?
/George
I follow Fabian's approach when I need to debug HLS streams. It shows useful info every time that there is an update related to the current stream being played. Here's the code I use, hope it helps anyone facing a similar issue!
func trackPlayerLogs() {
NotificationCenter.default.addObserver(self,
selector: #selector(handleAVPlayerAccess),
name: NSNotification.Name.AVPlayerItemNewAccessLogEntry,
object: nil)
}
func handleAVPlayerAccess(notification: Notification) {
guard let playerItem = notification.object as? AVPlayerItem,
let lastEvent = playerItem.accessLog()?.events.last else {
return
}
let indicatedBitrate = lastEvent.indicatedBitrate
print("--------------PLAYER LOG--------------")
print("EVENT: \(lastEvent)")
print("INDICATED BITRATE: \(indicatedBitrate)")
print("PLAYBACK RELATED LOG EVENTS")
print("PLAYBACK START DATE: \(lastEvent.playbackStartDate)")
print("PLAYBACK SESSION ID: \(lastEvent.playbackSessionID)")
print("PLAYBACK START OFFSET: \(lastEvent.playbackStartOffset)")
print("PLAYBACK TYPE: \(lastEvent.playbackType)")
print("STARTUP TIME: \(lastEvent.startupTime)")
print("DURATION WATCHED: \(lastEvent.durationWatched)")
print("NUMBER OF DROPPED VIDEO FRAMES: \(lastEvent.numberOfDroppedVideoFrames)")
print("NUMBER OF STALLS: \(lastEvent.numberOfStalls)")
print("SEGMENTS DOWNLOADED DURATION: \(lastEvent.segmentsDownloadedDuration)")
print("DOWNLOAD OVERDUE: \(lastEvent.downloadOverdue)")
print("--------------------------------------")
}
I don't know of an easy way to be notified of each segment request while it's happening. You should look at AVPlayerItem's accessLog property and look at the AVPlayerItemAccessLogEvents in the log. These will describe both network and playback events. I highly recommend this approach if it fits your needs.
Another way is to set up your app as an HTTP server and point an AVURLAsset/AVPLayerItem at the local server which will then have to translate those requests to an external server. This is much more complex, difficult, error-prone, and is nearly guaranteed to have bad performance. Please do not do this.
addendum:
You may be tempted to look at AVAssetResourceLoaderDelegate as it says you can handle resource requests on behalf of an AVURLAsset. Unfortunately it does not go through the loader for segments. It seems to be for playlists, decryption keys and other such assets.

Event to play mp3 file in specific time when app closed or in background

I have an application for Muslims prayer alert in IOS ,
Iam already playing mp3 file by using when i click a button
and this is my code
super.viewDidLoad()
let tapSound = NSBundle.mainBundle().URLForResource("mp", withExtension: "mp3")
self.soundFileURLRef = tapSound
do {
player = try AVAudioPlayer(contentsOfURL: soundFileURLRef)
} catch _ {
player = nil
}
player?.delegate = self
player?.prepareToPlay()
}
#IBAction func play(sender: AnyObject) {
NSLog("started playing")
player?.play()
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
//
NSLog("finished playing")
}
and it's working perfeclty
Now I am looking for a way to play mp3 file in specific time like
when time is 12:00 AM Play the mp3 file
even the application is closed
any suggestion how to make that in IOS ?
Thanks for help
To schedule events like this, you can use Local Notifications. It's how all (that I know of) alarm clock apps alert you when the app isn't open. Local notifications, however, only allow you to play a 30 sec (max) sound clip that you have bundled with your application.
Currently there is no way to have your app play music as a background service unless it's currently open (or was open when you locked the screen if you opt out of multitasking... see the above link).
The built-in alarm clock app is allowed to do this, however, because it is using a private API.
Local Notifications work as suggested here but you should keep in mind that there is no 100% guarantee the notification will be fired precisely when you schedule them. Depending on the workload, the OS can delay it a little while (it could change from seconds to minutes). Just wanted to point that out since prayer times are very precise ;)

Resources