Maxwell Smart, the protagonist of the 1960s spy comedy Get Smart, and his shoe phone,
quite possibly the first mobile phone to make regular appearances on a TV show.
While answering this question on Stack Overflow, I noticed that in the “Related” list running down the right-hand side of the page that there were several similar questions. They essentially asked the same question:
I’m not getting any errors, so why isn’t the AVPlayer
or AVAudioPlayer
in my iOS app playing anything?
I’ve seen this question asked often enough that I’ve decided to post this article as an answer that I can point to from now on.
Here’s the short answer:
Because your AVPlayer
or AVAudioPlayer
went out of scope just as the sound started playing.
The long answer, complete with code examples, appears below.
To make my point as clear as possible, the examples will be simple: they’ll be attempts at creating an app that plays an MP3 located at a given URL. That way, all you have to follow along is either type in or copy-and-paste code without having to bring a sound file into your project. While these examples use an AVPlayer
object to play an online MP3, what I’m demonstrating is equally valid for apps that use an AVAudioPlayer
object to play an MP3 stored as a local file or in memory.
First, let’s do it the wrong way
Many “why isn’t my app playing any sounds?” questions on Stack Overflow include code snippets that make the same mistake that I’m about to demonstrate. Fire up Xcode and follow along…
Create a new single view Swift application in Xcode, then replace the contents of ViewController.swift with the following code:
// Our quick and dirty sound streaming app, // done the WRONG way. It compiles, // but doesn't play any sound. import UIKit import AVFoundation // The AV Foundation framework lets us work // with audio and video files class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() println("Setting up.") // If you'd rather not use this sound file, // replace the string below with the URL of some other MP3. let urlString = "http://www.stephaniequinn.com/Music/Pachelbel%20-%20Canon%20in%20D%20Major.mp3" let url = NSURL(string: urlString)! // We'll instantiate our player here, inside viewDidLoad. let avPlayer = AVPlayer(URL: url) println("About to play...") avPlayer.play() println("...and we're playing!") } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
Feel free to run it in either the simulator or on a device. You’ll see a blank screen and hear…nothing. The only feedback you’ll see in the app is in Xcode’s output pane, where you’ll see the messages from the println
functions:
If you look at the code, you’ll see that everything happens in the viewDidLoad
method. The output from the println
functions — combined with the lack of error messages and the fact that the code compiled — suggests that code in viewDidLoad
is being executed, even though you’re not hearing anything.
Take a look at where the AVPlayer
instance is created. It’s inside the viewDidLoad
method, which means that its scope is limited to viewDidLoad
. Once viewDidLoad
has executed its final line, the println
function that outputs “…and we’re playing!”, all its local variables go out of scope, including avPlayer
, which contains the reference to the AVPlayer
object that’s supposed to be playing the MP3. Without that reference, that AVPlayer
object gets cleaned up by the system and gets zapped out of existence. Simply put, the app did start playing the MP3, but stopped very, very shortly afterwards; probably on the order of milliseconds.
Hopefully, what I just told you gave you the hint for making the app work properly. Let’s try doing it the right way…
The right way
Replace the contents of ViewController.swift with the following code:
// Our quick and dirty sound streaming app, // done the RIGHT way. It compiles, // *and* it streams audio from an URL. import UIKit import AVFoundation // The AV Foundation framework lets us work // with audio and video files class ViewController: UIViewController { // This time, we'll declare avPlayer as an instance variable, // which means it exists as long as our view controller exists. var avPlayer: AVPlayer! override func viewDidLoad() { super.viewDidLoad() println("Setting up.") // If you'd rather not use this sound file, // replace the string below with the URL of some other MP3. let urlString = "http://www.stephaniequinn.com/Music/Pachelbel%20-%20Canon%20in%20D%20Major.mp3" let url = NSURL(string: urlString)! avPlayer = AVPlayer(URL: url) println("About to play...") avPlayer.play() println("...and we're playing!") } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
Note the difference: this time, we’re creating avPlayer
as an instance variable. That means that its scope is the viewController
instance. As long as the viewController
exists, so will avPlayer
. And since this app has only one view controller, it means that avPlayer
— and the AVPlayer
object it references — will exist as long as the app is running.
Try running the app again. As long as the simulator or device can access the ‘net and there’s actually an MP3 at the URL specified in urlString
, you’ll hear that MP3 playing.
Once again: the answer to the problem is that your AVPlayer
or AVAudioPlayer
instance needs to be scoped at the view controller level, and not at the level of a method. That way, the player exists as long as the view controller instance does.