Reacting to ControlCenter events when using AVPlayer for HLS audio playback - iphone

I am looking for a way to handle the play/pause events from the iOS ControlCenter when playing audio (HLS) using AVPlayer.
I have it all working, but it is based on "named" notifications which are not exposed in the header files.
Is there an "official" way to do this?
Currently the following code works:
- (void) removeControlCenterNotifications
{
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
- (void) addControlCenterNotifications
{
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
__weak MyClass *pWeakSelf = self;
__weak MoviePlayer *pWeakPlayer = player_;
[[NSNotificationCenter defaultCenter] addObserverForName:#"UIApplicationSimpleRemoteActionNotification"
object:nil
queue:NULL
usingBlock:^(NSNotification *notification)
{
if(pWeakSelf == nil) return;
NSNumber *type = notification.userInfo[#"UIApplicationSimpleRemoteActionType"];
switch ([type intValue]) {
case 6: [pWeakPlayer play]; break;
case 7: [pWeakPlayer pause]; break;
}
}];
}

Solution
The solution to this was to watch the UIEvents entering the app and create my own notifications from here.
The relevant event type is:
UIEventTypeRemoteControl
The relevant event subtypes are:
UIEventSubtypeRemoteControlPlay = 100,
UIEventSubtypeRemoteControlPause = 101,
UIEventSubtypeRemoteControlStop = 102,
UIEventSubtypeRemoteControlTogglePlayPause = 103,
UIEventSubtypeRemoteControlNextTrack = 104,
UIEventSubtypeRemoteControlPreviousTrack = 105,
UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
UIEventSubtypeRemoteControlEndSeekingBackward = 107,
UIEventSubtypeRemoteControlBeginSeekingForward = 108,
UIEventSubtypeRemoteControlEndSeekingForward = 109,

Related

SpriteKit App Using Excessive CPU

I wrote a SpriteKit app last year targeting 10.10 (Yosemite). Everything ran fine, but when I upgraded to El Capitan this year it freezes in one particular spot. It's a tough problem to diagnose because there is a lot of code so I'll try to be as descriptive as possible. I've also created a YOUTUBE screen recording of the issue.
App's Purpose
The app is basically a leaderboard that I created for a tournament at the school that I teach at. When the app launches, it goes to the LeaderboardScene scene and displays the leaderboard.
The app stays in this scene for the rest of the time. The sword that says "battle" is a button. When it is pressed it creates an overlay and shows the two students that will be facing each other in video form (SKVideoNode).
The videos play continuously and the user of the app eventually clicks on whichever student wins that match and then the overlay is removed from the scene and the app shows the leaderboard once again.
Potential Reasons For High CPU
Playing video: Normally the overlay shows video, but I also created an option where still images are loaded instead of video just in case I had a problem. Whether I load images or video, the CPU usage is super high.
Here's some of the code that is most likely causing this issue:
LeaderboardScene.m
//when the sword button is pressed it switches to the LB_SHOW_VERSUS_SCREEN state
-(void) update:(NSTimeInterval)currentTime {
switch (_leaderboardState) {
...
case LB_SHOW_VERSUS_SCREEN: { //Case for "Versus Screen" overlay
[self showVersusScreen];
break;
}
case LB_CHOOSE_WINNER: {
break;
}
default:
break;
}
}
...
//sets up the video overlay
-(void) showVersusScreen {
//doesn't allow the matchup screen to pop up until the producer FLASHING actions are complete
if ([_right hasActions] == NO) {
[self addChild:_matchup]; //_matchup is an object from the Matchup.m class
NSArray *producers = #[_left, _right];
[_matchup createRound:_round WithProducers:producers VideoType:YES]; //creates the matchup with VIDEO
//[_matchup createRound:_round WithProducers:producers VideoType:NO]; //creates the matchup without VIDEO
_leaderboardState = LB_CHOOSE_WINNER;
}
}
Matchup.m
//more setting up of the overlay
-(void) createRound:(NSString*)round WithProducers:(NSArray*)producers VideoType:(bool)isVideoType {
SKAction *wait = [SKAction waitForDuration:1.25];
[self loadSoundsWithProducers:producers];
[self runAction:wait completion:^{ //resets the overlay
_isVideoType = isVideoType;
[self removeAllChildren];
[self initBackground];
[self initHighlightNode];
[self initOutline];
[self initText:round];
if (_isVideoType)
[self initVersusVideoWithProducers:producers]; //this is selected
else
[self initVersusImagesWithProducers:producers];
[self animationSequence];
_currentSoundIndex = 0;
[self playAudio];
}];
}
...
//creates a VersusSprite object which represents each of the students
-(void) initVersusVideoWithProducers:(NSArray*)producers {
Producer *left = (Producer*)[producers objectAtIndex:0];
Producer *right = (Producer*)[producers objectAtIndex:1];
_leftProducer = [[VersusSprite alloc] initWithProducerVideo:left.name LeftSide:YES];
_leftProducer.name = left.name;
_leftProducer.zPosition = 5;
_leftProducer.position = CGPointMake(-_SCREEN_WIDTH/2, _SCREEN_HEIGHT/3);
[self addChild:_leftProducer];
_rightProducer = [[VersusSprite alloc] initWithProducerVideo:right.name LeftSide:NO];
_rightProducer.name = right.name;
_rightProducer.zPosition = 5;
_rightProducer.xScale = -1;
_rightProducer.position = CGPointMake(_SCREEN_WIDTH + _SCREEN_WIDTH/2, _SCREEN_HEIGHT/3);
[self addChild:_rightProducer];
}
VersusSprite.m
-(instancetype) initWithProducerVideo:(NSString*)fileName LeftSide:(bool)isLeftSide {
if (self = [super init]) {
_isVideo = YES;
_isLeftSide = isLeftSide;
self.name = fileName;
[self initVideoWithFileName:fileName]; //creates videos
[self addProducerLabel];
}
return self;
}
...
//creates the videos for the VersusSprite
-(void) initVideoWithFileName:(NSString*)fileName {
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES);
NSString *desktopPath = [paths objectAtIndex:0];
NSString *resourcePath = [NSString stringWithFormat:#"%#/vs", desktopPath];
NSString *videoPath = [NSString stringWithFormat:#"%#/%#.mp4", resourcePath, fileName];
NSURL *fileURL = [NSURL fileURLWithPath:videoPath];
AVPlayer *avPlayer = [[AVPlayer alloc] initWithURL:fileURL];
_vid = [SKVideoNode videoNodeWithAVPlayer:avPlayer];
//[_vid setScale:1];
[self addChild:_vid];
[_vid play];
avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[avPlayer currentItem]];
}
//used to get the videos to loop
- (void)playerItemDidReachEnd:(NSNotification *)notification {
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
UPDATE
The issue has been identified and is very specific to my project, so it probably won't help anyone else unfortunately. When clicking on the "sword" icon that says "Battle", the scene gets blurred and then the overlay is put on top of it. The blurring occurs on a background thread as you'll see below:
[self runAction:[SKAction waitForDuration:1.5] completion:^{
[self blurSceneProgressivelyToValue:15 WithDuration:1.25];
}];
I'll have to handle the blur in another way or just remove it altogether.

video playing from application background to foreground takes 2 seconds lagging from pause to play in iOS 6.0

I have VideoController which plays video. Now VideoController is pushed in navigationController.
VideoController *ObjVideoController = [[VideoController alloc] init];
ObjVideoController.strVideoURL = [AnimationArr objectAtIndex:SequenceCount];
[self.navigationController pushViewController:ObjVideoController animated:NO];
[ObjVideoController play:[AnimationArr objectAtIndex:SequenceCount]];
Play method in VideoController is like this:
- (void)play:(NSString*)videoFile {
playbaktime = 0.0;
NSBundle *main = [NSBundle mainBundle];
NSURL *url = [NSURL fileURLWithPath:[[main resourcePath] stringByAppendingPathComponent:videoFile]];
if (!self.ctl)
{
self.ctl = nil;
self.ctl = [[MPMoviePlayerController alloc]init];
[self.view addSubview:self.ctl.view];
}
[self.ctl prepareToPlay];
self.ctl.contentURL = url;
self.ctl.controlStyle = 0;
//self.ctl.useApplicationAudioSession = NO;
[self.ctl.view setUserInteractionEnabled:NO];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
[self.ctl.view setFrame:CGRectMake(0,0,480,320)];
else
[self.ctl.view setFrame:CGRectMake(0,0,1024,768)];
self.ctl.backgroundView.backgroundColor = [UIColor whiteColor];
[self.ctl play];
}
Now observer has been add for UIApplicationWillResignActive, UIApplicationDidBecomeActive. Its selector are given below:
-(void)Pause_Video:(NSNotification*)notification {
Pausevideo = 1;
playbaktime = self.ctl.currentPlaybackTime;
[self.ctl pause];
}
-(void)Play_Video:(NSNotification*)notification {
if(self.ctl.loadState == MPMovieLoadStateUnknown)
{
[self play:self.strVideoURL];
Pausevideo = 0;
}
else{
if (self.ctl.playbackState == MPMoviePlaybackStatePaused) {
[self.ctl play];
Pausevideo = 0;
}
else
{
[self.ctl setInitialPlaybackTime:playbaktime];
[self.ctl play];
Pausevideo = 0;
}
}
}
Hope u understood question and help will be appreciated.
You seem to be calling prepareToPlay every time before playing, this is not necessary, just call it once when you load the file. There is no need to call it after you pause.
Additionally, you are incurring a lag because notifications do not fire instantly. Specifically, UIApplicationDidBecomeActive only fires well after the app has entered the foreground. What you want instead is to observe the UIApplicationWillEnterForegroundNotification, which will fire as the app is entering the foreground, and allow you to decrease the lag significantly.

How to get done button action in Movie Player when loading in webView?

I am new to Iphone and trying to play video in webView. So, in that i need to have the done button action. Pls help me out.
If the video is played in UIWebView then you can access when the video is played and when Done button is pressed using the following code along with the code you have used to load url in webview
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(videoPlayStarted:) name:#"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(videoPlayFinished:) name:#"UIMoviePlayerControllerDidExitFullscreenNotification" object:nil];
And it can be accessed through
BOOL isVideoInFullScreenMode;//Temperory added here.. must be in .h file or at teh top if required
-(void)videoPlayStarted:(NSNotification *)notification{
//Your stuff here
isVideoInFullScreenMode = YES;
}
-(void)videoPlayFinished:(NSNotification *)notification{
//Your stuffs here
isVideoInFullScreenMode = NO;
}
Finally i found solution for Done button action...This logic is working for both ipad & iphone also..
I solved this prob by using UIGestureRecognizer.
Here is my code.
firstly i am adding notification for EnterFullScreen by using
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(youTubeStarted:) name:#"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];
Now in youTubeStarted method i added this Code..
-(void)youTubeStarted:(NSNotification *)notification{
if (!tapRecognizer1) {
tapRecognizer1 = [[UITapGestureRecognizer alloc] init];
tapRecognizer1.delegate = self;
[tapRecognizer1 setNumberOfTapsRequired:1];
[self.view addGestureRecognizer:tapRecognizer1];
}
}
This will add TapRecognizer to the movieplayer..
And now the following delegate method will check that it is done button or not
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
NSLog(#"%#",touch.description);
NSString *ViewName = touch.description ;
CGPoint location = [touch locationInView:touch.view];
NSLog(#"location x===%f , location y=== %f",location.x,location.y);
NSRange Check = [ViewName rangeOfString:#"UINavigationButton"];
NSRange checkFrameOfButton;
if (UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPad) {
checkFrameOfButton = [ViewName rangeOfString:#"frame = (7 7; 50 30)"];
if (checkFrameOfButton.length <= 0) {
checkFrameOfButton = [ViewName rangeOfString:#"frame = (7 7; 51 30)"];
}
}
else {
checkFrameOfButton = [ViewName rangeOfString:#"frame = (5 7; 48 30)"];
if (Check.length<=0) {
Check = [ViewName rangeOfString:#"UIView"];
}
}
if (location.y<40 && location.x<85 && Check.length>0 && checkFrameOfButton.length>0) {
[self endplaying];
}
return YES;
}
Now in endplaying method you can do what you want...

Background Audio - Image on Lock Screen

There is a way to add an image to the lock screen for Background Audio, along with setting the Track and Artist name. It was also mentioned in a WWDC 2011 video, but nothing specific to go off of. I have looked everywhere in the docs and cannot find it. I know it is an iOS5 only thing, and Spotify's newest version has this feature. Does anyone know where they can point me in the right direction?
Thank You,
Matthew
Here's an answer I found for you:
(1) You must handle remote control events. You can't be the Now
Playing app unless you do. (See the AudioMixer (MixerHost) sample) code.)
(2) Set the Now Playing info:
MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
infoCenter.nowPlayingInfo =
[NSDictionary dictionaryWithObjectsAndKeys:#"my title", MPMediaItemPropertyTitle,
#"my artist", MPMediaItemPropertyArtist,
nil];
This is independent of whichever API you are using to play audio or
video.
as per Michaels answer above, simply append
#{MPMediaItemPropertyArtwork: [[MPMediaItemArtwork alloc] initWithImage:[UIImage ...]]}
to the nowPlayingInfo dict
the full options of available keys are ...
// MPMediaItemPropertyAlbumTitle
// MPMediaItemPropertyAlbumTrackCount
// MPMediaItemPropertyAlbumTrackNumber
// MPMediaItemPropertyArtist
// MPMediaItemPropertyArtwork
// MPMediaItemPropertyComposer
// MPMediaItemPropertyDiscCount
// MPMediaItemPropertyDiscNumber
// MPMediaItemPropertyGenre
// MPMediaItemPropertyPersistentID
// MPMediaItemPropertyPlaybackDuration
// MPMediaItemPropertyTitle
To make controls work....
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlPlay:
[player play];
break;
case UIEventSubtypeRemoteControlPause:
[player pause];
break;
case UIEventSubtypeRemoteControlTogglePlayPause:
if (player.playbackState == MPMoviePlaybackStatePlaying) {
[player pause];
}
else {
[player play];
}
break;
default:
break;
}
}
}
It only works on a real iOS Device, not on the simulator

MPMoviePlayerViewController repeatMode not working?

I have a problem with MPMoviePlayerViewController and it's property repeatMode. It's stated that setting it to a MPMovieRepeatModeOne value will cause player to repeat playback. I use following code to play video in a loop but it just stops after the end.
MPMoviePlayerViewController *mpViewController =[[MPMoviePlayerViewController alloc] init];
mpViewController.moviePlayer.contentURL= movieURL;
self.aPlayer=mpViewController;
self.aPlayer.moviePlayer.repeatMode=MPMovieRepeatModeOne;
mpViewController.repeatMode=MPMovieRepeatModeOne;
worked for me but I did not have the url line or the self.'s
My next line after the above was [mpViewController play];
Remove this line
[self.aPlayer.moviePlayer setRepeatMode:MPMovieRepeatModeOne];
and put:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerDidChangeState:)
name:MPMoviePlayerPlaybackStateDidChangeNotification
object:self.player];
and implement
- (void)moviePlayerDidChangeState:(NSNotification *)note
{
MPMoviePlaybackState playbackState = [self.player playbackState];
if(playbackState==MPMoviePlaybackStateStopped ||playbackState==MPMoviePlaybackStatePaused || playbackState==MPMoviePlaybackStateInterrupted)
{
if (note.object == self.player) {
NSInteger reason = [[note.userInfo objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] integerValue];
if (reason == MPMovieFinishReasonPlaybackEnded)
{
[self.player play];
}
}
}
}