In this code, Audio is a class where all things concerning AVAudioPlayer are handled. Currently there is some music playing. I want to have this fade out completely in 1 second and then play another song. Simultaneously, I want a new scene to appear. So I want the new song to start right as the transition (also lasting 1 second) ends. However, I find myself always waiting for the music to stop, before the scene transition starts. I've tried many ways, with and without selectors, with and without the various sleep functions, but without any luck. I've also tried to use stopMusic instead of transitionMusic and then use playMusic: in the new scene, but I always find myself waiting for stopMusic to finish before the transition starts.
Here's a part of the first scene, which is run if a certain button on the screen is pressed:
Audio *musicPlayer = [[Audio alloc] init];
NewScene *newScene = [NewScene sceneWithSize:self.size];
SKTransition *reveal = [SKTransition pushWithDirection:SKTransitionDirectionLeft duration:1.0];
reveal.pausesOutgoingScene = NO;
reveal.pausesIncomingScene = NO;
[self.view presentScene:newScene transition:reveal];
[musicPlayer transitionMusic:#"NewSong"];
The method transitionMusic: is as follows:
-(void)transitionMusic:(NSString *)fileName {
[self performSelectorOnMainThread:#selector(stopMusic) withObject:nil waitUntilDone:YES];
[self playMusic:fileName];
}
And stopMusic and playMusic: are:
-(void)stopMusic {
while(BGM.volume > 0.05) {
[BGM setVolume:(BGM.volume - 0.05)];
[NSThread sleepForTimeInterval:0.05];
}
[BGM stop];
}
-(void)playMusic:(NSString *)fileName {
NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:#"%#",fileName] withExtension:#"caf"];
BGM = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:nil];
[BGM setNumberOfLoops:-1];
[BGM setVolume:1.0];
[BGM prepareToPlay];
[BGM play];
}
Also, BGM is a static audioplayer declared in Audio.h. Can anyone point out what I'm doing wrong?
Your musicPlayer player object is dependent on your old scene. You need to create a singleton to handle your transition music independently from one scene to another.
BoxesOfBabies.com has a good tutorial on how to set up a singleton and use it to Control Music Across Multiple SKScene.
EDIT:
In your SKScene:
#import "MySingleton"
#implementation MainScene
{
MySingleton *_mySingleton;
}
In your init method:
_mySingleton = [MySingleton sharedInstance];
To start playing audio in any of your scenes:
[_mySingleton startPlayingSong1];
or
[_mySingleton startPlayingSong2];
whatever methods you have in your singleton.
Related
I want to play two videos next to each other.
When building Mac apps I did this by using a AVPlayer object.
It is a view that you could place inside of your ViewController.
But when I tried to find the AVPlayer object in the object library in an IOS project it didn't show up. Only the AVKit Player View Controller did.
But the AVKit Player View Controller always plays videos full screen in a separate ViewController. I have searched the internet to find a way to display two videos next to each other but so far, I have not found anything useful.
So my question is: Is there any way that I can display two videos next to each other in an IOS application?
// ViewController.h
#import AVFoundation;
#import AVKit;
#property(nonatomic, readonly) AVPlayerItem *currentItem;
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
i=0;
[self videoPlay:[_passdataArr valueForKey:#"videoUrl"]];
}
-(void)videoPlay:(NSArray*)links
{
NSLog(#"1");
//create an AVPlayer
_detailView.hidden=YES;
// NSMutableArray *videolinksArr=[[NSMutableArray alloc] init];
// videolinksArr=_videoLinkArr;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#",links]];
// create a player view controller
player = [AVPlayer playerWithURL:url];
AVPlayerViewController *controller = [[AVPlayerViewController alloc] init];
[self addChildViewController:controller];
[self.view addSubview:controller.view];
controller.view.frame = self.bgimg.frame;
controller.player = player;
controller.showsPlaybackControls = YES;
player.closedCaptionDisplayEnabled = NO;
[player pause];
[player play];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(AllVideoPlay:) name:AVPlayerItemDidPlayToEndTimeNotification object:_currentItem];
}
-(void)AllVideoPlay:(NSNotification*)aNotification
{
NSLog(#"2");
NSArray *temp = _videoLinkArr[i];
[self videoPlay:temp];
i++;
}
But the AVKit Player View Controller always plays videos full screen in a separate ViewController
That's false. If you want the movie view to appear as a subview, make the AVPlayerViewController a child view controller. Start with the Container View Controller in the storyboard and make an Embed segue to an AVPlayerViewController.
Or, you could use AVPlayer without an AVPlayerViewController (in code).
AVPlayer has been available on iOS since iOS 4. AVPlayer is a controller object that manages the playback of media assets with no UI elements directly tied to it, which is why it is not located in the IB object library. Rather you can build your own UI using AVPlayerLayer our use Apple's standard AVPlayerViewController, which includes default controls and standard playback behavior. The fastest way to get two videos playing side-by-side is to embed two instances of AVPlayerViewController inside another view controller. If you are using storyboards your code could be as short as:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController isKindOfClass:[AVPlayerViewController class]] && [segue.identifier isEqualToString:#"First Player"])
{
AVPlayerViewController *avPlayerViewController = (AVPlayerViewController *)segue.destinationViewController;
avPlayerViewController.player = [AVPlayer playerWithURL: [NSURL URLWithString:#"http://devstreaming.apple.com/videos/wwdc/2016/703rx8zlfedjfom6l93/703/hls_vod_mvp.m3u8"]];
[avPlayerViewController.player play];
} else if ([segue.destinationViewController isKindOfClass:[AVPlayerViewController class]] && [segue.identifier isEqualToString:#"Second Player"])
{
AVPlayerViewController *avPlayerViewController = (AVPlayerViewController *)segue.destinationViewController;
avPlayerViewController.player = [AVPlayer playerWithURL: [NSURL URLWithString:#"http://devstreaming.apple.com/videos/wwdc/2016/703rx8zlfedjfom6l93/704/hls_vod_mvp.m3u8"]];
[avPlayerViewController.player play];
}
}
#end
Or something similar. Where most of the work is done by two container views and embed segues to two AVPlayerViewControllers If you are looking to have custom video UI, you can look at Apple's PIP sample code where they show how you can use key value observing to a video player with custom UI.
I currently have a screen that loads a video through a method called - loadCurrentVideo and this method usually takes 2-3 seconds to complete while on wifi but 10-20 seconds to complete while on data. I have a button built into the player that exits out of the player when it is tapped, but the button only works after the video is loaded. I want to be able to add a button that exits out of the player whenever I tap it at any point during the loading and playing stages.
While the video is loading, I can't use any of the buttons on the player since i'm assuming the video is still loading and executing the method described above. How can I build a button that is capable of always being available to respond to a tap and interrupts a method while it is executing to return to the previous screen that the app was on?
Below is the method:
- (void) loadCurrentVideo {
__block UIActivityIndicatorView *pw = _pleaseWait;
__block KolorEyes *ke = _koloreyes;
__block UISlider *slider = _timeSlider;
_pleaseWait.hidden = NO;
[_pleaseWait startAnimating];
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url1 = [NSURL URLWithString:url];
[_koloreyes setDisplayDevice:kKE_DISPLAY_HMD forView:_viewRectHandle];
[_koloreyes setCameraControlMode:kKE_CAMERA_TOUCH forView:_viewRectHandle];
err = [_koloreyes setAssetURL:url1
withCompletionHandler:^{
NSLog(#"Video %#", url1.path);
[pw stopAnimating];
pw.hidden = YES;
CMTime duration = [ke getMediaDuration];
if (CMTIME_IS_INDEFINITE(duration)) {
slider.hidden = YES;
}
else {
[slider setMaximumValue:CMTimeGetSeconds(duration)];
}
[ke play];
[self postView];
Log("Kolor Eyes set asset URL completion handler");
}];
if (err != kKE_ERR_NONE) {
[pw stopAnimating];
pw.hidden = YES;
Log("Kolor Eyes setAssetURL failed with error %d", err);
}
});
}
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.
I have a sprite kit game with different scenes: the main menu ("MainMenuScene") and the game scene ("MyScene"). While the user is playing the game, I have an endless playing background music. But when the player wants to stop the game and return to main menu, the background music keeps playing. What should I do to make it stop? I have tried [self removeAllActions] but it didn't work.
MyScene:
#implementation MyScene
{
SKAction *_backgroundMusic;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.5 blue:0.3 alpha:1.0];
}
//Here I make the endless background music
_backgroundMusic = [SKAction playSoundFileNamed:#"Background 2.m4a" waitForCompletion:YES];
SKAction * backgroundMusicRepeat = [SKAction repeatActionForever:_backgroundMusic];
[self runAction:backgroundMusicRepeat];
return self;
}
- (void)selectNodeForTouch:(CGPoint)touchLocation
{
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
if ([_MainMenuButton isEqual:touchedNode]) {
SKScene *mainMenuScene = [[MainMenuScene alloc]initWithSize:self.size];
[self.view presentScene:mainMenuScene];
//Here is where the music should stop, when the player presses the 'return to main menu' button
}
}
I would not recommend using SKAction to play background music. Instead use AVAudioPlayer.
To use the AVAudioPlayer:
Add the AVFoundation to your project.
#import <AVFoundation/AVFoundation.h> into your .m file.
Add AVAudioPlayer *_backgroundMusicPlayer; in #implementation
Use this code snippet to run your audio:
- (void)playBackgroundMusic:(NSString *)filename
{
NSError *error;
NSURL *backgroundMusicURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
_backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
_backgroundMusicPlayer.numberOfLoops = -1;
_backgroundMusicPlayer.volume = 0.8;
_backgroundMusicPlayer.delegate = self;
[_backgroundMusicPlayer prepareToPlay];
[_backgroundMusicPlayer play];
}
Also read up on the AVAudioPlayer Class Reference so you know what all the properties do such as setting volume, number of loops, etc...
try this to play the music:
[self runAction:backgroundMusicRepeat withKey:#"bgmusic"];
and this to stop:
[self removeActionForKey:#"bgmusic"];
update
SKAction * backgroundMusicRepeat = [SKAction playSoundFileNamed:#"Background 2.m4a" waitForCompletion:YES];
backgroundMusicRepeat = [SKAction repeatActionForever:backgroundMusicRepeat];
[self runAction:backgroundMusicRepeat];
I have run those code in my own project and seems work.
but not your way, it only stop when I quit the view, I don't even need removeActionForKey . [self removeActionForKey:#"bgmusic"]; won't work in the scene.
So if you want stop sound while switch between diferent scenes of same view, I suggest you using AVAudioPlayer.
I also found some other questions in stackoverflow has the same problem as you, like this:
how to pause the sound when use SpriteKit
and this:
Spritekit stopping sound
they both go for the AVAudioPlayer.
as one of the comment in those links said: you should use the playSoundFileNamed method for playing sound effects... something short 1 or 2 seconds, like explosion sound --don't use it for background sound.
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