I'm building an application that uses an AVQueuePlayer to stream audio. I have my AVAudioSession set up with the category AVAudioSessionCategoryPlayback and I'm able to receive events from remoteControlReceivedWithEvent:. However, when I pause my audio session, the icon in the status bar does not disappear, nor does the pause/play button in the App Switcher change to play. The audio pauses, but the icons never change. Do you know why this might be happening?
I also use an MPMusicPlayerController to play music from the iPod music player. Both players are always initialized, however only one plays at a time. When the app opens, if I play something from the MPMusicPlayerController first, the AVQueuePlayer doesn't have this pause conflict anymore. It only has this problem when you play from the AVQueuePlayer first. I included the code to show you what I'm doing.
FYI- My MPMusicController is self.musicPlayer. My AVQueuePlayer is self.radioPlayer. After pausing, the status of my AVQueuePlayer is AVPlayerStatusReadyToPlay.
- (void)viewDidLoad {
[super viewDidLoad];
self.musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
self.musicPlayer.nowPlayingItem = self.currentSong;
self.musicPlayer.shuffleMode = MPMusicShuffleModeSongs;
self.musicPlayer.repeatMode = MPMusicRepeatModeAll;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:nil error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(radioItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[_radioPlayer currentItem]];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated
{
[self resignFirstResponder];
if (self.source == LIBRARY)
{
[self.parentDelgate reloadInformation];
}
}
- (void)viewDidAppear:(BOOL)animated
{
if (self.source == RADIO)
{
// Code that sets the URLs for the AVQueuePlayer
[self initializeMusicPlayer];
[self prepareRadioPlayer];
}
else if ( self.source == LIBRARY ) {
if ( self.currentSong != self.musicPlayer.nowPlayingItem || self.playlist != nil)
{
[self initializeMusicPlayer];
self.musicPlayer.nowPlayingItem = self.currentSong;
}
}
}
- (void)initializeMusicPlayer {
if ( self.source == LIBRARY )
{
[self.musicPlayer setQueueWithItemCollection:[MPMediaItemCollection collectionWithItems:(NSArray *)self.tracks]];
if ([self.radioPlayer rate] != 0.0)
[self.radioPlayer pause];
[self.musicPlayer play];
self.status = PLAYING;
}
else if ( self.source == RADIO )
{
if ( self.musicPlayer.playbackState == MPMusicPlaybackStatePlaying )
[self.musicPlayer pause];
}
}
- (IBAction)pausePressed:(UIButton *)sender {
if ( self.source == LIBRARY )
{
[self.musicPlayer pause];
}
else if (self.source == RADIO)
{
[self.radioPlayer pause];
}
[self onStatePaused];
}
- (void)onStatePaused {
// UI Stuff
}
- (void)endSession {
[self.musicPlayer stop];
[self.radioPlayer pause];
[self.radioPlayer removeAllItems];
// Present different view
}
Any ideas?
I figured out the problem. I was adding multiple instances of observers to the AVQueuePlayer/AVPlayerItems which was interfering with this.
Related
I play audio using AVPlayer in the background and foreground from AppDelegate.After adding observer to check the play, a crash occurs when app goes to foreground from background while audio is playing
Crash : "an instance of class AVPlayerItem was deallocated while key value observers were still registered with it"
Many threads explains to remove observer so that we could avoid the crash.But thats in the case of moving to different viewControllers.I want to keep the audio playing in foreground and background.
Can someone help me to solve this?
This is my code:
case AudioLocation:{
audioUrl = [NSURL URLWithString:[responseDictionary
objectForKey:#"result"]];
if(!([[[NSUserDefaults
standardUserDefaults]objectForKey:#"audioURL"] isEqual:
[responseDictionary objectForKey:#"result"]]))
{
playerItem = [AVPlayerItem playerItemWithURL:audioUrl];
player = [AVPlayer playerWithPlayerItem:playerItem];
player = [AVPlayer playerWithURL:audioUrl];
player.automaticallyWaitsToMinimizeStalling = false;
[player.currentItem addObserver:self forKeyPath:#"status"
options:0 context:nil];
[player addObserver:self forKeyPath:#"rate" options:0
context:nil];
[player play];
isPlaying = YES;
}
-(void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
if ([keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusFailed) {
}
}else if ([keyPath isEqualToString:#"rate"]) {
if (player.rate == 0 && //if player rate dropped to 0
CMTIME_COMPARE_INLINE(player.currentItem.currentTime, >,
kCMTimeZero) &&
CMTIME_COMPARE_INLINE(player.currentItem.currentTime, <,
player.currentItem.duration) &&isPlaying)
[self handleStalled];
}
}
-(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
}
}
- (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;
}
For playing Video I am using MPMoviePlayerController,my app supports only portrait mode but For playing Video I need to support Landscape too. Every thing is working fine till iOS 7.x. In iOS 8.0, After exiting Full screen there remains a navigation bar. Please refer below image.
In App delegate, under supportedInterfaceOrientationsForWindow, following is used.
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (self.forceLandscapeRight) {
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskPortrait;
}
Noe in my controller class for initiating MPMoviePlayerController:
[self.mpMoviePlayerContr setContentURL:object];
[theMoviPlayer prepareToPlay];
self.mpMoviePlayerContr.controlStyle = MPMovieControlStyleEmbedded;
self.mpMoviePlayerContr.scalingMode = MPMovieScalingModeAspectFit;
[self.mpMoviePlayerContr play];
self.mpMoviePlayerContr.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
self.mpMoviePlayerContr.shouldAutoplay = YES;
[theMoviPlayer setFullscreen:false animated:YES];
Also, I have some notification for entering and exiting Full screen.
- (void) moviePlayerWillEnterFullscreenNotification:(NSNotification*)notification {
ED_APPDelegate.forceLandscapeRight = YES;
[self.mpMoviePlayerContr setFullscreen:YES animated:YES];
[self.mpMoviePlayerContr play];
}
- (void) moviePlayerWillExitFullscreenNotification:(NSNotification*)notification {
ED_APPDelegate.forceLandscapeRight = NO;
[self.mpMoviePlayerContr setFullscreen:NO animated:YES];
[self.mpMoviePlayerContr play];
}
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
ED_APPDelegate.forceLandscapeRight = NO;
if (currentOrientation == UIDeviceOrientationLandscapeLeft ||currentOrientation == UIDeviceOrientationLandscapeRight ) {
[self.mpMoviePlayerContr setFullscreen:YES animated:YES];
[self.mpMoviePlayerContr play];
ED_APPDelegate.forceLandscapeRight = YES;
} else {
if (self.mpMoviePlayerContr.playbackState == MPMoviePlaybackStatePaused) {
[self.mpMoviePlayerContr play];
}
[self.mpMoviePlayerContr setFullscreen:NO animated:YES];
ED_APPDelegate.forceLandscapeRight = NO;
}
}
Above code is working fine in iOS 7.x, but have issue in iOS 8.0.
I use a MPMoviePlayerController to play a video from Internet.
player = [player initWithContentURL:[NSURL URLWithString:videoURL]];
player.view.frame = CGRectMake(0, 0, videoView.frame.size.width, videoView.frame.size.height - 20);
[player setControlStyle:MPMovieControlStyleEmbedded];
player.scalingMode = MPMovieScalingModeAspectFit;
[player prepareToPlay];
player.shouldAutoplay = NO;
[videoView addSubview:player.view];
I notified that after I clicked the full screen button (2-arrows-button), I was navigated to the full size video screen. I couldn't restore down the screen by touching the Done button. I even used NSNotification but can't resolve the problem. Here is the Notification code:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieEventFullscreenHandler:)
name:MPMoviePlayerWillEnterFullscreenNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieEventFullscreenHandler:)
name:MPMoviePlayerDidEnterFullscreenNotification
object:nil];
}
- (void)movieEventFullscreenHandler:(NSNotification*)notification {
[player setFullscreen:NO animated:NO];
[player setControlStyle:MPMovieControlStyleEmbedded];
}
How can I dismiss that video screen by touching the Done button? Thanks guys.
You can use the notification of MPMoviePlayerPlaybackDidFinishNotification to observe the done button.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playObserver:)name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
and then,make a judge of quitting.
- (void) playObserver:(NSNotification *)notification
{
MPMoviePlayerController* player = moviePlayerView.moviePlayer;
if (player == [notification object]) {
if (_invalidVideoCount > MOVIE_TRY_TIMES) {
[self dismissViewController];
}
_invalidVideoCount++;
int reason = [[[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
//Whether continuous playback
if (![SINGLETON_CALL(SystemInfoManager) boolValueForKey:UserContinuousPlayEnableKey]) {
[self playFinishWithForce:YES];
return;
}
switch (reason) {
case MPMovieFinishReasonUserExited:
[self playFinishWithForce:YES];
break;
case MPMovieFinishReasonPlaybackError:
[self playFinishWithForce:YES];
break;
case MPMovieFinishReasonPlaybackEnded:
movieTryTimes = 0;
[self playFinishWithForce:NO];
break;
default:
break;
}
}
}
and the last.
- (void)playFinishWithForce:(BOOL)force
{
FileInfoItem *item = ARRAY_OBJECT_AT_INDEX(_playlist, _currentIndex);
BOOL quit = force || !item;
if (quit) {
[self dismissViewController];
} else {
[self playMovieWithItem:item];
}
}
Also you can use the notification of MPMoviePlayerPlaybackStateDidChangeNotification to make any observe.something detail see MPMoviePlayerController.h or https://developer.apple.com/library/ios/documentation/MediaPlayer/Reference/MPMoviePlayerController_Class/Reference/Reference.html
I have found the problem. That's I laid [player stop] in the viewWillDisAppear so can't handle the notification. I fixed temporary by changing it to [player pause]. I appreciated any kind of your helps.
I am experimenting with this code.
In one of viewController's I am using next snippet:
- (BOOL)textView:(UITextView *)textView1 shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if([text isEqualToString:#"\n"])
{
[textView resignFirstResponder];
return NO;
}
return YES;
}
I found in web to dismissing the keyboard by pressing returnButton. It works well when I am calling it from this viewController. In root KLNoteViewController I am adding notification in handle state changes-method:
- (void) setState:(KLControllerCardState)state animated:(BOOL) animated
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"refresh" object:self];
if (animated)
{
[UIView animateWithDuration:self.noteViewController.cardAnimationDuration animations:^{
[self setState:state animated:NO];
}];
return;
}
//Full Screen State
if (state == KLControllerCardStateFullScreen)
{
[self expandCardToFullSize: animated];
[self setYCoordinate: 0];
}
//Default State
else if (state == KLControllerCardStateDefault)
{
[self shrinkCardToScaledSize: animated];
[self setYCoordinate: originY];
}
//Hidden State - Bottom
else if (state == KLControllerCardStateHiddenBottom)
{
//Move it off screen and far enough down that the shadow does not appear on screen
[self setYCoordinate: self.noteViewController.view.frame.size.height + abs(self.noteViewController.cardShadowOffset.height)*3];
}
//Hidden State - Top
else if (state == KLControllerCardStateHiddenTop)
{
[self setYCoordinate: 0];
}
//Notify the delegate of the state change (even if state changed to self)
KLControllerCardState lastState = self.state;
//Update to the new state
[self setState:state];
//Notify the delegate
if ([self.delegate respondsToSelector:#selector(controllerCard:didChangeToDisplayState:fromDisplayState:)]) {
[self.delegate controllerCard:self
didChangeToDisplayState:state fromDisplayState: lastState];
}
}
and add adding observer:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissKeyboard) name:#"refresh" object:nil];
in every viewControllers -viewDidLoad. So when this observer is used with UITextField everything works fine, in viewController when I am using UIText View I have crash with log:
[NSStackBlock isEqualToString:]: unrecognized selector sent to
instance 0xbfffd228
I searched in web but found only two links with explanation of what is the NSStackBlock is and they are not informative well for me to solve the problem. Can somebody explain what it can be?
I have problem with the attached codе. It have to close the currently displayed modalViewController when the user tap once on the cancel button instead of twice as it do now.
Code
- (BOOL)isIOS6 {
BOOL native = YES;
if([[[UIDevice currentDevice] systemVersion] floatValue] < 6.0f){
native = NO;
}
return native;
}
- (void)twitterShare {
DLog(#"is ios6: %d", [self isIOS6]);
if ([self isIOS6]) {
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])
{
NSString *textToShare = #"Test";
SLComposeViewController *twitterComposeViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
twitterComposeViewController.completionHandler = ^(SLComposeViewControllerResult result){
DLog(#"result: %d", result);
if (result == 1) {
Dlog(#"Shared");
[[NSNotificationCenter defaultCenter] postNotificationName:#"notitificationName" object:nil];
DLog(#"Sended provide bonus notification");
[self disableButtonWithTag:GrowthButtonTwitterConnectTag];
DLog(#"disable that button.");
} else {
Dlog(#"canceled...");
}
};
[twitterComposeViewController setInitialText:textToShare];
[self presentViewController:twitterComposeViewController animated:YES completion:nil];
} else {
DLog(#"Twitter not available");
}
} else {
// iOS 5 not supported message
[[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"ATTENTION", nil)
message:NSLocalizedString(#"IOS6_OR_ABOVE_FEATURE", nil)
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil, nil] autorelease] show];
}
}
I've manage to fix that issue with the following code:
tweetSheet.completionHandler = ^(SLComposeViewControllerResult result) {
switch(result) {
// This means the user cancelled without sending the Tweet
case SLComposeViewControllerResultCancelled:
break;
// This means the user hit 'Send'
case SLComposeViewControllerResultDone:
[[NSNotificationCenter defaultCenter] postNotificationName:#"kGrowthProvideTwitterBonus" object:nil];
DLog(#"Sended provide bonus notification");
[self disableButtonWithTag:TTGrowthButtonTwitterConnectTag];
break;
}
// dismiss the Tweet Sheet
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"Tweet Sheet has been dismissed.");
}];
});
};