Im am developing an iOS app in Xcode. I am still new to programming.
I am trying to set the volume of an audiotrack while playing it with an AVPlayer. It is working great if a set the time at kCMTimeZero but I want it to set the volume one second after the button was pressed.
Not working
- (IBAction)maxVolButtonPressed:(id)sender {
[audioInputParams1 setVolume:1 atTime:time1];
[self applyAudioMix];
}
Working
- (IBAction)minVolButtonPressed:(id)sender {
[audioInputParams1 setVolume:0 atTime:kCMTimeZero];
[self applyAudioMix];
}
What should I write after atTime if I want a one second delay?
ANSWER:
Ok so I figured it out. You have to add the time where the current item is at the moment. I use setVolumeRampFromStartVolume with a very little time interval instead of setVolume. setVolume fades to the given volume for some reason I haven't figured it out why.
It works for me like this:
CMTime time1 = CMTimeMake(1000, 1000); //2s
CMTime time2 = CMTimeMake(1001, 1000); //3s
CMTime timeCurrent = [player currentTime];
CMTime time1Added = CMTimeAdd(timeCurrent, time1);
CMTime time2Added = CMTimeAdd(timeCurrent, time2);
CMTimeRange fadeInTimeRange = CMTimeRangeFromTimeToTime(time1Added, time2Added);
[audioInputParams1 setVolumeRampFromStartVolume:0 toEndVolume:1 timeRange:fadeInTimeRange];
[self applyAudioMix];
You should do something like this:
- (IBAction)maxVolButtonPressed:(id)sender {
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(setVolume:) userInfo:nil repeats:NO];
}
- (void)setVolume:(id)sender{
[audioInputParams1 setVolume:1 atTime:kCMTimeZero];
[self applyAudioMix];
}
In first method you are saying: wait for 1 second and then call method setVolume:.
Related
I am making an app that plays back audio and I have set it up so that the lock screen gets updated through MPNowPlayingInfoCenter, but I've run into a problem.
At seemingly random times, I get an EXC_BAD_ACCESS error when trying to update the now playing info.
Here's the code that does so:
- (void)updatePlayback
{
if(!active)
return;
NowPlayingController* npc = [AudioController nowPlayingController];
CMTime elapsed = player.currentTime;
Float64 elInterval = CMTimeGetSeconds(elapsed);
[npc setElapsed:elInterval];
CMTime duration = player.currentItem.duration;
Float64 durInterval = CMTimeGetSeconds(duration);
[npc setRemaining:ceilf(durInterval - elInterval)];
[npc setPlayPauseValue:isPlaying];
if(durInterval > 0)
{
[npc setProgressValue:elInterval/durInterval];
[npc setAudioDuration:durInterval];
}
_activeMetadata[MPMediaItemPropertyPlaybackDuration] = #(durInterval);
_activeMetadata[MPNowPlayingInfoPropertyPlaybackRate] = #(isPlaying);
_activeMetadata[MPNowPlayingInfoPropertyElapsedPlaybackTime] = #(elInterval);
MPNowPlayingInfoCenter* npInfoCenter = [MPNowPlayingInfoCenter defaultCenter];
if(npInfoCenter && _activeMetadata)
{
if([npInfoCenter respondsToSelector:#selector(setNowPlayingInfo:)])
{
//////////THE FOLLOWING LINE TRIGGERS EXC_BAD_ACCESS SOMETIMES////////////
[npInfoCenter setNowPlayingInfo:_activeMetadata];
}
}
}
99.9% of the time, this works, but sometimes when resigning the app to the background or when changing audio files, or just randomly,
[npInfoCenter setNowPlayingInfo:_activeMetadata];
throws EXC_BAD_ACCESS.
Also, _activeMetadata is declared as:
#property (atomic, strong, retain) NSMutableDictionary* activeMetadata;
It is instantiated when the AVPlayer is created:
AVAsset* asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:path]];
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:asset];
player = [AVPlayer playerWithPlayerItem:playerItem];
CMTime duration = player.currentItem.duration;
NSTimeInterval durInterval = CMTimeGetSeconds(duration);
NSLog(#"%f", durInterval);
MPMediaItemArtwork* albumArtwork = [[MPMediaItemArtwork alloc] initWithImage:[downloader useCachedImage:CacheKeySeriesBanners withName:nil withURL:info[#"image"]]];
NSDictionary* nowPlayingInfo = #{MPMediaItemPropertyTitle:ptString,
MPMediaItemPropertyArtist:spString,
MPMediaItemPropertyArtwork:albumArtwork,
MPMediaItemPropertyAlbumTitle:info[#"title"],
MPMediaItemPropertyPlaybackDuration:#(durInterval),
MPNowPlayingInfoPropertyPlaybackRate:#(1),
MPNowPlayingInfoPropertyElapsedPlaybackTime:#(0)};
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nowPlayingInfo];
_activeMetadata = [nowPlayingInfo mutableCopy];
updatePlayback is called via a CADisplayLink on every frame.
Any ideas what could be causing the exception?
I think you're calling setNowPlayingInfo way too often. Granted, it really shouldn't crash but there's no need to use CADisplayLink to call it 60 times a second.
So why are you calling it so often? If it's because you want to progress bar to track smoothly, there's still no need. From the MPNowPlayingInfoPropertyElapsedPlaybackTime declaration:
// The elapsed time of the now playing item, in seconds.
// Note the elapsed time will be automatically extrapolated from the previously
// provided elapsed time and playback rate, so updating this property frequently
// is not required (or recommended.)
p.s. I tried the code with an m4a file and found durInterval was NotANumber. With the correct duration and calling setNowPlayingInfo only once, the progress bar tracked fine & nothing crashed.
Apple fixed this crash in iOS 10.3 and above.
So if you want to support iOS 10.2.1 and below, be sure to throttle how often you set [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo property. Perhaps limiting setting the property only once a second.
I am using an NSTimer to call a method which is titled Lose. I had a timer which when it ran out, it called Lose, but I lost everything due to a hard drive error. After trying to code it all again, I can't seem to get the method to be called.
Timer = [NSTimer timerWithTimeInterval:timeMax target:self selector:#selector(Lose) userInfo:nil repeats:NO];
Lose is declared in my .h file, like this:
-(void)Lose;
Also, my method looks like this:
-(void)Lose{
Text.hidden = NO;
scoreLabel.hidden = NO;
Target.hidden = YES;
Targetx.hidden = YES;
if (Score > highScoreNumber) {
highScoreAchieved.hidden = NO;
highScoreNumber = Score;
}
}
The variable timeMax is an int declared in my .h file, like last time.
whenever a target is tapped in my game, timeMax becomes .03 seconds shorter. I do it like this:
timeMax = 5 - (Score * 0.03);
I don't remember it looking different before the massive hardware failure, but why isnt it working?
You have to schedule the timer on a run loop or just use this line instead which schedules it for you:
Timer = [NSTimer scheduledTimerWithTimeInterval:timeMax
target:self
selector:#selector(Lose)
userInfo:nil
repeats:NO];
It's also a good idea to always back up your code...which reminds me.
Here i am sending code for player view of panframe sdk
CMTime t = [pfAsset getPlaybackTime];
CMTime seekingCM = CMTimeMake(t.value+10000000, t.timescale);
[pfView run];
//[pfAsset stop];
[pfAsset playWithSeekTo:seekingCM onKeyFrame:YES];
slider.value = CMTimeGetSeconds(t);
[pfAsset play];return;
Please advise how it will work.
I would like my AVPlayer object to automaticly update my UISlider when playing.
I have found a code on this forum that seems to work for other but I'm broken at some point:
CMTime interval = CMTimeMakeWithSeconds(1.0, NSEC_PER_SEC); // 1 second
self.playbackTimeObserver = [self.player addPeriodicTimeObserverForInterval:interval queue:NULL usingBlock:^(CMTime time) {
// update slider value here...
}];
I have inserted this code in my viewDidLoad but I removed "self.playbackTimeObserver" as I can't find what type of object is this. I guess that's why it s not working correctly.
Can you please tell me what type is it and where/how to declare it?
Here is my current code:
- (void)viewDidLoad
{
[super viewDidLoad];
CMTime interval = CMTimeMakeWithSeconds(1.0, NSEC_PER_SEC); // 1 second
[songPlayer addPeriodicTimeObserverForInterval:interval queue:NULL usingBlock:^(CMTime time) {
NSLog(#"seconds = %f", CMTimeGetSeconds(songPlayer.currentTime));
}];
self.mmContainerSearch.hidden = NO;
self.mmContainerDownload.hidden = YES;
self.mmContainerLibrary.hidden = YES;
}
Its type is id. It right in the documentation.
Return Value
An opaque object that you pass as the argument to removeTimeObserver: to cancel observation.
If you never need to remove the time observer, then you don't really need to save the return value. My guess is that as some point you will want to cleanup. At that point, then you will need to call -removeTimeObserver:.
I'm playing songs in AVPlayer. I have created a separate view controller for my media player and initialization, and all the methods that I'm using for the player (play, pause, repeat, shuffle) are there in the same view controller.
I update a slider like this:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(sliderUpdate:) userInfo:nil repeats:YES];`
- (void) sliderUpdate:(id) sender{
int currentTime = (int)((song.player.currentTime.value)/song.player.currentTime.timescale);
slider.value=currentTime;
NSLog(#"%i",currentTime);
song.currentTime=currentTime;
int currentPoint=(int)((song.player.currentTime.value)/song.player.currentTime.timescale);
int pointMins=(int)(currentPoint/60);
int pointSec=(int)(currentPoint%60);
NSString *strMinlabel=[NSString stringWithFormat:#"%02d:%02d",pointMins,pointSec];
lblSlidermin.text=strMinlabel;
song.strslidermin=strMinlabel;
}
Once I'm going out of the viewcontroller and when come again, song is playing but the problem is slider is not updating. So I created a singleton class to assign currently playing song details. Also inside the slider update I asigned playerCurrentTime (slidercurrent value) for a singleton class variable. And my viewdidload method I assigned like this:
if (song.isPlaying==NO) {
[self prePlaySong];
}else{
lblAlbum.text=song.currentAlbum;
lblArtist.text=song.currentArtist;
lblSong.text=song.currentSong;
slider.value=song.currentTime;
slider.maximumValue=song.sliderMax;
slider.minimumValue=song.sliderMin;
imgSong.image=song.songImage;
[btnMiddle setBackgroundImage:[UIImage imageNamed:#"pause.png"] forState:UIControlStateNormal];
}
but slider is not getting updated. Why is that and how I can solve this problem?
You should not use a timer for this, AVPlayer provides an API you observe the time.
Register an observer (e.g. in viewWillAppear) like this:
CMTime interval = CMTimeMakeWithSeconds(1.0, NSEC_PER_SEC); // 1 second
self.playbackTimeObserver =
[self.player addPeriodicTimeObserverForInterval:interval
queue:NULL usingBlock:^(CMTime time) {
// update slider value here...
}];
To unregister the observer (e.g. in viewDidDisappear), do:
[self.player removeTimeObserver:self.playbackTimeObserver];
Hope that helps.
Swift 3 version.
Register an observer:
// Invoke callback every second
let interval = CMTime(seconds:1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
// Queue on which to invoke the callback
let mainQueue = DispatchQueue.main
// Keep the reference to remove
self.playerObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: mainQueue) { time in
print(time)
}
Remove an obserber:
if let observer = self.playerObserver {
self.player.removeTimeObserver(observer)
self.playerObserver = nil
}