Im trying to fully load a video URL into an AVPlayer before the user is able to play the video, for seamless playback.
I add two observers to the AVPlayerItem:
[item addObserver:self forKeyPath:#"loadedTimeRanges"
options:NSKeyValueObservingOptionNew context:&timeRanges];
[item addObserver:self forKeyPath:#"playbackBufferFull"
options:NSKeyValueObservingOptionNew context:&playbackBufferFull];
I wait until value is equal to the duration of the video - fully loaded.
if (context == &timeRanges) {
NSLog(#"BUFFERING");
NSArray *timeRanges = (NSArray *)[change objectForKey:NSKeyValueChangeNewKey];
CMTimeRange timerange = [timeRanges[0] CMTimeRangeValue];
CGFloat value = CMTimeGetSeconds(CMTimeAdd(timerange.start, timerange.duration));
CGFloat duration = CMTimeGetSeconds(self.player.currentItem.duration);
NSLog(#"VALUE:DURATION == %f:%f", value, duration);
if(value == duration) {
NSLog(#"FINISHED");
[self prerollVideoPlayer];
}
return;
}
But this rarely happens. The player usually stops buffering at around 3 for a five second video. Then the "playbackBufferFull" is called
if (context == &playbackBufferFull){
if (self.player.currentItem.playbackBufferFull) {
NSLog(#"IS FULL");
//[self prerollVideoPlayer];
}
return;
}
How can I increase the buffer size? Or how to buffer the rest of the video once playbackBufferFull is called? The videos being played, are only five seconds long.
Take a look at the preferredForwardBufferDuration property on AVPlayerItem.
Something like this should work:
AVPlayerItem* theItem = self.player.currentItem;
theItem.preferredForwardBufferDuration = CMTimeGetSeconds( theItem.duration );
Then you can observe playback buffer full, like you show in your code, to begin playback.
I have a master-detail app. Tapping on each cell of the tableView in the MasterViewController navigates to a DetailViewController where you can download and play an audio file. I had problems keeping the AVAudioPlayer instance, single, so that different files from multiple cells wouldn't play at the same time. Before fixing that, my remoteControlReceivedWithEvent was working fine and I could manage events on all view controllers.
Fixing that issue now, my player plays only one file at the same time, so if I happen to navigate back to the MasterViewController and then select another row and enter a different cell and start playing that, it will stop the first file and start the second. BUT the problem at the moment is that trying to use the control center while the user is on else where out of the DetailViewController, remoteControlReceivedWithEvent is called, but the value is nil, so nothing happens. This is while I can detect which row is now playing in my cellForRowAtIndexPath: and highlight it on the MasterViewController as follows:
if (entry.audioManager && [entry.audioManager soundPlayer].isPlaying)
{
equalizer.hidden = NO;
[self animateTheEqualizer];
}
However I can't spot that in remoteControlReceivedWithEvent since I don't have any access to the indices.
I have already included canBecomeFirstResponder and so on, and I'm sure that it's only the confusion of the table view understanding which file is currently being played. Everything works fine if I try this in the DetailViewController but not outside it in another VC or even in the background.
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
//if it is a remote control event handle it correctly
if (event.type == UIEventTypeRemoteControl)
{
if (event.subtype == UIEventSubtypeRemoteControlPlay)
{
[entry.audioManager playAudio];
// [[NIKDetailViewController sharedController].feedEntry.audioManager playAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlPause)
{
[entry.audioManager pauseAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause)
{
[entry.audioManager togglePlayPause];
}
else if (event.subtype == UIEventSubtypeRemoteControlBeginSeekingBackward)
{
[entry.audioManager rewindTheAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlBeginSeekingForward)
{
[entry.audioManager fastForwardTheAudio];
}
}
}
and in my AudioManager class:
- (void)playAudio
{
//Play the audio and set the button to represent the audio is playing
if ([soundPlayer isPlaying])
{
[soundPlayer pause];
[self deleteTimer];
}
else
{
[self createTimer];
[soundPlayer play];
}
[self refreshButton];
// [[NIKMasterViewController sharedController] markEntryAsPlaying:detailViewController.feedEntry];
}
- (void)pauseAudio
{
//Pause the audio and set the button to represent the audio is paused
[soundPlayer pause];
[self deleteTimer];
[self refreshButton];
}
Should I markAsNowPlaying manually or is there a delegate method I could use for this to spot the right index and avoid getting nil?
Ok... Problem finally solved! It was something totally different that I needed to do. I'm just using the wrong object entry.audioManager. I was supposed to use currentAudioManager which is an extern declared in AudioManager class. That would keep the currently-played-file and won't be nil.
I've implemented an audio player using AVAudioPlayer (not AVPlayer). I'm able to handle the remote control events with the following method. It works quite alright so far, however I see two more subtypes for these events: UIEventSubtypeRemoteControlEndSeekingForward and UIEventSubtypeRemoteControlEndSeekingBackward.
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
//if it is a remote control event handle it correctly
if (event.type == UIEventTypeRemoteControl)
{
if (event.subtype == UIEventSubtypeRemoteControlPlay)
{
[self playAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlPause)
{
[self pauseAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause)
{
[self togglePlayPause];
}
else if (event.subtype == UIEventSubtypeRemoteControlBeginSeekingBackward)
{
[self rewindTheAudio]; //this method rewinds the audio by 15 seconds.
}
else if (event.subtype == UIEventSubtypeRemoteControlBeginSeekingForward)
{
[self fastForwardTheAudio]; //this method fast-forwards the audio by 15 seconds.
}
}
So the questions:
In order to have things work right, am I supposed to implement those two subtypes, too?
This method only enables the rewind, play/pause, and fast forward buttons on lock screen, but it doesn't display the file title, artwork, and duration. How can I display that info using AVAudioPlayer or AVAudioSession (I don't really want one more library/API to implement this)?
2-a. I discovered MPNowPlayingInfoCenter while searching and I don't know much about it. Do I have to use it to implement those stuff above? :-[
You are correct, MPNowPlayingInfoCenter is the only way to do this. So go ahead and link with MediaPlayer.framework. In the class that handles playing tracks, import <MediaPlayer/MediaPlayer.h>. Whenever your track changes, do this:
NSDictionary *info = #{ MPMediaItemPropertyArtist: artistName,
MPMediaItemPropertyAlbumTitle: albumName,
MPMediaItemPropertyTitle: songName };
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = info;
In my application I have to play audio files stored on a web server. I'm using AVPlayer for it. I have all the play/pause controls and all delegates and observers there which work perfectly fine. On playing small audio files everything works great.
When a long audio file is played it also starts playing fine but after some seconds the AVPlayer pauses the playing (most probably to buffer it). The issue is it doesn't resume on its own again. It keeps in a pause state and if I manually press the play button again it plays smoothly again.
I want to know why AVPlayer doesn't resume automatically and how can I manage to resume the audio again without user pressing the play button again? Thanks.
Yes, it stops because the buffer is empty so it has to wait to load more video. After that you have to manually ask for start again. To solve the problem I followed these steps:
1) Detection: To detect when the player has stopped I use the KVO with the rate property of the value:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"rate"] )
{
if (self.player.rate == 0 && CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime) && self.videoPlaying)
{
[self continuePlaying];
}
}
}
This condition: CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime) is to detect the difference between arriving at the end of the video or stopping in the middle
2) Wait for the video to load - If you continue playing directly you will not have enough buffer to continue playing without interruption. To know when to start you have to observe the value playbackLikelytoKeepUp from the playerItem (here I use a library to observe with blocks but I think it makes the point):
-(void)continuePlaying
{
if (!self.playerItem.playbackLikelyToKeepUp)
{
self.loadingView.hidden = NO;
__weak typeof(self) wSelf = self;
self.playbackLikelyToKeepUpKVOToken = [self.playerItem addObserverForKeyPath:#keypath(_playerItem.playbackLikelyToKeepUp) block:^(id obj, NSDictionary *change) {
__strong typeof(self) sSelf = wSelf;
if(sSelf)
{
if (sSelf.playerItem.playbackLikelyToKeepUp)
{
[sSelf.playerItem removeObserverForKeyPath:#keypath(_playerItem.playbackLikelyToKeepUp) token:self.playbackLikelyToKeepUpKVOToken];
sSelf.playbackLikelyToKeepUpKVOToken = nil;
[sSelf continuePlaying];
}
}
}];
}
And that's it! problem solved
Edit: By the way the library used is libextobjc
I am working with video files, so there's more to my code than you need, but the following solution should pause the player when it hangs, then check every 0.5 second to see whether we've buffered enough to keep up. If so, it restarts the player. If the player hangs for more than 10 seconds without restarting, we stop the player and apologize to the user. This means you need the right observers in place. The code below is working pretty well for me.
properties defined / init'd in a .h file or elsewhere:
AVPlayer *player;
int playerTryCount = -1; // this should get set to 0 when the AVPlayer starts playing
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
partial .m:
- (AVPlayer *)initializePlayerFromURL:(NSURL *)movieURL {
// create AVPlayer
AVPlayerItem *videoItem = [AVPlayerItem playerItemWithURL:movieURL];
AVPlayer *videoPlayer = [AVPlayer playerWithPlayerItem:videoItem];
// add Observers
[videoItem addObserver:self forKeyPath:#"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
[self startNotificationObservers]; // see method below
// I observe a bunch of other stuff, but this is all you need for this to work
return videoPlayer;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// check that all conditions for a stuck player have been met
if ([keyPath isEqualToString:#"playbackLikelyToKeepUp"]) {
if (self.player.currentItem.playbackLikelyToKeepUp == NO &&
CMTIME_COMPARE_INLINE(self.player.currentTime, >, kCMTimeZero) &&
CMTIME_COMPARE_INLINE(self.player.currentTime, !=, self.player.currentItem.duration)) {
// if so, post the playerHanging notification
[self.notificationCenter postNotificationName:PlayerHangingNotification object:self.videoPlayer];
}
}
}
- (void)startNotificationObservers {
[self.notificationCenter addObserver:self
selector:#selector(playerContinue)
name:PlayerContinueNotification
object:nil];
[self.notificationCenter addObserver:self
selector:#selector(playerHanging)
name:PlayerHangingNotification
object:nil];
}
// playerHanging simply decides whether to wait 0.5 seconds or not
// if so, it pauses the player and sends a playerContinue notification
// if not, it puts us out of our misery
- (void)playerHanging {
if (playerTryCount <= 10) {
playerTryCount += 1;
[self.player pause];
// start an activity indicator / busy view
[self.notificationCenter postNotificationName:PlayerContinueNotification object:self.player];
} else { // this code shouldn't actually execute, but I include it as dummyproofing
[self stopPlaying]; // a method where I clean up the AVPlayer,
// which is already paused
// Here's where I'd put up an alertController or alertView
// to say we're sorry but we just can't go on like this anymore
}
}
// playerContinue does the actual waiting and restarting
- (void)playerContinue {
if (CMTIME_COMPARE_INLINE(self.player.currentTime, ==, self.player.currentItem.duration)) { // we've reached the end
[self stopPlaying];
} else if (playerTryCount > 10) // stop trying
[self stopPlaying];
// put up "sorry" alert
} else if (playerTryCount == 0) {
return; // protects against a race condition
} else if (self.player.currentItem.playbackLikelyToKeepUp == YES) {
// Here I stop/remove the activity indicator I put up in playerHanging
playerTryCount = 0;
[self.player play]; // continue from where we left off
} else { // still hanging, not at end
// create a 0.5-second delay to see if buffering catches up
// then post another playerContinue notification to call this method again
// in a manner that attempts to avoid any recursion or threading nightmares
playerTryCount += 1;
double delayInSeconds = 0.5;
dispatch_time_t executeTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(executeTime, dispatch_get_main_queue(), ^{
// test playerTryCount again to protect against changes that might have happened during the 0.5 second delay
if (playerTryCount > 0) {
if (playerTryCount <= 10) {
[self.notificationCenter postNotificationName:PlayerContinueNotification object:self.videoPlayer];
} else {
[self stopPlaying];
// put up "sorry" alert
}
}
});
}
Hope it helps!
Accepted answer gives a possible solution to the problem, but it lacks flexibility, also it's hard to read. Here's more flexible solution.
Add observers:
//_player is instance of AVPlayer
[_player.currentItem addObserver:self forKeyPath:#"status" options:0 context:nil];
[_player addObserver:self forKeyPath:#"rate" options:0 context:nil];
Handler:
-(void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
if ([keyPath isEqualToString:#"status"]) {
if (_player.status == AVPlayerStatusFailed) {
//Possibly show error message or attempt replay from tart
//Description from the docs:
// Indicates that the player can no longer play AVPlayerItem instances because of an error. The error is described by
// the value of the player's error property.
}
}else if ([keyPath isEqualToString:#"rate"]) {
if (_player.rate == 0 && //if player rate dropped to 0
CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, >, kCMTimeZero) && //if video was started
CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, <, _player.currentItem.duration) && //but not yet finished
_isPlaying) { //instance variable to handle overall state (changed to YES when user triggers playback)
[self handleStalled];
}
}
}
Magic:
-(void)handleStalled {
NSLog(#"Handle stalled. Available: %lf", [self availableDuration]);
if (_player.currentItem.playbackLikelyToKeepUp || //
[self availableDuration] - CMTimeGetSeconds(_player.currentItem.currentTime) > 10.0) {
[_player play];
} else {
[self performSelector:#selector(handleStalled) withObject:nil afterDelay:0.5]; //try again
}
}
The "[self availableDuration]" is optional, but you can manually launch playback based on amount of video available. You can change how often the code checks whether enough video is buffered. If you decide to use the optional part, here's the method implementation:
- (NSTimeInterval) availableDuration
{
NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges];
CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue];
Float64 startSeconds = CMTimeGetSeconds(timeRange.start);
Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;
return result;
}
Don't forget the cleanup. Remove observers:
[_player.currentItem removeObserver:self forKeyPath:#"status"];
[_player removeObserver:self forKeyPath:#"rate"];
And possible pending calls to handle stalled video:
[UIView cancelPreviousPerformRequestsWithTarget:self selector:#selector(handleStalled) object:nil];
I had a similar issue.
I had some local files i wanted to play, configured the AVPlayer and called [player play], the player stops at frame 0 and wouldn't play anymore until i called play again manually.
The accepted answer was impossible for me to implement due to faulty explanation, then i just tried delaying the play and magically worked
[self performSelector:#selector(startVideo) withObject:nil afterDelay:0.2];
-(void)startVideo{
[self.videoPlayer play];
}
For web videos i also had the problem, i solve it using wallace's answer.
When creating the AVPlayer add an observer:
[self.videoItem addObserver:self forKeyPath:#"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// check that all conditions for a stuck player have been met
if ([keyPath isEqualToString:#"playbackLikelyToKeepUp"]) {
if (self.videoPlayer.currentItem.playbackLikelyToKeepUp == NO &&
CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, >, kCMTimeZero) &&
CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, !=, self.videoPlayer.currentItem.duration)) {
NSLog(#"hanged");
[self performSelector:#selector(startVideo) withObject:nil afterDelay:0.2];
}
}
}
Remember to remove observer before dismissing the view
[self.videoItem removeObserver:self forKeyPath:#"playbackLikelyToKeepUp"]
I think use AVPlayerItemPlaybackStalledNotification to detect the stalled is a better way.
First I observe for playback stalling
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerStalled),
name: AVPlayerItemPlaybackStalledNotification, object: videoPlayer.currentItem)
Then I force playback continuation
func playerStalled(note: NSNotification) {
let playerItem = note.object as! AVPlayerItem
if let player = playerItem.valueForKey("player") as? AVPlayer{
player.play()
}
}
This is probably not the best way of doing it, but I'm using it until I find something better :)
I also ran into this issue as described here
I tested this answer below multiple times and it worked every time so far.
Here is what I came up with for the Swift 5 version of #wallace's answer.
1- instead of observing the keyPath "playbackLikelyToKeepUp" I use the .AVPlayerItemPlaybackStalled Notification and inside there I check to see if the buffer is full or not via if !playerItem.isPlaybackLikelyToKeepUp {...}
2- instead of using his PlayerHangingNotification I use a function named playerIsHanging()
3- instead of using his PlayerContinueNotification I use a function named checkPlayerTryCount()
4- and inside checkPlayerTryCount() I do everything the same as his (void)playerContinue function except when I ran into } else if playerTryCount == 0 { nothing would happen. To avoid that I added 2 lines of code above the return statement
5- like #PranoyC suggested under #wallace's comments I set the playerTryCount to a max of 20 instead of 10. I also set it as a class property let playerTryCountMaxLimit = 20
You have to add/remove your activity indicator/spinner where the comment suggests to do so
code:
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemPlaybackStalled(_:)),
name: NSNotification.Name.AVPlayerItemPlaybackStalled,
object: playerItem)
#objc func playerItemPlaybackStalled(_ notification: Notification) {
// The system may post this notification on a thread other than the one used to registered the observer: https://developer.apple.com/documentation/foundation/nsnotification/name/1387661-avplayeritemplaybackstalled
guard let playerItem = notification.object as? AVPlayerItem else { return }
// playerItem.isPlaybackLikelyToKeepUp == false && if the player's current time is greater than zero && the player's current time is not equal to the player's duration
if (!playerItem.isPlaybackLikelyToKeepUp) && (CMTimeCompare(playerItem.currentTime(), .zero) == 1) && (CMTimeCompare(playerItem.currentTime(), playerItem.duration) != 0) {
DispatchQueue.main.async { [weak self] in
self?.playerIsHanging()
}
}
}
var playerTryCount = -1 // this should get set to 0 when the AVPlayer starts playing
let playerTryCountMaxLimit = 20
func playerIsHanging() {
if playerTryCount <= playerTryCountMaxLimit {
playerTryCount += 1
// show spinner
checkPlayerTryCount()
} else {
// show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
print("1.-----> PROBLEM")
}
}
func checkPlayerTryCount() {
guard let player = player, let playerItem = player.currentItem else { return }
// if the player's current time is equal to the player's duration
if CMTimeCompare(playerItem.currentTime(), playerItem.duration) == 0 {
// show spinner or better yet remove spinner and show a replayButton or auto rewind to the beginning ***BE SURE TO RESET playerTryCount = 0 ***
} else if playerTryCount > playerTryCountMaxLimit {
// show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
print("2.-----> PROBLEM")
} else if playerTryCount == 0 {
// *** in his answer he has nothing but a return statement here but when it would hit this condition nothing would happen. I had to add these 2 lines of code for it to continue ***
playerTryCount += 1
retryCheckPlayerTryCountAgain()
return // protects against a race condition
} else if playerItem.isPlaybackLikelyToKeepUp {
// remove spinner and reset playerTryCount to zero
playerTryCount = 0
player?.play()
} else { // still hanging, not at end
playerTryCount += 1
/*
create a 0.5-second delay using .asyncAfter to see if buffering catches up
then call retryCheckPlayerTryCountAgain() in a manner that attempts to avoid any recursion or threading nightmares
*/
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
DispatchQueue.main.async { [weak self] in
// test playerTryCount again to protect against changes that might have happened during the 0.5 second delay
if self!.playerTryCount > 0 {
if self!.playerTryCount <= self!.playerTryCountMaxLimit {
self!.retryCheckPlayerTryCountAgain()
} else {
// show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
print("3.-----> PROBLEM")
}
}
}
}
}
}
func retryCheckPlayerTryCountAgain() {
checkPlayerTryCount()
}
in very bad network playbackLikelyToKeepUp most probably to be false.
use kvo to observe playbackBufferEmpty is better, more sensitive to if existing buffer data that can be used for playback .if the value change to true you can call play method to continue playback.
In my case,
I was trying to record video with imagePickerController and playback recorded video with AVPlayerController. But it starts playing video and gets stops after 1 second. Somehow it gets time to save video and if you playback it immediately, it won't play.
So solution is ,
call play video after 0.5 seconds (delay). like below
-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info {
[self performSelector:#selector(playVideo) withObject:self
afterDelay:0.5];
}
-(void) playVideo {
self.avPlayerViewController = [[AVPlayerViewController alloc] init];
if(self.avPlayerViewController != nil)
{
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithURL:Vpath];
AVPlayer* player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
self.avPlayerViewController.player = player;
self.avPlayerViewController.showsPlaybackControls = NO;
[self.avPlayerViewController setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[self.avPlayerViewController.view setFrame:[[UIScreen mainScreen] bounds]];
self.avPlayerViewController.view.clipsToBounds = YES;
self.avPlayerViewController.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerDidFinishPlaying) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
[self.viewVideoHolder addSubview:self.avPlayerViewController.view];
[self.avPlayerViewController.player play];
}
}
-(void) playerDidFinishPlaying
{
[avPlayer pause];
}
swift ios
I have an iPhone app (5.1 SDK) which plays audio in the background. Many times when there is an incoming call, the incoming call is displayed but the user cannot slide the slider to answer it and the call is missed.
I am using - (void)beginInterruption to pause all audio when an incoming call arrives but it doesn't seem to stop this issue from occurring.
Has anyone ever encountered this before?
In case you wanna mark this as the answer since it was indeed your problem I'll repost my comment.
Is your beginInterruption code being called? If so, is the music actually stopping? Are you streaming audio over the network or local files? What player are you using?
- (void) beginInterruption {
if (playing) {
playing = NO;
interruptedWhilePlaying = YES;
[self updateUserInterface];
}
}
NSError *activationError = nil;
- (void) endInterruption {
if (interruptedWhilePlaying) {
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if (!success) { /* handle the error in activationError */ }
[player play];
playing = YES;
interruptedWhilePlaying = NO;
[self updateUserInterface];
}
}