Categories
Uncategorized

How to fix the common “no sound from AVPlayer/AVAudioPlayer” problem in iOS/Swift programming

maxwell sound and his shoe phone

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.

swift kickWhile 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…

xcode new single view application

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:

output

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.

viewdidload 1

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.

viewdidload 2

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.

21 replies on “How to fix the common “no sound from AVPlayer/AVAudioPlayer” problem in iOS/Swift programming”

Nice tutorial , in this xcode 7 swift 2 , should put do{} into , sth like this :
import AVFoudation
var player:AVPlayer!

override func viewDidLoad() {
super.viewDidLoad()

let linkString = “http://m.mp3.zing.vn/xml/song-load/MjAxNSUyRjA4JTJGMDQlMkY3JTJGYiUyRjdiNTI4YTc0YWU2MGExYWJjMDZlYzA5NmE5MzFjMjliLm1wMyU3QzEz”

let link = NSURL(string: linkString)!
player = AVPlayer(URL: link)

do{
player.play()
}
}

Thanks, but that’s not what my problem is.
I have an app that plays local media, then rewinds after each play of the last item in an AVQueuePlayer’s queue and plays from the given point. The AVQueuePlayer itself is a property of my UIViewController. My problem is, each item will play just once.

Code:

@interface ViewController ()

@end

@implementation ViewController

– (void)viewDidLoad {
[super viewDidLoad];
self.queuePlayer = [[AVQueuePlayer alloc] init];

AVPlayerItem *playerItem1 = [AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:@”placeholder” withExtension:@”m4a”]];
[self.queuePlayer insertItem:playerItem1 afterItem:nil];
AVPlayerItem *item2 = [AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:@”man-2″ withExtension:@”m4a”]];
[self.queuePlayer insertItem:item2 afterItem:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shouldReplay) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
[self.queuePlayer play];
}

-(void)doReplay:(BOOL)insertLastItem

{
/*
if (insertLastItem) {
[self.queuePlayer insertItem:lastItem afterItem:nil];

}*/
AVPlayerItem *lastItem = [self.queuePlayer.items lastObject];

[lastItem seekToTime:CMTimeMakeWithSeconds(1, NSEC_PER_SEC) toleranceBefore:CMTimeMake(1, 10)
toleranceAfter:CMTimeMake(1, 10) completionHandler:^(BOOL finished) {

}];
while (self.queuePlayer.status != AVPlayerStatusReadyToPlay) {
// do nothing
}
BOOL readyToPlay = lastItem.status == AVPlayerItemStatusReadyToPlay;
NSLog(@”Ready to play: %@”,readyToPlay ? @”Yes” : @”No”);
NSLog(@”Item time: %d”,lastItem.currentTime);
[self.queuePlayer play];
}

-(void)shouldReplay
{
if ([[self.queuePlayer items] count] == 1)
{

[self doReplay:false];
}

}

@end

Hello there! Thank you so much for making this post. You have no idea how long I was struggling to get my code to work! This was my issue/problem.

Hello Team,

Greetings! I am using same AVPlayer and style to play some audio stream url in my apps but with IPv6 network all these urls doesn’t play any streaming urls so please assist me to overcome with it.

URLS :
http://s1.voscast.com:8438/listen.pls
http://us2.streamingpulse.com:7080
http://us2.streamingpulse.com:7080/listen.pls
http://us2.streamingpulse.com:7080
http://kanga.college.columbia.edu:8000

Same code i am using which you mentioned & all the urls works only for IPv4 address but doesn’t work on IPv6.

Your help would be very much appreciable!

Thanks,
Azad

I have same problem. AVPlayer works great, all videos play but there is no sound with the running video on a device. The simulators always work perfect.
The instance are created prior to viewdidload. Anyway it’s all very strange. Are there any suggestions? Help please.

OMG!! Thank you soo much!! I made the player inside my URLSession.. moved it outside and PURRRRFECT!! ^-^

Arigatogosaimasu!

Hi,

I’m using the swift playgrounds on an iPad Pro 12,9

There’s no sound coming from the adventure of blue app and a lot of errors.

There are sounds coming from other apps in the playground.

I see a lot of people commenting on it.

Any ideas on how to fix it?

Thanks

Thank you so much; in hindsight it’s all so obvious; but it took me hours of (failed) attempts, and the just the title of the first section saved my day…

Comments are closed.