multiple instances of the same sound on iOS - ios

i'm making a bubble popping game and hit a bit of a snag :/
i have a sound (pop.mp3), this sound needs to play EVERY time one of the bubbles are tapped
all the bubbles are buttons that call the same method (-(IBACTION)bubblePop)
i initialised an AVAudioPlayer in the Viewdidload method
and call it's play function inside the bubblePop method
this clearly did not work however because the bubble's pop sound only plays one at a time, it doest overlap like i would expect it to
does anybody know how i can resolve this?
Extra info
if i initialise the audio player inside bubblePop i get no sound at all

You should initialize AVPlayer inside bubblePop and keep strong reference to it.
Try this:
#interface ViewController()
#property (strong, nonatomic) NSMutableDictionary *players;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.players = [[NSMutableDictionary alloc] init];
}
- (void)bubblePop {
NSURL *URL = your sound URL;
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:URL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
self.players[playerItem] = player;
[player play];
}
-(void)itemDidFinishPlaying:(AVPlayerItem *)sender {
[self.players removeObjectForKey:sender];
}
#end

Related

AVPlayerViewController exit fullscreen once played

Hi, I replaced MPMoviePlayerController with AVPlayerViewController since MPMoviePlayerController is deprecated.
I'm nearly there, but have one question. My movie start as a view within a view. When playing fullscreen I want it to jump back to NO fullscreen when finished playing. But I don't know how. Here is my code:
- (void)viewDidLoad {
// grab a local URL to our video
NSURL *videoURL = [[NSBundle mainBundle]URLForResource:#"movie" withExtension:#"m4v"];
// create an AVPlayer
AVPlayer *player = [AVPlayer playerWithURL:videoURL];
// create a player view controller
self.controller = [[AVPlayerViewController alloc]init];
controller.player = player;
[player play];
// show the view controller
[self addChildViewController:controller];
[self.view addSubview:controller.view];
controller.view.frame = CGRectMake(0,25, 750, 422);
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:player];
}
With MPMoviePlayer it used to work with this code:
- (void) playerPlaybackDidFinish:(NSNotification*)notification{
// movie finished playing
[moviePlayerController setFullscreen:NO];
}
With what code do I need to replace it??
-(void)itemDidFinishPlaying:(NSNotification *) notification {
// Will be called when AVPlayer finishes playing playerItem
???????????}
Thanks, Meg
#iOS 10 and higher and Swift 4.2 this code are working.
Write this code in your player init methods
if #available(iOS 11.0, *) {
self.playerVC?.exitsFullScreenWhenPlaybackEnds = true
}
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd(notification:)), name: .AVPlayerItemDidPlayToEndTime, object:self.playerVC?.player!.currentItem)
This is youy notification delegate
func playerItemDidReachEnd(note:NSNotification){
print("finished")
dismissViewControllerAnimated(true, completion: nil)
}

How to detach AVPlayerLayer from AVPlayer to play video in background state?

I am trying to play video in background.I follow so many tutorial but i did not get appropriate results.For that i am using AVPlayer.I am able to play my video whenever application state is active.But i want to play music in background for that i need to detach AVPlayerLayer from AVPlayer,If you have any alternate solution so that i can play my video in background.Please help me.
This is my code:
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
[[AVAudioSession sharedInstance] setDelegate: self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"sample"
withExtension:#"m4v" subdirectory:nil];
avPlayerItem = [AVPlayerItem playerItemWithURL:url];
self.songPlayer = [AVPlayer playerWithPlayerItem:avPlayerItem];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer: self.songPlayer];
self.avPlayerLayer.frame = self.view.layer.bounds;
UIView *newView = [[UIView alloc] initWithFrame:self.view.bounds];
[newView.layer addSublayer:avPlayerLayer];
[self.view addSubview:newView];
[self.songPlayer play];
}
AppDelegate.m
- (void)applicationWillResignActive:(UIApplication *)application
{
ViewController *vc=[[ViewController alloc]init];
vc.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
ViewController *vc=[[ViewController alloc]init];
vc.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:vc.songPlayer];
}
You are creating a new view controller in both your application delegate methods, with a new player etc.. That will not change a thing. You must use the player and layer you created in the view controller's viewDidLoad method, and modify that one later in the delegate methods. For this, your application delegate could store the main view controller in it's properties, or you could implement the NSNotifications for application delegate in your view controller class, like so: (could be added to the view controller's viewDidLoad method)
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
To detach the player layer from the player, as it's said in the documentation, is slightly incorrect, at least confusing. In fact you have to detach the player from the player layer.
Add this code to the applicationDidEnterBackground (not applicationWillResignActive) method:
// Detach player from playerlayer to avoid pause while in background
if (self.playerLayer && self.player)
{
[self.playerLayer setPlayer: nil];
}
In your applicationWillEnterForeground, store a flag to remember that when the application becomes active, it is due to entering the foreground, and not one of the other numerous reasons for which applicationDidBecomeActive may be called:
self.enteringForeground = true;
And then, in your applicationDidBecomeActive: method,
if (self.enteringForeground)
{
// Re-attach player to playerlayer
if (self.playerLayer && self.player)
{
if (self.playerLayer.player != self.player)
[self.playerLayer setPlayer: self.player];
}
self.enteringForeground = false;
}
For this to work, you will need a new property in your application delegate .h file or view controller .h file, depending which implementation you chose:
#property (readwrite) BOOL enteringForeground;
Of course, you also need to add the entry UIBackgroundModes with value 'audio' to your application's info.plist file (or in the Info tab of your project, add the entry 'Required background modes' and then the value 'App plays audio or streams audio/video using AirPlay').
I hope this is helpful. I too struggled at first to implement background playback, and this method seems to work quite well.

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 plays only when called twice. Only occurs in iOS 4

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
}
}

MPMoviePlayerController causing leak

I am developing a very video heavy iPad only iOS app which uses ARC but it appears that I have a leak when I try using MPMoviePlayerController, instruments throws a memory leak on the line of code that allocates the memory for the video player object, any ideas? also the clean up of the video player doesn't seem to be happening when the videos completes playback.
Any help would be very much appreciated, been looking everywhere for an answer for this as you can tell the problem is very much an show stopper with the nature of the application.
the code:
#interface ViewController ()
#property(nonatomic,strong) MPMoviePlayerController * vidPlayer;
#end
#implementation ViewController
#synthesize vidPlayer;
- (void)viewDidLoad
{
#autoreleasepool {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self playVideoForFile:#"01_intro"];
}
}
-(void)playVideoForFile:(NSString*)p_fileName
{
NSString *path = [[NSBundle mainBundle] pathForResource:p_fileName ofType:#"mp4"];
NSURL *tempURI = [NSURL fileURLWithPath:path];
vidPlayer = [[MPMoviePlayerController alloc] initWithContentURL:tempURI];
[vidPlayer setControlStyle:MPMovieControlStyleNone];
[vidPlayer setAllowsAirPlay:NO];
[vidPlayer.view setFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.height,[[UIScreen mainScreen] bounds].size.width)];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(vidFinishedCallback:) name:MPMoviePlayerPlaybackDidFinishNotification object:vidPlayer];
[vidPlayer play];
[self.view addSubview:vidPlayer.view];
}
-(void)vidFinishedCallback:(NSNotification*)aNotification
{
[vidPlayer pause];
vidPlayer.initialPlaybackTime = -1;
[vidPlayer stop];
vidPlayer.initialPlaybackTime = -1;
[vidPlayer.view removeFromSuperview];
vidPlayer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:vidPlayer];
}
I tried
MPMoviePlayerController *movieController = [notification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:movieController];
movieController = nil;
and this seems to work.
This is a hunch, but try calling removeObserver before vidPlayer is destroyed. The docs specify that you should "Be sure to invoke removeObserver: or removeObserver:name:object: before notificationObserver or any object specified in addObserver:selector:name:object: is deallocated." --NSNotification Center
Also, you may try removing the explicit autoreleasepool in viewDidLoad. It should not be necessary if it's just one vidPlayer per view, and there have been some recent issues discovered with ARC and autoreleasepools. See this bugfix
I had this problem and it was sending me grey! Every time a new video was instantiated, it was never released (using ARC) and as more instances of the viewController were created it eventually caused a clunky crash.
I too checked all the MPMoviePlayer calls ensuring it had stopped and set to nil.
The problem wasn't in the allocation and release of the MPMoviePlayerController, but in the delegate that was used to pass the view controller details.
In the child view controller, I had a delegate to check for the passed model data from a tableViewController:
#property (strong, nonatomic) id<MyViewControllerDelegate> delegate;
The assignment as a strong pointer caused a huge memory leak. By assigning this as 'weak' it cleared the problem up.
#property (weak, nonatomic) id<MyViewControllerDelegate> delegate;
As a general rule, for view controllers delegates, assign them as a weak property pointer.
Good luck. Hope this clears the leak up!

Resources