Audio player duration changes while playing - iphone

When I take the duration of an audio file before playing it with:
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
...
[audioPlayer prepareToPlay];
duration = audioPlayer.duration;
I get a duration of for example 16.71s. Then when taking samples every 0.2s while playing, the duration changes. It changes every 1.2 to 1.6 seconds to: 16.71s, 17.02s, 17.23s, 17.33s, 17.38s, 17.43s, 17.38s, 17.29s, 17.31s, then stays at 17.51s for the last 2.5 seconds. So it goes up and down.
I sample in a method that updates a slider position, and also shows the elapsed time and total duration. You can imagine it looks really weird to see the duration (which is int-truncated) go from 16 to 17 while playing. Additionally, the slider position will be off with all this drifting.
Does anyone have an idea why this is?
Now that we're talking about duration: Does anyone know why audio player.duration can return values that are about twice the actual duration when [audioPlayer prepareToPlay] is omitted?

The duration returned by avaudioplayer's duration method is a best estimate of the total duration of the file based on how much of the file has been decoded so far. That's why the duration continues to get refined as you check it over time.
In order to get a better time, I use AVAsset's duration method. It explains what is going on a bit better here:
https://developer.apple.com/library/mac/documentation/AVFoundation/Reference/AVAsset_Class/Reference/Reference.html#//apple_ref/occ/instp/AVAsset/duration
If you specify providesPreciseDurationAndTiming = YES, then AVAsset will decode the file if needed to determine its duration with accuracy. If the decode time is too long for your use, you can disable it.
In my situation, I use the AVURLAsset subclass:
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:localURL options:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], AVURLAssetPreferPreciseDurationAndTimingKey, nil]];
float t = CMTimeGetSeconds(asset.duration);

#AndyKorth's answer is the best! Here it is in Swift
guard let audioPlayer = audioPlayer, let url = audioPlayer.url else { return }
let assetOpts = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
let asset = AVURLAsset(url: url, options: assetOpts)
let assetDuration: Float64 = CMTimeGetSeconds(asset.duration)
// compare both
print("assetDuration: ", assetDuration)
print("audioPlayerDuration: ", Float(audioPlayer.duration))

Related

How to perform operations when playing sound in iPhone?

I play a MP3 in my iPhone app using AVAudioPlayer; i need to perform some operations at certain times (say 30th seconds, 1 minute); is there a way to invoke callback functions based on mp3 playing time?
I believe the best solution is to start an NSTimer as you start the AVAudioPlayer playing. You could set the timer to fire every half second or so. Then each time your timer fires, look at the currentTime property on your audio player.
In order to do something at certain intervals, I'd suggest you kept an instance variable for the playback time from last time your timer callback was called. Then if you had passed the critical point between last callback and this, do your action.
So, in pseudocode, the timer callback:
Get the currentTime of your AVAudioPlayer
Check to see if currentTime is greater than criticalPoint
If yes, check to see if lastCurrentTime is less than criticalPoint
If yes to that too, do your action.
Set lastCurrentTime to currentTime
If you're able to use AVPlayer instead of AVAudioPlayer, you can set boundary or periodic time observers:
// File URL or URL of a media library item
AVPlayer *player = [[AVPlayer alloc] initWithURL:url];
CMTime time = CMTimeMakeWithSeconds(30.0, 600);
NSArray *times = [NSArray arrayWithObject:[NSValue valueWithCMTime:time]];
id playerObserver = [player addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
NSLog(#"Playback time is 30 seconds");
}];
[player play];
// remove the observer when you're done with the player:
[player removeTimeObserver:playerObserver];
AVPlayer documentation:
http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVPlayer_Class/Reference/Reference.html
I found this link describing a property property which seems to indicate you can get the current playback time.
If the sound is playing, currentTime is the offset of the current
playback position, measured in seconds from the start of the sound. If
the sound is not playing, currentTime is the offset of where playing
starts upon calling the play method, measured in seconds from the
start of the sound.
By setting this property you can seek to a specific point in a sound
file or implement audio fast-forward and rewind functions.
To check the time and perform your action you can simply query it:
if (avAudioPlayerObject.currentTime == 30.0) //You may need a more broad check. Double may not be able to exactly represent 30.0s
{
//Do Something
}
with multithreading your goal is simple, just do like this :
1 : in your main thread create a variable for storing time passed
2 : create new thread like "checkthread" that check each 30-20 sec(as you need)
3 : if the time passed is what you want do the callback
Yes Sure you can ...it's tricky i hope it works for you but it works for me ..
1- you play your mp3 file.
2- [self performSelector:#selector(Operation:) withObject:Object afterDelay:30];
then the function
-(void)Operation:(id)sender;
called; so you fired function after 30 second of mp3 file .. you can make many of function based on time you want..
3- there is other solution using timers
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(CheckTime:) userInfo:nil repeats:YES];
it will fire function called Check Time
-(void)CheckTime:(id)sender{
if (avAudioPlayerObject.currentTime == 30.0)
{
//Do Something
//Fire and function call such
[self performSelector:#selector(Operation:) withObject:Object]
}
}
then you can change time interval you want and repeats is for you to control repeat this action every 5 seconds or not..
Hope that helpful..
Thanks
i think ,you want to play different sound-files after 30sec then use this code :
1) all sound-files put in Array and then retrieve from document directory
2)then try this:
-(IBAction)play_sound
{
BackgroundPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:[Arr_tone_selected objectAtIndex:j]ofType:#"mp3"]]error:NULL];
BackgroundPlayer.delegate=self;
[BackgroundPlayer play];
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[BackgroundPlayer stop];
j++;
[self performSelector:#selector(play_sound) withObject:Object afterDelay:30];
}

certain movie frames edit using GPUImage

I want to apply effects on certain frames of movie using GPUImage. I have successfully added effect on entire movie file, so is there a way to add different effects on different frames?
For example, I want to apply effect of Sepia on video from 5 seconds to 10 seconds. So I need 0-5 seconds to be original video, 5-10 seconds with Sepia effect and 10 - video total seconds with original video.
Also, I want to draw text/image on certain frames using GPUImage, is it possible?
Any response will be greatly appreciated.
You could ask MPMoviePlayerController or AVAssetImageGenerator to generate a thumbnail at the time you specify.
iPhone Read UIimage (frames) from video with AVFoundation
AVAssetImageGenerator provides images rotated
If you'd like videos instead of just frames, you could trim a section out of the video and apply an effect to that. This takes the URL of your video and trims it to the specified time.
AVURLAsset *videoAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:AVAssetExportPresetHighestQuality];
exportSession.outputURL = [NSURL fileURLWithPath:outputURL];
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
CMTimeRange timeRange = CMTimeRangeMake(CMTimeMake(startMilliseconds, 1000), CMTimeMake(endMilliseconds - startMilliseconds, 1000));
exportSession.timeRange = timeRange;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch (exportSession.status) {
case AVAssetExportSessionStatusCompleted:
///
// Call something to apply the effect
///
break;
case AVAssetExportSessionStatusFailed:
NSLog(#"Failed:%#", exportSession.error);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Canceled:%#", exportSession.error);
break;
default:
break;
}
}];
Upon completion, you'd then apply your effect and if you went with the video clip route, combine them, and encode them.
How to combine video clips with different orientation using AVFoundation

iphone MPMoviePlayerViewController : extract total duration

how can i get the video'a total time, before it plays the video in MPMoviePlayerViewController?
To get total duration of movie you can use :
1). Use AVPlayerItem class and AVFoundation and CoreMedia framework. (I have used UIImagePickerController for picking the movie)
#import <AVFoundation/AVFoundation.h>
#import <AVFoundation/AVAsset.h>
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
selectedVideoUrl = [info objectForKey:UIImagePickerControllerMediaURL];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:selectedVideoUrl];
CMTime duration = playerItem.duration;
float seconds = CMTimeGetSeconds(duration);
NSLog(#"duration: %.2f", seconds);
}
2). MPMoviePlayerController has a property duration .Refer Apple Doc
To get the total Duration in MPMoviePlayerViewController
MPMoviePlayerViewController *mp;
float seconds =mp.moviePlayer.duration;
Note: Above code give the total Duration of related Media in seconds
If you don't want to get the total time from MPMoviePlayerViewController's duration property (because that brings up the movie player UI), you could instead create an AVAsset object with your video file passed in via a file URL and then check the duration property on that.
This trick would only work on iOS 5 (which is where AVAsset's assetWithURL: came in with).
YOu can get using this method in swift
func getMediaDuration(url: NSURL!) -> Float64{
var asset : AVURLAsset = AVURLAsset.assetWithURL(url) as AVURLAsset
var duration : CMTime = asset.duration
return CMTimeGetSeconds(duration)
}

How to play a segment of a movie in iOS

I have a video, and I want to display the video at a specific time time and stop it at a specific time. I am using MPMoviePlayerController.
You should take a look at the MPMoviePlayerController Class Reference, and the initialPlaybackTime and endPlaybackTime properties.
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentURL:url];
player.initialPlaybackTime = 5; // beginning time in seconds
player.endPlaybackTime = 15; // end time for playback in seconds

How to get file size and current file size from NSURL for AVPlayer iOS4.0

self.player = [[AVPlayer playerWithURL:[NSURL URLWithString:#"http://myurl.com/track.mp3"]] retain];
I am trying make a UIProgressView for the above track. How do I obtain the file size and current file size from that URL? Please help, thanks!
You need to start observing the loadedTimeRanges property of the current item, like this:
AVPlayerItem* playerItem = self.player.currentItem;
[playerItem addObserver:self forKeyPath:kLoadedTimeRanges options:NSKeyValueObservingOptionNew context:playerItemTimeRangesObservationContext];
Then, in the observation callback, you make sense of the data you're passed like this:
-(void)observeValueForKeyPath:(NSString*)aPath ofObject:(id)anObject change:(NSDictionary*)aChange context:(void*)aContext {
if (aContext == playerItemTimeRangesObservationContext) {
AVPlayerItem* playerItem = (AVPlayerItem*)anObject;
NSArray* times = playerItem.loadedTimeRanges;
// there is only ever one NSValue in the array
NSValue* value = [times objectAtIndex:0];
CMTimeRange range;
[value getValue:&range];
float start = CMTimeGetSeconds(range.start);
float duration = CMTimeGetSeconds(range.duration);
_videoAvailable = start + duration; // this is a float property of my VC
[self performSelectorOnMainThread:#selector(updateVideoAvailable) withObject:nil waitUntilDone:NO];
}
Then the selector on the main thread updates a progress bar, like so:
-(void)updateVideoAvailable {
CMTime playerDuration = [self playerItemDuration];
double duration = CMTimeGetSeconds(playerDuration);
_videoAvailableBar.progress = _videoAvailable/duration;// this is a UIProgressView
}
I think you do not want to know anything about file-sizes, but you're more interested in times.
Try self.player.currentItem.asset.duration for duration of currently playing item, self.player.currentTime for current time.
#"loadedTimeRange" is a KVO value for the AVPlayerItem class. You can find its definition in the AVPlayerItem.h file in the
#interface AVPlayerItem (AVPlayerItemPlayability)
category definition.