MPMoviePlayerController plays only when called twice. Only occurs in iOS 4 - ios

I have an app for iOS 4.0-5.1 that uses HTTP Live Streaming to play videos. I have a simple setup with a button in a view that starts playing the stream when it is tapped. This works fine in iOS 5 but the button needs to be tapped twice before the stream begins playing in iOS 4. Does anybody know why this is happening and how to make the video play on the first tap of the button?
Here is what I'm doing:
.h
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#interface ViewController : UIViewController{
MPMoviePlayerController *moviePlayer;
}
#property (nonatomic, strong) MPMoviePlayerController *moviePlayer;
-(IBAction)playApple:(id)sender;
#end
.m
-(IBAction)playApple:(id)sender{
if(self.moviePlayer){
self.moviePlayer = nil;
}
//Use Apple's sample stream
NSURL *mediaURL = [NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:mediaURL];
//Begin observing the moviePlayer's load state.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerLoadStateChanged:)
name:MPMoviePlayerLoadStateDidChangeNotification
object:self.moviePlayer];
[self.moviePlayer setMovieSourceType:MPMovieSourceTypeStreaming];
[self.moviePlayer setShouldAutoplay:NO];//Stop it from autoplaying
[self.moviePlayer prepareToPlay];//Start preparing the video
}
#pragma mark Notification Center
- (void)moviePlayerLoadStateChanged:(NSNotification *)notification{
NSLog(#"State changed to: %d\n", moviePlayer.loadState);
if(self.moviePlayer.loadState == MPMovieLoadStatePlayable){
//if load state is ready to play
[self.view addSubview:[self.moviePlayer view]];
[self.moviePlayer setFullscreen:YES];
[self.moviePlayer play];//play the video
}
}

I can see some possible issues - just try it out and let us know if any or all of this did the trick.
1. loadstate masking
The loadstate should not be directly compared but masked before comparing as it might contain a mixture of multiple states.
MPMovieLoadState
Constants describing the network load state of the movie player.
enum {
MPMovieLoadStateUnknown = 0,
MPMovieLoadStatePlayable = 1 << 0,
MPMovieLoadStatePlaythroughOK = 1 << 1,
MPMovieLoadStateStalled = 1 << 2,
};
typedef NSInteger MPMovieLoadState;
So instead of directly comparing it, as you are, mask it to be on the safe side.
2. view setup
Why not setting up the player view right before starting the playback. I have never seen it the way you do it (not that I was positively sure that this is the problem - still, seems odd to me). Additionally, even though the player will not actually use the view you are assigning (fullscreen mode does it a bit differently), you may want to enhance the view-setup with assigning a proper frame.
All of the above rolled into this new version of your code...
-(IBAction)playApple:(id)sender
{
if(self.moviePlayer)
{
self.moviePlayer = nil;
}
//Use Apple's sample stream
NSURL *mediaURL = [NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:mediaURL];
//Begin observing the moviePlayer's load state.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerLoadStateChanged:)
name:MPMoviePlayerLoadStateDidChangeNotification
object:self.moviePlayer];
self.moviePlayer.view.frame = self.view.bounds;
[self.view addSubview:[self.moviePlayer view]];
[self.moviePlayer setFullscreen:YES];
[self.moviePlayer setMovieSourceType:MPMovieSourceTypeStreaming];
[self.moviePlayer setShouldAutoplay:NO]; //Stop it from autoplaying
[self.moviePlayer prepareToPlay]; //Start preparing the video
}
- (void)moviePlayerLoadStateChanged:(NSNotification *)notification
{
NSLog(#"State changed to: %d\n", moviePlayer.loadState);
if((self.moviePlayer.loadState & MPMovieLoadStatePlayable) == MPMovieLoadStatePlayable)
{
//if load state is ready to play
[self.moviePlayer play];//play the video
}
}

Related

Looping video in iOS cause a small pause/flash (MPMoviePlayerController & AVPlayer)

I'm really going crazy with my welcome view controller.
I have a video in background in continuos loop but every solution that I used causes a small pause/flash when the video is finished and loop.
I use two solution: MPMoviePlayerController and AVPlayer from AVFoundation but I got the same result, a small white flash when video is looped for replay.
My MPMoviePlayerController solution (I prefer a fix for this)
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:#"welcome_video" withExtension:#"mp4"];
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:videoURL];
self.moviePlayer.controlStyle = MPMovieControlStyleNone;
self.moviePlayer.scalingMode = MPMovieScalingModeAspectFill;
self.moviePlayer.view.frame = self.view.frame;
[self.view insertSubview:self.moviePlayer.view atIndex:0];
[self.moviePlayer prepareToPlay];
// Loop video
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loopVideo) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
}
- (void)loopVideo
{
[self.moviePlayer play];
}
My AVPlayer solution
(void)viewDidLoad
{
[super viewDidLoad];
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&sessionError];
[[AVAudioSession sharedInstance] setActive:YES error:&sessionError];
//Set up player
NSURL *movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"welcome_video" ofType:#"mp4"]];
AVAsset *avAsset = [AVAsset assetWithURL:movieURL];
AVPlayerItem *avPlayerItem =[[AVPlayerItem alloc]initWithAsset:avAsset];
self.avplayer = [[AVPlayer alloc]initWithPlayerItem:avPlayerItem];
AVPlayerLayer *avPlayerLayer =[AVPlayerLayer playerLayerWithPlayer:self.avplayer];
[avPlayerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[avPlayerLayer setFrame:[[UIScreen mainScreen] bounds]];
[self.movieView.layer addSublayer:avPlayerLayer];
//Config player
[self.avplayer seekToTime:kCMTimeZero];
[self.avplayer setVolume:0.0f];
[self.avplayer setActionAtItemEnd:AVPlayerActionAtItemEndNone];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.avplayer currentItem]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerStartPlaying)
name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.avplayer pause];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.avplayer play];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
- (void)playerItemDidReachEnd:(NSNotification *)notification
{
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
- (void)playerStartPlaying
{
[self.avplayer play];
}
What's wrong with these implementation? I really try different fixes found on this site but nothing seems to work.
Any suggestions?
Hm.. I might have an idea for the AVPlayer-approach.
The AVPlayer has this method:
- (void)setRate:(float)rate
time:(CMTime)itemTime
atHostTime:(CMTime)hostClockTime
This means that you can specify WHEN you want the AVPlayer to start at kCMTimeZero. I have not actually used it, but I have an idea for how it can work.
You need to know exactly the moment you start the video the first time. I see you have your own -(void)playerStartPlaying which is called by the notificationCenter on didBecomeActive. I suggest using this method for app variations of [player play];, so put
[self playerStartPlaying];
inside viewDidAppear instead of [self.avplayer play];. It might be good enough.
If you here manage to find the device's hostClockTime, and add the length of the video, you should end up with the exact time when you want it to start from scratch. I am not testing any of this, and I'm typing from head, so you need to understand what I'm doing, and fix it yourself.
- (void)playerStartPlaying
{
//The device's hostClockTime. Basically a number indicating how long the device has been powered on.
CMTime hostClockTime = CMClockGetHostTimeClock;
//A CMTime indicating when you want the video to play the next time.
CMTime nextPlay = CMTimeAdd(hostClockTime, self.avplayer.currentItem.duration);
/* I don't know if that was correct or not, but you'll find out */
//Start playing if we're not already playing. There might be an avplayer.isPlaying or something, I don't know, this is probably working as well..
if(self.avplayer.rate != 1.0)
[self.avplayer play];
//Tell the player to restart the video at the correct time.
[self.avplayer setRate:1.0 time:kCMTimeZero atHostTime:nextPlay];
}
You'll have to remove the entire AVPlayerItemDidPlayToEndTimeNotification-thing. When the video has reached the end, it's already too late. What we're doing now is telling it when to play the second turn when we start the first. We want nothing to happen when didPlayToEndTime is fired, we're handling it manually.
So, if you understand what I have done above, you'll also notice that the video only will play twice. We tell the video to play, at the same time as we tell it to replay at time = now+videoLength. When that replay is done, nothing happens. It simply reaches end. To fix this, you'll need to somehow call -(void)playerStartPlaying at the same time as the setRate:time:atHostTime is executed on the AVPlayer. I guess you could start an NSTimer or dispatch_time and let it execute he method in exactly nextPlay-amount of time, but that would kinda defeat the purpose of this thing. Maybe not. You could try different stuff out. You probably CAN do this with some success, but I suggest finding a way to register for when the player started from the start. Maybe you can observe the rate or something, I don't know.. If you want to try it with a delayed method, you can try this:
double delayInSeconds = CMTimeGetSeconds(self.avplayer.currentItem.duration); //or something
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self playerStartPlaying];
});
Just keep in mind that this is some recursive shit, so even if you pause the video, this will still keep calling after that duration. In that case I suggest making a BOOL paused; and cancel the execution of the entire playerStartPlaying if it's set to YES. ..and of course set paused = YES; whenever you want to pause, next to wherever you say [player pause];
If this actually works, but you still get flashes, I know there are several ways to improve this. For instance, the CMTimeAdd() should probably be using some kind of synchronization-tool to make sure the times add up using the correct timeScale etc.
I have now spent way too much time writing this, and it might not even work. I have no idea. Good luck and good night.

How to play a video randomly without pressing the play button

I'm new in iPhone Application Development, and now i faced a problem, can any one tell me how can i play a video randomly without pressing the play button. The video should start automatically while the application launch and play randomly without stopping. I don't have any idea how to do this. I don't have any code also. Please help me any one...
Thanks in advanced.
You could implement it like this:
You need a movie player configured with e.g. a file url and add it to your view
NSURL *url = [[NSBundle mainBundle] URLForResource:movieName withExtension:#"mov"];
moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
moviePlayer.view.frame = // set the frame
[self.view addSubview:moviePlayer.view];
Start the movie player
[moviePlayer play];
Listen to the notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleMoviePlayerStatChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:moviePlayer];
Start the video again
- (void)handleMoviePlayerStatChange:(NSNotification *)note {
NSLog(#"mp playback state %i", moviePlayer.playbackState);
if (moviePlayer.playbackState == MPMoviePlaybackStatePaused) {
[moviePlayer play];
}
}
To hide the video controls set the controlStyle property of the movie player instance:
moviePlayer.controlStyle = MPMovieControlStyleNone;

iOS 7 MPMoviePlayerController seek forward button brings the video to the End and displays Black screen

I am facing an issue with MPMoviePlayerController in iOS 7. I enter the fullscreen and then click (just a single tap) on seek forward button (>>|) , and the video playback ends and gives a black screen with a text "Loading" on the header.
I registered notification for "MPMoviePlayerPlaybackStateDidChangeNotification".
**[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerPlaybackStateDidChange:)
name:MPMoviePlayerPlaybackStateDidChangeNotification
object:self.player];**
It does not get fired on a single click of seek forward button.
Also on registration of "MPMoviePlayerPlaybackDidFinishNotification"
**[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerPlaybackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];**
I get "MPMovieFinishReasonPlaybackEnded" event fired on that single click of seek forward button.
Any one knows the reason why? Is this a bug in apple?
I need to either stop this behavior of showing a black screen on single click , or just disable single click of seek forward button so that nothing happens.
Any one knows how to achieve this?
I fixed this by removing the MPMoviePlayer object completely, setting it to nil, removing it from it's superview and re-adding it using the original video Url. Code below:
- (void)addPlayerForUrl:(NSURL *)url {
self.player = [[MPMoviePlayerController alloc] initWithContentURL:url];
self.player.view.frame = self.videoView.bounds;
self.player.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.player.controlStyle = MPMovieControlStyleDefault;
[self.videoView insertSubview:self.player.view atIndex:0];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerLoadStateDidChangedNotification:)
name:MPMoviePlayerReadyForDisplayDidChangeNotification
object:self.player];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerPlaybackStateDidChangeNotification:)
name:MPMoviePlayerPlaybackStateDidChangeNotification
object:self.player];
}
#pragma mark - Notifications
- (void)moviePlayerLoadStateDidChangedNotification:(NSNotification *)notification {
self.isVideoPreloaded = YES;
self.videoPlayButton.hidden = YES;
self.photoImageView.hidden = YES;
self.videoLoadingImageView.hidden = YES;
}
- (void)moviePlayerPlaybackStateDidChangeNotification:(NSNotification *)notification {
NSURL *url = self.player.contentURL;
switch (self.player.playbackState) {
case MPMoviePlaybackStateSeekingBackward:
case MPMoviePlaybackStateSeekingForward:
break;
case MPMoviePlaybackStatePlaying:
self.videoPlayButton.hidden = YES;
if (!self.isVideoPreloaded) {
self.videoLoadingImageView.hidden = NO;
[self.videoLoadingImageView startAnimating];
} else {
self.videoLoadingImageView.hidden = YES;
}
break;
case MPMoviePlaybackStatePaused:
case MPMoviePlaybackStateStopped:
self.videoPlayButton.hidden = NO;
self.videoLoadingImageView.hidden = YES;
[self.player endSeeking];
[self.player.view removeFromSuperview];
[self.player setFullscreen:NO];
self.player = nil;
[self addPlayerForUrl:url];
break;
default:
break;
}
}
Notice how I keep the NSURL, right before the switch statement in the moviePlayerPlaybackStateDidChangeNotification. That way, I can re-initialize and re-add the MPMoviePlayer object.
Btw, my mpmovieplayer is on a tableviewCell if you're wondering. Hope this helps and let me know if you have questions. Good luck!
MPMoviePlayerLoadStateDidChangeNotification will be called when you single tap on the fast-forward or rewind button. You should check the loadState and just give it the path to your video and prepareToPlay again.
- (void)moviePlayerLoadStateChanged:(NSNotification *)notification {
MPMoviePlayerController *moviePlayer = notification.object;
MPMovieLoadState loadState = moviePlayer.loadState;
if(loadState == MPMovieLoadStateUnknown) {
moviePlayer.contentURL = [NSURL fileURLWithPath:videoPath]
[moviePlayer prepareToPlay];
}
.....
}
The reason you're getting MPMovieFinishReasonPlaybackEnded is because playback reached the end of the video (sorry if this is obvious). So it seem's your seek forward actions are seeking all the way to the end of the video. You can check the playback state with MPMoviePlaybackStateSeekingForward.
A quick solution could be to create your own forward button that seek's ahead by a specified time (ie. 5 seconds). But perhaps this isn't the functionality you're looking for.

MPMoviePlayer - multiple notifications

I'm trying to observe playback ended notifications so I can loop the video:
#property (nonatomic, strong) MPMoviePlayerController *moviePlayer;
...
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:theURL];
self.moviePlayer.controlStyle = MPMovieControlStyleNone;
[self.moviePlayer prepareToPlay];
self.moviePlayer.shouldAutoplay = YES;
[[NSNotificationCenter defaultCenter] addObserver: self selector:#selector(moviePlayBackDidFinish:) name: MPMoviePlayerPlaybackStateDidChangeNotification
object: self.moviePlayer];
- (void)moviePlayBackDidFinish:(NSNotification *)note {
if (note.object == self.moviePlayer) {
NSInteger reason = [[note.userInfo objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] integerValue];
if (reason == MPMovieFinishReasonPlaybackEnded) {
NSLog(#"THIS HAPPENS FOUR TIMES every time the movie ends");
[self.moviePlayer play];
}
}
}
As noted in the comment, I get this notification 4 times every time the video ends. The player still loops, but I don't like that I'm telling it to play 4 times.
Also, if I use MPMoviePlayerPlaybackDidFinishNotification instead of MPMoviePlayerPlaybackStateDidChangeNotification I only get notified once. However in that case the movie doesn't loop. This is all because the following doesn't work at all:
self.moviePlayer.repeatMode = MPMovieRepeatModeOne;
So my question is why do I get 4 MPMovieFinishReasonPlaybackEnded notifications every time the movie ends? Also is there a more foolproof way to loop videos?

MPMoviePlayerController - Split view controller should have its children set before layout

What is the appropriate point to send the play message to a MPMoviePlayerController instance instantiated in a splitView detail View Controller?
My app is receiving the above console message (with a !) but not crashing...
The app is utilizing MPMoviePlayerController to play a movie from an asset URL
and the responsilble method is called as follows:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startPlayingVideo:self];
}
It plays the video just fine but the console message is looming...
If I move the method call to viewWillAppear:animate:, the console message does not show up. The issue is now I can only hear audio and do not see the video.
For completeness, here is the called method...
- (void) startPlayingVideo:(id)sender
NSURL *movieURL = [NSURL URLWithString:self.movieURLString];
if (self.moviePlayer != nil){
[self stopPlayingVideo:nil];
}
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:movieURL ];
if (self.moviePlayer != nil){
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(videoHasFinishedPlaying:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:self.moviePlayer];
self.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
[self.moviePlayer prepareToPlay];
[self.moviePlayer play];
[self.view addSubview:self.moviePlayer.view];
[self.moviePlayer setFullscreen:YES animated:YES];
} else {
NSLog(#"Failed to instantiate the movie player.");
}
}
My original issue stemmed from having the MoviePlayerController as an entirely different viewController (embedded in a detail navigation controller). I redesigned the parent view to include a moviePlayer child view. This solved the issue.

Resources