Scrubber (UISlider) in AVPlayer? - ios

When you play remote video via AVPlayer and start rewinding, the scrubber is buggy.
I'm doing a player based on this Apple example.
How to implement it smoothly?
Code from my project follows - https://github.com/nullproduction/Player
- (void)initScrubberTimer
{
double interval = .1f;
CMTime playerDuration = [self playerItemDuration];
if (CMTIME_IS_INVALID(playerDuration))
{
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
CGFloat width = CGRectGetWidth([scrubberSlider bounds]);
interval = 0.5f * duration / width;
}
__weak id weakSelf = self;
CMTime intervalSeconds = CMTimeMakeWithSeconds(interval, NSEC_PER_SEC);
mTimeObserver = [self.player addPeriodicTimeObserverForInterval:intervalSeconds
queue:dispatch_get_main_queue()
usingBlock:^(CMTime time) {
[weakSelf syncScrubber];
}];
}
- (void)syncScrubber
{
CMTime playerDuration = [self playerItemDuration];
if (CMTIME_IS_INVALID(playerDuration))
{
scrubberSlider.minimumValue = 0.0;
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
float minValue = [scrubberSlider minimumValue];
float maxValue = [scrubberSlider maximumValue];
double time = CMTimeGetSeconds([self.player currentTime]);
[scrubberSlider setValue:(maxValue - minValue) * time / duration + minValue];
}
}
- (IBAction)beginScrubbing:(id)sender
{
mRestoreAfterScrubbingRate = [self.player rate];
[self.player setRate:0.f];
[self removePlayerTimeObserver];
}
- (IBAction)scrub:(id)sender
{
if ([sender isKindOfClass:[UISlider class]])
{
UISlider* slider = sender;
CMTime playerDuration = [self playerItemDuration];
if (CMTIME_IS_INVALID(playerDuration))
{
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
float minValue = [slider minimumValue];
float maxValue = [slider maximumValue];
float value = [slider value];
double time = duration * (value - minValue) / (maxValue - minValue);
[self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];
}
}
}
- (IBAction)endScrubbing:(id)sender
{
if (!mTimeObserver)
{
CMTime playerDuration = [self playerItemDuration];
if (CMTIME_IS_INVALID(playerDuration))
{
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
CGFloat width = CGRectGetWidth([scrubberSlider bounds]);
double tolerance = 0.5f * duration / width;
__weak id weakSelf = self;
CMTime intervalSeconds = CMTimeMakeWithSeconds(tolerance, NSEC_PER_SEC);
mTimeObserver = [self.player addPeriodicTimeObserverForInterval:intervalSeconds
queue:dispatch_get_main_queue()
usingBlock: ^(CMTime time) {
[weakSelf syncScrubber];
}];
}
}
if (mRestoreAfterScrubbingRate)
{
[self.player setRate:mRestoreAfterScrubbingRate];
mRestoreAfterScrubbingRate = 0.f;
}
}

I guess the problem is, that your scrubber is still updating from the video, while you are using the seekbar. Implement it in a way, that you pause the player during the scrubbing, and you won't have this bug anymore. Checkout my solution:
The function to update your player:
- (IBAction)seekbarAction:(UISlider *)sender {
CMTime videoLength = playerItem1.duration; //gets the video duration
float videoLengthInSeconds = videoLength.value/videoLength.timescale; //transfers the CMTime duration into seconds
[player1 seekToTime:CMTimeMakeWithSeconds(videoLengthInSeconds*sender.value, 1)];
}
And another seekbar action with "Touch down" in order to pause the video:
- (IBAction)pauseSeek:(id)sender {
[player1 pause];
}
And another seekbar action with "Touch up" in order to resume the video when you release the scrubber. Hope this helps.

Related

Avplayer video playing Method

I am working on App in which I want to display current playing time and total time of video. I got the total time. Now for showing current playing time Which method will get called. Can anyone help? I have used avplayer. This is the code:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.avplayer pause];
self.avplayer = [AVQueuePlayer playerWithURL:[NSURL URLWithString:#""]];
self.avplayer = nil;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:NO];
AVPlayerItem *currentItem = self.avplayer.currentItem;
CMTime duration = currentItem.duration; //total time
CMTime currentTime = currentItem.currentTime; //playing time
NSUInteger durationSeconds = (long)CMTimeGetSeconds(duration);
NSUInteger minutes = floor(durationSeconds % 3600 / 60);
NSUInteger seconds = floor(durationSeconds % 3600 % 60);
NSString *time = [NSString stringWithFormat:#"%02ld:%02ld", (unsigned long)minutes, (unsigned long)seconds];
NSLog(#"Time|%#", time);
lblTotaltime.text = time;
NSUInteger durationSeconds1 = (long)CMTimeGetSeconds(currentTime);
NSUInteger minutes1 = floor(durationSeconds1 % 3600 / 60);
NSUInteger seconds1 = floor(durationSeconds1 % 3600 % 60);
NSString *time1 = [NSString stringWithFormat:#"%02ld:%02ld", (unsigned long)minutes1, (unsigned long)seconds1];
NSLog(#"Time|%#", time1);
lblRemaningTime.text = time1;
}
#pragma mark PlayerMethods
- (void)itemDidFinishPlaying:(NSNotification *)notification {
AVPlayerItem *player = [notification object];
[player seekToTime:kCMTimeZero];
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
AVPlayerItem *p = [notification object];
[p seekToTime:CMTimeMake(0, 3)];
}
- (void)playerStartPlaying
{
[self.avplayer play];
}
You can get the current played time by using currentItem property using AVPlayerItem
AVPlayerItem *getcurrentItem = yourAVPlayerName.currentItem;
for get total Duration
CMTime fullDuration = getcurrentItem.duration;
for get current Time
CMTime playercurrentTime = getcurrentItem.currentTime;
alternate
NSTimeInterval playercurrentTime = CMTimeGetSeconds(getcurrentItem.currentTime);
NSLog(#" get current Time of video :%f ",playercurrentTime);

AVPlayer (or AVAudioSession?) plays after I leave the ViewController, and when I return two are then playing

I have a viewController that streams audio from the web. When I leave that page of my app, the audio keeps playing (this is good). However, when I go back, the viewdidload method creates a second audioplayer. I can do this over and over until I have so many AVplayers. It sounds incredibly echo-y. Here is all my relevant code.
#import "ListenViewController.h"
#import <AVFoundation/AVAudioPlayer.h>
#import <AVFoundation/AVPlayerItem.h>
#import <AVFoundation/AVPlayer.h>
#import <AVFoundation/AVFoundation.h>
#import "RSPlayPauseButton.h"
#interface ListenViewController () <AVAudioPlayerDelegate, AVAudioSessionDelegate>
#property (nonatomic, strong) AVPlayer *player;
#property (nonatomic, strong) AVPlayerItem *playerItem;
#property (nonatomic, strong) RSPlayPauseButton *playPauseButton;
#property (nonatomic, copy) NSDate *now;
#end
#implementation ListenViewController
static int min = -2;
-(void) viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
//[self.player pause];
min = -2;
}
-(void) viewDidLoad
{
[super viewDidLoad];
/*
if ([AVAudioSession sharedInstance].isInputAvailable) {
NSLog(#"XXXXXXXXX");
}
*/
NSURL *streamingURL = [NSURL URLWithString:#"http://URL.m3u"];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:streamingURL];
self.playerItem = playerItem;
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
[player setAllowsExternalPlayback:NO];
CMTime tm = CMTimeMake(1, 1);
[player addPeriodicTimeObserverForInterval:tm
queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
/*
NSDateFormatter *DateFormatter=[[NSDateFormatter alloc] init];
[DateFormatter setDateFormat:#"yyyy-MM-dd hh:mm:ss:mmm"];
NSLog(#"%#",[DateFormatter stringFromDate:[NSDate date]]);
*/
if (!self.now) {
self.now = [NSDate date];
}
/*
NSDate *now = [NSDate date];
NSTimeInterval interval = [now timeIntervalSinceDate:self.now];
self.interval += 0.01;
interval -= (10 * self.rw2);
interval -= (60 * self.rw3);
interval += (10 * self.ff2);
self.timeLabel.text = [NSString stringWithFormat:#"%f", interval];
NSLog(#"INTERVAL: %#", [NSString stringWithFormat:#"%f", self.interval]);
*/
if (CMTimeGetSeconds(self.player.currentTime) >= 0) {
self.timeLabel.text = [NSString stringWithFormat:#"%.0f", CMTimeGetSeconds(self.player.currentTime)];
NSTimeInterval time = CMTimeGetSeconds(self.player.currentTime);
//int min = time/60;
int sec = lroundf(time) % 60;
if (sec == 0) {
++min;
}
NSLog(#"sec: %d", sec);
// update your UI with timeLeft
self. timeLabel.text = [NSString stringWithFormat:#"%.2d:%.2d", min,sec];
}
}];
[self setPlayer:player];
[player play];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
if (!self.playPauseButton) {
self.playPauseButton = [[RSPlayPauseButton alloc] initWithFrame:self.view.frame];
self.playPauseButton.tintColor = [UIColor colorWithRed:1.0 green:155.0/255.0 blue:0.0 alpha:1.0];
self.playPauseButton.animationStyle = RSPlayPauseButtonAnimationStyleSplitAndRotate;
self.playPauseButton.paused = NO;
[self.playPauseButton addTarget:self action:#selector(playPauseButtonDidPress:) forControlEvents:UIControlEventTouchUpInside];
}
[self.view addSubview:self.playPauseButton];
}
- (IBAction)rewind:(id)sender
{
//NSLog(#"currenttime: %f", CMTimeGetSeconds(self.player.currentTime));
//NSLog(#"timescale: %d", self.player.currentTime.timescale);
CMTime cmTime = CMTimeMake(CMTimeGetSeconds(self.player.currentTime) - 1.0, 1);
CMTime zero = CMTimeMake(1, 2);
if (CMTimeGetSeconds(cmTime) > CMTimeGetSeconds(zero)) {
[self.player.currentItem seekToTime:cmTime];
NSLog(#"!");
}
NSLog(#"RW");
}
- (IBAction)rewind2:(id)sender
{
/*
CMTime cmTime = CMTimeMakeWithSeconds(CMTimeGetSeconds(self.player.currentTime) - 10.0, self.player.currentTime.timescale);
CMTime zero = CMTimeMake(0, 10000);
if (CMTimeGetSeconds(cmTime) > CMTimeGetSeconds(zero)) {
[self.player.currentItem seekToTime:cmTime];
self.rw2 += 1;
NSLog(#"RW2!");
}
*/
NSLog(#"RW2");
CMTime cmTime = CMTimeMakeWithSeconds(CMTimeGetSeconds(self.player.currentTime) - 10.0, self.player.currentTime.timescale);
CMTime zero = CMTimeMake(1, 2);
NSLog(#"cmTime: %f", CMTimeGetSeconds(cmTime));
NSLog(#"zero: %f", CMTimeGetSeconds(zero));
if (CMTimeGetSeconds(cmTime) > CMTimeGetSeconds(zero)) {
[self.player.currentItem seekToTime:cmTime];
NSLog(#"!");
} else {
[self rewindAll];
}
}
- (IBAction)rewind3:(id)sender
{
CMTime cmTime = CMTimeMakeWithSeconds(CMTimeGetSeconds(self.player.currentTime) - 60.0, self.player.currentTime.timescale);
CMTime zero = CMTimeMake(0, 10000);
if (CMTimeGetSeconds(cmTime) > CMTimeGetSeconds(zero)) {
[self.player.currentItem seekToTime:cmTime];
NSLog(#"RW3!");
}
NSLog(#"RW3");
}
- (void) rewindAll
{
CMTime one = CMTimeMake(1, 1);
[self.player.currentItem seekToTime:one];
}
- (IBAction)fastForward:(id)sender
{
CMTime cmTime = CMTimeMakeWithSeconds(CMTimeGetSeconds(self.player.currentTime) + 5.0, self.player.currentTime.timescale);
NSDate *now = [NSDate date];
NSTimeInterval interval = [now timeIntervalSinceDate:self.now];
CMTime seekingCM = CMTimeMake(interval, 1);
if (CMTimeGetSeconds(cmTime) < CMTimeGetSeconds(seekingCM)) {
[self.player.currentItem seekToTime:cmTime];
}
}
- (IBAction)fastForward2:(id)sender
{
CMTime cmTime = CMTimeMakeWithSeconds(CMTimeGetSeconds(self.player.currentTime) + 10.0, self.player.currentTime.timescale);
NSDate *now = [NSDate date];
NSTimeInterval interval = [now timeIntervalSinceDate:self.now];
CMTime seekingCM = CMTimeMake(interval, 1);
if (CMTimeGetSeconds(cmTime) < CMTimeGetSeconds(seekingCM)) {
[self.player.currentItem seekToTime:cmTime];
NSLog(#"FF2!");
} else {
[self fastForward3:nil];
}
}
- (IBAction)fastForward3:(id)sender
{
NSDate *now = [NSDate date];
NSLog(#"FIRSTDATE: %#", self.now);
NSTimeInterval interval = [now timeIntervalSinceDate:self.now];
interval -= 0.5f;
NSLog(#"INT: %f", interval);
CMTime seekingCM = CMTimeMake(interval, 1);
/*
NSTimeInterval interval = self.interval - 1.0;
CMTime seekingCM = CMTimeMake(interval, 1);
[self.player.currentItem seekToTime:seekingCM];
*/
//CMTime seekingCM = CMTimeMake(self.interval, 1);
[self.player.currentItem seekToTime:seekingCM];
NSLog(#"FF3!");
}
- (NSTimeInterval)currentPlaybackTime
{
return CMTimeGetSeconds(self.player.currentItem.currentTime);
}
- (void)setCurrentPlaybackTime:(NSTimeInterval)time
{
CMTime cmTime = CMTimeMakeWithSeconds(time, NSEC_PER_SEC);
[self.player.currentItem seekToTime:cmTime];
}
- (void)viewDidLayoutSubviews
{
[self.playPauseButton sizeToFit];
self.playPauseButton.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 1.2);
}
- (void)playPauseButtonDidPress:(RSPlayPauseButton *)playPauseButton
{
[playPauseButton setPaused:!playPauseButton.isPaused animated:YES];
if (playPauseButton.isPaused) {
[self.player pause];
}
else {
[self.player play];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
By the way, there is a UIlabel that gives the current time of the player, but if I leave the page and go back, the label automatically starts again at 00:00. This is because it's giving the current time of the new AVPlayer. Is there a way to make it give the current time of the original AVPlayer?
I would create a singleton object to control a AVPlayer, so no matter how many view controllers you have they all would communicate with this one player controller. So basically I'd advise you to move all the code you have in viewDidLoad to some MYPlayerController singleton class.
here is my view controller from swift, if it can help you, will be fine, I have implemented all radio stuff there:
swift view controller

How to make delays between AVPlayerItems in AVQueuePlayer in background task?

I trying to make delays between AVPlayerItems, but I don't understand how to make this. I need to play sounds in background (when iOS app in background) with delays between these sounds. But AVQueuePlayer doesn't support this feature. When i trying to pause the sound after it finished, the next sound don't want to play. However, in foreground that algorithm works fine.
This is code, which i running in background task:
AVQueuePlayer *player = [[AVQueuePlayer alloc] initWithItems: items];
[player play];
NSUInteger currentIndex = 0;
AVPlayerItem *lastItem = nil;
while(currentIndex < items.count && self.enabled) {
AVPlayerItem *currentItem = [player currentItem];
while(currentItem == lastItem || currentItem == nil) {
[NSThread sleepForTimeInterval:0.2f];
currentItem = [player currentItem];
}
while(currentItem.status != AVPlayerItemStatusReadyToPlay) {
[NSThread sleepForTimeInterval:0.2f];
NSLog(#"Loading...");
}
lastItem = currentItem;
CGFloat time = 0.0f;
CGFloat duration = 0.0f;
while(duration < 0.5f) {
[NSThread sleepForTimeInterval: 0.5f];
if(currentItem.duration.timescale > 0)
duration = (double) currentItem.duration.value / (double) currentItem.duration.timescale;
}
while(time < duration - 0.1f) {
[NSThread sleepForTimeInterval: 0.08f];
time = (double) currentItem.currentTime.value / (double) currentItem.currentTime.timescale;
}
[player pause];
CGFloat delay = someDelayBetweenSounds;
[NSThread sleepForTimeInterval: delay];
[player play];
currentIndex++;
}
My English is very bad, therefore I apologize
When an item finishes, you can pause the player and start it up again after a given delay. It's much better to do it this way than blocking an entire thread.
- (void) AVPlayerItemDidPlayToEndTimeNotification: (NSNotification *)notification {
self.avQueuePlayer.rate = 0;
CGFloat delay = 500;
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (delay) * NSEC_PER_MSEC);
dispatch_after(delay, dispatch_get_main_queue() , ^{
self.avQueuePlayer.rate = 1;
});

AVPlayer seekToTime: backward not working

I have the following code:
AVPlayerItem *currentItem = [AVPlayerItem playerItemWithURL:soundURL];
[self.audioPlayer replaceCurrentItemWithPlayerItem:currentItem];
[self.audioPlayer play];
where soundURL is a remoteURL. It works fine. The AVPlayer plays the music perfectly. I have a progress bar and i am updating it based on the current time of the player.
Everything works fine. My issue is when i drag the progress bar forward the audioplayer starts from the new location but if i drag the progressbar it doesn't start from the new location in fact it resumes from the previous location. Here is my progress bar drag start and stop code:
- (IBAction)progressBarDraggingStart:(id)sender
{
if (self.audioPlayer.rate != 0.0)
{
[self.audioPlayer pause];
}
}
- (IBAction)progressBarDraggindStop:(id)sender
{
CMTime newTime = CMTimeMakeWithSeconds(self.progressBar.value, 1);
[self.audioPlayer seekToTime:newTime];
[self.audioPlayer play];
}
Can anyone help me fix this issue?
I suggest doing a couple of things. First, get the timescale value and pass it to the CMTime struct. Second, use the seekToTime:toleranceBefore:toleranceAfter:completionHandler: method for more accurate seeking. For example, your code would look like:
- (IBAction)progressBarDraggindStop:(id)sender {
int32_t timeScale = self.audioPlayer.currentItem.asset.duration.timescale;
[self.audioPlayer seekToTime: CMTimeMakeWithSeconds(self.progressBar.value, timeScale)
toleranceBefore: kCMTimeZero
toleranceAfter: kCMTimeZero
completionHandler: ^(BOOL finished) {
[self.audioPlayer play];
}];
}
I am using below code for dragging- Added completionHandler after #Corey's answer and it works great without any web-service dependency:
- (void) sliderValueChanged:(id)sender {
if ([sender isKindOfClass:[UISlider class]]) {
UISlider *slider = sender;
CMTime playerDuration = self.avPlayer.currentItem.duration;
if (CMTIME_IS_INVALID(playerDuration)) {
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration)) {
float minValue = [slider minimumValue];
float maxValue = [slider maximumValue];
float value = [slider value];
double time = duration * (value - minValue) / (maxValue - minValue);
[self.avPlayer seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
[self.avPlayer play];
}];
}
}
}

AVPlayer causes crash while updating progress slider with Assertion failure in -[AVPlayerPeriodicCaller initWithPlayer:interval:queue:block:]

I am getting the error within the app
" Assertion failure in -[AVPlayerPeriodicCaller initWithPlayer:interval:queue:block:], /SourceCache/EmbeddedAVFoundation/EmbeddedAVFoundation-461.12/Fig/AVPlayer.m:3993
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: CMTIME_COMPARE_INLINE(interval, >, kCMTimeZero)'
Any ideas why this may be happening?
I am limited to use AvPlayer only because I need to stream large file .AVaudioplayer is not an option. So I have also implemented a scrubber slider. My implementation of AVplayer is as follows:
-(void)playAudioForFile:(NSString *)fileName{
[self.suraPlayer removeTimeObserver:playbackObserver];
[self.suraPlayer removeTimeObserver:mTimeObserver];
self.suraPlayer = nil;
NSURL* url = [self getURLStringForFileName:fileName];
AVPlayer *player = [[AVPlayer alloc]initWithURL:url];
self.suraPlayer = player;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[suraPlayer currentItem]];
[self.suraPlayer play];
[self.suraPlayer addObserver:self forKeyPath:#"status" options:0 context:nil];
[self initScrubberTimer];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(updateProgress) userInfo:nil repeats:YES];
//[self metadataForAsset:self.suraPlayer.currentItem.asset];
}
-(void)updateProgress {
// NSLog(#"self.mScrubber.value %f", self.mScrubber.value);
//THIS IS THE LINE WHERE THE DEBUGGER SOMETIME STUMPS
float duration = CMTimeGetSeconds(self.suraPlayer.currentItem.duration);
if (self.suraPlayer.currentItem.status==AVPlayerStatusReadyToPlay )
[self.mScrubber setMaximumValue:duration];
self.mScrubber.value = CMTimeGetSeconds(self.suraPlayer.currentTime);
int seconds = self.mScrubber.value, minutes = seconds/60,hours = minutes/60;
int secondsRemain = self.mScrubber.maximumValue - seconds,minutesRemain = secondsRemain/60,hoursRemain = minutesRemain/60;
seconds = seconds-minutes*60;
minutes = minutes-hours*60;
secondsRemain = secondsRemain - minutesRemain*60;
minutesRemain = minutesRemain - hoursRemain*60;
NSString *hourStr,*minuteStr,*secondStr,*hourStrRemain,*minuteStrRemain,*secondStrRemain;
hourStr = hours > 9 ? [NSString stringWithFormat:#"%d",hours] : [NSString stringWithFormat:#"0%d",hours];
minuteStr = minutes > 9 ? [NSString stringWithFormat:#"%d",minutes] : [NSString stringWithFormat:#"0%d",minutes];
secondStr = seconds > 9 ? [NSString stringWithFormat:#"%d",seconds] : [NSString stringWithFormat:#"0%d",seconds];
hourStrRemain = hoursRemain > 9 ? [NSString stringWithFormat:#"%d",hoursRemain] : [NSString stringWithFormat:#"0%d",hoursRemain];
minuteStrRemain = minutesRemain > 9 ? [NSString stringWithFormat:#"%d",minutesRemain] : [NSString stringWithFormat:#"0%d",minutesRemain];
secondStrRemain = secondsRemain > 9 ? [NSString stringWithFormat:#"%d",secondsRemain] : [NSString stringWithFormat:#"0%d",secondsRemain];
self.timePlayerLabel.text = [NSString stringWithFormat:#"%#:%#:%#",hourStr,minuteStr,secondStr];
self.timeRemainingLabel.text = [NSString stringWithFormat:#"-%#:%#:%#",hourStrRemain,minuteStrRemain,secondStrRemain];
}
-(void)initScrubberTimer
{
double interval = .1f;
CMTime playerDuration = self.suraPlayer.currentItem.duration;
if (CMTIME_IS_INVALID(playerDuration))
{
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
CGFloat width = CGRectGetWidth([self.mScrubber bounds]);
interval = 0.5f * duration / width;
}
/* Update the scrubber during normal playback. */
mTimeObserver = [self.suraPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC)
queue:NULL /* If you pass NULL, the main queue is used. */
usingBlock:^(CMTime time)
{
}];
[self syncScrubber];
}
/* Set the scrubber based on the player current time. */
- (void)syncScrubber
{
CMTime playerDuration = self.suraPlayer.currentItem.duration;
if (CMTIME_IS_INVALID(playerDuration))
{
self.mScrubber.minimumValue = 0.0;
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
float minValue = [self.mScrubber minimumValue];
float maxValue = [self.mScrubber maximumValue];
double time = CMTimeGetSeconds([self.suraPlayer currentTime]);
[self.mScrubber setValue:(maxValue - minValue) * time / duration + minValue];
}
}
/* The user is dragging the movie controller thumb to scrub through the movie. */
/* Set the player current time to match the scrubber position. */
- (IBAction)scrub:(id)sender
{
if ([sender isKindOfClass:[UISlider class]])
{
UISlider* slider = sender;
CMTime playerDuration = self.suraPlayer.currentItem.duration;
if (CMTIME_IS_INVALID(playerDuration)) {
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
float minValue = [slider minimumValue];
float maxValue = [slider maximumValue];
float value = [slider value];
double time = duration * (value - minValue) / (maxValue - minValue);
[self.suraPlayer seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];
}
}
}
- (IBAction)beginScrubbing:(id)sender {
mRestoreAfterScrubbingRate = [self.suraPlayer rate];
[self.suraPlayer setRate:0.f];
/* Remove previous timer. */
[self removePlayerTimeObserver];
}
/* The user has released the movie thumb control to stop scrubbing through the movie. */
- (IBAction)endScrubbing:(id)sender {
__weak typeof(self) weakSelf = self;
if (!mTimeObserver)
{
CMTime playerDuration = self.suraPlayer.currentItem.duration;
if (CMTIME_IS_INVALID(playerDuration))
{
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
CGFloat width = CGRectGetWidth([self.mScrubber bounds]);
double tolerance = 0.5f * duration / width;
mTimeObserver = [self.suraPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(tolerance, NSEC_PER_SEC) queue:NULL usingBlock:
^(CMTime time)
{
[weakSelf syncScrubber];
}];
}
}
if (mRestoreAfterScrubbingRate)
{
[self.suraPlayer setRate:mRestoreAfterScrubbingRate];
mRestoreAfterScrubbingRate = 0.f;
}
}
- (BOOL)isScrubbing
{
return mRestoreAfterScrubbingRate != 0.f;
}
-(void)enableScrubber
{
self.mScrubber.enabled = YES;
}
-(void)disableScrubber
{
self.mScrubber.enabled = NO;
}
-(void)removePlayerTimeObserver
{
if (mTimeObserver)
{
[self.suraPlayer removeTimeObserver:mTimeObserver];
mTimeObserver = nil;
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == suraPlayer && [keyPath isEqualToString:#"status"]) {
if (suraPlayer.status == AVPlayerStatusFailed) {
NSLog(#"AVPlayer Failed");
} else if (suraPlayer.status == AVPlayerStatusReadyToPlay) {
NSLog(#"AVPlayerStatusReadyToPlay");
// NSLog(#"Common MetaData %#", self.suraPlayer.currentItem.asset.commonMetadata);
AVAsset * asset = self.suraPlayer.currentItem.asset;
[self metadataForAsset:asset];
} else if (suraPlayer.status == AVPlayerItemStatusUnknown) {
NSLog(#"AVPlayer Unknown");
}
}
}
- (NSString *)titleForAsset:(AVAsset *)asset{
NSArray *titles = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon];
AVMetadataItem *title;
NSString * currentTitle;
if(titles.count>0)
{
title= [titles objectAtIndex:0];
currentTitle= [title.value copyWithZone:nil];
}
if (self.suraPlayer.currentItem.asset != asset)
asset = nil;
return currentTitle;
}
- (void)metadataForAsset:(AVAsset *)asset{
self.artWorkImage.image = [UIImage imageNamed:#"Colorful-Burst-iPad-wallpaper-ilikewallpaper_com.jpg"];
NSArray *titles = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon];
NSArray *artists = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtist keySpace:AVMetadataKeySpaceCommon];
NSArray *albumNames = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyAlbumName keySpace:AVMetadataKeySpaceCommon];
AVMetadataItem *artist;
AVMetadataItem *title;
AVMetadataItem *albumName;
NSString * currentTitle;
NSString * currentArtist;
NSString * currentAlbumName;
if(titles.count>0)
{
title= [titles objectAtIndex:0];
currentTitle= [title.value copyWithZone:nil];
self.audioTitle.text = currentTitle;
}
if(artists.count>0)
{
artist= [artists objectAtIndex:0];
currentArtist = [artist.value copyWithZone:nil];
}
if (albumNames.count>0){
albumName= [albumNames objectAtIndex:0];
currentAlbumName = [albumName.value copyWithZone:nil];
}
NSArray *keys = [NSArray arrayWithObjects:#"commonMetadata", nil];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata
withKey:AVMetadataCommonKeyArtwork
keySpace:AVMetadataKeySpaceCommon];
for (AVMetadataItem *item in artworks) {
if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
NSDictionary *d = [item.value copyWithZone:nil];
self.artWorkImage.image = [UIImage imageWithData:[d objectForKey:#"data"]];
} else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
self.artWorkImage.image = [UIImage imageWithData:[item.value copyWithZone:nil]];
}
}
}];
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
// code here to play next sound file
if (indexOfSelectedItem<self.filteredAudiosArray.count){
indexOfSelectedItem = indexOfSelectedItem+1;
[self playAudioForFile:[self.filteredAudiosArray objectAtIndex:indexOfSelectedItem]];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:indexOfSelectedItem inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
- (IBAction)sliderValueChangedForDuration:(id)sender {
if ([sender isKindOfClass:[UISlider class]])
{
UISlider* slider = sender;
CMTime playerDuration = self.suraPlayer.currentItem.duration;
if (CMTIME_IS_INVALID(playerDuration)) {
return;
}
double duration = CMTimeGetSeconds(playerDuration);
if (isfinite(duration))
{
float minValue = [slider minimumValue];
float maxValue = [slider maximumValue];
float value = [slider value];
double time = duration * (value - minValue) / (maxValue - minValue);
[self.suraPlayer seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];
}
}
}
Late to the game but in case someone else runs into this, I had this issue because of the way I was setting the time interval to the periodic observer.
Changing to:
CMTime interval = CMTimeMake(33, 1000);
Made things work for me.

Resources