AVQueuePlayer unable to play local MP3 files - ios

I'm currently writing an iOS app that allows users to stream music from soundcloud. Currently I am able to stream just fine from Soundcloud, in booth forefront and background app states. I have an issue when I download an MP3 files and attempt to play it and have continuously playback when the app in in the background, more specifically in the locked screen or the screen is off.
When I play a downloaded file and lock the screen, the audio continues to play for a while. Usually it plays for 2 -3 mins. After that playback will stop and any other downloaded mp3 files in the playlist will not playback until the user returns to the app. Items in the playlist that are not downloaded will playback perfectly if the user has an internet connection, regardless if a downloaded item failed to play previously.
There are times when I receive the following error:
AVPlayerItemStatusFailed: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not becompleted" UserInfo=0x170270500 {NSUnderlyingError=0x170059380 "The operation couldn’t be completed. Operation not permitted", NSLocalizedFailureReason=An unknown error occurred (1), NSLocalizedDescription=The operation could not be completed}
I will additionally get the following notification from the AVPlayerItem
Notification: NSConcreteNotification 0x1700538c0 {name = AVPlayerItemDidPlayToEndTimeNotification; object = AVPlayerItem: 0x178009300, asset = AVURLAsset: 0x17822aaa0, URL = file:///var/mobile/Applications/51118E74-3334-4EFC-B148-B485DE675F9E/Documents/Downloads/SC_165903784.mp3}
This notification is received when the first item that is playing stop playing. It doesn't make sense that I get this notification when it doesn't finish playing to end of its duration.
My guess is that because the app is in background mode there is a limited time set for reading files, but I somehow doubt that.
I'm using the following to create a AVPlayerItem from a Local file.
NSURL* url = [NSURL fileURLWithPath:song.downloadFilePath];
[AVPlayerItem playerItemWithURL:url];
I have tried building my own AVQueuePlayer and also using iOS Hysteria Player, but both instances have given me the same bug for offline download playback.
Any insights or solutions are greatly appreciated.
Thanks!

Are you playing your files from the Documents directory? (Or anywhere besides the app bundle?)
If so, be sure to save the files to the directory using
[fileToSave writeToFile:filePath options:NSDataWritingFileProtectionNone error:nil];.
The NSDataWritingFileProtectionNone is the critical bit! What's going on, at least in my case, is this: iOS is trying to be secure, and so is by default enabling file protection on the files you add to the Documents directory (and also the other non-app-bundle directories as well, I believe). By adding NSDataWritingFileProtectionNone, you're requesting the system to no longer enable this protection on your files.
From Apple's iOS Documentation:
If you protect a file, your app must be prepared to lose access to
that file. When complete file protection is enabled, your app loses
the ability to read and write the file’s contents when the user locks
the device.
Now, as soon as I read that, I was almost positive that this was the cause for my app not being able to continue playback once the device was locked. For security's sake, iOS seems to default to automatically putting files under protection… but this keeps everything, including your app, from being able to access it while the device is locked. My bug was that it only played part of the song as soon as the device was locked, and that makes sense… since its access to the actual song file was revoked, it was only able to play the part of the song that had already been buffered into RAM!
This was the fix for a month-long problem I've been pulling my hair out to try to solve. I sincerely hope it fixes the issue for you too.

I believe this issue is caused by the limitation of AVQueuePlayer, and HysteriaPlayer using AVQueuePlayer as core player so that's why.
I've seen AVQueuePlayer document mentioned (I can't find it unfortunately) that it can't handle the queue mixed with local and remote audios.
There's some workarounds:
Use AVPlayer
Don't use AVQueuePlayer, handle the queue by yourself and feed it to AVPlayer.
Separate two AVQueuePlayer
One for local media, one for remote media. Insert and use PlayerItem and Player properly.
I would patch HysteriaPlayer to meet this requirement a month later, you can come back that time.

Related

Any reasons why AVQueuePlayer would sometimes work/sometimes not?

I'm creating a video player view where I download URLs from the Internet to play using an AVQueuePlayer. It generally works pretty fine except sometimes I'll get the play icon slashed out and the AVQueuePlayer breaks.
I'm not sure what triggers this broken AVQueuePlayer state.
I create new AVQueuePlayers on viewappear and on viewdisappear I run these lines on the player
ViewAppear
if (self.player == nil){
self.player = AVQueuePlayer()
}
ViewDisappear
self.player.pause()
self.player.replaceCurrentItemWithPlayerItem(nil)
The main thing I'm curious of are there any potential reasons why a AVQueuePlayer will sometimes work and sometimes not? (It'll stop working after I trigger viewAppear, viewDisappear, viewAppear, etc. a few times)
Checking the network requests, when I do get the play button of death, no videos are streamed at all (0 network activity).
Potential reasons:
1)I'm thinking it might have something to do with the number of AVPlayers instantiated and I'm just not removing them correctly?
2) I also build the app on my phone a lot and close it while running it. Maybe this breaks the phone's avplayer over time?
Any directions for this type of behavior are appreciated.
EDIT: Interestingly enough, once in a while, it breaks so hard that I break every single app that plays video. Snapchat and Youtube don't work either after a certain point. I'm not sure how I broke every single player.
UPDATE: Getting this generic error for BOTH the AVPlayer & AVPlayerItem
Optional(Error Domain=AVFoundationErrorDomain Code=-11819 "Cannot Complete Action" UserInfo={NSLocalizedDescription=Cannot Complete Action, NSLocalizedRecoverySuggestion=Try again later.})

Can not play sound in background objective c

I have a problem in playing audio file from URL in the background while the iPhone is locked.
my app plays podcasts, but while it is playing the podcast at specific second the podcast will stop and an ad will play, the ad consist of three audio tracks, first and last are the messages "please wait, ad will play" and "thanks, your track will complete now", and the middle track is the ad it self from URL.
I tried to make two AVPlayers one for track and the other for the ad, and I tried to make four players for each track, the process works well if the app is running, but when the iPhone is locked, just the first message starts, and nothing happen after.
any suggestions !!
Finally I found the solution of my problem,
When I save the ad file I use [file writeToFile: atomically:], but the app can't reach the document directory when the device locked for a long time unless the file is in the noneProtection mode, so I use [file writeToFile:filePath options:NSDataWritingFileProtectionNone error:nil];
It Works :)

PhoneGap/Cordova iOS playing sound disables microphone

I am currently working on a PhoneGap application that, upon pressing a button, is supposed to play an 8 seconds long sound clip, while at the same time streaming sound from the microphone over RTMP to a Wowza server through a Cordova plugin using the iOS library VideoCore.
My problem is that (on iOS exclusively) when the sound clip stops playing, the microphone - for some reason - also stops recording sound. However, the stream is still active, resulting in a sound clip on the server side consisting of 8 seconds of microphone input, then complete silence.
Commenting out the line that plays the sound results in the microphone recoding sound without a problem, however we need to be able to play the sound.
Defining the media variable:
_alarmSound = new Media("Audio/alarm.mp3")
Playing the sound and starting the stream:
if(_streamAudio){
startAudioStream(_alarmId);
}
if(localStorage.getItem("alarmSound") == "true"){
_alarmSound.play();
}
It seems to me like there is some kind of internal resource usage conflict occuring when PhoneGap stops playing the sound clip, however I have no idea what I can do to fix it.
I've encountered the same problem on iOS, and I solved it by doing two things:
AVAudioSession Category
In iOS, apps should specify their requirements on the sound resource using the singleton AVAudioSession. When using Cordova there is a plugin that enables you to do this: https://github.com/eworx/av-audio-session-adapter
So for example, when you want to just play sounds you set the category to PLAYBACK:
audioSession.setCategoryWithOptions(
AVAudioSessionAdapter.Categories.PLAYBACK,
AVAudioSessionAdapter.CategoryOptions.MIX_WITH_OTHERS,
successCallback,
errorCallback
);
And when you want to record sound using the microphone and still be able to playback sounds you set the category as PLAY_AND_RECORD:
audioSession.setCategoryWithOptions(
AVAudioSessionAdapter.Categories.PLAY_AND_RECORD,
AVAudioSessionAdapter.CategoryOptions.MIX_WITH_OTHERS,
successCallback,
errorCallback
);
cordova-plugin-media kills the AVAudioSession
The Media plugin that you're using for playback handles both recording and playback in a way that makes it impossible to combine with other sound plugins and the Web Audio API. It deactivates the AVAudioSession each time it has finished playback or recording. Since there is only one such session for your app, this effectively deactivates all sound in your app.
There is a bug registered for this: https://issues.apache.org/jira/browse/CB-11026
Since the bug is still not fixed, and you still want to use the Media plugin, the only way to fix this is to download the plugin code and comment out/remove the lines where the AVAudioSession is deactivated:
[self.avSession setActive:NO error:nil];
I found it took some effort to get the existing answers on this topic working with my code so I'm hoping this helps someone who needs a more explicit solution.
As of version 5.0.2 of the Cordova Media plugin this issue still persists. If you want to use this plugin, the most straightforward solution that I have found is to fork the plugin repository and make the following changes to src/ios/CDVSound.m. After quite some hours looking into this, I was unable to find a working solution without modifying the plugin source, nor was I able to find suitable alternative plugins.
Force session category
The author of the plugin mentioned in Edin's answer explains the issue with the AVAudioSession categories on a similar question. Since changes to CDVSound.m are necessary regardless, I found the following change easier than getting the eworx/av-audio-session-adapter plugin working.
Locate the following lines of code:
NSString* sessionCategory = bPlayAudioWhenScreenIsLocked ? AVAudioSessionCategoryPlayback : AVAudioSessionCategorySoloAmbient;
[self.avSession setCategory:sessionCategory error:&err];
Replace them with:
[self.avSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err];
Prevent session deactivation
Further more, the changes suggested in Edin's answer are necessary to prevent deactivation of additional audio sessions, however, not all instances need to be removed.
Remove the following line and surrounding conditional statements from the functions audioRecorderDidFinishRecording(), audioPlayerDidFinishPlaying and itemDidFinishPlaying():
[self.avSession setActive:NO error:nil];
The other instances of the code are used for error handling and resource release and, in my experience, do not need to be and should not be removed.
Update: Playback Volume
After apply these changes we experienced issues with low audio volume during playback (while recording was active). Adding the following lines after the respective setCategory lines from above allowed for playback at full volume. These changes are explained in the answer from which this fix was sourced.
[self.avSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
and
[weakSelf.avSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];

AVAudioSessionDelegate called at endInterruption, but beginInterruption not called

I'm setting up an AVAudioSession when the app launches and setting the delegate to the appDelegate. Everything seems to be working (playback, etc) except that beginInterruption on the delegate is not being called when the phone receives a call. When the call ends endInterruption is being called though.
The only thought I have is that the audio player code I'm using used to be based on AVAudioPlayer, but is now using AVPlayer. The callbacks for the AVAudioPlayer delegate for handling interrupts are still in there, but it seems odd that they would conflict in any way.
Looking at the header, in iOS6, it looks like AVAudioSessionDelegate is now deprecated.
Use AVAudioSessionInterruptionNotification instead in iOS6.
Update: That didn't work. I think there's a bug in the framework.
Yes, in my experience, beginInterruption, nor the newly documented AVAudioSessionInterruptionNotification work properly. What I had to do was track the status of the player using a local flag, then handle the endInterruption:withFlags: method in order to track recovery from interruptions.
With iOS 6, the resuming from an interruption will at least keep your AudioPlayer in the right place, so there was no need for me to store the last known play time of my AVAudioPlayer, I simply had to hit play.
Here's the solution that I came up with. It seems like iOS 6 kills your audio with a Media Reset if an AVPlayer stays resident too long. What ends up happening, is the AVPlayer plays, but no sound comes out. The rate on the AVPlayer is 1, but there's absolutely no sound. To add pain to the situation, there's no error on either the AVAudioSession setActive, nor the AVPlayer itself that indicates that there's a problem.
Add to the fact that you can't depend on appWillResignActive, because your app may already be in the background if you're depending on remote control gestures at all.
The final solution I implemented was to add a periodic observer on the AVPlayer, and record the last known time. When I receive the event that I've been given back control, I create a new AVPlayer, load it with the AVPlayerItem, and seekToTime to the proper time.
It's quite an annoying workaround, but at least it works, and avoids the periodic crashes that were happening.
I can confirm that using the C api, the interruption method is also not called when the interruption begins; only when it ends
(AudioSessionInitialize (nil, nil, interruptionListenerCallback, (__bridge void *)(self));
I've also filed a bug report with apple for the issue.
Edit: This is fixed in iOS 6.1 (but not iOS 6.0.1)
Just call:
[[AVAudioSession sharedInstance] setDelegate: self];
I just checked on my iPhone 5 (running iOS 6.0) by setting a breakpoint in the AudioSessionInterruptionListener callback function that was declared in AudioSessionInitialize(), and this interrupt callback does, in fact, get called when the app has an active audio session and audio unit and is interrupted with an incoming phone call (Xcode shows the app stopped at the breakpoint at the beginning of the interruption, which I then continue from).
I have the app then stop its audio unit and de-activate its audio session. Then, on the end interruption callback, the app re-activates the audio session and restarts the audio unit without problems (the app is recording audio properly afterwards).
I built a brand new audio streaming (AVPlayer) application atop iOS 6.0.x and found the same problem.
Delegates are now deprecated and we have to use notifications, that's great, however here's my findings:
During an incoming phone call I get only AVAudioSessionInterruptionTypeEnded in my handler, along with AVAudioSessionInterruptionOptionShouldResume. Audio session gets suspended automatically (audio fades) and I just need to resume playback of AVPlayer.
However when attempting to launch a game, such as CSR Racing, I oddly get the dreaded AVAudioSessionInterruptionTypeBegan but no sign when my application can resume playback, not even killing the game.
Now, this may depend on other factors, such as my audio category (in my case AVAudioSessionCategoryPlayback) and the mixing settings of both applications (kAudioSessionProperty_OverrideCategoryMixWithOthers), I'm not sure, but definitely I see something out of place.
Hopefully others reported that on 6.1beta this is fixed and I yet have to upgrade, so we'll see.

Playing Movies from ITunes using MPMoviePlayerController?

I'm trying to play a movie acquired from iTunes. The movie was placed in my sandbox via File Sharing/iTunes Transfer. The application is using MPMoviePlayerController or MPMoviePlayerViewController. I can use either, but neither have work properly on an iTunes movie.
Below is what I am observing:
Test Movie
Acquired from Hillegass (Chapter 20) - OK [1]
Acquired from Apple's sample - OK [2]
Apple Video app - OK
iTunes Movie
No video
No audio
Silent failure in my application [3]
Silent failure in Hillegass' sample [3]
Silent failure in Apple's sample [3]
Apple Video app - OK
When the view is launched, I get a black screen and the PLAY button is available. Tapping PLAY causes the player to attempt the operation, but it fails (?) and the PLAY button is shown again.
Checking the state of the movie controller shows it is Ready. When PLAY is tapped, the state goes to Playing and then immediately to paused. Trying to PLAY again repeats the cycle.
During the Ready → Playing → Paused transitions, I do not get a finished notification, and I do not get an error from other notifications.
I thought orientation might be the issue, but rotating or changing the orientation mode crashes Xcode.
Apple's documentation does not mention anything special for iTunes movies, protected content, or DRM (perhaps I missed a relevant document). It seems like a moot point, since the movie was purchased from iTunes, the device is authorized (all under the same account), and I am the licensee.
For what its worth, NSZombieEnabled is YES, so I don't believe I am smashing memory (memory errors are probably not present in Apple's and Hillegass' samples).
Any ideas on what I might be doing wrong or how I should play an iTunes movie I purchased?
References
[1] http://www.bignerdranch.com/book/iphone_programming_the_big_nerd_ranch_guide
[2] http://developer.apple.com/library/ios/#samplecode/MoviePlayer_iPhone/Introduction/Intro.html
[3] http://www.apple.com/itunes/charts/tv-shows/the-simpsons/lost-verizon/
The DRM protection is the problem.
Apple does not allow playing DRM protected music or videos from iTunes in the MPMoviePlayerController (which includes the previews).
Please also see following link on Stackoverflow
Regarding the crash: You may post some code.

Resources