iOS get audio duration from avplayer - ios

I'm new to ios, i have an app which contain online audio player. i need to get total duration for the audio. i have tried lot but all codes returns NaN or 0 duration. What is the best way to get total duration for the audio..?
MY CODE
NSString *songUrl = #"http://9xmusiq.com/songs2/tamil/Kaatru%20Veliyidai/Azhagiye%20%5bStarmusiq.cc%5d.mp3"
AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL URLWithString:songUrl]];
AVPlayerItem *playerItem1 = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player1 = [AVPlayer playerWithPlayerItem:playerItem1];
AVPlayerLayer *playerLayer1 = [AVPlayerLayer playerLayerWithPlayer:player1];
playerLayer1.videoGravity = AVLayerVideoGravityResizeAspectFill;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
playerLayer1.frame = self.view.frame;
});
[self.view.layer insertSublayer:playerLayer1 atIndex:1];
[player1 play];
[playerItem1 addObserver:self forKeyPath:#"status" options:0 context:nil];
[playerItem1 addObserver:self forKeyPath:#"playbackBufferEmpty" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([object isKindOfClass:[AVPlayerItem class]]){
AVPlayerItem *item = (AVPlayerItem *)object;
if ([keyPath isEqualToString:#"status"]){
switch(item.status){
case AVPlayerItemStatusFailed:
NSLog(#"player item status failed");
break;
case AVPlayerItemStatusReadyToPlay:
NSLog(#"player item status is ready to play");
Float64 duration = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
NSLog(#"Duration--> %f",duration) // NaN returns
break;
case AVPlayerItemStatusUnknown:
NSLog(#"player item status is unknown");
break;
}
}else if ([keyPath isEqualToString:#"playbackBufferEmpty"]){
if (item.playbackBufferEmpty){
NSLog(#"player item playback buffer is empty");
}
}
}
}

Thanks for your support friends, Finally i find the solution for my problem instead of using AVPlayer i used AVAudioPlayer and i fixed the problem and i got the audio duration.
NSString* resourcePath = #"http://9xmusiq.com/songs2/tamil/Kaatru%20Veliyidai/Azhagiye%20%5bStarmusiq.cc%5d.mp3"; //your url
NSData *_objectData = [NSData dataWithContentsOfURL:[NSURL URLWithString:resourcePath]];
NSError *error;
AVAudioPlayer *player1 = [[AVAudioPlayer alloc] initWithData:_objectData error:&error];
player1.numberOfLoops = 0;
player1.volume = 1.0f;
[player1 prepareToPlay];
NSLog(#"Total Duration : %f",player1.duration);
if (player1 == nil){
NSLog(#"%#", [error description]);
}else{
[player1 play];
}

I'm not that familiar with AVPlayer, but in digging around in the docs it looks like the AVAsset (or in your case AVURLAsset) is the object that holds a duration.
Try querying the asset:
AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL
URLWithString:songUrl]];
CMTime durationCMTime = asset.duration;
Float64 duration =
CMTimeGetSeconds(durationCMTime);
NSLog(#"Duration of asset is %f", duration);

When your AVPlayer ready to play (under the case of AVPlayerItemStatusReadyToPlay), you can use
CMTime duration = self.player.currentItem.asset.duration;
float seconds = CMTimeGetSeconds(duration);

You can access the duration of an AVPlayerItem's asset using duration property. If you need precise seconds with decimals, use Float64 to receive time from CMTimeGetSeconds. For regular use cases, I guess int would be sufficient.
CMTime duration = playerItem1.asset.duration;
int durationTotalSeconds = CMTimeGetSeconds(duration);
int durationHours = floor(durationTotalSeconds / 3600);
int durationMinutes = floor(durationTotalSeconds % 3600 / 60);
int durationSeconds = floor(durationTotalSeconds % 3600 % 60);
NSString *audioDurationString = [NSString stringWithFormat:#"%d:%d:%d",durationHours, durationMinutes, durationSeconds];

you can get duration - (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
#property(nonatomic,strong) AVPlayer *player;
#property(nonatomic,strong) id obsever;
self.player = [[AVPlayer alloc]initWithURL:URL];
[self.player play];
self.obsever = [self.player addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {//you can get duration in block
CMTimeGetSeconds(theItem.currentTime);
CMTimeGetSeconds(theItem.duration)
}];

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);

iOS objective c How to load next coming video while playing current video without delay

In my application ai have loaded all video url's to AVPlayer and it has previous and next buttons here i have setup player
-(void)setUpMyNewPlayer
{
[self addTimer];
NSURL *url=[NSURL URLWithString:_videosArray[0]];
_currentIndex =0;
videoPlayer = [[AVPlayer alloc]init]; //WithPlayerItem:_avPlayerItem];
videoPlayer.automaticallyWaitsToMinimizeStalling = NO;
AVAsset *asset = [AVAsset assetWithURL:url];
[asset loadValuesAsynchronouslyForKeys:#[#"playable"] completionHandler:^{
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
videoPlayer = [AVPlayer playerWithPlayerItem:item];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:videoPlayer];
playerLayer.videoGravity = AVLayerVideoGravityResize;
playerLayer.frame = CGRectMake(0, 0, self.playView.frame.size.width, self.playView.frame.size.height);
[self.playView.layer addSublayer:playerLayer];
[videoPlayer play];
CMTime interval = CMTimeMakeWithSeconds(0.5, NSEC_PER_SEC);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
__weak typeof(self) weakSelf = self;
[videoPlayer addPeriodicTimeObserverForInterval:interval
queue:mainQueue
usingBlock:^(CMTime time) {
// Use weak reference to self
if (_currentIndex==_contentImages.count-1) {
weakSelf.nextButton.hidden=YES;
weakSelf.previousButton.hidden=NO;
}
else if (_currentIndex==0)
{
weakSelf.previousButton.hidden=YES;
if (_contentImages.count>1) {
weakSelf.nextButton.hidden=NO;
}
else
{
weakSelf.nextButton.hidden=YES;
}
}
else if (_currentIndex>0 && _currentIndex!=_contentImages.count-1)
{
// NSLog(#"Showing Both");
weakSelf.nextButton.hidden=NO;
weakSelf.previousButton.hidden=NO;
}
}];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying1:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];
}];
}
-(void)itemDidFinishPlaying1:(NSNotification *) notification {
//
// Will be called when AVPlayer finishes playing playerItem
if (_currentIndex == _videosArray.count-1) {
}
else{
_currentIndex = _currentIndex+1;
NSURL *url=[NSURL URLWithString:_videosArray[_currentIndex]];
AVAsset *asset = [AVAsset assetWithURL:url];
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
[videoPlayer replaceCurrentItemWithPlayerItem:item];
[self changePage:UIPageViewControllerNavigationDirectionForward];
[self addTimer];
}
}
-(void)addTimer
{
myTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self
selector: #selector(callAfterOneSecond1:) userInfo: nil repeats: YES];
}
-(void) callAfterOneSecond1:(NSTimer*)t
{
[[AppDelegate shared] showLoading];
if (videoPlayer.rate !=0 && videoPlayer.error == nil && videoPlayer.status == AVPlayerStatusReadyToPlay) {
[[AppDelegate shared]removeLoading];
[myTimer invalidate];
myTimer=nil;
}
}
pragma mark- PreviousAction
- (IBAction)previousButtonAction:(id)sender {
if (_currentIndex == 0) {
}
else{
_currentIndex = _currentIndex-1;
NSURL *url=[NSURL URLWithString:_videosArray[_currentIndex]];
AVAsset *asset = [AVAsset assetWithURL:url];
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
[videoPlayer replaceCurrentItemWithPlayerItem:item];
[videoPlayer play];
[self changePage:UIPageViewControllerNavigationDirectionReverse];
[self addTimer];
}
}
pragma mark- NextAction
- (IBAction)nextButtonAction:(id)sender {
if (_currentIndex == _videosArray.count-1) {
}
else{
_currentIndex = _currentIndex+1;
NSURL *url=[NSURL URLWithString:_videosArray[_currentIndex]];
AVAsset *asset = [AVAsset assetWithURL:url];
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
[videoPlayer replaceCurrentItemWithPlayerItem:item];
[videoPlayer play];
[self changePage:UIPageViewControllerNavigationDirectionForward];
[self addTimer];
}
}
Here the player is playing well but with small delay. . How to load next coming video while playing current video without delay.
I'm a bit rusty and the iOS documentation on the whole of AVFoundation is woefully inadequate if you want to do anything slightly complicated. Its been a while since I did this but something like this should work (I'm assuming you have an NSArray*, pAssets containing all your videos as AVAsset*)
// Define the composition.
AVMutableComposition* pComposition = [AVMutableComposition composition];
// Define the tracks in the composition.
AVMutableCompositionTrack* pCompositionVideoTrack = [pComposition addMutableTrackWithMediaType: AVMediaTypeVideo preferredTrackID: 1];
AVMutableCompositionTrack* pCompositionAudioTrack = [pComposition addMutableTrackWithMediaType: AVMediaTypeAudio preferredTrackID: 2];
CMTime time = kCMTimeZero;
for ( AVAsset* pAssetsAsset in pAssets )
{
// Grab first video and audio tracks
AVAssetTrack* pAssetsAssetVideoTrack = [pAssetsAsset tracksWithMediaType: AVMediaTypeVideo].firstObject;
AVAssetTrack* pAssetsAssetAudioTrack = [pAssetsAsset tracksWithMediaType: AVMediaTypeAudio].firstObject;
// Get time range of entire video.
CMTimeRange timeRange = CMTimeRangeMake( kCMTimeZero, timepAssetsAsset.duration );
// Insert the entire video and audio into their respective tracks at "time".
NSError* pVideoError = nil;
NSError* pAudioError = nil;
[pCompositionVideoTrack insertTimeRange: timeRange ofTrack: pAssetsAssetVideoTrack atTime: time error: &pVideoError];
[pCompositionAudioTrack insertTimeRange: timeRange ofTrack: pAssetsAssetAudioTrack atTime: time error: &pAudioError];
// Move time along appropriately.
time = CMTimeAdd( time, pAssetsAsset.duration );
}
If you then pass the AVMutableComposition is derived from AVAsset so you can use this as normal and drop it into an AVPlayer (via an AVPlayerItem).
Now you can seek to any point in the video. Store the start/finish points of each video and you can easily seek to it.
Edit: Its ultra simple to use AVPlayer. First you need to create an AVPlayerItem .. and then you need to play it.
AVPlayerItem* pPlayerItem = [AVPlayerItem playerItemWithAsset: pComposition];
AVPlayer* pPlayer = [AVPlayer playerWithPlayerItem: pPlayerItem];
Now you need to attach it to a view's layer. So from inside your ViewController do something like this:
AVPlayerLayer* pLayer = [AVPlayerLayer playerLayerWithPlayer: pPlayer];
[self.view.layer addSublayer: pLayer];
Use AVQueuePlayer instead of simple AVPlayer. At the end of current video it will preload next video.
See how to use AVQueuePlayer here: https://stackoverflow.com/a/22785665/1271424

AVPlayer crashes after multiple time play video from local directory

When i click my local video url and present Viewcontroller and Play video in AVPlayer.Play 14-15 times and Player crash.Avplayer showing like
- (void)viewDidLoad
{
[self PlayVideoinPlayer:_videoURL];
}
-(void)PlayVideoinPlayer:(NSString *)URL
{
NSURL *fileURL = [NSURL fileURLWithPath:URL];
_Avcontroller=[[AVPlayerViewController alloc]init];
asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
anItem = [AVPlayerItem playerItemWithAsset:asset];
_avPlayer = [AVPlayer playerWithPlayerItem:anItem];
[_avPlayer addObserver:self forKeyPath:#"status" options:0 context:nil];
self.Avcontroller.view.frame = self.view.bounds;
[self.Avcontroller setPlayer:_avPlayer];
_Avcontroller.videoGravity=AVLayerVideoGravityResizeAspectFill;
[self.view addSubview:self.Avcontroller.view];
[self.view addSubview:self.Avcontroller.view];
[_avPlayer play];
CMTime interval = CMTimeMake(1, 1800);
__strong __typeof(self) weakself = self;
playbackObserver = [_avPlayer addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock: ^(CMTime time) {
CMTime endTime = CMTimeConvertScale (_avPlayer.currentItem.asset.duration, _avPlayer.currentTime.timescale, kCMTimeRoundingMethod_RoundHalfAwayFromZero);
if (CMTimeCompare(endTime, kCMTimeZero) != 0)
{
// double normalizedTime = (double) avPlayer.currentTime.value / (double) endTime.value;
//NSLog(#"--------->>>>%#",playbackObserver);
}
int CurrentSecond=[[weakself getStringFromCMTime:_avPlayer.currentTime] intValue];
_avPlayer.rate=1.0f;
}
Anyone have solution.? Please help me.Thanks

Preload streaming with avplayer/avaudioplayer

I have a problem with avplayer/avaudioplayer. I'm using these classes for streaming aac or pls audio content (radio).
I would like to preload specified time od content and play streaming with delay, for example 20 seconds. it occurs that when I turn on, for example, airplane mode and network is lost, player should play from this moment 20 seconds while it stops.
I use this code for mp4 from network and it seems to works but when i change this for aac/pls streaming...it's loading and everything seems to work but when I turn on airplane mode, it stops immidiately...
is it possible to keep in buffer content and play with delay with streaming?
My code:
#interface AVFViewController ()
{
AVPlayer *player;
PlayerView *playerView;
id t
imeObserver;
NSInteger _playing;
}
#end
#implementation AVFViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self playURL:[NSURL URLWithString:#"http://nba.cdn.turner.com/nba/big/channels/top_plays/2012/02/03/20120203_top10.nba_nba_ipad.mp4"]];
//http://acdn.smcloud.net/t042-1_mp3.pls
//http://nba.cdn.turner.com/nba/big/channels/top_plays/2012/02/03/20120203_top10.nba_nba_ipad.mp4
}
- (void)playURL:(NSURL *)videoURL
{
if (!player) {
player = [[AVPlayer alloc] init];
playerView = [[PlayerView alloc] init];
[playerView setPlayer:player];
playerView.frame = CGRectInset(self.view.bounds, 20, 20);
playerView.backgroundColor = [UIColor greenColor];
playerView.alpha = 1;
[self.view addSubview:playerView];
[player addObserver:self forKeyPath:#"currentItem.loadedTimeRanges" options:NSKeyValueObservingOptionNew context:kTimeRangesKVO];
}
[player replaceCurrentItemWithPlayerItem:[[AVPlayerItem alloc] initWithURL:videoURL]];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (kTimeRangesKVO == context) {
NSArray *timeRanges = (NSArray *)[change objectForKey:NSKeyValueChangeNewKey];
if (timeRanges && [timeRanges count]) {
NSArray *loadedTimeRanges = [[player currentItem] loadedTimeRanges];
CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue];
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;
NSLog(#"duration is %f",result);
if (!_playing && result > 20) {
[player.currentItem seekToTime:CMTimeMakeWithSeconds(0, 1)];
[player play];
_playing = 1;
}
}
}
}
#end

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];
}];
}
}
}

Resources