I'm displaying a short clip fullscreen, and at the end I would like NOT to dismiss the MPMoviePlayerController, as is the default behaviour, but to simple show the controls and allow the user to tap "Done" when they are ready to move on.
So far I managed to disable the auto-dismiss with the help of this answer: https://stackoverflow.com/a/19596598/362657 now I just need to show the controls.
I have the following:
-(void)videoFinished:(NSNotification*)aNotification{
int value = [[aNotification.userInfo valueForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
if (value == MPMovieFinishReasonUserExited) { // user tapped Done
[self dismissMoviePlayerViewControllerAnimated];
} else { // movie finished playing
MPMoviePlayerController *player = aNotification.object;
// do something to show the controls, but what?
}
}
I tried setting the controlStyle to none and then back to fullscreen, but that still has no effect until user taps the screen. Is there any way to trigger the display of the movie controls programmatically?
Related
I have a UITableViewCell. It has 2 AVPlayers, 2 AVPlayerItems, and 2 AVPlayerLayers. One AVPlayer and its contents are on the right side of the cell, and one on the left side of the cell. I am using the AVAssetResourceLoader to cache and play videos at the same time. This works great, except the second video does not display. It does cache properly, because after scrolling the tableview, it plays correctly as it should from the cache. But just the AVPlayerLayer does not display the video content as it should.
The AVAssetResourceLoader can not handle 2 simultaneous downloads. Upon first loading the tableview, the video on the left starts to download/play. The code for playing the video on the right side is read immediately after the code for downloading/playing the video on the left side--meaning of course, that the download process has not yet finished. To fix this problem, I have created a timer, like so:
if (self.downloadInProgress) {
// download is in progress. wait to start next download
self.downloadQueueCount++;
NSDictionary *videoSettings = #{#"id": activity2.objectId,
#"activity": activity2, #"cell": videoCell, #"left": #NO}
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self selector:#selector(DownloadNextVideo:)
userInfo:videoSettings repeats:YES];
} else {
[self playStreamingVideoOnRightInCell:videoCell withActivity:activity2];
}
And then the selector method:
- (void)DownloadNextVideo:(NSTimer *)timer {
if (self.downloadInProgress == NO) {
NSString *activityId = [[timer userInfo] objectForKey:#"id"];
self.videoChallengeID1 = activityId;
CompletedTableViewCell *videoCell = (CompletedTableViewCell *)[[timer userInfo]objectForKey:#"cell"];
Activity *activity = [[timer userInfo] objectForKey:#"activity"];
BOOL left = [[timer userInfo] objectForKey:#"left"];
[self.timer invalidate];
if (left) {
[self playStreamingVideoOnLeftInCell:videoCell withActivity:activity];
} else {
[self playStreamingVideoOnRightInCell:videoCell withActivity:activity];
}
}
}
Basically, this works correctly--the AVAssetResourceLoader gets the correct URL, correct activityID, and downloads/caches the correct video--except the video does not play as it downloads. I changed background colors, and I can confirm that the AVPlayerLayer is indeed present in the frame it should be. Just no video shows up.
I tried switching left and right videos and download order--and in that case, the right video played, and the left video didn't play. Basically, whichever video has to use this timer and continually check if a download is in progress or not, is the video that does not display correctly, and that makes me think it is something I am doing wrong with the timer concerning scope/passing variables.
One last detail, in case it may help. If I say the following (instead of the first method I had here):
if (self.downloadInProgress) {
[self playStreamingVideoOnRightInCell:videoCell withActivity:activity2];
}
Then, the playerLayer DOES show video--but it's the wrong video. It shows the same video as in the left side of the cell.
This has been a nightmare to debug, and I am fairly certain at this point that it has something to do with using a timer here to play the video instead of doing it inside the regular scope. Anyone have any ideas what I could be doing wrong?
In order to fix the proper video playing, you need to change the following. You are using the NSNumber value instead of a BOOL for the left value.
Your code:
BOOL left = [[timer userInfo] objectForKey:#"left"];
Should be:
BOOL left = [[[timer userInfo] objectForKey:#"left"] boolValue];
Using an MPMoviePlayerController.view as a background (think spotify). A user can tap login or signup and they are taken to the appropriate viewController, which has a clear background so that the moviePlayer.view remains as the background (i.e., user continues to see the video regardless of the currently active viewController) throughout the flow.
On some viewControllers the form needs to be lifted up so that the keyboard doesn't cover the field. I'm doing this using a transform.
The background video of the moviePlayer is set to repeat, so the video is on a continuous loop. Each time the video resets (video status goes from 1 to 2 - paused to playing) the transform resets in the child viewControllers. My initial thought was that the view was being redrawn, but this doesn't appear to be the case based on logs (I put nslogs in the drawRect of the views but it's only ever called once at instantiation).
Has anyone come across this?
My setup in the root viewController:
// lazy load moviePlayer
-(MPMoviePlayerController *)moviePlayer
{
if (_moviePlayer) return _moviePlayer;
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:#"resources.bundle/videos/auth_bg" withExtension:#"mp4"];
_moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:videoURL];
_moviePlayer.controlStyle = MPMovieControlStyleNone;
_moviePlayer.scalingMode = MPMovieScalingModeAspectFill;
_moviePlayer.repeatMode = MPMovieRepeatModeOne;
_moviePlayer.shouldAutoplay = true;
return _moviePlayer;
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.moviePlayer.view.frame = self.view.frame;
self.moviePlayer.view.hidden = false;
// 'still' is an imageView of the first frame to show while video loading
[self.navigationController.view insertSubview:self.moviePlayer.view aboveSubview:still];
}
I suspect this has to do with Autolayout -- I found a few other questions where views were being reset (one example here: https://stackoverflow.com/a/17849584/1542275) ... My solution was to adjust the layout constraint constants as opposed to transforming the view coordinates. Things are now staying put.
All of that said, I'm still not sure why the video restart is resetting the transforms.
I have a button with an image, and after a little while the button image will change, then change back after a few seconds. I want to be able to tell if the button is clicked while the image is different. thanks!
You have several options, but I'll detail two of them. The first is more self-contained and foolproof, but the second is arguably easier to read and understand.
Check the background image
The easiest way to do this would probably be to just test against the image itself. The image changes from time to time, but you don't really care when the button is pressed, you just care which background is visible when it is.
In other words, all you really need to know is whether the button's background is the MainBackground or the AlternateBackground, so when the button is pressed, you can simply check which one it is.
Try something like this, for when the button is pressed:
-(void)buttonPressed:(UIButton*)sender {
UIImage *mainBackground = [UIImage imageNamed:#"YOUR_IMAGE_NAME"];
NSData *imgdata1 = UIImagePNGRepresentation(sender.image);
NSData *imgdata2 = UIImagePNGRepresentation(mainBackground);
if ([imgdata1 isEqualToData:imgdata2]) {
// The button's background is the MainBackground, do one thing
} else {
// The button's background is the AlternateBackground, do another thing
}
}
Keep track of which image is currently displayed
Alternatively, you could have a BOOL value flip whenever the image's background is changed. Something like...
#property BOOL isMainBackground;
...in your H file, and then whenever you set the button's background image, you also set self.isMainBackground = YES; or self.isMainBackground = NO;
Then, your method for the button press would look something like this:
-(void)buttonPressed:(UIButton*)sender {
if (self.isMainBackground) {
// The button's background is the MainBackground, do one thing
} else {
// The button's background is the AlternateBackground, do another thing
}
}
So i am creating a tic tac toe game (user vs computer) and i want to have -(IBAction) statements with if statements thats would say if(buttonIsPressed), do ____. I cant just say
-(IBAction)button:(id)selector;{
//stuff i want
}
because the buttons are pressed in different order and the computer would have different responses (i want there to be an order). For example, this is what i tried,
-(IBAction)move2;{
if (buttona2){
c2.image = [UIImage imageNamed:#"imgres-3.jpg"];
}
it didnt work because the the result of this if statement ( c2.image = [UIImage imageNamed:#"imgres-3.jpg"];) would start automatically and not run depending on the pressing of the button (it runs right away). Pretty much i want the if statement to run on press of a button (or not work if the button is not pressed.
Can anyone help?
If I understand your question right, you want to check if a button is toggled on or off before running the if statement right? In that case, I would declare a BOOL in your header file
#property BOOL toggle;
Then in your implementation file when the user presses the button the target of the button should have the code:
if(self.toggle == TRUE){
self.toggle = FALSE;
//and any visual UI change you want to show that the button is now off
}else{
self.toggle = TRUE;
//and any visual change you want to show that the button is now on
}
Then in your -(IBAction) your code becomes:
-(IBAction)move2;{
if (self.toggle){
c2.image = [UIImage imageNamed:#"imgres-3.jpg"];
}
}
And that should work.
I havn't found the way how to rewind song from lock screen iOS 7. Volume slider is available, all the buttons work correctly but the upper slider is not available!
AVPlayer is the class I'm using.
Any help is appreciated!
You'll need to set this in the "now-playing info".
Whenever the item (track) changes, do something like
NSMutableDictionary *nowPlayingInfo = [[NSMutableDictionary alloc] init];
[nowPlayingInfo setObject:track.artistName forKey:MPMediaItemPropertyArtist];
[nowPlayingInfo setObject:track.trackTitle forKey:MPMediaItemPropertyTitle];
...
[nowPlayingInfo setObject:[NSNumber numberWithDouble:self.player.rate] forKey:MPNowPlayingInfoPropertyPlaybackRate];
[nowPlayingInfo setObject:[NSNumber numberWithDouble:self.currentPlaybackTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlayingInfo;
Whenever the playback rate changes (play/pause), do
NSMutableDictionary *nowPlayingInfo = [[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo mutableCopy];
[nowPlayingInfo setObject:[NSNumber numberWithDouble:self.player.rate] forKey:MPNowPlayingInfoPropertyPlaybackRate];
[nowPlayingInfo setObject:[NSNumber numberWithDouble:self.currentPlaybackTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlayingInfo;
You can get the current playback time like this:
- (NSTimeInterval)currentPlaybackTime {
CMTime time = self.player.currentTime;
if (CMTIME_IS_VALID(time)) {
return time.value / time.timescale;
}
return 0;
}
You have to register for different events like below in swift
MPRemoteCommandCenter.shared().playCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().nextTrackCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().previousTrackCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().stopCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().pauseCommand.addTarget(self, action: #selector(self.handleRemoteCommandActions))
MPRemoteCommandCenter.shared().changePlaybackPositionCommand.isEnabled = true
MPRemoteCommandCenter.shared().changePlaybackPositionCommand.addTarget(self, action: #selector(self.handleChangePlaybackPositionRemoteCommandActions))
let rcc = MPRemoteCommandCenter.shared()
let skipBackwardIntervalCommand: MPSkipIntervalCommand? = rcc.skipBackwardCommand
skipBackwardIntervalCommand?.isEnabled = false
let skipForwardIntervalCommand: MPSkipIntervalCommand? = rcc.skipForwardCommand
skipForwardIntervalCommand?.isEnabled = false
let seekForwardCommand: MPRemoteCommand? = rcc.seekForwardCommand
seekForwardCommand?.isEnabled = true
rcc.changePlaybackPositionCommand.isEnabled = true
rcc.changePlaybackRateCommand.isEnabled = true
rcc.ratingCommand.isEnabled = true
rcc.playCommand.isEnabled = true
rcc.togglePlayPauseCommand.isEnabled = true
here handleChangePlaybackPositionRemoteCommandActions this method will be your method which will have to manage seeking of song when scrubber (upper slider) changes its value
it will look something like below:-
#objc func handleChangePlaybackPositionRemoteCommandActions(event:MPChangePlaybackPositionCommandEvent) -> MPRemoteCommandHandlerStatus
{
print("handleChangePlaybackPositionRemoteCommandActions : \(event.positionTime)")
self.appDelegate.audioPlayer?.seek(to: CMTime(seconds: event.positionTime, preferredTimescale: (self.appDelegate.audioPlayer?.currentItem?.currentTime().timescale)!))
MPNowPlayingInfoCenter.default().nowPlayingInfo![MPNowPlayingInfoPropertyElapsedPlaybackTime] = event.positionTime
return MPRemoteCommandHandlerStatus.success
}
I can not seem to find a way to add the functionality of dragging the progress bar to scrub through the currently playing song either, however getting the progress bar to show is partly covered by Chris above, but you have to pass the song's duration in as well as the other information to get the progress bar to show up. So in addition to what Chris pointed out, you need to add the following to the NSMutableDictionary:
[nowPlayingInfo setObject:[track valueForProperty: MPMediaItemPropertyPlaybackDuration]
forKey:MPMediaItemPropertyPlaybackDuration];
Also, you can handle a long press of the forward and back buttons and scroll your music. In the:
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent
you can look for these event subtypes:
if (receivedEvent.subtype == UIEventSubtypeRemoteControlBeginSeekingBackward){};
if (receivedEvent.subtype == UIEventSubtypeRemoteControlBeginSeekingForward){};
if (receivedEvent.subtype == UIEventSubtypeRemoteControlEndSeekingBackward){};
if (receivedEvent.subtype == UIEventSubtypeRemoteControlEndSeekingForward){};
The BeginSeekingForward and BeginSeekingBackward are triggered by long presses of the forward and back buttons respectively, and of course the EndSeekingForward and EndSeekingBackward event subtypes are when the finger is lifted off the button.
When you get these event subtypes, pass a new NSDictionary to
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo
with all of the information that was already in it (otherwise properties that you do not pass in will be cleared) plus a new MPNowPlayingInfoPropertyElapsedPlaybackTime and MPNowPlayingInfoPropertyPlaybackRate. For the rate, you decide how fast you want to scroll the audio. 1 is normal speed, negative values play backwards, so -2 is 2x normal speed in reverse. You will have to set the playback rate of the MPNowPlayingInfoCenter to be the same as the playback rate of the AVPlayer or they will not line up. This is why you want to also pass in the current time. You can get this with:
[player currentTime]
so to set the current time and rate:
[nowPlayingInfo setObject:[player currentTime]
forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[nowPlayingInfo setObject:[[NSNumber alloc] initWithFloat:10]
forKey:MPNowPlayingInfoPropertyPlaybackRate];
and set the rate of the player with:
[player setRate:10]
This should line up both the player and the progress bar while scrolling with a 10x forward scroll speed. So you would do this when you get the BeginSeekingForward event subtype. When the scrolling ends, set the rate back to one, but still pass in the time and other information to the information center. For BeginSeekingBackward, just use a negative number for the rate.
I know this is an old post, but there was not a good solution and I ran across this thread while searching for how to get music information on to the lock screen. I was trying to implement this functionality in C# with Xamarin iOS and found a nice guide and sample Objective-C app with some of this functionality at http://www.sagorin.org/ios-playing-audio-in-background-audio/. But it did not have the scrolling functionality, nor did it make use of the information center, it just covered handling pause/play from the lock screen. I made a sample app in C# with Xamarin that demonstrates supplying info to the information center and scrolling with the back and forward buttons from the lock and control screens, which you can get here: https://github.com/jgold6/Xamarin-iOS-LockScreenAudio
I hope someone finds this helpful.
Here's how you do this thing with the timeline slider:
First of all the nowPlayingInfo must be set right. You can find how to do that anywhere (don't forget the key MPNowPlayingInfoPropertyPlaybackRate);
Second is to define the action for 'sliding':
MPChangePlaybackPositionCommand *changePlaybackPositionCommand = [[MPRemoteCommandCenter sharedCommandCenter] changePlaybackPositionCommand];
[changePlaybackPositionCommand addTarget:self action:#selector(changePlaybackPositionEvent:)];
[[MPRemoteCommandCenter sharedCommandCenter].changePlaybackPositionCommand setEnabled:YES];
Inside the action you can do some additional things, but the main thing here is
[yourPlayer seekTo:event.positionTime completionHandler:^(BOOL finished) {
[self updatePlaybackRateMetadata];
}];
updatePlaybackRateMetadata method has to update MPNowPlayingInfoPropertyElapsedPlaybackTime and MPNowPlayingInfoPropertyPlaybackRate.
A lot of time passed, but i hope this will help someone!