I have an app that has a piano built into it. Each key (Button1) when pressed down starts to play the note and when the user lifts their finger I want the note to stop playing. What happens is that the audio does not stop playing when the stop function is set to Touch Up Inside on the key (Button1). If I change the the Action from the piano key (Button1) to a different button (Button2) it stops playing as it should if I press the button 2.
.h
#import <AVFoundation/AVAudioPlayer.h>
#interface FifthViewController : UIViewController <AVAudioPlayerDelegate> {
AVAudioPlayer *theNote;
}
.m
#import <AVFoundation/AVAudioPlayer.h>
#import <AVFoundation/AVFoundation.h>
//White
-(IBAction)note1w:(id)sender { //Touch Down
[self whiteKey:self];
[self downKey:self];
NSString *path = [[NSBundle mainBundle] pathForResource:#"white1" ofType:#"aif"];
theNote = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theNote.delegate = self;
theNote.currentTime = 0;
[theNote play];
}
-(IBAction)stopMusic { //Touch Up Inside - This is the action that I change from the Button1 (The Piano Key) to Button2 (A Reg UIButton) and it works.
[theNote stop];
}
I tried your code in a project and it worked fine. I suspect you misconfigured the touchUp connection (perhaps connected touchUpOutside as I have accidentally done on occasion).
I ended up creating a work around to fix the problem. I already had a long press gesture recognizer in the app so I added to the recognizer state ended [self stopMusic]; and it ran fine. I couldn't replicate the problem in another app. It must be a conflict with other code that is being ran.
Related
I am working on play video on GMSMarker pin and below is my code. The display and hide/show pin view, everything work fine. Except video not play. I am using story board with pinview.
#interface MapViewController () <GMSMapViewDelegate>
#property (strong, nonatomic) IBOutlet GMSMapView *mapView;
#property (strong, nonatomic) IBOutlet UIView *pinView;
#property (strong, nonatomic) GMSMarker *london;
#property (strong, nonatomic) AVPlayer *player;
#property (strong, nonatomic) AVPlayerLayer *playerLayer;
#end
#implementation MapViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:51.5 longitude:-0.127 zoom:18];
self.mapView.camera = camera;
self.mapView.delegate = self;
CLLocationCoordinate2D position = CLLocationCoordinate2DMake(51.5, -0.127);
_london = [GMSMarker markerWithPosition:position];
_london.title = #"London";
_london.tracksViewChanges = YES;
_london.map = self.mapView;
[self setUpVideoPlayer];
}
- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
[self.player play];
return self.pinView;
}
-(void)setUpVideoPlayer{
NSString *videoFilePath = [[NSBundle mainBundle] pathForResource:#"SampleVideo" ofType:#"mp4"];
AVAsset *avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:videoFilePath]];
AVPlayerItem *avPlayerItem =[[AVPlayerItem alloc]initWithAsset:avAsset];
self.player = [[AVPlayer alloc]initWithPlayerItem:avPlayerItem];
self.playerLayer =[AVPlayerLayer playerLayerWithPlayer:self.player];
[self.playerLayer setFrame:self.pinView.frame];
[self.pinView.layer addSublayer:self.playerLayer];
[self.player seekToTime:kCMTimeZero];
}
Please help me to fix the issue. Why the video is not playing.
Thanks in advance.
Normally, info window will not regenerate after showing it(its adding as an image). To show video frames it need to regenerate when video play. It means window need to refresh automatically. To do it you need to set one property for your GMSMarker. Line as follows,
_london.tracksInfoWindowChanges = YES;
So your full code is
CLLocationCoordinate2D position = CLLocationCoordinate2DMake(51.5, -0.127);
_london = [GMSMarker markerWithPosition:position];
_london.title = #"London";
_london.tracksViewChanges = YES;
_london.tracksInfoWindowChanges = YES;
_london.map = self.mapView;
Google documentation is https://developers.google.com/maps/documentation/ios-sdk/marker#set_an_info_window_to_refresh_automatically
I believe that the reason why your code doesn't work is the fact that Google Maps for iOS renders Custom Info Views as Images. I'm not sure, but I believe, since I have also tried with animations and it also doesn't work. When I use animation, I first see the initial state of animation and when I open marker info window again, I see the final state, without animation. Also, when I add a text field on custom info window view, I can't click on that text field. When I try with the video like you, I see a blank window like the player is going to start and hear the sound, but the video never starts.
I have found this note on many questions on Stackoverflow (they say that it is from Google Docs), but I don't see it on Google Docs:
Note: The info window is rendered as an image each time it is displayed on the map. This means that any changes to its properties, while it is active, will not be immediately visible. The contents of the info window will be refreshed the next time that it is displayed.
Source: Google Maps Showing speech bubble in marker info window iOS
Maybe you can try with tracksInfoWindowChanges = YES:
- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
marker.tracksInfoWindowChanges = YES;
self.player play];
return self.pinView;
}
Source: How to force refresh contents of the markerInfoWindow in Google Maps iOS SDK
I know that you have set it in viewDidLoad method, but maybe setting it in a
- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker
method maybe will work, but I don't believe. A lot of people say that setting markerInfoWindow to YES works, but I have tried, and it doesn't work.
I have tried to use iOS Maps and video plays without problems. I have never had a problem with using Apple Maps. So, I'm sure that Google Maps isn't a good solution for this problem. Maybe you could fix it by some hack, but I have lost several hours to find a solution, without results. Maybe it can be done, for example, to move a UIView above map when the user moves his finger on the map or zooms in/out, or some other solution, but I prefer system solutions above hacks. In this case, I don't see system solution, maybe I'd find it if I spent more time to research. If I were you, I wouldn't use Google Maps for this.
Edit: I've just seen that my solution works on simulator. On real device (iPhone 7+) it doesn't work, i.e. I see player controls are animating, I see sound, but I don't see the video. But even if it works on real device like it works on simulator, user can't play or pause video, because he only sees images which are updating regularly.
we are having problem with our background sound. If the sound is on when we change view and then go back to the menu it adds another loop of sound. If the sound is muted when we go back it starts again. Please help. This is my code.
// Meny.m
#import "Meny.h"
#import <AVFoundation/AVFoundation.h>
#interface Meny () {
AVAudioPlayer *audioPlayerBG;
}
#end
#implementation Meny
- (void)viewDidLoad {
[super viewDidLoad];
NSString *music = [[NSBundle mainBundle]pathForResource:#"TestSwoong" ofType:#"wav"];
audioPlayerBG = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
audioPlayerBG.numberOfLoops = -1;
audioPlayerBG.volume = 0.5;
[audioPlayerBG play];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
// LjudKnapp stop
- (IBAction)stopBG:(id)sender {
[playBG setHidden:NO];
[pauseBG setHidden:YES];
[audioPlayerBG stop];
}
// LjudKnapp play
- (IBAction)playBG:(id)sender {
[pauseBG setHidden:NO];
[playBG setHidden:YES];
[audioPlayerBG play];
}
It appears that you need to brush up on what classes and instances are. Understanding classes and instances is fundamental if you're going to do object-oriented programming, which is what you do when you use Objective-C.
we change view and then go back to the menu it adds another loop of sound
That phrase suggests that when you "go back", you are not actually going back to the already existing Meny instance - instead, you are creating another Meny instance. So now, you see, you have two of them! Well, each Meny instance has its own AVAudioPlayer, which starts playing when the Meny instance is created — so now you have two audio players, and that's why you hear two loops playing simultaneously.
Ive set a music track to play within the ViewController.h and .m files as shown below. I want to stop this when a new scene is loaded. (.h):
#interface ViewController : UIViewController<AVAudioPlayerDelegate>{
AVAudioPlayer *startingMusic;
}
#property (nonatomic, retain) AVAudioPlayer *startingMusic;
#end
and then .m
#synthesize startingMusic;
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *music = [[NSBundle mainBundle] pathForResource:musicTrack ofType:#"mp3"];
startingMusic=[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
startingMusic.delegate=self;
startingMusic.numberOfLoops=-1;
[startingMusic play];
}
I then try and stop this from playing when the new scene loads is a different class called PlayLevel.m in an init method using this code.
ViewController *test = [[ViewController alloc] init];
[test.startingMusic stop];
However the music just continues to play when the new seen loads. Any suggestions?
When you do ViewController *test = [[ViewController alloc] init]; you created the new ViewController instance, it's mean you now have two ViewController's objects in the memory.
What you really need to do is call stop on your current one.
If you want to stop music from another controller which has no connection to your ViewController you can try doing this via notifications.
For example:
in your ViewController.h file, after imports:
static NSString * const kStopMusicNotificationName = #"kStopMusicNotificationName";
in your ViewController viewDidLoad:
[NSNotificationCenter.defaultCenter addObserverForName:kStopMusicNotificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self.startingMusic stop];
}];
and when you need to stop music:
[NSNotificationCenter.defaultCenter postNotificationName:kStopMusicNotificationName object:nil];
You're going about this the wrong way around, you should really be stopping the music within ViewController and not from another class. ViewController should be self contained where ever possible.
How are you loading the 2nd scene? I imagine you're using a Segue?
If you are using a segue you can override the function -prepareForSegue which is a UIViewController function. Within -prepareForSegue stop the music then.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Stop Music
}
I am trying to create AVAudioplayer programatically.The audio player successfully playing.But i have some issues.I am displaying 12 audio items in UITableview.If i click first item that should navigate audio player view controller.And i click the play button the song will play.If i click back button the song play continuously.But if i click again same item the Audio view controller the view will display initial state like progress bar should not move and current time and song duration are empty.but song play continuously.
And another issue is there. If i click first item that corresponding song will play.I should not click any pass button.i click back button and click second item.if i click second item the audio view controller will display.I click play button the song display.In background first song play and second song also play.This is my second issue.how to solve these two issues.please help me any body.
-(void)playOrPauseButtonPressed:(id)sender
{
if(playing==NO)
{
[playButton setBackgroundImage:[UIImage imageNamed:#"Pause.png"] forState:UIControlStateNormal];
// Here Pause.png is a image showing Pause Button.
NSError *err=nil;
AVAudioSession *audioSession=[AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
NSLog(#"%# %d",urlsArray,selectedIndex);
NSString *sourcePath=[urlsArray objectAtIndex:selectedIndex];
NSData *objectData=[NSData dataWithContentsOfURL:[NSURL URLWithString:sourcePath]];
audioPlayer = [[AVAudioPlayer alloc] initWithData:objectData error:&err];
audioPlayer.numberOfLoops = 0;
[audioPlayer prepareToPlay];
audioPlayer.delegate=self;
[audioPlayer play];
playing=YES;
}
else if (playing==YES)
{
[playButton setBackgroundImage:[UIImage imageNamed:#"play.png"] forState:UIControlStateNormal];
[audioPlayer pause];
playing=NO;
}
if (self.audioPlayer)
{
[self updateViewForPlayerInfo];
[self updateViewForPlayerState];
[self.audioPlayer setDelegate:self];
}
}
-(void)updateViewForPlayerInfo
{
self.songDuration.text = [NSString stringWithFormat:#"%d:%02d", (int)self.audioPlayer.duration / 60, (int)self.audioPlayer.duration % 60, nil];
NSLog(#"%f", self.audioPlayer.duration);
self.progressBar.maximumValue = self.audioPlayer.duration;
self.volumeSlider.value = self.audioPlayer.volume;
}
-(void)updateViewForPlayerState
{
[self updateCurrentTime];
if (self.updatedTimer)
{
[self.updatedTimer invalidate];
}
if (self.audioPlayer.playing)
{
self.updatedTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(updateCurrentTime) userInfo:self.audioPlayer repeats:YES];
}
}
-(void)updateCurrentTime
{
//NSLog(#"self.audioPlayer.currentTime = %f", self.audioPlayer.currentTime);
self.currentTime.text = [NSString stringWithFormat:#"%d:%02d", (int)self.audioPlayer.currentTime / 60, (int)self.audioPlayer.currentTime % 60, nil];
self.progressBar.value = self.audioPlayer.currentTime;
}
-(void)volumeSliderMoved:(UISlider *)sender
{
self.audioPlayer.volume=[sender value];
}
I think the problem is here
audioPlayer = [[AVAudioPlayer alloc] initWithData:objectData error:&err];
Each time you are creating the audioPlayer object. So my suggestion is check for audioPlayer before creating the object like this
if(audioPlayer)
{
audioPlayer.delegate=nil;
audioPlayer=nil;
}
Then create the object.
In short what's happening is the following:
you click on the table cell
you create an audio player view controller
You play the audio - your audio player itself is retained for some reason (Unclear why - are you using ARC?)
You click the back button - the Audio player view controller is released
you click the table cell again - and create a new audio player view controller with it's own player!
Basically - if you want the previous song continue playing even when your exiting the Audio player view controller - and to have only a single player for the audio - I would hold it in a singleton class that will manage the player , or as a property of the app delegate.
This way - when you enter the Audio Player view controller - It will check the following:
If a player doesn't exist - create it and assign it the item - ready to play
If a player exists but it's the same item - just update the controls to show it's current playing status.
If a player exists but it's another item - either don't update the controls - and when the user hits play - replace the item in the player and start updating the controls - or stop the song as you display the Audio player view controller. (Up to you - I don't know the design of your app obviously)
In working with AVAudioPlayer, I use a property to keep up with the player. Based on looking at your code it isn't clear to me that you are keeping the player around, or that you aren't instantiating a new instance every time. Based on your description, it sounds like you ARE instantiating a new instance - don't think you want that.
So in your .h file, something like this:
#property (nonatomic, strong) AVAudioPlayer *myAVAudioPlayer;
and then the corresponding #synthesize in your .m file.
When you want to move from one song to the next, simply stop playing current - then reload a new URL using an init for your self.myAVAudioPlayer property. You MUST then call the prepareToPlay method again for the AVAudioPlayer option.
You probably will make it easier on yourself by not managing the variables regarding playing or not (it looked like you were doing that from your post). Use your property and check by accessing the setter/getter for the property for state of the player:
if(!self.myAVAudioPlayer)
{
// allocate and initialize using the code you have
[self.myAVAudioPlayer prepareToPlay];
} else
if ([self.myAVAudioPlayer isPlaying])
{
//do whatever you need for isPlaying - looks like pause
} else
if (![self.myAVAudioPlayer isPlaying])
{
// do whatever you need to do if the player isn't playing when the button is pressed
// at this point you will likely just re-init with a new URL and reset other items
// don't forget to call -prepareToPlay again
}
Here is a link to another approach which takes advantage of AVPlayerItem --> click https://stackoverflow.com/a/22254588/3563814
I have this could which allows me to play a youtube video within my ipad app. However when I switch views, the video does not stop playing in the background. Do I have to add anything to make it stop playing when the views switch?
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *videoURL = [NSURL URLWithString:#"http://www.youtube.com/embed/123456"];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:videoURL];
UIWebView * YoutubeVideo = [[UIWebView alloc] init];
YoutubeVideo.frame = CGRectMake(176,243,556,335);
[self.view addSubview:YoutubeVideo];
[YoutubeVideo loadRequest:requestObj];
}
-(void)viewWillDisappear:(BOOL)animated
{
[self.YoutubeVideo loadHTMLString:nil baseURL:nil];
}
This should work.When the view disappears, webview have nothing and when view comes back in viewWillAppear instead of viewDidLoad, load the video again, keep the youtubeVideo webview as a property so that you can access it in every method.
Youtube has provided its player called YTPlayer. You can import its library, and use as follow:
In your podfile, you should have this line:
pod "youtube-ios-player-helper", "~> 0.1.4"
In the ViewController.h file you have to:
a. Import the YTPlayer.h file:
#import "YTPlayer.h"
b. Define this within the interface:
#property(nonatomic, strong) IBOutlet YTPlayerView *playerView;
In the ViewController.m file, you have this function:
(void)viewWillDisappear: (BOOL)animated {
[self.playerView stopVideo];
}
It works for me.