I'm trying to create an app which plays videos from a file using AVFoundation. The videos are shown in a view accessed by tapping on a row in a parent tableview. The real app will have a video for each row, but at the moment I'm using just one for testing.
When run on the simulator the app is ok, but when run on the device (under ios 5.1) the video plays ok for about 5 times, then crashes unpredictably in a variety of ways.
Most commonly, the video view loads but the video itself doesn't play, but sometimes
I get an EXC_BAD_ACCESS on a coremedia.remote thread, complaining about objects being allocated with no autorelease pool in place. I've added an #autoreleasepool block wrapping the code launching the AVPlayer, but this doesn't seem to help.
I'm wondering whether what is happening is that GCD is creating multiple threads on the main queue to play the items, but they are not terminating.
So the key question is- how do I clear up the superfluous GCD threads the AVPlayer is running on
if the user hits the back button in the video view
As far as possible I've followed the example code provided in Apple's AVFoundation documentation here
I've added some logging and (as mentioned above) an #autoreleasepool block inside one of the GCD blocks- other than that I haven't changed the code.
The viewDidLoad method is as follows:
-(void)viewDidLoad{
[super viewDidLoad];
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:#"TestLapCar2Vid" withExtension:#"m4v"];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
NSString *tracksKey = #"tracks";
[asset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:tracksKey] completionHandler:
^{
dispatch_async(dispatch_get_main_queue(),
^{
#autoreleasepool {
NSError *error = nil;
AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];
if(status == AVKeyValueStatusLoaded){
avPlayerItem = [AVPlayerItem playerItemWithAsset:asset];
[avPlayerItem addObserver:self forKeyPath:#"status"
options:0 context:&ItemStatusContext];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification object:avPlayerItem];
avPlayer = [AVPlayer playerWithPlayerItem:avPlayerItem];
[videoView setPlayer:avPlayer];
NSLog(#"Asset loaded");
[avPlayer play];
}
else{
NSLog(#"The asset's tracks were not loaded");
}
}
});
}];
}
The viewWillDisappear method is:
-(void)viewWillDisappear:(BOOL)animated{
NSLog(#"view will disappear called");
[super viewWillDisappear:animated];
dispatch_async(dispatch_get_main_queue(),
^{
[avPlayer pause];
[avPlayerItem removeObserver:self forKeyPath:#"status"];
[[NSNotificationCenter defaultCenter]removeObserver:self];
NSLog(#"Race timeline nav controller has %d sub controllers",self.navigationController.childViewControllers.count);
avPlayerItem = nil;
avPlayer = nil;
videoView = nil;
dataStore = nil;
pkReader = nil;
receivedData = nil;
revDial = nil;
speedDial = nil;
mapView = nil;
throttle = nil;
NSLog(#"releasing stuff");
});
}
I've been struggling with this for most of today- any help would be gratefully received
You should remove from superview first as it will reduce the retain count by 1 and ARC will take care of the release for your code.
like this
[videoView removeFromSuperview];
[self setVideoView:nil];
May be you leave your videoView retained in some place? Because if you do, your avPlayerItem and avPlayer stay alive and according to this topic you came up to iOS limitation for "render pipeline" with 4 videos staying in memory.
Remember that setting var to nil does not actually release underlying object. So your
videoView = nil;
can have zero effect.
Related
For MacOS, not iOS. I am playing a video in an AVPlayerView in an XIB. Just experimenting with video playback. I would like to be able to choose a selection from the File Menu (For instance File / Videos / Video 1, Video2, Video3, etc) and change the currently playing video in the XIB when I select the menu item.
Currently I am using this to play a video:
- (void)windowDidChangeOcclusionState:(NSNotification *)notification
{
if (self.window.occlusionState & NSWindowOcclusionStateVisible)
{
loopPlayer = YES;
[_aspectView setAspectRatio:NSMakeSize(16, 9)];
NSBundle *mb = [NSBundle mainBundle];
NSURL *demoURL = [mb URLForResource:#"Video1" withExtension:#"mp4"];
player = [[AVPlayer alloc] initWithURL:demoURL];
self.playerView.player = player;
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(movieEndDetected:)
name:#"AVPlayerItemDidPlayToEndTimeNotification"
object:player.currentItem];
[player play];
}
else
{
[player pause];
[player seekToTime:kCMTimeZero];
}
}
and to loop the video playing:
- (void) movieEndDetected:(NSNotification *) note
{
if (loopPlayer) {
[player seekToTime:kCMTimeZero];
[player play];
}
}
But I would like to change the playing video in the XIB on the fly by choosing a menu button. Any ideas how this can be done? I have heard of AVPlayerQueue maybe working for this kind of thing, but I am very new and can't get it working on MacOS.
My solution was to use many avplayer views in xibs and when one was open from a menu item, close another.
- (void)Visual02Menu:(id)sender {
if (!visual02Window) {
visual02Window = [[Visual02 alloc] initWithWindowNibName:#"Visual02"]; }
[visual01Window close];
[visual02Window showWindow:self];
I have a settings panel in my app. Whenever the user presses a button, the object is updated on another thread as the UI updates. I have a separate label on the main view that is supposed to update the object count when the object has finished updating (which I want to happen regardless of whether the settings panel is up or down). I've tried following the apple documentation regarding this very topic, but it doesn't seem to work out for me - that is, it seems that the main view controller never receives the notification for some reason. Does anyone have any suggestions on how to alert the main view controller that an object passed to another thread has finished updating? Here's the code I'm using (most of which was copied from that doc):
Object Class:
[[NSNotificationCenter defaultCenter] postNotificationName: #"ScaleCountUpdated" object: self];
Main View Controller
- (void)setUpThreadingSupport
{
if (self.notifications) {
return;
}
self.notifications = [[NSMutableArray alloc] init];
self.notificationLock = [[NSLock alloc] init];
self.notificationThread = [NSThread currentThread];
self.notificationPort = [[NSMachPort alloc] init];
[self.notificationPort setDelegate: self];
[[NSRunLoop currentRunLoop] addPort: self.notificationPort
forMode: (NSString *)kCFRunLoopCommonModes];
}
- (void)handleMachMessage:(void *)msg
{
[self.notificationLock lock];
while ([self.notifications count]) {
NSNotification *notification = [self.notifications objectAtIndex: 0];
[self.notifications removeObjectAtIndex: 0];
[self.notificationLock unlock];
[self processNotification: notification];
[self.notificationLock lock];
};
[self.notificationLock unlock];
}
- (void)processNotification:(NSNotification *)notification{
if ([NSThread currentThread] != self.notificationThread) {
// Forward the notification to the correct thread.
[self.notificationLock lock];
[self.notifications addObject: notification];
[self.notificationLock unlock];
[self.notificationPort sendBeforeDate: [NSDate date]
components: nil
from: nil
reserved: 0];
} else {
[self updateScaleCount];
}
}
- (void)updateScaleCount
{
NSLog(#"[ScalesViewController - updateScaleCount]: Scales updated from notification center.");
if([UserDefinedScales areScalesGrouped] == YES){
self.groupCountLabel.text = [NSString stringWithFormat: #"Group Count: %i", [[UserDefinedScales sortedKeys] count]];
} else {
self.groupCountLabel.text = #"Group Count: 1";
}
self.scaleCountLabel.text = [NSString stringWithFormat: #"Scale Count: %i", [UserDefinedScales scaleCount]];
}
Main View Controller - View Did Load:
[self setUpThreadingSupport];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(processNotification:)
name: #"ScaleCountUpdated"
object: nil];
If you have any suggestions on how to alter this code to make it function correctly, or have another solution to offer for achieving this, it would be greatly appreciated! Thank you.
It looks to me like you are doing it correctly, i.e. register for notification and send it.
As far as I can see from your code and the information you give, you can basically completely forget about the setupThreadingSupport. You should definitely test it without it. Not sure what you want to achieve, but looks like overkill where probably a simple block would suffice. Is there a compelling reason to listen to the notification on a background thread? Why not let the notification center decide?
Log the sending and receiving of the notifications - addObserver and postNotification is really all this mechanism needs to work as expected.
I would go to a simpler implementation. The NSNotificationCenter already provides all the mechanisms you need to broadcast and receive messages across your app.
If you fire the notification from a background thread you can use a GCD dispatch_async to make make it delivered on the main thread.
Object Class
dispatch_async(dispatch_get_main_queue(), ^{
// You don't need to pass the object itself here as you are not using it later.
[[NSNotificationCenter defaultCenter] postNotificationName:#"ScaleCountUpdated"];
}
MainViewController
-(void)viewDidLoad
{
[super viewDidLoad];
// Register your controller as an observer for a specific message name
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(updateScaleCount)
name: #"ScaleCountUpdated"
object: nil];
}
- (void)updateScaleCount
{
NSLog(#"[ScalesViewController - updateScaleCount]: Scales updated from notification center.");
if([UserDefinedScales areScalesGrouped] == YES){
self.groupCountLabel.text = [NSString stringWithFormat: #"Group Count: %i", [[UserDefinedScales sortedKeys] count]];
} else {
self.groupCountLabel.text = #"Group Count: 1";
}
self.scaleCountLabel.text = [NSString stringWithFormat: #"Scale Count: %i", [UserDefinedScales scaleCount]];
}
I am placing an MPMoviePlayerController in my view at a certain size and position. This is working fine. However, in certain circumstances, I want the video to play full screen. My view is part of a UISPlitViewController, so if I just get the view width, it is not full screen. What I need is to show the video as if the user had clicked the double ended arrow in the player controls to maximise the video.
Here is my code so far. Can anyone fill in the missing bit that forces the full screen playing?
- (void)viewDidLoad
{
[super viewDidLoad];
// FIND OUT HOW MANY VIDEOS ARE AVAILABLE
int videoCount = [[self videos] count];
// GET THE FILE NAME OF THE FIRST AVAILABLE VIDEO
NSString* fileName = [NSString stringWithFormat:#"%#.mp4", [self videos] objectAtIndex:0]];
NSArray *arrayPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString *filePath = [path stringByAppendingPathComponent:fileName];
NSURL *url = [NSURL fileURLWithPath:filePath];
// PLACE THE MOVIE AT THE CORRECT LOCATION ON THE PAGE
moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
[[moviePlayer view] setFrame:CGRectMake(100, 100, 600, 360)];
[[moviePlayer view] setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[[self view] addSubview:moviePlayer.view];
[moviePlayer play];
if(videoCount == 1)
{
-- MAXIMISE THE VIDEO TO FULL SCREEN AND LANDSCAPE --
}
}
Thanks
As per Apple's documentation, you just have to set the `MPMoviePlayerController to go full screen:
[moviePlayer setFullscreen:YES animated:YES]
Have you tried this?
Please use MPMoviePlayerViewController Because of MP4 file. When you use MOV then working perfect!!
MPMoviePlayerViewController *moviePlayerViewController;
-(void)PlayVedioController:(NSString*)videoUrl1
{
NSURL *fileURL = [NSURL URLWithString:videoUrl1];
moviePlayerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:fileURL];
// Register for the playback finished notification.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMovieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:moviePlayerViewController.moviePlayer];
//Present
[self presentMoviePlayerViewControllerAnimated:moviePlayerViewController];
// Play the movie!
moviePlayerViewController.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
[moviePlayerViewController.moviePlayer play];
}
-(void)myMovieFinishedCallback:(NSNotification*)aNotification
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:moviePlayerViewController.moviePlayer];
[moviePlayerViewController release], moviePlayerViewController = nil;
}
In my project, I used MPMoviePlayerController to stream video from an http url. It plays fullscreen. When the video is playing, if you tap on "Done" button the video stops and it disappears, but the problem is; if you pinch to close video screen the video screen disappears but it still plays, sound of video continues to play.
I tried to detect exit fullscreen notification and manually stop the video but it didn't work. My moviePlayerDidExitFullScreen method didn't called.
To control that if I am getting the notifications on the right way I tried to get another notification : MPMoviePlayerPlaybackStateDidChangeNotification, and it is working. It calls the method on video launching.
I searched many forums and Apple documentations but I couldn't find enough information.
Here is my code to open a fullscreen video and detect exit fullscreen :
- (void)openFullVideo
{
NSString* path = #"http://trtvizyon.mysys.com/test/leyla_ile_mecnun.mp4";
NSURL *fileURL = [NSURL URLWithString:path];
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentURL:fileURL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerDidExitFullScreen:) name:MPMoviePlayerDidExitFullscreenNotification object:nil];
player.controlStyle = MPMovieControlStyleDefault;
player.movieSourceType = MPMovieSourceTypeStreaming;
[self.view addSubview:player.view];
[player setFullscreen:YES animated:YES];
[player play];
}
- (void) moviePlayerDidExitFullScreen:(id)sender {
NSLog(#"moviePlayerDidExitFullScreen");
}
OK, I played with your code for a while, and finally shot that little bug in the gut.
Your first problem is not retaining the player object (assuming you are using ARC, if not, then skip this). So, just make sure you retain it as an instance variable for example:
//Header File
#interface ViewController : UIViewController {
MPMoviePlayerController* _player;
}
// Implementation File
- (void)openFullVideo {
// ...
_player = player;
}
Now, if that just works, then great!! But I am getting a dreaded unsolved bug on apple's side:
An AVPlayerItem can occupy only one position in a player's queue at a time
To solve this issue, do it like so:
NSString* path = #"http://trtvizyon.mysys.com/test/leyla_ile_mecnun.mp4";
NSURL *fileURL = [NSURL URLWithString:path];
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerDidExitFullScreen:) name:MPMoviePlayerDidExitFullscreenNotification object:nil];
player.controlStyle = MPMovieControlStyleDefault;
player.movieSourceType = MPMovieSourceTypeStreaming;
[self.view addSubview:player.view];
[player setContentURL:fileURL];
[player setInitialPlaybackTime:-1.f];
[player setFullscreen:YES animated:YES];
[player prepareToPlay];
[player play];
_player = player;
That should do it!
Some Other Friendly Advice:
Make sure you remove yourself from NSNotificationCenter before playing the movie again.
I would suggest adding something like if (_player != nil) to avoid recreating the object.
I am facing a problem in ipad video incorporating. My code works fine I mean it plays the video, but once the video reaches to its end. The callback method is not called.
This method is called when play video button is pressed.
-(IBAction) playVideo : (id) sender
{
[self initPlayingVideo:#"toyVid.mp4"];
}
This method handles the playing of video.
-(void) initPlayingVideo: (NSString *) videoFile
{
NSString *moviePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:videoFile];
theMovie = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL fileURLWithPath:moviePath]];
theMovie.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
theMovie.moviePlayer.controlStyle = MPMovieControlStyleFullscreen;
[self.view addSubview:theMovie.view];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(videoPlayerDidFinishPlaying
name:MPMoviePlayerPlaybackDidFinishNotification
object:theMovie];
videoPlayer = [theMovie moviePlayer];
[videoPlayer play];
}
This is the callback method.
-(void) videoPlayerDidFinishPlaying: (NSNotification*)aNotification
{
theMovie = [aNotification object];
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:theMovie.moviePlayer];
[videoPlayer stop];
[theMovie.moviePlayer release];
[videoPlayer release];
[theMovie.view removeFromSuperview];
}
Where I am doing mistake? Please guide.
Regards
Ranjan
Did you miss the : and ) in your selector? I guess ) may be your typo otherwise you cannot compile your codes. Your selection takes one parameter. It should be:
selector:#selector(videoPlayerDidFinishPlaying:)
That will match to your instance method. I guess you don't have one without parameter.