I am creating a CarPlay music app, everything works fine except when I get a call in between my music is playing. Carplay pauses music in between but when I end phone call its not resuming back.
Please help if anyone has faced same issue
CarPlay is not actually playing or pausing the music, it is still your app on the user's device that is doing all the work and CarPlay is just presenting an interface to interact with your app which is what you do in your CarPlaySceneDelegate
Therefore, the handling of how your app resumes after the call is not part of CarPlay.
You can try the following in your player management module:
1. Listen for audio interruption notifications
NotificationCenter.default
.addObserver(self,
selector: #selector(handleInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil)
2. Check the status of the interruption and handle accordingly
#objc
private func handleInterruption(_ notification: Notification)
{
// Check if we have valid information from the notification
// with regards to the interruption
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else
{
return
}
// We check if the interruption has begun
if type == .began
{
// Pause the AVPlayer in that case
player?.pause()
}
else if type == .ended
{
// We check if we have instructions on how to handle the interruptions
guard let optionsValue = info[AVAudioSessionInterruptionOptionKey] as? UInt else
{
return
}
// Convert the optionsValue into an AVAudioSession.InterruptionOptions
// which can be tested
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
// Check if the playback can resume after the interruption
if options.contains(.shouldResume)
{
// Request AVPlayer to resume the playback
player?.play()
}
}
}
Give this a try and see if it helps your situation
Update based on OP comments
The audio not re-starting is not related to CarPlay, if you app resumes audio after a call, this will be carried over to CarPlay.
With that being said, if you have not already, add this code when your player initializes:
UIApplication.shared.beginReceivingRemoteControlEvents()
Maybe my code does not work for your exact scenario but I believe you will need to use the AVAudioSession.interruptionNotification which is what get's called when a phone call, siri or similar things interrupt your audio.
When this interruption has ended, you need to restart your audio.
I can't show you a CarPlay example, but here is an example of the above code in action when Siri interrupts the audio, the lock screen shows the status of the audio being paused, when Siri is dismissed, the app resumes the player as you can see.
So I found solution: - I was missing one line of code,
"try AVAudioSession.sharedInstance() .setCategory(AVAudioSession.Category.playback)" this is must for maintaining audio session
Related
I am currently working on a workout plan app. Part of the app is an exercise timer that will count up or down (this works). Additionally a sound should be played as soon as the timer finishes. Ideally that sound would not interfere with music being played, no volume changes, no pause, nothing. Just play a loud sound while music keeps playing. I have tried two ways to do that, both have critical drawbacks.
First solution:
I tried using an AVAudioPlayer that does play the alarm sound after x seconds, with x being the time set for the timer. It does work sometimes as expected, but sometimes the sound will play early. If the timer is set to 60 seconds, the alarm might play after 60 seconds, but sometimes after 55-57 seconds. The alarm is implemented like this:
private let audioPlayer: AVAudioPlayer
// init audioPlayer
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: alarmSound)
} catch {
audioPlayer = AVAudioPlayer()
}
audioPlayer.play(atTime: audioPlayer.deviceCurrentTime + timeTarget)
My second solution that always fires at the right time is using a userNotification. This approach has different drawbacks. The notification will not show if my app is in foreground and the sound will not play if the phone is in silent mode. Aditionally, it will interfere with music being played and show a banner, but I want my alert to be as less intrusive as possible. It is implemented like this:
private var notificationCenter: UNUserNotificationCenter
private var alarmMessage: UNMutableNotificationContent
notificationCenter = UNUserNotificationCenter.current()
alarmMessage = UNMutableNotificationContent()
alarmMessage.title = "Workout Plan"
alarmMessage.subtitle = "Exercise Timer"
alarmMessage.categoryIdentifier = "alarm"
alarmMessage.sound = UNNotificationSound(named: UNNotificationSoundName("dummy.wav"))
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeTarget, repeats: false)
let identifier = "exerciseTimer"
let request = UNNotificationRequest(identifier: identifier, content: alarmMessage, trigger: trigger)
notificationCenter.add(request)
How do I play a sound at a specific time without any offsets or annoying the user with useless notifications?
I have an AVQueuePlayer which is working as expected except for after returning from a suspended state.
It's playing a HLS audio stream. The logic for my pause button is very simple
isPlaying ? player?.play() : player?.pause()
I've checked the state of the player and the currentItem, all of which say it's "playing". But the audio does not progress or playback
player.rate = 1
player.currentItem.asset.isPlayable = true
player.status == readyToPlay
player.currentItem.status == readyToPlay
I'm also setting the session with:
do {
try session.setCategory(AVAudioSession.Category.playback, options: [])
try session.setActive(true, options: [])
} catch {
print("Failed to set session active")
}
Any advice on how to troubleshoot this would be greatly appreciated. Do I need to keep track of the app entering a suspended state and reload the AVPlayerItem?
Do I need to keep track of the app entering a suspended state and reload the AVPlayerItem?
It is not suspension of the app that you need to keep track of; it's interruption of the audio session. AVAudioSession provides a notification for exactly this purpose.
I am trying to check if the screen is recording before allowing a following action, I initially tried to use ReplayKit to automatically record, but this isn't a viable solution because it doesn't allow for recording outside the app, so basically what I want to do is check if the user has began screen recording using the IOS Control Centre recorder, before allowing them to execute another piece of code.
Something like:
func handleScreen() {
var isRecording: Bool = false
if ScreenIsRecording { //(this is what i don't know how to check)
isRecording = true
}
if isRecording == true {
// execute this code.
}
}
I am open to other solutions of being able to execute screen recording, but it has to be able to record all screens not just the in-app screen.
Thanks
UIScreen includes the UIScreen.isCaptured property which you should be able to reference to determine if the screen is likely being recorded. However this will also return true if the device is being AirPlayed or otherwise broadcast:
A value of YES indicates the system is actively recording, mirroring,
or using AirPlay to stream the contents of the screen.
if UIScreen.mainScreen().isCaptured {
isRecording = true
}
I just tested this on iOS 16, and even though the documentation says that isCaptured should return true for AirPlay. It does not when I tested it!
Observe changes to isCaptured with NotificationCenter:
NotificationCenter.default.addObserver(self, selector: #selector(screenCaptureDidChange),
name: UIScreen.capturedDidChangeNotification,
object: nil)
Handle the notification:
#objc func screenCaptureDidChange() {
debugPrint("screenCaptureDidChange.. isCapturing: \(UIScreen.main.isCaptured)")
if !UIScreen.main.isCaptured {
//TODO: They stopped capturing..
} else {
//TODO: They started capturing..
debugPrint("screenCaptureDidChange - is recording screen")
}
}
This notification does NOT get fired when you start AirPlay, and if you start screen recording while AirPlaying, when you stop recording the notification will get fired but UIScreen.main.isCaptured is false even though AirPlay is still active.
I'm still a beginner in coding but I'm working on a very basic music player to get an idea of how Swift works.
////Functions
//Play chosen file function
func playChosenFile() {
//Set up the music file URL.
let musicFilePathURL = MenuBarModel.mainMenu.URL
if musicFilePathURL != nil {
//Initialize audioPlayer with safety.
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: musicFilePathURL!)
}
catch {
}
}
else if musicFilePathURL == nil {
//Some code that will make the player do nothing.
}
}
//Play function
func play() {
playChosenFile()
audioPlayer.play()
}
So musicFilePathURL gets initialized with a URL if I choose an audio file using NSOpenPanel().
If I do select a file using NSOpenPanel(), then musicFilePathURL has the location of the selected file and is then passed to AVAudioPlayer. Once I press the Play button, the function "play()" executes and then it executes playChosenFile() and audioPlayer.play().
The music player plays the song if I do this before pressing the play button.
If I press the play button before selecting any file to play, then the program crashes because musicFilePathURL is nil.
I'm confused on how to make the program not crash if there is no file selected before pressing the play button. As an example, if you open VLC, if you press the Play button, it prompts to pick a file, but if you decide to press cancel, the player does nothing.
How can I make my program do nothing if I press the play button with no audio file picked at startup?
You've got the right idea with the try/catch statements for the Audio Player, but you could probably use another one in the play() function.
That way when the audioPlayer attempts to play a null object, an exception will be caught and you can process it handle it on your own rather than having the app crash.
Perhaps something like this? I would try it, but unfortunately I left my Mac at home.
func play(){
do {
playChosenFile()
audioPlayer.play()
} catch {
print "Error Occurred (Did the user specify a file?)
}
}
I need to make views visible at fixed time while running a MP3 audio. I am using Swift. Any suggestions?
I don't like using timers if drawing depends on them. They may fire more frequently than the screen redraws or out of sync with the screen drawing. I use a CADisplayLink instead. It's similar to a timer but it is designed to fire in synch with the screen's refresh rate. You could implement something like this.
var messagePlaybackTimer: CADisplayLink?
var player: AVAudioPlayer?
func playAudioData(){
let path = NSBundle.mainBundle().pathForResource("audio", ofType: "mp3")
var url: NSURL
if let audioResourcePath = path {
url = NSURL.fileURLWithPath(audioResourcePath)!
}else{
return;
}
player = AVAudioPlayer(contentsOfURL: url, error: nil)
messagePlaybackTimer?.invalidate()
messagePlaybackTimer = nil;
messagePlaybackTimer = CADisplayLink(target: self, selector: Selector("messagePlaybackTimerFired"))
messagePlaybackTimer?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
player?.play()
}
func messagePlaybackTimerFired(){
println(player?.currentTime)
}
In messagePlaybackTimerFired() you can check the AVAudioPlayer's current time and fire events based on that. (E.g. if player?.currentTime > 5 && player?.currentTime < 6 { do something } )
This will allow you to fire the method at as close to 5 seconds as possible while still being optimal for doing UI work.
When you are done with the CADisplayLink (when the player stops playing) be sure to invalidate it. If you don't, it will keep firing when you don't need it anymore.
Another thing you can do is fire off an NSNotification in messagePlaybackTimerFired() and your UIViewController can subscribe to the notification and show the views when appropriate.
Use an NSTimer or the AVAudioPlayer's addPeriodicTimeObserverForInterval:queue:usingBlock: method to check the value, it's (very most likely) not possible to get updates when a certain time is reached.
Oh and for a Swiftier NSTimer I suggest this