How handle streaming error with AVPlayer - error-handling

I use AVPlayer to stream tracks.I'm trying to handle all errors like network unavailable or stream unaivalable
But I find any handler for this kind of error.
I've already added a KVO for on the avplayer's status.
[self.player addObserver:self forKeyPath:#"status" options:0 context:nil];
But even the stream doesn't exist (example: wrong url), the status switch to AVPlayerStatusReadyToPlay.
EDIT
Solution was to work with AVPLayerItems and use AVQueuPlayer. The other problem was I reallocated this player at every tracks.

Add KVO to AVPlayerItem instead of AVPlayer with NSKeyValueObservingOptionNew as well.
[playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:NULL];
[self.player replaceCurrentItemWithPlayerItem:playerItem];

There is a notification you shall subscribe
[nc addObserver:self selector:#selector(_itemDidError:) name:AVPlayerItemNewErrorLogEntryNotification object:_playerItem];
But that won't solve all the problem. I see it will not fire in the case like 404

Related

MPMoviePlayerViewController not playing on 2nd run

I created a MPMoviePlayerViewController which plays a live video. However, if I play the video twice meaning opening the player, clicking done, and playing the stream again. The result is only a black screen with no controls of the MPMoviePlayerViewController. And I need to stop the simulator cause I think the application is crashing. Here's how I did it
- (void) playUrl:(NSURL *)movieInfo
{
NSURL *streamUrl = movieInfo;
MPMoviePlayerViewController *mpvc = [[MPMoviePlayerViewController alloc] initWithContentURL:streamUrl];
[[mpvc view] setFrame:self.view.bounds];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
mpvc.moviePlayer.movieSourceType = MPMovieSourceTypeStreaming;
[mpvc.moviePlayer setControlStyle:MPMovieControlStyleFullscreen];
[mpvc.moviePlayer setShouldAutoplay:YES];
[mpvc.moviePlayer setFullscreen:NO animated:YES];
[mpvc setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[mpvc.moviePlayer setScalingMode:MPMovieScalingModeNone];
[mpvc.moviePlayer setUseApplicationAudioSession:NO];
[self presentMoviePlayerViewControllerAnimated:mpvc];
}
- (void) movieFinishedCallback:(NSNotification*) aNotification
{
MPMoviePlayerController *player = [aNotification object];
[player stop];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:player];
[player.view removeFromSuperview];
NSLog(#"stopped?");
}
I see that in your movieFinishedCallback: implementation, you remove the MPMoviePlayerController view, but in your playUrl: implementation, you are only setting the view's frame, presumably after you have already added the view in viewDidLoad.
One obvious change which is worth trying, is update you code to use the AVPictureInPictureController or AVPlayerViewController class from the AVKit framework, or the WKWebView class from WebKit. According to the MPMoviePlayerViewController docs, it is deprecated as of iOS 9:
The MPMoviePlayerViewController class is formally deprecated in iOS 9. (The MPMoviePlayerController class is also formally deprecated.) To play video content in iOS 9 and later, instead use the AVPictureInPictureController or AVPlayerViewController class from the AVKit framework, or the WKWebView class from WebKit.
Try moving the line where you add the view to the hierarchy, to the playUrl: method. Generally, it is good practice to have countering implementations in opposing methods for your event counterparts. For instance, implement a method to build and add a view when an event starts, and have a corresponding method where you tear down and remove the same view when the same event ends. But, I say 'generally' because there are always exceptions, and you may have very compelling reasons for not doing so. So, in this case, the opposing calls are presentMoviePlayerViewControllerAnimated: and dismissMoviePlayerViewControllerAnimated:, available from the UIViewController category.
After changing the view access to using dot-notation, to be consistent with your callback implementation, here is what your new playUrl: implemntation would look like, assuming you're adding the view to self.view:
- (void) playUrl:(NSURL *)movieInfo
{
NSURL *streamUrl = movieInfo;
MPMoviePlayerViewController *mpvc = [[MPMoviePlayerViewController alloc] initWithContentURL:streamUrl];
[mpvc.view setFrame:self.view.bounds];
[self.view addSubview:mpvc.view];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
mpvc.moviePlayer.movieSourceType = MPMovieSourceTypeStreaming;
[mpvc.moviePlayer setControlStyle:MPMovieControlStyleFullscreen];
[mpvc.moviePlayer setShouldAutoplay:YES];
[mpvc.moviePlayer setFullscreen:NO animated:YES];
[mpvc setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[mpvc.moviePlayer setScalingMode:MPMovieScalingModeNone];
[mpvc.moviePlayer setUseApplicationAudioSession:NO];
[self presentMoviePlayerViewControllerAnimated:mpvc];
}
Another option is to simply not remove the player's view in your callback method. If that is not the culprit, then the next thing I would investigate is check if you are sending messages to nil objects. Also, see what happens when you take out all the implementation from movieFinishedCallback:, except for getting and stopping the player.
I hope that helps!
Fixed the issue by removing the [player.view removeFromSuperview] line

NSNotificationCenter removeObserver:name:object: not removing observer

I have a method in a view controller that sets up some notifications:
- (void)processState
{
MYGame *game = [[MYGameManager sharedInstance] getGameAtIndex:self.indexPath.row];
if(game)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notification_gameUpdated:) name:kMYNotificationGameUpdated object:game];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notification_gameEnded:) name:kMYNotificationGameEnded object:game];
}
}
Then there's a game updated method, which is called every so often:
- (void)notification_gameUpdated:(NSNotification *)notification
{
MYGame *game = notification.object;
_game_status = (game.entity.bet.isWinning) ? MYGameStatusWin : MYGameStatusLose;
}
And finally, when the game ends:
- (void)notification_gameEnded:(NSNotification *)notification
{
MYGame *game = notification.object;
// Clear the notifications
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameUpdated object:game];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameEnded object:game];
self.gameIsActive = NO;
}
Trouble is, that even when I remove the observers (and a breakpoint shows that this is happening), then the notification_gameUpdated: method is still being called. If I change it to
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameUpdated object:nil];
This still won't clear it. But if I change it to
[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:game];
Then that does clear it. As does
[[NSNotificationCenter defaultCenter] removeObserver:self];
But I'm not keen on doing either, because I'd rather the code was clean and I don't want any "gotchas" further down the line if I need to add more observers. I've checked the rest of the code and cannot find any other classes adding observers to this object, although other view controllers do listen to the same messages.
Is processState called more than once? That would explain the behavior you are seeing.
If it is, one way to fix the issue would be to always remove listeners before adding them. See e.g. this answer.
edit #2
try registering with object:nil and when you post the notification include the reference to game in the userInfo dictionary. then, in the receiver, you can compare against game and perform whatever action you want if it is a match. this should get you the same behavior as if you were using object:game, although it does not explain why your current implementation isn't working
when you register for notifications like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(notification_gameUpdated:)
name:kMYNotificationGameUpdated
object:game];
the #selector will only be performed if that particular instance of game is the sender.
is it possible that you're re-initializing your shared instance of game after registering? that could cause the behavior you're experiencing
try registering for notifications with object:nil and see what happens. (assuming there are not multiple games running concurrently)
So it turned out that the reason for the issue was Method Swizzling. The project I'm working on has addObserver:selector:name:object: and removeObserver:name:object: swizzled. The issue was that although addObserver has been handled correctly, removeObserver is only removing objects on specific conditions. This will obviously need to be changed...
But I post this as a warning to others... Swizzling can be dangerous to your health!
Apologies for any time wasted.

Handle device rotation for "More" UINavigationController in UITabBarController.moreNavigationController

I have an app which has to work in both portrait and landscape more and the UITabBar should adjust to current orientation (it has custom background and selected items). So, for the rest of views I just override the - (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation method and it works perfectly.
How would I do that for the .moreNavigationController of UITabBarController ? I've tried adding an observer (the selector is in extension of UITabBarController):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:UIApplicationDidChangeStatusBarOrientationNotification
object:self.moreNavigationController];
but it never get called.
Am I missing something or what would be the best way to handle this situation ?
Solution: somewhy UIDeviceOrientation is not firing correctly, so better to use statusBarOrientation, works as a charm.
the final code which work is this:
in main UITabBarController, viewDidLoad:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:UIApplicationDidChangeStatusBarOrientationNotification
object:nil];
the didRotate selector method:
- (void) didRotate:(NSNotification *)notification{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if(UIInterfaceOrientationIsPortrait(orientation)) {
// Portrait
} else {
// Landscape
}
}
Thanks for help.
You are registering your UITabBarController for a notification which never gets posted. Take a look at the documentation NSNotificationCenter Reference for the addObserver:selector:name:object method
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
notificationSender:
The object whose notifications the observer wants to receive; that is, only notifications sent by this sender are delivered to the observer.
If you pass nil, the notification center doesn’t use a notification’s sender to decide whether to deliver it to the observer.
so, if you specify the . moreNavigationController as the sender, you wont get those notifications, because it never posts such ones. Instead pass nil to ignore the sender and listen to the status bar change regardless of who sent it.
By the way, in this SO Answer is a summary of how you can react to orientation change.
And at last. If it still doesn't work, you can try this:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
Yes, you forgot to post notification, which will call you own notification:
[[NSNotificationCenter defaultCenter] postNotificationName: UIApplicationDidChangeStatusBarOrientationNotification object:self];
or if you dont wont to send anything just set object as nil:
[[NSNotificationCenter defaultCenter] postNotificationName: UIApplicationDidChangeStatusBarOrientationNotification object:nil];
The best way to implement the same is first addObserver and then remove observer to avoid the crash:-
-(void)viewDidLoad{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:#"UIDeviceOrientationDidChangeNotification" object:nil];
}
//Now Remove Observer
-(void)viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self #"UIDeviceOrientationDidChangeNotification" object:nil];
}

UIWebView + TVOut issues

When a video is playing through the uiwebview, i am not getting the UIScreen Notification For TV-Out Cable Plug-in & Plug-Out. The issue is when a video is playing with a TV-Out cable connected, and the user unplug the cable in between the video starts in MPMoviePlayer, i dont want that, instead i want to shuts off the player.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(blockMirroring)name:UIScreenDidConnectNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(blockMirroring) name:UIScreenDidDisconnectNotification
object:nil];
Any help???
Thanks in Advance
you can try below solution to solve your problem.
In blockMirroring method, implement below code and may be it helps you.
[webview loadHTMLString:#"" baseURL:nil];
[self.navigationController popViewControllerAnimated:YES];

How to reloadData?

I am not dumb and I know how to reload data. I am in a tricky situation where I have a UIView inside another UIView both named OHGridView. I have to keep them named the same way.
With the OHGridView example code, the refresh looked a little like this:
[(OHGridView *)self.view reloadData];
But now that I added a UIView, it no longer works.
Any help is appreciated!
Edit:
Code removed
The NSNotificationCenter may be what you need. You can register for events (eg a perform update event) and then post these events from anywhere. These go to the notification center and then to your class/view. When the event is received, you just do what is needed.
The docs are here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
Inside the OHGridView you would call during initialization:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReloadNotification:) name:#"ReloadOHGridView" object:nil];
Then, just define the method:
- (void)ReloadNotification:(NSNotification *)notification
{
[self reloadData];
}
So, when you want an update to occur, you then just call:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReloadOHGridView" object:self];
When you deallocate the OHGridView you should remove the observer:
[[NSNotificationCenter defaultCenter] removeObserver:self];