How can I know if AVAudioPlayer is paused? - ios

To discover whether an AVAudioPlayer object is playing, I can simply use the isPlaying property:
if audioPlayer.isPlaying {
// ...
}
If the audio player is not playing however, how can I distinguish between it being paused and it being stopped?
I can't use the currentTime property for this purpose. This is because if I call stop(), then the current time stays at whatever time it was at the time of the call, which is the same behaviour as when pause() is called.

I think it's as simple as keep a BOOL property in view controller class which set to YES while play/resume buttons clicked and set to NO while stop/pause button pressed.

Its made from this this link:
if ((player.rate != 0) && (player.error == nil)) {
// player is playing
}else {
//notplaying
}

Simply add Bool variable and based on that you check is audio paused or not.
Ex:
var isAudioPaused = false//Add this on top
//Pause audio
func pauseAudio() {
if isAudioPaused == false {
if audioPlayer?.isPlaying == true {//Check is audio playing
audioPlayer?.pause()//Pause audio
isAudioPaused = true
btnPauseAudio.setTitle("Play Audio", for: .normal)
}
} else {
audioPlayer?.play()//Play audio
isAudioPaused = false
btnPauseAudio.setTitle("Pause Audio", for: .normal)
}
}

You can check player that is paused or not though code that is player.isPlaying && player.duration != 0
func buttonOneClicked(button: UIButton) {
if player.isPlaying && player.duration != 0 {
button.setImage(UIImage(systemName: "stop"), for: .normal)
player.pause()
}
else{
button.setImage(UIImage(systemName: "play"), for: .normal)
player.play()
}
}

Related

How to disable the Haptic Feedback when playing a Live Photo

In my app, I allow the user to play Live Photos from their photo library.
To do this, I use the following:
#IBOutlet weak var livePhotoView: PHLivePhotoView!
#objc func handleTapGesture(_ recognizer: UITapGestureRecognizer) {
if let currentType = currentMediaType {
if currentType == .livePhoto && livePhotoIsPlaying == false {
playImageView.alpha = 0
backButtonView.alpha = 0
ivePhotoView.startPlayback(with: .full)
livePhotoIsPlaying = true
} else if currentType == .livePhoto && livePhotoIsPlaying == true {
livePhotoView.stopPlayback()
livePhotoIsPlaying = false
}
}
}
By using this method, I get Haptic Feedback whenever the Live Photo is played which I don't want. Is this the normal behavior of PHLivePhotoView, and is there a way to disable it?
PHLivePhotoViews have an enum property called PHLivePhotoViewPlaybackStyle that determines if the playback should include haptic feedback.
To disable haptic feedback on playback use:
livePhotoView.startPlayback(with: .hint)
instead of:
livePhotoView.startPlayback(with: .full)

Best way to prevent AVPlayer playing more than one track at a time

I'm using AVPlayer to play streaming audio and would like to prevent it from playing more than one audio track at a time. What's the best way to do this? My approach is entirely programmatic -- I don't use the storyboard. When an url is sent to the ViewController (VC, the delegate) it sets up a new instance of the player -- and I think this is the problem, but I'm not sure. Thanks.
//this routine returns the url from control to the VC (delegate)
func selectedAudio(_ audioUrl:String?) {
if let urlString = audioUrl {
setupPlayer(urlString)
}
}
//single button which toggles between play/pause
func playOrPauseTouched() {
if isPlaying {
pausePlayer()
} else {
playPlayer()
}
}
func playPlayer() {
if player == nil || selectedTrackName != currentTrackLabel.text{
if let urlString = selectedAudioUrl {
setupPlayer(urlString)
currentTrackLabel.text = "Playing: \(selectedTrackName ?? "")"
}
}
player?.play()
isPlaying = true
playPauseButton.setImage(UIImage(named: "pause"), for: .normal)
activityIndicatorView.startAnimating()
}
func pausePlayer() {
player?.pause()
isPlaying = false
playPauseButton.setImage(UIImage(named: "play"), for: .normal)
}
func setupPlayer(_ urlString:String) {
if let url = URL(string:urlString) {
player = AVPlayer(url: url)
let playerLayer = AVPlayerLayer(player: player)
player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
let interval = CMTime(value: 1, timescale: 5) //gives smooth thumb movement w/short clips
player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in
let seconds = CMTimeGetSeconds(progressTime)
let secondsString = String(format: "%02d", Int(seconds.truncatingRemainder(dividingBy: 60)))
let minutesString = String(format: "%02d", Int(seconds / 60))
self.currentTimeLabel.text = "\(minutesString):\(secondsString)"
if let duration = self.player?.currentItem?.duration {
let durationSeconds = CMTimeGetSeconds(duration)
self.audioSlider.value = Float(seconds / durationSeconds)
}
})
}
}
My opinion is use KVO that will notify you for current state of AVPlayerItem.
Means you will get state that will help you to identify current item is playing, pushed, buffering or finish to play.
So you should take one flag that will handle your current item state.
Ex.
Set flag true when item is playing
Set flag false when item finish to play.
When you tap on button for play another item then check with flag like if flag == false then start playing another item otherwise not.
Second Way:
Use NotificationCenter like below
NotificationCenter.default.addObserver(self, selector: Selector(("playerDidFinishPlaying:")),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
and below automatically call when item finish playing
func playerDidFinishPlaying(note: NSNotification) {
print("Item finished playing ")
// Set flag here
}
And managed flag as same.

Swift, Continue recording after interruption

I am trying to allow the user to record audio for long duration.
So what am trying to achieve?
Start recording will stop any other media like music
If the AVAudioRecorder gets interrupted, it pauses.
When the interruption ended, or the user resume the recording manually the AVAudioRecorder stops any other media and resumes and continue recording.
My problem is when the AVAudioRecorder gets interrupted and resumed few times, it loses the recoded data, for example if I recorded for 30 seconds when I play it back, its duration becomes 10 seconds.
Amazingly, this happen only when it get's interrupted, but if the user pauses and resumes the recoding many times, no problem.
Any suggestions?
Thanks.
func startRecording() {
if !audioRecord!.recording {
do {
secondCounter = 0
try self.audioSession?.setCategory(AVAudioSessionCategoryRecord)
try self.audioSession?.setActive(true)
self.stopAudioPlayer()
self.playButton.enabled = false
self.playButtonInPlayingMode(false)
self.hideAudioContainer(true)
audioRecord?.prepareToRecord()
audioRecord?.meteringEnabled = true
audioRecord!.record()
self.micButton.tag = recording_state;
self.monitorRecoderTime()
} catch {
}
}
}
func pauseRecording() {
if audioRecord != nil && audioRecord!.recording {
self.recordingPauseMode()
audioRecord?.pause()
}
}
func recordingPauseMode() {
self.micButton.setImage(UIImage(named: "micro.png"), forState: .Normal);
self.micButton.tag = pause_state;
self.stopMonitoring()
}
func resumeRecoding() {
if audioRecord != nil && !audioRecord!.recording {
self.micButton.setImage(UIImage(named: "micro_filled.png"), forState: .Normal);
self.micButton.tag = recording_state;
if audioRecord!.record() {
print("Recording Resumed")
}else{
print("Unable to resume recording")
}
self.monitorRecoderTime()
}
}

Playing video using AVPlayer does not get resume when come in foreground ios

I am playing video using AVPlayer. When I go in background player is paused and when it is brought in foreground player is played. But still video is not appearing though it contains URL in its current Item. I am stuck and not getting and solution.Please help me to resolve. Thanks in advance.
Below code is used to play initially:-
let item1 = AVPlayerItem.init(URL: NSURL(string:path))
player = AVPlayer(playerItem: item1)
layer?.player = player;
player?.play()
Below code is used to pause and resume:-
func pausePlayerOnBackgroundAppearance()
{
if(player != nil)
{
player?.pause()
}
}
func resumePlayerOnForegroundAppearance()
{
if(player != nil)
{
player?.play()
}
}
If I am seeking some time to play video at some time where it was paused before then also it is not playing video
func pausePlayerOnBackgroundAppearance()
{
if(player != nil){
let currentItem:AVPlayerItem = player!.currentItem
currentTime = CMTimeGetSeconds(currentItem.currentTime())
player?.pause()
}
}
func resumePlayerOnForegroundAppearance()
{
if(player != nil){
player!.seekToTime(CMTimeMake(Int64(currentTime!), 1))
player?.play()
}
}

Check play state of AVPlayer

Is there a way to know whether an AVPlayer playback has stalled or reached the end?
You can tell it's playing using:
AVPlayer *player = ...
if ((player.rate != 0) && (player.error == nil)) {
// player is playing
}
Swift 3 extension:
extension AVPlayer {
var isPlaying: Bool {
return rate != 0 && error == nil
}
}
In iOS10, there's a built in property for this now: timeControlStatus
For example, this function plays or pauses the avPlayer based on it's status and updates the play/pause button appropriately.
#IBAction func btnPlayPauseTap(_ sender: Any) {
if aPlayer.timeControlStatus == .playing {
aPlayer.pause()
btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
} else if aPlayer.timeControlStatus == .paused {
aPlayer.play()
btnPlay.setImage(UIImage(named: "control-pause"), for: .normal)
}
}
As for your second question, to know if the avPlayer reached the end, the easiest thing to do would be to set up a notification.
NotificationCenter.default.addObserver(self, selector: #selector(self.didPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)
When it gets to the end, for example, you can have it rewind to the beginning of the video and reset the Pause button to Play.
#objc func didPlayToEnd() {
aPlayer.seek(to: CMTimeMakeWithSeconds(0, 1))
btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
}
These examples are useful if you're creating your own controls, but if you use a AVPlayerViewController, then the controls come built in.
To get notification for reaching the end of an item (via Apple):
[[NSNotificationCenter defaultCenter]
addObserver:<self>
selector:#selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>];
And to track playing you can:
"track changes in the position of the playhead in an AVPlayer object" by using addPeriodicTimeObserverForInterval:queue:usingBlock: or addBoundaryTimeObserverForTimes:queue:usingBlock:.
Example is from Apple:
// Assume a property: #property (retain) id playerObserver;
Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = [NSArray arrayWithObjects:[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird], nil];
self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
// Passing NULL for the queue specifies the main queue.
NSString *timeDescription = (NSString *)CMTimeCopyDescription(NULL, [self.player currentTime]);
NSLog(#"Passed a boundary at %#", timeDescription);
[timeDescription release];
}];
rate is NOT the way to check whether a video is playing (it could stalled). From documentation of rate:
Indicates the desired rate of playback; 0.0 means "paused", 1.0 indicates a desire to play at the natural rate of the current item.
Key words "desire to play" - a rate of 1.0 does not mean the video is playing.
The solution since iOS 10.0 is to use AVPlayerTimeControlStatus which can be observed on AVPlayer timeControlStatus property.
The solution prior to iOS 10.0 (9.0, 8.0 etc.) is to roll your own solution. A rate of 0.0 means that the video is paused. When rate != 0.0 it means that the video is either playing or is stalled.
You can find out the difference by observing player time via: func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: #escaping (CMTime) -> Void) -> Any
The block returns the current player time in CMTime, so a comparison of lastTime (the time that was last received from the block) and currentTime (the time that the block just reported) will tell whether the player is playing or is stalled. For example, if lastTime == currentTime and rate != 0.0, then the player has stalled.
As noted by others, figuring out whether playback has finished is indicated by AVPlayerItemDidPlayToEndTimeNotification.
For Swift:
AVPlayer:
let player = AVPlayer(URL: NSURL(string: "http://www.sample.com/movie.mov"))
if (player.rate != 0 && player.error == nil) {
println("playing")
}
Update:
player.rate > 0 condition changed to player.rate != 0 because if video is playing in reverse it can be negative thanks to Julian for pointing out.
Note: This might look same as above(Maz's) answer but in Swift '!player.error' was giving me a compiler error so you have to check for error using 'player.error == nil' in Swift.(because error property is not of 'Bool' type)
AVAudioPlayer:
if let theAudioPlayer = appDelegate.audioPlayer {
if (theAudioPlayer.playing) {
// playing
}
}
AVQueuePlayer:
if let theAudioQueuePlayer = appDelegate.audioPlayerQueue {
if (theAudioQueuePlayer.rate != 0 && theAudioQueuePlayer.error == nil) {
// playing
}
}
A more reliable alternative to NSNotification is to add yourself as observer to player's rate property.
[self.player addObserver:self
forKeyPath:#"rate"
options:NSKeyValueObservingOptionNew
context:NULL];
Then check if the new value for observed rate is zero, which means that playback has stopped for some reason, like reaching the end or stalling because of empty buffer.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:#"rate"]) {
float rate = [change[NSKeyValueChangeNewKey] floatValue];
if (rate == 0.0) {
// Playback stopped
} else if (rate == 1.0) {
// Normal playback
} else if (rate == -1.0) {
// Reverse playback
}
}
}
For rate == 0.0 case, to know what exactly caused the playback to stop, you can do the following checks:
if (self.player.error != nil) {
// Playback failed
}
if (CMTimeGetSeconds(self.player.currentTime) >=
CMTimeGetSeconds(self.player.currentItem.duration)) {
// Playback reached end
} else if (!self.player.currentItem.playbackLikelyToKeepUp) {
// Not ready to play, wait until enough data is loaded
}
And don't forget to make your player stop when it reaches the end:
self.player.actionAtItemEnd = AVPlayerActionAtItemEndPause;
Currently with swift 5 the easiest way to check if the player is playing or paused is to check the .timeControlStatus variable.
player.timeControlStatus == .paused
player.timeControlStatus == .playing
Swift extension based on the answer by maz
extension AVPlayer {
var isPlaying: Bool {
return ((rate != 0) && (error == nil))
}
}
The Swift version of maxkonovalov's answer is this:
player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)
and
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "rate" {
if let rate = change?[NSKeyValueChangeNewKey] as? Float {
if rate == 0.0 {
print("playback stopped")
}
if rate == 1.0 {
print("normal playback")
}
if rate == -1.0 {
print("reverse playback")
}
}
}
}
Thank you maxkonovalov!
Answer in Objective C
if (player.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
//player is playing
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusPaused) {
//player is pause
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) {
//player is waiting to play
}
player.timeControlStatus == AVPlayer.TimeControlStatus.playing
You can check if the player is playing with a timer like this :
let playerObserver = self.player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main, using: { [weak self] time in
if self?.player.timeControlStatus == .playing {
debugPrint("#player - info: isPlaying")
self?.playButton.isSelected = true
} else if self?.player.timeControlStatus == .paused {
debugPrint("#player - info: isPaused")
self?.playButton.isSelected = false
} else if self?.player.timeControlStatus == .waitingToPlayAtSpecifiedRate {
debugPrint("#player - info: isWaiting") //Buffering
}
})

Resources