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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With