In my application, I have one audio file. I want pause or stop the audio by clicking the button. I have tried with some code, but it still plays the audio. Please tell how to pause the audio or stop it by clicking the button.
Stop code.
- (IBAction)back:(id)sender {
[self.audioPlayer isPlaying];
[self.audioPlayer stop];
}
I have used the above to stop the playing audio but its not working.
where am I doing wrong? How to stop the audio?
Stopping Audio is easy just you need to take 2 Action Method for 2 separate buttons 1->Play and 2->Stop then code is as fallows
-(IBAction)Play:(id)sender
{
[self.audioPlayer play];
}
-(IBAction)Stop:(id)sender
{
[self.audioPlayer stop];
}
Another way is to use BOOL and use the 1 Action method to play and stop like this
First declare BOOl object
BOOL AudioBool;
-(IBAction)Audio:(id)sender
{
if(AudioBool == YES)
{
[self.audioPlayer play];
AudioBool = NO;
}
else
{
[self.audioPlayer stop];
AudioBool = YES;
}
}
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 am starting to get convinced that iOS is just pure magic. I looked through numerous posts on AVAudioPlayer for solutions, but none worked.
When the application starts I have a movie starting off from MPMoviePlayerController and I have AVAudioPlayer kicking off with a sound as follows:
-(void)createAndPlayMovieFromURL:(NSURL *)movieURL sourceType:(MPMovieSourceType)sourceType{
/* Create a new movie player object. */
player = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
player.controlStyle = MPMovieControlModeDefault;
if (player)
{
/* Save the movie object. */
[self setMoviePlayerController:player];
/* Specify the URL that points to the movie file. */
[player setContentURL:movieURL];
/* If you specify the movie type before playing the movie it can result
in faster load times. */
[player setMovieSourceType:sourceType];
CGRect frame = CGRectMake(0, 0, 480, 320);
/* Inset the movie frame in the parent view frame. */
[[player view] setFrame:frame];
[player view].backgroundColor = [UIColor blackColor];
/* To present a movie in your application, incorporate the view contained
in a movie player’s view property into your application’s view hierarchy.
Be sure to size the frame correctly. */
[self.view addSubview: [player view]];
}
[[self moviePlayerController] play];
}
and for sound:
- (void)setupAudioPlayBack:(NSString *) path : (NSString *) extension{
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:path
ofType:extension]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:url
error:&error];
if (error)
{
NSLog(#"Error in audioPlayer: %#",
[error localizedDescription]);
} else {
audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
}
}
And now lets talk magic. When the first movie plays and the sound, the sound is not responding to either the volume controls on the phone nor to Silent Mode On switch.
But then immediately a second movie plays with its own sound and everything works fine - there is no sound with Silent Mode On, if Silent Mode Off it responds to Volume Controls. 3rd movie - plays perfectly too.
I tried using the player.useApplicationAudioSession = NO for MPMoviePlayerController - fixed the problem with the first time sound not responding to Silent Mode or Volume Controls, but now when the second movie starts to play it freezes instantly and the sound played is cut from the original size to something around 2 seconds.
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategorySoloAmbient error:nil]; didn't help either wherever I set it.
So my question is - how to make the MPMoviePlayerController interact with AVAudioPlayer without any problems...
Ok I actually found a solution (once again, magic).
When you start your ViewController you set it up as follows:
- (void)viewDidLoad
{
[self setupAudioPlayBack:#"sound1" :#"caf"];
[self playMovieFile:[self localMovieURL:#"mov1" :#"mov"]];
audioTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(playAudio) userInfo:nil repeats:NO];
[super viewDidLoad];
}
Where the playAudio method is simply:
- (void)playAudio{
[audioPlayer play];
}
Just 0.1 millisecond timer is enough to make everything work (0.0 doesn't work) and make sure a new video doesn't start before the audio finishes or it will destroy the settings so you will have to invalidate the timer and redo the timer again.
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];
}
}
I am using AVPlayer to play a live stream from the Internet using the following code :
NSString *u = #"http://192.192.192.192:8036";
NSURL *url = [NSURL URLWithString:u];
radiosound = [[AVPlayer alloc] initWithURL:url];
[radiosound play];
And I have one button play :
[radiosound play];
and Pause :
[radiosound pause];
My issue is that I want to use only one button Play/Pause, but when I am using this code
if (radiosound.isPlaying) {
[radiosound pause];
} else {
[radiosound play];
}
My app crashes, because AVPlayer doesn´t recognize "isPlaying".
Any tips?
AVPlayer doesn't have an isPlaying property. Use the rate property (0.0 means stopped, 1.0 playing).
if (radiosound.rate == 1.0) {
[radiosound pause];
} else {
[radiosound play];
}
You can look at the AVPlayer class reference here.
After some research I found out that when there is no network connection, AVPlayer still sets the rate to 1.0 after receiving a -play message.
Thus, I also check for the currentItem and modified my method like that:
-(BOOL)isPlaying
{
if (self.player.currentItem && self.player.rate != 0)
{
return YES;
}
return NO;
}
Please share your opinion if you think something is wrong with this approach.