I have a single view application with a few view each having their own view controller class. I also have a game game and a game view controller, I was wondering how I could play a game music starting from the start screen view controller and have it continue to play through out all of my views and game scene? So that way I don't have to say play when x-screen is loaded so it doesn't restart every time the player changes views.
You can play the music form your AppDelegate. Put this code in your applicationDidFinishLaunching()
let url = Bundle.main.url(forResource: "soundName", withExtension: "mp3")!
var player: AVAudioPlayer!
do {
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error {
print(error.localizedDescription)
}
This will play the music as soon as your app is launched and continue until told to stop or the app closes.
Related
I have an app where the user can play voice messages received from other users. Playing the voice messages should interrupt device audio (music, podcast, etc playing from other apps), play the voice messages and then let the device audio continue.
Here is a use specific use case I am trying to achieve
the user starts playing music on the device via Apple Music
the user opens the app and taps a voice message
the Apple Music stops
voice message in the app plays
Apple Music continues playing
With setting AVAudioSessions category to .ambient I can play the voice message "over" the playing Apple Music, but that is not what I need exactly.
If I use the .playback category that makes the Apple Music stop, plays the voice message in the app but Apple Music does not continue playing afterwards.
In theory, Apple has provided a "protocol" for interrupting and resuming background audio, and in a downloadable example, I show you what it is and prove that it works:
https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/bk2ch14p653backgroundPlayerAndInterrupter
In that example, there are two projects, representing two different apps. You run both of them simultaneously. BackgroundPlayer plays sound in the background; Interrupter interrupts it, pausing it, and when it is finished interrupting, BackgroundPlayer resumes.
This, as you will see, is done by having Interrupter change its audio session category from ambient to playback while interrupting, and changing it back when finished, along with first deactivating itself entirely while sending the .notifyOthersOnDeactivation signal:
func playFile(atPath path:String) {
self.player?.delegate = nil
self.player?.stop()
let fileURL = URL(fileURLWithPath: path)
guard let p = try? AVAudioPlayer(contentsOf: fileURL) else {return} // nicer
self.player = p
// error-checking omitted
// switch to playback category while playing, interrupt background audio
try? AVAudioSession.sharedInstance().setCategory(.playback, mode:.default)
try? AVAudioSession.sharedInstance().setActive(true)
self.player.prepareToPlay()
self.player.delegate = self
let ok = self.player.play()
print("interrupter trying to play \(path): \(ok)")
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { // *
let sess = AVAudioSession.sharedInstance()
// this is the key move
try? sess.setActive(false, options: .notifyOthersOnDeactivation)
// now go back to ambient
try? sess.setCategory(.ambient, mode:.default)
try? sess.setActive(true)
delegate?.soundFinished(self)
}
The trouble, however, is that response to .notifyOthersOnDeactivation is entirely dependent on the other app being well behaved. My other app, BackgroundPlayer, is well behaved. This is what it does:
self.observer = NotificationCenter.default.addObserver(forName:
AVAudioSession.interruptionNotification, object: nil, queue: nil) {
[weak self] n in
guard let self = self else { return } // legal in Swift 4.2
let why = n.userInfo![AVAudioSessionInterruptionTypeKey] as! UInt
let type = AVAudioSession.InterruptionType(rawValue: why)!
switch type {
case .began:
print("interruption began:\n\(n.userInfo!)")
case .ended:
print("interruption ended:\n\(n.userInfo!)")
guard let opt = n.userInfo![AVAudioSessionInterruptionOptionKey] as? UInt else {return}
let opts = AVAudioSession.InterruptionOptions(rawValue: opt)
if opts.contains(.shouldResume) {
print("should resume")
self.player.prepareToPlay()
let ok = self.player.play()
print("bp tried to resume play: did I? \(ok as Any)")
} else {
print("not should resume")
}
#unknown default:
fatalError()
}
}
As you can see, we register for interruption notifications, and if we are interrupted, we look for the .shouldResume option — which is the result of the interrupter setting the notifyOthersOnDeactivation in the first place.
So far, so good. But there's a snag. Some apps are not well behaved in this regard. And the most non-well-behaved is Apple's own Music app! Thus it is actually impossible to get the Music app to do what you want it to do. You are better off using ducking, where the system just adjusts the relative levels of the two apps for you, allowing the background app (Music) to continue playing but more quietly.
You've already discovered that you can interrupt other audio apps by activating a .playback category audio session. When you finish playing your audio and want the interrupted audio to continue, deactivate your audio session and pass the notifyOthersOnDeactivation option.
e.g.
try! audioSession.setActive(false, options: .notifyOthersOnDeactivation)
I think those apps that should continue like Apple Music, Spotify, Radio apps etc implement the functionality to handle interruptions and when another app's audio is deactivated / wants to hand back responsibility of the audio.
So could you try and see if this works
// I play the audio using this AVAudioPlayer
var player: AVAudioPlayer?
// Implement playing a sound
func playSound() {
// Local url for me, but you could configure as you need
guard let url = Bundle.main.url(forResource: "se_1",
withExtension: "wav")
else { return }
do {
// Set the category to playback to interrupt the current audio
try AVAudioSession.sharedInstance().setCategory(.playback,
mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url)
// This is to know when the sound has ended
player?.delegate = self
player?.play()
} catch let error {
print(error.localizedDescription)
}
}
extension AVAudioInterruptVC: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool) {
do {
// When the sound has ended, notify other apps
// You can do this where ever you want, I just show the
// example of doing it when the audio has ended
try AVAudioSession.sharedInstance()
.setActive(false, options: [.notifyOthersOnDeactivation])
}
catch {
print(error)
}
}
}
I am using xcode 9 and swift 4 for my app. In my app i have music playing in the viewDidLoad. When i exit the view controller to go to another View, it continues to play like it should. How ever, when i return to that view controller the song starts to play again. This song is overlapping the song that first loaded. Do you guys have any ideas on how to stop this from happening?
do
{
let audioPath = Bundle.main.path(forResource: "APP4", ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
}
catch
{
//catch error
}
let session = AVAudioSession.sharedInstance()
do
{
try session.setCategory(AVAudioSessionCategoryPlayback)
}
catch
{
}
player.numberOfLoops = -1
player.play()
It starts playing again, because your viewDidLoad is called again, which asks it to play it again. A simplest fix would be to keep a static bool variable to keep track if you have already made this call.
static var isMusicPlaying: Bool = false
In your viewDidLoad, you can put code before the code that calls the play.
guard !isMusicPlaying else {
return
}
isMusicPlaying = true
I am calling this function playSound() in viewWillAppear, the audio starts playing and then suddenly volume increases with a minor break in playback.
here is my code
func playSound() {
let url = Bundle.main.url(forResource: "music", withExtension: "mp3")!
do {
try self.player = AVAudioPlayer(contentsOf: url)
player?.prepareToPlay()
player?.numberOfLoops = -1
player?.play()
} catch let error as NSError {
KBLog.log(object: error.description)
}}
Edit 1 :
I am using this code for an iPad app, so ipad only have speaker, no earpiece, if this has something to do with the issue.
The problem was i was playing this audio and at the same time i was initialising my publisher view for starting the video call, thus an instance of AVPlayer was initialised to take audio input i.e. two instances of AVPlayer were getting initialised causing the break in playback.
I am trying to add sound to my app. I would like it for when I tap a button, it plays a quick sound. However, when the button is tapped quickly and repeatedly, the sound does not work as well (It only plays 1 or 2 times when I tap the button 5 or 6 times). Here is my code in the button
player.play()
I have this outside
var player = AVAudioPlayer()
let audioPath = NSBundle.mainBundle().pathForResource("illuminati", ofType: "wav")
Viewdidload:
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath!))
} catch {}
How can I play the sound repeatedly better? Thanks.
The problem is that you are calling the play multiple time before the previous call finished. You need to keep track of how many times the user click the button and the play song one by one.
This is what you can do:
Use an integer in you class to keep track of number of times that the button is clicked
var numClicks = 0
var buttonClickTime:NSDate? = nil // The last time when the button is clicked
#IBAction func yourbuttonclickfunction() {
numClicks++;
buttonClickTime = NSDate()
player.play()
}
Register the delegate of AVAudioPlayerDelegate
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath!))
// Add this
player.delegate = self
} catch {}
In the delegate function, play the song again when the previous one reach the end:
optional func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool)
{
if --numClicks > 0
{
let now = NSDate()
let duration = now.timeIntervalSinceDate(buttonClickTime!)
// If the button click was less than 0.5 seconds before
if duration < 0.5
{
// Play the song again
player.play();
}
}
}
The reason your sound only plays a few time is because the song plays until finished or until you stop it, even if you press the button multiple times in succession.
You could just stop the music manually, so before you press a button that plays a sound you say
player.stop()
and than
player.play()
Is that helping?
At the moment I have a check in my app to see if iTunes is playing, and if it is, to proceed with iTunes music and not play the in game music. If iTunes is not playing anything, then to play the in game music.
I am trying to incorporate this into 2 functions and have a button that switches between:
Play iTunes rather in game music
Play in game music rather iTunes
This is currently in my viewDidLoad()
let check = isPlaying()
let path = NSBundle.mainBundle().pathForResource("music.mp3", ofType: nil)!
let url = NSURL(fileURLWithPath: path)
do {
if check == false {
let sound = try AVAudioPlayer(contentsOfURL: url)
music = sound
sound.play()
}
} catch { }
The isPlaying() function
func isPlaying() -> Bool {
let musicPlayer = MPMusicPlayerController.systemMusicPlayer()
if musicPlayer.playbackState == MPMusicPlaybackState.Playing {
ingame = false;
return true
}
return false
}
I tried to incorporate the previous code into a function that will be triggered by a button:
func playInGameMusic() {
let path = NSBundle.mainBundle().pathForResource("music.mp3", ofType: nil)!
let url = NSURL(fileURLWithPath: path)
do {
let sound = try AVAudioPlayer(contentsOfURL: url)
music = sound
sound.play()
ingame = true
} catch { }
}
However, I have no idea how to go about my playSystemMusic() function. Is there a way to resume iTunes from within my app? I understand their is an MPMusicPlayerController class, but I am not sure what I am supposed to use to make iTunes play from a button within my app.
Any guidance is appreciated, thanks.