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);
}
});
}
Related
I am reading video from local using AVPlayer. When I scrub through the video, sometimes I am getting pixelated video for few second or green screen appears for a while.
Following is my code for scrubbing:
- (void)playerControlView:(PlayerControlView *)playerControlView beganScrubbingAtPercent:(float)timePercent
{
if (_avPlayer.rate > 0) {
_resumePlayAfterScrub = YES;
} else {
_resumePlayAfterScrub = NO;
}
}
- (void)playerControlView:(PlayerControlView *)playerControlView scrubbedToPercent:(float)timePercent
{
if (_avPlayer.rate > 0) {
[_avPlayer pause];
[_loadingView startAnimating];
}
float durationSeconds = CMTimeGetSeconds(_avPlayerItem.duration);
CMTime cmTime = CMTimeMakeWithSeconds(durationSeconds * timePercent, NSEC_PER_SEC);
[_avPlayer seekToTime:cmTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[_hideControlsTimer invalidate];
}
- (void)playerControlView:(PlayerControlView *)playerControlView finishedScrubbingAtPercent:(float)timePercent
{
if (_resumePlayAfterScrub) {
[_avPlayer play];
}
[self resetCurrentEventWithCopy:YES];
}
what can I optimize on above code?
It could be a case wherein the gap between consecutive IDR frames is high so when you seek, it takes a while to hit the closest IDR frame and clear picture buffer. Till the player hits an IDR frame, it will not be able to decode the stream properly which will result in you seeing green screens and pixelations.
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.
I'm developing a radio streaming app with XCode 4.5 for iOS 6 and mainly using storyboards.
I successfully made it to be able to play in background. So wherever I go from my app, whether to other tabs or even after clicking the Home button on the simulator,it keeps playing.
I'm using Matt Gallagher's audio streamer, which I include in my app delegate .m file below
#pragma mark - audio streaming
- (void)playAudio:(indoRadio *)np withButton:(NSString *)currentButton
{
if ([currentButton isEqual:#"playbutton.png"])
{
[self.nowplayingVC.downloadSourceField resignFirstResponder];
[self createStreamer:np.URL];
[self setButtonImageNamed:#"loadingbutton.png"];
[audioStreamer start];
}
else
{
[audioStreamer stop];
}
}
- (void)createStreamer:(NSString *)url
{
if (audioStreamer)
{
return;
}
[self destroyStreamer];
NSString *escapedValue = (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(nil,(CFStringRef)url,NULL,NULL,kCFStringEncodingUTF8);
NSURL *streamurl = [NSURL URLWithString:escapedValue];
audioStreamer = [[AudioStreamer alloc] initWithURL:streamurl];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(playbackStateChanged:)
name:ASStatusChangedNotification
object:audioStreamer];
}
- (void)playbackStateChanged:(NSNotification *)aNotification
{
NSLog(#"playback state changed? %d",[audioStreamer isWaiting]);
if ([audioStreamer isWaiting])
{
[self setButtonImageNamed:#"loadingbutton.png"];
}
else if ([audioStreamer isPlaying])
{
[self setButtonImageNamed:#"stopbutton.png"];
}
else if ([audioStreamer isIdle])
{
[self destroyStreamer];
[self setButtonImageNamed:#"playbutton.png"];
}
}
- (void)destroyStreamer
{
if (audioStreamer)
{
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:ASStatusChangedNotification
object:audioStreamer];
[audioStreamer stop];
audioStreamer = nil;
}
}
- (void)setButtonImageNamed:(NSString *)imageName
{
if (!imageName)
{
imageName = #"playButton";
}
self.nowplayingVC.currentImageName = imageName;
UIImage *image = [UIImage imageNamed:imageName];
[self.nowplayingVC.playBtn.layer removeAllAnimations];
[self.nowplayingVC.playBtn setImage:image forState:0];
if ([imageName isEqual:#"loadingbutton.png"])
{
[self.nowplayingVC spinButton];
}
}
The problem I got is that when I click the play button, it starts the audio streamers but doesn't change into either loading button or stop button. This makes me unable to stop the audio since the button image is not changed. And since I put an NSLog inside the playbackStateChanged: method,I know that the playback state did change. It seems like the setButtonImagedNamed: method is not firing. Any thoughts?
Please have look of my answer:Disabled buttons not changing image in
For a button there is normal, selected, highlighted and disabled state.
So in your case you have to handle the normal and selected state.
In the xib, set image to a button for normal and selected state.
Now in the function of playAudio instead of checking the image name check the button state as below:
- (void)playAudio:(indoRadio *)np withButton:(NSString *)currentButton
{
if (playButton.selected)
{
//Stop the streaming
//set selection NO
[playButton setSelected:NO];
}
else
{
//Start the streaming
//set selection YES
[playButton setSelected:YES];
}
}
playButton is the IBOutlet of play button.
As you have set the images in xib, so the image automatically get changed as per the state of the image
In case you have added the playBtn programmatically, check if the button is declared as weak or its getting released somewhere. If it is weak, make it strong.
In case the button is in the nib file, then the problem is in the IBOutlet. Redo the connection properly.
It turned out that the delegate didn't know which nowplayingVC I was calling. #sarp-kaya's question about Calling a UIViewController method from app delegate has inspired me.
I actually have put this code in my view controller viewDidLoad:
myAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
but I forgot to add this line:
appDelegate.nowplayingVC = self;
So, all I need to do is adding that one line and it works now. okay, so silly of me for not noticing such a basic thing. But thanks for your helps :D
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 want to implement startup loader in my app. It should be like this: after startup splash screen, user will watch simple animataion and in meanwhile app preload sound effects, background music, sprite images, spritesheets and so on. Current implementation:
- (id)init {
if((self = [super init])) {
// Some other setup ...
CGRect rect;
rect = waveSprite.textureRect;
waveInitialTexRectOrigin = rect.origin;
rect.size.width = 91;
waveSprite.textureRect = rect;
assetFilenames = [[NSArray alloc] initWithObjects:
// images
#"background.png",
// spritesheets
#"sprites.plist",
// fonts
#"main.png",
// sound effects
#"button.wav",
nil];
assetCounter = 0;
[self loadAsset];
}
return self;
}
- (void)update:(ccTime)dt {
CGRect rect;
rect = waveSprite.textureRect;
rect.origin.x += dt*kLoaderWaveSpeed;
while (rect.origin.x > waveInitialTexRectOrigin.x + kLoaderWavePeriod) {
rect.origin.x -= kLoaderWavePeriod;
}
waveSprite.textureRect = rect;
}
#pragma mark Private
- (void)loadAsset {
// CCLOG(#"loadAsset");
NSString *filename = [assetFilenames objectAtIndex:assetCounter];
CCLOG(#"loading %#", filename);
NSString *ext = [filename pathExtension];
if ([ext doesMatchRegStringExp:#"[png|jpg]"]) {
[[CCTextureCache sharedTextureCache] addImage:filename];
} else if ([ext isEqualToString:#"plist"]) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:filename];
} else if ([ext doesMatchRegStringExp:#"[caf|wav|mp3]"]) {
[[SimpleAudioEngine sharedEngine] preloadEffect:filename];
}
assetCounter++;
if (assetCounter < [assetFilenames count]) {
[self performSelector:#selector(loadAsset) withObject:self afterDelay:0.1f];
} else {
[self performSelector:#selector(loadingComplete) withObject:self afterDelay:0.2];
}
But the animation is SO abrupt.
UPD I've already tried
[self performSelectorInBackground: withObject:]
but it didn't seem to work (hung on loading first asset). Maybe I should try better in this direction.
UPD2 Smooth = not abrupt, without delays and flicker. fps doesn't matter, 20 fps quite OK
I'm not experienced with CC2D, but here's what I used in my ViewController to show a smooth animated loading indicator:
//Set up and run your loading scene/indicator here
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//Load your scene
dispatch_async( dispatch_get_main_queue(), ^{
//This fires when the loading is complete
//Show menu and destroy the loader scene/indicator here
});
});
I guess it's worth a try.
You should not be using performSelectorInBackground anymore, it is not deprecated but it might be soon, with GCD now in the picture.
What I would try is run the animation in the main thread since it is the only one that can do UI updates. And spawn one or more threads using GCD to load all your assets.
I would use concurrent dispatch queues to load everything in parallel and simply dismiss the animation when this is done.
You can get complete information about concurrent dispatch queues here:
Concurrency Programming Guide
Hope that helps.
That is because you are doing everything on the same thread. Use a separate thread for the loader.
Hope this helps.
Cheers!
EDIT : Take a look at this.