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
}
Related
I want to play a sound (bells) with multiple scheduledTimerWithTimeInterval set like Every 5, Every 9 Every 35 after 6 ....etc now I Bells start playing with Interval 55, 56, 59, 60, 45, 42....but i want to pause time on 52 then i invalidate all timer because not got pause method or property of NSTimer after that I again start timer is start with new interval like 48 , 47 ,43 .... but I want maintain old Interval So any one have idea about it please help me .
-(void) bellsSchedual {
arrBellsListAllData = [DBModel getDataFromBellsList:prop.userId];
DBProperty *bellProp = [[DBProperty alloc]init];
for (int i = 0; i < arrBellsListAllData.count; i++) {
bellProp=[arrBellsListAllData objectAtIndex:i];
NSString* bellsTime=bellProp.bTime;
if ([bellProp.bTimeSchedule isEqualToString: #"after"]) {
NSTimer* bellTimer = [NSTimer scheduledTimerWithTimeInterval: [bellsTime intValue]
target: self
selector: #selector(playSound:)
userInfo: nil
repeats: NO]
[arrTimers addObject:bellTimer];
} else if ([bellProp.bTimeSchedule isEqualToString: #"every"]) {
NSTimer* bellTimer = [NSTimer scheduledTimerWithTimeInterval: [bellsTime intValue]
target: self
selector: #selector(playSound:)
userInfo: nil
repeats: YES];
[arrTimers addObject: bellTimer];
}
}
}
Thanks
If I understand your question correctly you require a timer that can be paused, and have correctly determined that an NSTimer cannot be paused.
What you could consider in outline is:
Your own timer class which provides a method, say tick, which causes the timer to progress. Use an instance of this class for each bell; and
Use a repeating NSTimer to provide the tick - when it fires it calls the tick methods of all your registered custom timers. Invalidating this NSTimer will stop the ticks, effectively pausing the custom timers. Creating an new NSTimer to provide the ticks will restart your custom timers.
HTH
Question is not really clear…
As far as I understand, each bell has it's own timer and varying interval time.
Add a property bCurrentTime to your bellProp. You set it to bTime when you reset your system or create the bellProp object.
Use this value when you fire your timers instead of the bTime.
In your playSound method, you update this value with the current time interval.
This way, when you invalidate all your timers and restart them, all previous times are stored.
Add a reset method to set all bells bCurrentTime to bTime.
If you want control over your timers, you need to make them properties. That way you can start, stop, and do whatever you want within the entire scope of the file under which you declared them.
My app adds a custom SKNode object to a SKScene at a fixed interval (sub second) using an NSTimer. At certain times I want the timer to speed up. Here's what I do (code simplified):
#interface MyScene : SKScene
#end
#implementation MyScene {
MyNode *node;
NSTimer *timer;
int speed;
}
- (id):initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
speed = 0.8f;
timer = [NSTimer = scheduledTimerWithTimeInterval:speed target:self selector:#selector(addNodeToScene) userInfo:nil repeats:YES];
}
}
- (id):addNodeToScene {
if (node != nil) {
[node removeFromParent];
node = nil;
}
CGPoint location = //random x y coordinates using arc4random()...
node = [[MyNode alloc] initAtLocation:location];
[self addChild:node];
}
// at some point I call this method (called regularly throughout life of app)
- (id):speedUp {
[timer invalidate];
timer = nil;
speed *= 0.9f;
timer = [NSTimer = scheduledTimerWithTimeInterval:speed target:self selector:#selector(addNodeToScene) userInfo:nil repeats:YES];
}
I've noticed a slight lag every time i call speedUp. I'm very new to Objective-C so not sure how to resolve. Is it more efficient to use dispatch queues over a timer or can I not avoid this issue because the internal is so fast?
I ran time profiling on instruments and this was not highlighted - instead my heaviest method was addNodeToScene.
My guess is that you're calling -speedUp outside of the normal work in -addNodeToScene. If so, the lag is likely coming from the fact that you've already used up a bit of the previous delay, then -speedUp invalidates the old timer and creates a new one, which starts the delay all over again. You'll see a lag any time you're more than 10% beyond the previous timer fire.
I'll try some ASCII art to illustrate. + is a timer fire, * is when you call -speedUp and it resets the timer.
+---------+--------+---------+---------+---------+
+---------+---*--------+--------+--------+--------+
Found the solution in another question: Xcode / Objective-C: Why is NSTimer sometimes slow/choppy?
(use CADisplayLink instead of NSTimer)
In my viewController, I have a button that calls a method to start a timer in a Timer class. Just like this
main view controller
[self.timer startTimer];
In the timer class, startTimer calls a countdownTime method, both of which you can see below. Very simple. At the end of the countdownTime, method, the time is put in the label in the view like this as I iterate through a loop of the clocks.
Timer *timerblah = self.gameClocks[i];
[self.gameClocks[i] setText: [NSString stringWithFormat:#"%#", self.time]];
In other words, the Timer class has an array property that holds all the clocks and that is connected to the view, hence the ability to set the text.
The problem with this code is that the model (i.e. Timer class) is setting the text in the view. I want the view Controller to get the time from the model and have the text set in the view controller i.e. have the viewController and only the viewController communicate with the view. It is no problem for me to get the array of clocks in the viewController, however, I'm sure how to pass the time back from the countdownTime method to the viewController. It would seem like overkill to set up an NSNotification class to send the time back every second, wouldn't it?
-(void)startTimer{
self.NStimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(countdownTime:)
userInfo:nil
repeats:YES];
}
-(void)countdownTime:(NSTimer *)timer
{
#logic to countdown time
self.minutes = self.secondsRemaining / 60;
self.stringMinutes = [NSString stringWithFormat:#"%i", self.minutes];
self.seconds = self.secondsRemaining - (self.minutes * 60);
self.stringSeconds = [NSString stringWithFormat:#"%i", self.seconds];
if (self.seconds < 10) self.stringSeconds = [NSString stringWithFormat:#"0%i", self.seconds];
self.time = [NSString stringWithFormat:#"%#:%#", self.stringMinutes, self.stringSeconds];
self.secondsRemaining -= 1;
if (self.secondsRemaining == 0){
[self stopTimer];
}
#adding to the view (from the model i.e. in Timer.m class)
for(int i = 0; i < [self.gameClocks count]; i++){
Timer *timerblah = self.gameClocks[i];
if (timerblah.systemClock == YES){
[self.gameClocks[i] setText: [NSString stringWithFormat:#"%#", self.time]];
}
}
}
Update
There's a comment suggesting that I use a block to do this. In the timer class, I added a method like this to return a string with the time (the time is set to self.time above)
-(NSString *)passTheTime:(NSString (^)(void))returnBlock
{
return self.time;
}
I then, in view controller, call the method passTheTime on the timer class. I created a property in the view controller that will store the time, so I pass that as a parameter to the block,
[self.timer passTheFoo:^NSString * (self.timeToSet){
}];
a) I'm unsure of what to do in the block here.
b) I'm unsure of whether it was necessary to pass self.timeToSet into the block. How do I connect self.time from the Timer class to self.timeToSet in the view controller
c) there's an error incompatible block pointer type sending NSString *((^)(void)) to parameter of type NSString(^)(void)
d) alternatively, can I pass a block to startTimer, and have that block passed in the selector countdownTime and return the self.time once the calculations in countdownTime are finished?
You can use block to update the label in the view controller class as below
In Timer Class, create a block
TimerClass.h
#import <Foundation/Foundation.h>
typedef void(^TimerCallback)(NSString *time);
#interface TimerClass : NSObject{
TimerCallback timerCallback;
NSTimer *timer;
}
- (void)startTimerWithCallBack:(TimerCallback)callback;
#end
In TimerClass.m , update the code as below
#import "TimerClass.h"
#implementation TimerClass
- (void)startTimerWithCallBack:(TimerCallback)callback{
timerCallback = callback;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(countdownTime:)
userInfo:nil
repeats:YES];
}
-(void)countdownTime:(NSTimer *)timer
{
// countdown logic here
// call the callback method with time as a parameter to the viewcontroller class
// here for demo purpose I am passing random number as a value in callback, you need to pass the time that you want to display here
NSString *str = [NSString stringWithFormat:#"%d",1+arc4random()%10];
timerCallback(str);
}
#end
In ViewController class, call the timer start method as follows
TimerClass *timer = [[TimerClass alloc] init];
[timer startTimerWithCallBack:^(NSString *time) {
lbl.text = time; // display time in label
}];
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:.
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:.