AVPlayer replaceCurrentItemWithPlayerItem not working on iOS 4.3.3+ - objective-c

I have an audio player that I'm building using AVPlayer.
Currently, I keep the player instance around and when I need to swap tracks (either from a manual selection or the track has reached the end) I create a new AVPlayerItem and I call replaceCurrentItemWithPlayerItem with the new item.
According to the docs, replaceCurrentItemWithPlayerItem is an asynchronous operation, so I also observe the currentItem key path of the player. When that gets called, I tell my player to play.
Here is the relevant code:
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerItemStatusChangedContext];
if (!_player) {
_player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
[_player addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerStatusChangedContext];
[_player addObserver:self forKeyPath:#"currentItem" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerCurrentItemChangedContext];
} else {
[_player replaceCurrentItemWithPlayerItem:playerItem];
}
And here is the key value observation callback:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == CHStreamingAudioPlayer_PlayerCurrentItemChangedContext) {
NSLog(#"Player's current item changed! Change dictionary: %#", change);
if (_player.currentItem) {
[self play]; //<---- doesn't get called
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
On iOS 4.3.3 (and iOS 5) my key observation method is called but _player.currentItem is always nil. On 4.2.1 and 4.3.2 this property contains the actual value. This method is never called again. So in essence, replacing seems to always fail.
This seems like a bug, but perhaps I'm doing something wrong.

I had this issue with iOS 5 (including 5.0.1). It used to work fine on iOS 4.x.
There are two ways to workaround this, release and recreate your AVPlayer with the desired AVPlayerItems each time you need to swap tracks. Or, simply call replaceCurrentItemWithPlayerItem: on the main thread.
I tried both options and they worked fine.
Credits to: Apple Developer Forums

I've been experiencing similar problems. You probably got started from AVPlayerDemoPlaybackViewController from Apple sample code like me. Maybe the problem why currentItem is nil is because it's not loaded yet or ready for playback (my problem was I couldn't get the duration of the new AVPlayerItem).
You could try starting the playback when observed status of the currentItem is ReadyToPlay.
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
switch (status) {
case AVPlayerStatusUnknown: {
NSLog(#"PLAYER StatusUnknown");
}
break;
case AVPlayerStatusReadyToPlay: {
NSLog(#"PLAYER ReadyToPlay");
[self play];
}
break;
case AVPlayerStatusFailed: {
AVPlayerItem *playerItem = (AVPlayerItem *)object;
[self handleError: [playerItem.error localizedDescription]];
}
break;
}
I don't know if this will wok for you, I didn't try this on lower or higher than 4.3.4 iPad, so I guess I'll run into complications soon.

Related

App did not update the location when app is killed by User from iPhone app Switcher (Objective C)

I am working on location update.
When app is in foreground then app update the location using KVO,
self.mapView_locUpd.myLocationEnabled = YES;
[self.mapView_locUpd addObserver:self forKeyPath:#"myLocation" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([keyPath isEqualToString:#"myLocation"]) {
NSLog(#"Kvo....location");
CLLocation *location = [object myLocation];
self.currentLatitude= [NSString stringWithFormat:#"%f",location.coordinate.latitude];
self.currentLongitude=[NSString stringWithFormat:#"%f",location.coordinate.longitude];
[self updateCurrentLocation];
}
}
When app is in Background (Not killed) then app update the location using CLLocationManager,
-(void)initLocationmManager
{
if(self.locationManagerCurrent == nil)
self.locationManagerCurrent = [[CLLocationManager alloc]init];
self.locationManagerCurrent.delegate=self;
self.locationManagerCurrent.desiredAccuracy= kCLLocationAccuracyBestForNavigation; //provide precise location but more battery consumer
self.locationManagerCurrent.activityType = CLActivityTypeOtherNavigation;
self.locationManagerCurrent.pausesLocationUpdatesAutomatically = YES;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
[self.locationManagerCurrent requestAlwaysAuthorization];
}
if ([self.locationManagerCurrent respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManagerCurrent setAllowsBackgroundLocationUpdates:YES];
}
}
-(void)configureLocationManagerOnAppinBackground{
if(self.locationManagerCurrent == nil)
{
[self initLocationmManager];
}
[self.locationManagerCurrent startMonitoringSignificantLocationChanges];
if ( [[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground)
{
[self.locationManagerCurrent startUpdatingLocation];
}
}
#pragma mark -CllocationmanagerDelegate
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
NSLog(#"Loctaionmanager....DidUpadetLoc");
CLLocation *location =[locations objectAtIndex:0];
if (location.horizontalAccuracy < 0) {
return;
}
self.currentLatitude= [NSString stringWithFormat:#"%f",location.coordinate.latitude];
self.currentLongitude=[NSString stringWithFormat:#"%f",location.coordinate.longitude];
[self updateCurrentLocation];
}
Using CllocationManager the location updates very well when app in background. Suppose in any case, the iOS suspend the app then iOS will launch my app in background and slowly start location updates.
Now if user killed the app from iPhone app switcher then there is not location update arise.
So i want to update the location (continously or slowly) when app get killed by user.
I tried so many thing to launch the app on killed.
Pls help.
You should explore https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
You need UIBackgroundModes with location key.
Also you need to request Privacy - Location Always Usage and provide
"Privacy - Location Always Usage Description" key in your Info.plist
This will allow iOS to partial relaunch your app when user kills it. iOS will return a key UIApplicationLaunchOptionsLocationKey to the app delegate method didFinishLaunchingWithOptions.
Also this answer should help you: How to Get Location Updates for iOS 7 and 8 Even when the App is Suspended

Scripting Bridge does not work(KVO,nil values)

I am currently writing an app extension for Spotify that allows me to control the playback. I am using the Spotify AppleScript API in combination with the Scripting Bridge in Objective-C. The first thing I would like to ask,does the Scripting API support Key Value Observing? Because when I add an observer I don't get any notifications from the API and when I try to get data manually from the Scripting API I always get nil values,why?
I have the following code:
-(id)init
{
self=[super init];
if(self)
{
spotify=[SBApplication applicationWithBundleIdentifier:#"com.spotify.client"];
//Not sure if KVO is implemented,so I use this to get data from the API
timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(observeValueForKeyPath) userInfo:nil repeats:YES];
if([self isSpotifyRunning])
{
//Useless?
[spotify addObserver:self forKeyPath:#"currentTrack" options:NSKeyValueObservingOptionNew context:nil];
[spotify addObserver:self forKeyPath:#"playerPosition" options:NSKeyValueObservingOptionNew context:nil];
[spotify addObserver:self forKeyPath:#"playerState" options:NSKeyValueObservingOptionNew context:nil];
[self.playBackSlider setTarget:self];
[self.playBackSlider setAction:#selector(sliderDidMove:)];
if(spotify.playerState==SpotifyEPlSPaused||spotify.playerState==SpotifyEPlSStopped)
{
[self.playButton setStringValue:#"Play"];
}
else
{
[self.playButton setStringValue:#"Stop"];
}
}
}
return self;
}
-(void)observeValueForKeyPath
{
[self.titleTextField setStringValue:spotify.currentTrack.name];
[self.artistTextField setStringValue:spotify.currentTrack.artist];
[self.currentPlayBackPositionTextField setStringValue:[self formatTime:spotify.playerPosition]];
[self.remainingTimeTextField setStringValue:[self formatTime:spotify.currentTrack.duration]];
[self.playBackSlider setMaxValue:spotify.currentTrack.duration];
[self.playBackSlider setDoubleValue:spotify.playerPosition];
[self.playBackSlider setDoubleValue:spotify.playerPosition];
[self.currentPlayBackPositionTextField setStringValue:[self formatTime:spotify.playerPosition]];
if(spotify.playerState==SpotifyEPlSPaused||spotify.playerState==SpotifyEPlSStopped)
{
[self.playButton setStringValue:#"Play"];
}
else
{
[self.playButton setStringValue:#"Stop"];
}
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if([keyPath isEqualToString:#"currentTrack"])
{
[self.titleTextField setStringValue:spotify.currentTrack.name];
[self.artistTextField setStringValue:spotify.currentTrack.artist];
[self.currentPlayBackPositionTextField setStringValue:[self formatTime:spotify.playerPosition]];
[self.remainingTimeTextField setStringValue:[self formatTime:spotify.currentTrack.duration]];
[self.playBackSlider setMaxValue:spotify.currentTrack.duration];
[self.playBackSlider setDoubleValue:spotify.playerPosition];
}
else if([keyPath isEqualToString:#"playerPosition"])
{
[self.playBackSlider setDoubleValue:spotify.playerPosition];
[self.currentPlayBackPositionTextField setStringValue:[self formatTime:spotify.playerPosition]];
}
else if([keyPath isEqualToString:#"playerState"])
{
if(spotify.playerState==SpotifyEPlSPaused||spotify.playerState==SpotifyEPlSStopped)
{
[self.playButton setStringValue:#"Stop"];
}
else
{
[self.playButton setStringValue:#"Play"];
}
}
}
EDIT:
I have set a delegate for the SBApplication Object and I get the following error:
Error Domain=NSOSStatusErrorDomain Code=-600 "procNotFound: no
eligible process with specified descriptor"
UserInfo={ErrorNumber=-600}
What exactly does that mean? Spotify starts when the SBApplication is created,so why is the SBApplication telling me that it didn't found the process? I also took a look at Info.plist in the Spotify Bundle and it is scriptable, so why is it not working?
Thank you in advance for any help!
The AppleScript scripting dictionary of any application – what you're calling AppleScript API - doesn't support KVO.
Regarding to use the scripting dictionary in Cocoa apps forget ScriptingBridge.
AppleScriptObjC (introduced in 10.6 Snow Leopard) provides a much easier way to interact with AppleScript. You can even write a Cocoa application completely in AppleScriptObjC using AppleScript and a Objective-C like terminology in the same file.

Setting object to nil iOS7 vs iOS8

WI have an iPad kiosk app that displays videos on an external monitor connected to the iPad via HDMI. I have a viewController that manages the view on the external monitor. When I am done playing back a video I nil out the MPMoviePlayerController instance. In iOS7 this works fine, but in iOS8 I get a hard crash after setting the moviePlayer to nil.
- (void)removeMoviePlayer {
[self.moviePlayerController.view removeFromSuperview];
[self removeMovieNotificationHandlers];
self.moviePlayerController = nil;}
With Zombies enabled I get a message in the debugger:
[MPAVController release]: message sent to deallocated instance
Again, this crash does not happen when the app runs under iOS7. What has changed that is causing this crash?
After a couple days of trial and error I discovered that when trying to nil out the MPMoviePlayerController instance when the MPMoviePlayerPlaybackState was MPMoviePlaybackStatePaused, the app would crash. When a video reaches the end, the MPMoviePlayerController sends a MPMoviePlaybackDidFinish notification that reports the playback state as MPMoviePlaybackStatePaused. The fix was to test for playback state and if paused call [MPMoviePlayerController stop]. That changes the MPMoviePlaybackState to MPMoviePlaybackStateStopped, and you can then nil out the instance without a crash.
This crash did not happen before iOS 8. Code below:
-(void)moviePlayBackDidFinish:(NSNotification *)notification {
[self stopVideo:notification];
}
- (void)stopVideo:(NSNotification *)notification {
if (self.moviePlayerController) {
if (self.moviePlayerController.playbackState == MPMoviePlaybackStatePlaying || self.moviePlayerController.playbackState == MPMoviePlaybackStatePaused) {
[self.moviePlayerController stop];
}
[self cleanUpVideo];
}
}
- (void)cleanUpVideo {
[self killProgressTimer];
[UIView animateWithDuration:1.0f animations:^{
self.closedCaptionLabel.alpha = 0.0f;
self.moviePlayerController.view.alpha = 0.0f;
self.backgroundImageView.alpha = 1.0f;
} completion:^(BOOL finished) {
[self removeMoviePlayer];
[self resetClosedCaptions];
[self.delegate videoDidStop];
}];
}
- (void)removeMoviePlayer {
[self.moviePlayerController.view removeFromSuperview];
[self removeMovieNotificationHandlers];
self.moviePlayerController = nil;
}

UIImagePickerController delegate is not calling

Iam using the following code for taking the picture automatically from IPAD front camera:
UIImagePickerController *imgPkr = [[UIImagePickerController alloc] init];
imgPkr.sourceType = UIImagePickerControllerSourceTypeCamera;
imgPkr.delegate = self;
imgPkr.cameraDevice=UIImagePickerControllerCameraDeviceFront;
[self presentModalViewController:imgPkr animated:YES];
imgPkr.showsCameraControls = NO;
[imgPkr takePicture];
But this code Dont take any picture and dont call the delegate:
-(void)imagePickerController:(UIImagePickerController*)picker
didFinishPickingMediaWithInfo:(NSDictionary*)info
Any idea what is wrong with the code
My first guess is that [imgPkr takePicture]; is being called before the picker is done being presented. Try it like this:
UIImagePickerController *imgPkr = [[UIImagePickerController alloc] init];
imgPkr.sourceType = UIImagePickerControllerSourceTypeCamera;
imgPkr.delegate = self;
imgPkr.cameraDevice=UIImagePickerControllerCameraDeviceFront;
[self presentModalViewController:imgPkr animated:YES];
imgPkr.showsCameraControls = NO;
[self performSelector:#selector(takePicture) withObject:self afterDelay:1.0];
OR
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(takePicture)
name:AVCaptureSessionDidStartRunningNotification
object:nil];
&
- (void)takePicture
{
[imgPkr takePicture];
}
I bet that you get a message in your logs saying that
UIImagePickerController: ignoring request to take picture; camera is
not yet ready.
This is a common problem because the underlying capture session needs some time to start, so you just have to be sure that the camera is ready to take a picture and then call takePicture method. Now, a way to get notified is explained in detail in my answer here: How to know if iPhone camera is ready to take picture?
Note though that this method will work on iOS5+ (there is a bug in older versions that prevents the system notifications for this event, contrary to what is described in the documentation). I hope that this helps.
another way to wait for camera ready until take picture is completion block ->
[self presentViewController:imagePicker animated:YES
completion:^ {
[imagePicker takePicture];
}];
thank you 'GrandSteph' at
iOS taking photo programmatically

Can't beginReceivingRemoteControlEvents in iOS

In my app i want let user to control audio playback in background. I set backGround modes in .plist, and in plays in bg just like i wanted.But i can't get any response from touching the control buttons.
I set the AudioSession like this
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance]setActive:YES error:nil];
Then, in viewContoller where my player is placed i beginReceivingRemoteControlEvents like this
if ([[UIApplication sharedApplication] respondsToSelector:#selector(beginReceivingRemoteControlEvents)]){
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
[self becomeFirstResponder];
NSLog(#"Responds!");
}
And it prints Responds!
But the problem is that this method is never called
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
NSLog(#"Where is my event?");
if(event.type == UIEventTypeRemoteControl)
{
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
NSLog(#"Pause");
[self playWords:playButton];
break;
case UIEventSubtypeRemoteControlNextTrack:
NSLog(#"Next");
[self next];
break;
case UIEventSubtypeRemoteControlPreviousTrack:
NSLog(#"Prev");
[self prev];
break;
}
}
I even tried to write a category on UIApplication to let it become the first responder, but it doesn't help
#implementation UIApplication (RemoteEvents)
-(BOOL)canBecomeFirstResponder
{
return YES;
}
#end
Why can this happen?
SOLUTION
Here's what solved my problem Entering background on iOS4 to play audio
I have done the same work in my project, it's working fine. Please follow this, perhaps it will help you. Change the event name etc. In my code _audio is the object of AVAudioPlayer.
- (void)viewDidLoad {
NSError *setCategoryErr = nil;
NSError *activationErr = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &setCategoryErr];
[[AVAudioSession sharedInstance] setActive: YES error: &activationErr];
}
- (void)viewWillAppear {
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
switch (event.subtype) {
case UIEventSubtypeRemoteControlPlay:
[_audio play];
break;
case UIEventSubtypeRemoteControlPause:
[_audio pause];
break;
default:
break;
}
}
There is a newer mechanism for listening to remote control events. For example, to execute a block when the headphone play/pause button is pressed:
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
NSLog(#"toggle button pressed");
return MPRemoteCommandHandlerStatusSuccess;
}];
or, if you prefer to use a method instead of a block:
[commandCenter.togglePlayPauseCommand addTarget:self action:#selector(toggleButtonAction)];
To stop:
[commandCenter.togglePlayPauseCommand removeTarget:self];
or:
[commandCenter.togglePlayPauseCommand removeTarget:self action:#selector(toggleButtonAction)];
You'll need to add this to the includes area of your file:
#import MediaPlayer;
For it to work in the background, you must have the background audio mode added to your app's capabilities.
Review this sample code from Apple for an example of usage. Likely your primary view controller is not actually becoming the first responder. An alternative usage is to place this code in your Application Delegate (which will always be the first responder) and respond to these events before they have had a chance to propagate, and potentially be consumed by other responders.
You have
respondsToSelector:#selector(beginReceivingRemoteControlEvents)]){
but in the method signature you have
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
The place where you are registering the signature is wrong. Just replace it by
respondsToSelector:#selector(beginReceivingRemoteControlEvents:)]){
You are missing the : which is making the app send the even to a non existing method I am assuming. I am surprised that your app is not crashing.