So I'm using ARC in my project and when I add an AVPlayerLayer it works just fine and dandy, but when I pop the UIViewController from my UINavigationItem the video continues to play in the background. Does anyone know how you would handle this? It seems like something easy I'm just overlooking. Here's the code I have for the initially instantiations.
self.currentItem = [[AVPlayerItem alloc] initWithURL:url];
self.player = [[AVPlayer alloc]initWithPlayerItem:self.currentItem];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
self.avPlayerLayer.bounds = self.view.bounds;
self.avPlayerLayer.frame = CGRectMake(0,55, 1024, 670);
self.view.backgroundColor = [UIColor clearColor];
[self.view.layer addSublayer:avPlayerLayer];
Also this is how I have the properties definied.
#property (strong) AVPlayer *player;
#property (strong) AVPlayerLayer *avPlayerLayer;
#property (strong) AVPlayerItem *currentItem;
Maybe that's entirely wrong as well. I'm not exactly sure when to use (strong) vs (weak). Any case thank you ahead of time for any help.
If avPlayerLayer is the only class interacting with the avPlayer, you don't need to maintain a reference to it with a property in the class you're using to present it (unless this class uses it outside the code you've shared). In fact, this is probably why it isn't working the way you expect.
The reason the clip continues to play (I think) is because the player isn't being deallocated. You create it, own it with the strong property in your class, than it's owned again by the AVPlayerLayer class you hand it to. So when the AVPlayerLayer is deallocated, the AVPlayer looses one owner. But it still has an owner (your class), so it isn't deallocated, and it keeps playing. The solution here is to get rid of your owning property altogether for *avPlayer. You don't need it. Create the AVPlayer and pass it to AVPlayerLayer. That should be all that's needed.
Something else you could do which might fix the behavior but not the problem, call:
[avPlayer pause]
In your AVPlayerLayer's dealloc method.
Re: Strong vs. Weak references: A strong reference implies ownership. As ARC is managing memory, it'll be doing all the [object retain]ing and [object release]ing that you previously would have done in code OR that you would have done with properties, ie:
#property (retain) NSObject *iAmRetainedWhenProperyIsAssigned;
So now with ARC, we simple users don't use words like retain or release in code or when defining properties. But we still design software. ARC is smart, but not smart enough to infer the architectural implications of relationships that we're defining. It still needs to be told that a class "owns" a reference to the object that the property refers to. Which reduces us, in the most basic terms:
(strong) means to ARC what (retain) meant to properties pre-ARC (owned/retained)
(weak) means to ARC what (assign) meant to properites pre-ARC (not owned/not retained)
Maybe you did not pause. Do it and then remove layer and nullify player.
[player Pause];
[player removefromsuperlayer];
player = nil;
When you want to stop an AVPlayerItem from loading, use AVQueuePlayer's removeAllItems and then re-initialize it.
[self.avPlayer removeAllItems];
self.avPlayer = [AVQueuePlayer playerWithURL:[NSURL URLWithString:#""]];
self.avPlayer = nil;
This will stop the current item from loading -- it is the only way I found to accomplish this.
2023 answer ...
As there are only very old answers. These days in fact:
( 1 ) All you need to do is nil it in the playerLayer:
///Unplay and return to blankness.
public func unplay() {
playerLayer.player = nil
}
However, as #isaac astutely pointed out, you almost always also have just a variable with the player in it (to make it easier for you to control it). Don't forget that you of course have to nil that as well.
Thus when you lunch the player, your code will look something like
var handyPlayer: AVPlayer?
...
handyPlayer = AVPlayer(url: u)
playerLayer.player = handyPlayer
handyPlayer?.play()
handyPlayer? ... seek, etc etc
( 2 ) Thus, almost always, the code is:
///Unplay and return to blankness.
public func unplay() {
playerLayer.player = nil
handyPlayer = nil
}
There is no need to pause, release anything, or anything else. That's all there is to it.
Related
I have an AVPlayerItem that I have been able to grab from my UIWebView (visiting a website with an AVPlayer/video-file in the HTML) using some AVPlayer callbacks in my ViewController.
I need to access the AVPlayer that has that item set as its AVPlayerItem.
As of now I am doing this by calling [TheAVPlayerItem valueForKey:#"player"]; but this is undocumented and not guaranteed to work in the future.
I'm looking for a more authoritative way of achieving this. I'd even be happy with literally starting at the UIWebView and looping down through all subviews (or sub-objects?) and grabbing any AVPlayer then checking if it has its AVPlayerItem set to my AVPlayerItem.... alas I don't know how to do that. But I have included my attempt at doing so:
//[TheAVPlayerItem valueForKey:#"player"];//undocumented and prone to breaking.
for (NSObject *anObject in [myWebView subviews]) {
if ([anObject isKindOfClass:[AVPlayer class]]) {
NSLog(#"Found an AVPlayer, now check if AVPlayerItem is a match");//never called
} else {
}
}
Of course the NSLog was never called.
Good day.
I am developing an app to play multiple musics but I'm stuck where the music stop when I select share at the sidebar (I want the music to continue to play because user didn't pause it)
I am using api from RESideMenu and I suspect initRootController is the cause to made the music stop.
Someone suggested me to put the music at the appDelegate because the music might be deallocated when it switch view controller. However, I think that this is not a good way to do as I will later add on more musics with different image background and the architecture of the app will be very messy as I stock each music in ThemeObject and call the music in cafeViewController.
Is there a better way to do this?
This is my code >>> source.
I've check your repo and the sound seems to happen inside your ThemeObject and the only place where you create and link one of those is inside your CafeViewController. So every time the CafeViewController gets unloaded this will remove the only reference to your ThemeObject and it will be garbage collected. To check if the CafeViewController gets unloaded you could put a breakpoint inside this method:
- (void)dealloc {
// Just a line where you can put your breakpoint
}
The advice to put it inside the AppDelegate isn't completely backwards as indeed you would be better off to put it inside an object that is around all of the time. However to abuse AppDelegate as a dumping ground for all your centralised features is a bad practice. For simple apps you might be better off with a Singleton approach, where you always have one instance of an object and that object maintains itself during the existence of your application.
This is what a typical singleton looks like:
#interface ThemeManager : NSObject
#property NSArray *themes;
+ (id)sharedManager;
// Add other methods here
#end
#implementation ThemeManager
+ (id)sharedInstance {
static ThemeManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
if (self = [super init]) {
ThemeObject *cafeTheme = [[ThemeObject alloc] initWithBackgroundImg:#"cafeBG.png" audio:#"cafeAudio"];
ThemeObject *cafeTheme1 = [[ThemeObject alloc] initWithBackgroundImg:#"cafeBG.png" audio:#"cafeAudio"];
// Create as much as you need in the same way
self.themes = #[cafeTheme, cafeTheme1]; // And add them to the array of themes
}
return self;
}
// Implement other methods
#end
So you never init directly but always ask for the shared instance by calling something like
MusicManager *manager = [MusicManager sharedInstance];
ThemeObject *firstTheme = (ThemeObject *) [manager.themes firstObject];
[firstTheme setAudioPlay];
You can start, pause, stop and change songs with this central object without worrying about the lifecycle of your ViewControllers. You also can start a song from for example CafeViewController and you can stop the song CafeViewController started from the HotelViewController when you start the Hotel song.
I am trying to make a class that plays YouTube videos, but I am having several problems with it.
Here is my class that handles YouTube videos:
// this is the only property declared in the .h file:
#property (strong, nonatomic) UIView * view
// the rest of this is the .m file:
#import "MyYouTube.h"
#interface MyYouTube()
#property (strong, nonatomic) NSDictionary * contentData;
#property (strong, nonatomic) UIWebView * webView;
#property (nonatomic) int videoOffset;
#end
#implementation MyYouTube
#synthesize view,contentData,webView,videoOffset;
- (MyYouTube*) initIntoView: (UIView*) passedView withContent: (NSDictionary*) contentDict {
NSLog(#"YOUTUBE: begin init");
self=[super init];
videoOffset=0;
view=[[UIView alloc] initWithFrame:passedView.bounds];
[view setBackgroundColor:[UIColor blackColor]];
[view setAutoresizesSubviews:YES];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
contentData=contentDict;
NSString * compiledUrl=[[NSString alloc] initWithFormat:#"http://_xxx_.com/app/youtube.php?yt=%#",[contentData objectForKey:#"cnloc"]];
NSURL * url=[[NSURL alloc] initWithString:compiledUrl];
NSURLRequest * request=[[NSURLRequest alloc] initWithURL:url];
webView=[[UIWebView alloc] initWithFrame:passedView.bounds];
[webView loadRequest:request];
[webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[[webView scrollView] setScrollEnabled:NO];
[[webView scrollView] setBounces:NO];
[webView setMediaPlaybackRequiresUserAction:NO];
[webView setDelegate:self];
NSLog(#"YOUTUBE: self: %#",self);
NSLog(#"YOUTUBE: delegate: %#",webView.delegate);
[view addSubview:webView];
NSLog(#"YOUTUBE: end init");
return self;
}
-(void)webViewDidFinishLoad:(UIWebView*)myWebView {
NSLog(#"YOUTUBE: send play command");
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"playVideo(%d)", videoOffset]];
}
- (void) dealloc {
NSLog(#"YOUTUBE: dealloc");
}
#end
Here is the code that calls this class (this code is located in the appDelegate):
NSLog(#"about to load youtube");
ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
NSLog(#"loaded youtube");
[mainView addSubview:[ytObj view]];
FYI mainView and ytObj are declared as this:
#property (nonatomic,strong) UIView * mainView;
#property (nonatomic,strong) MyYouTube * ytObj;
When this code is run, the app crashes and I get this in the console:
about to load youtube
YOUTUBE: begin init
YOUTUBE: self: <MyYouTube: 0x16503d40>
YOUTUBE: delegate: <MyYouTube: 0x16503d40>
YOUTUBE: end init
YOUTUBE: dealloc
loaded youtube
*** -[MyYouTube respondsToSelector:]: message sent to deallocated instance 0x166f19f0
If I set the UIWebView delegate to nil then the app doesn't crash, but, as expected, the YouTube video doesn't autoplay.
Can anyone explain to me:
Why does the object deallocates immediately after it has been
initialized?
Why the respondsToSelector: message is sent to an
instance other than the MyYouTube one?
How can I get the YouTube video to autoplay without the app crashing?
Many thanks.
EDIT
Totally my bad - ytObj is a strong property - I forgot to mention this. Ive added the code to reflect this.
EDIT 2
Ive added a breakpoint on the dealloc method, and this is the call stack:
0 -[MyYouTube dealloc]
1 objc-object::sidetable_release(bool)
2 -[MyAppDelegate playYouTube:]
The last entry here (playYouTube:) is the method that contains the code in my app delegate that is in the original post above. So, one more question:
What is objc-object::sidetable_release(bool), what does it do, and why is it releasing my YouTube object?
Why does the object deallocates immediately after it has been initialised?
Because nothing owns it.
You set it as the delegate to a view, but the delegate property of UIWebView is an assign property. The view doesn't take ownership of its delegate. This means that when ytObj goes out of scope (possibly before, depending on optimisation) nothing owns it, so it goes away.
EDIT
You also need to make sure that when a ytObj is deallocated, you set the delegate property of any views it is still a delegate of to nil. Otherwise the views will continue to try to send messages to the deallocated object.
How can I get the YouTube video to autoplay without the app crashing?
You need to make sure that ytObj lasts as long as the view of which it is the delegate and when it is deallocated, it's view's delegate is set to nil.
Another minor issue. Your -init function should test that self is not nil after invoking self = [super init]. It shouldn't run any of the rest of the initialisation code if self is nil.
1) (my first answer to your first question, before your edit)
This is happening because you're using ARC (automated reference counting) and you are not keeping your local "ytObj" variable ((which is NOT the "self.ytObj" property) around in the object that you created it in. As soon as the function that created the local "ytObj" variable finishes up and returns, the object is automagically dealloc'd.
Change this:
ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
to this:
self.ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
And for "best practices", I'd also suggest not doing so much work and/or code in your application delegate. App delegates are meant to receive application specific messages (like "application is suspending", "application is becoming active again", etc.), and you should do stuff like this in a subclassed view controller.
2)
The "respondsToSelector:" error you're seeing is because your YouTube object has been automagically dealloc'd. If it were still living, you wouldn't see that message.
3)
If you search here on Stackoverflow, you'll find other questions and answers that explain how to do autoplaying... like how about this one?
The object ytObj since is not strong referenced anywhere, it exists only inside the scope where is defined. Delegates most of time are declared as weak properties. None is keeping a strong reference to this object, thus ARC releases it.
Create a strong properties to ytObj and you will see everything working fine.
I am having some EXC_BAD_ACCESS problems whilst trying to stop a video that is being played through MPMoviePlayerController. Here is some code:
Video Class:
#interface MyVideo()
#property (nonatomic, strong) MPMoviePlayerController * videoController
#end
#implementation MyVideo
#synthesize videoController;
- (MyVideo*) initIntoView: (UIView*) view withContent (NSDictionary*) contentDict {
self=[super init];
NSString * rawUrl=[[NSString alloc] initWithFormat:#"http://.../%#.mp4", [contentDict objectForKey:#"filename"]];
NSURL * videoUrl=[[NSURL alloc] initWithString:rawUrl];
videoController = [[MPMoviePlayerController alloc] initWithContentURL:videoUrl];
videoController.movieSourceType=MPMovieSourceTypeFile;
videoController.view.frame = viewRef.bounds;
[videoController.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
videoController.controlStyle=MPMovieControlStyleNone;
[view addSubview:videoController.view];
return self;
}
/* other code */
- (void) stop {
NSLog(#"video stop");
[videoController stop];
}
#end
This MyVideo class is a property within my AppDelegate class, like so:
#property (nonatomic, strong) MyVideo video;
A line in my AppDelegate class fires off the public method stop of this MyVideo class, like so:
[video stop];
This usually works fine. But occasionally I get an EXC_BAD_ACCESS error on the line with [videoController stop]. The line before it, the one with NSLog on it, outputs to the console as expected.
This crash happens while the video that has been loaded into the videoController is still playing. But it does not happen every time.
Can anyone suggest why this crash is happening? I suspect its because videoController is no longer in memory, despite it being strong and still in use.
Am I right in thinking there is absolutely no way of testing the videoController to see if it is still in memory?
Am I right in thinking there is absolutely no way of forcing videoController to stay in memory while it is being used to play a video?
So instead of trying to stop the video and shut down the MyVideo class properly when I dont want to play the video anymore, I am now thinking of just setting the MyVideo class to nil, and let ARC deal with stopping the video and clearing it from memory. Is this the right way of doing this? Would there be any disadvantages to this?
Are there any other solutions to this problem that I am missing?
With EXC_BAD_ACCESS my first port of call is to enable Zombie Objects in my Debug Scheme.
This should give you an idea of what object is causing the EXC_BAD_ACCESS. Just to double check it is your videoController.
When does your stop function get called on MyVideo
Is the crash on specific devices, iPad iPod, does it occur on specific os's iOS 6,7
Is it the same video file?
It cant randomly break there must be some pattern that is causing the EXC_BAD_ACCESS
I may be wrong but I have a feeling that its a threading issue. I suspect the thread calling the [myVideo stop] function is not aware of the videoController(probably initialised on the main thread). Try calling the [videoController stop] within the main thread by using the following:
dispatch_async(dispatch_get_main_queue(), ^{
[videoController stop];
});
Do let me know if this works!
Ive used SystemSound in my app in order to play simple sound effects. In addition to this I play a musicvideo through the MPMoviePlayerController - now when I turn the volume up/down the music from the video responds as intended (lowering the volume up/down).
But the system sounds that are played does not respond to the volume. Im playing the system sounds when the user taps certain areas in the app. Heres a snippet of my code for that:
- (void)handleTap:(UITapGestureRecognizer *)recognizer {
SystemSoundID completeSound = nil;
//yellow folder in xcode doesnt need subdirectory param
//blue folder (true folder) will need to use subdirectory:#"dirname"
NSURL *sound_path = [[NSBundle mainBundle] URLForResource: target_sound_filename withExtension: #"wav"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)sound_path, &completeSound);
AudioServicesPlaySystemSound(completeSound);
}
PS. I doublechecked that my "Settings->Sounds->Ringer and Alerts->Change With Buttons" is set to ON (as I read on some other SO answers that leaving this option OFF will cause systemsound to not respond to the volume buttons)
Further the reason for using systemsound is that it gave the most accurate and responsive results when playing multiple sounds (like in a game).
Id prefer to not use OpenAL if possible (even through 3rd party sound libraries like Finch or CocosDenshion)
Any ideas?
Use the AVAudioPlayer class for playing sounds that are controlled by the user's volume settings (non-system sounds).
You can retain instances of AVAudioPlayer for each sound file that you use regularly and simply call the play method. Use prepareToPlay to preload the buffers.
Cheers to Marcus for suggesting that i could retain instances of AVAudioPlayer for each sound file and use prepareToPlay to preload the sounds. It might be to help for others looking for the same solution so here is how I did it (feel free to comment if anyone have suggestions for improvements)
//top of viewcontroller.m
#property (nonatomic, strong) NSMutableDictionary *audioPlayers;
#synthesize audioPlayers = _audioPlayers;
//on viewDidLoad
self.audioPlayers = [NSMutableDictionary new];
//creating the instances and adding them to the nsmutabledictonary in order to retain them
//soundFile is just a NSString containing the name of the wav file
NSString *soundFile = [[NSBundle mainBundle] pathForResource:s ofType:#"wav"];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:soundFile] error:nil];
//audioPlayer.numberOfLoops = -1;
[audioPlayer prepareToPlay];
//add to dictonary with filename (omit extension) as key
[self.audioPlayers setObject:audioPlayer forKey:s];
//then i use the following to play the sound later on (i have it on a tap event)
//get pointer reference to the correct AVAudioPlayer instance for this sound, and play it
AVAudioPlayer *foo = [self.audioPlayers objectForKey:target_sound_filename];
[foo play];
//also im not sure how ARC will treat the strong property, im setting it to nil in dealloc atm.
-(void)dealloc {
self.audioPlayers = nil;
}