update control center playback seek when player view controller of slider value changed - ios

I got to change the slider value when the control center of change playback media position is changed...
commandCenter.changePlaybackPositionCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
let event = event as! MPChangePlaybackPositionCommandEvent
let time = CMTime(seconds: event.positionTime, preferredTimescale: self.player.currentTime().timescale)
self.player.seek(to: time)
return .success;
}
and
#IBAction func handleSliderChange(_ sender: UISlider) {
let seconds : Int64 = Int64(sender.value)
let targetTime:CMTime = CMTimeMake(seconds, 1)
myPlayer.player.seek(to: targetTime)
}
#IBAction func sliderTapped(_ sender: UILongPressGestureRecognizer) {
if let slider = sender.view as? UISlider {
if slider.isHighlighted { return }
let point = sender.location(in: slider)
let percentage = Float(point.x / slider.bounds.width)
let delta = percentage * (slider.maximumValue - slider.minimumValue)
let value = slider.minimumValue + delta
slider.setValue(value, animated: false)
let seconds : Int64 = Int64(value)
let targetTime: CMTime = CMTimeMake(seconds, 1)
self.myPlayer.player.seek(to: targetTime)
}
}
however, in inverse method, I have no idea what to do. I want to update current time when the slider value is changed.

Related

Unable to play audio file using url in my application in iOS swift

audio link
I uploaded a recording in my google drive link(mentioned above) and set that to public. I am trying to play the audio using audio kit and other audio player(cocoapods) with url, downloading the file and play and I tried converting to other formats nothing worked for me. I am unable to play it. This audio is not playing in safari browser also. The recording is done from the web platform and the generated audio format is .opus
Try please the code below and tell me if is working for you.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player: AVPlayer?
var playerItem:AVPlayerItem?
fileprivate let seekDuration: Float64 = 10
#IBOutlet weak var labelCurrentTime: UILabel!
#IBOutlet weak var labelOverallDuration: UILabel!
#IBOutlet weak var playbackSlider: UISlider!
#IBOutlet weak var playButtonOutlet: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
initAudioPlayer()
}
//call this mehtod to init audio player
func initAudioPlayer() {
let url = URL(string: "https://argaamplus.s3.amazonaws.com/eb2fa654-bcf9-41de-829c-4d47c5648352.mp3")
let playerItem:AVPlayerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem: playerItem)
playbackSlider.minimumValue = 0
//To get overAll duration of the audio
let duration : CMTime = playerItem.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
labelOverallDuration.text = self.stringFromTimeInterval(interval: seconds)
//To get the current duration of the audio
let currentDuration : CMTime = playerItem.currentTime()
let currentSeconds : Float64 = CMTimeGetSeconds(currentDuration)
labelCurrentTime.text = self.stringFromTimeInterval(interval: currentSeconds)
playbackSlider.maximumValue = Float(seconds)
playbackSlider.isContinuous = true
player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if self.player!.currentItem?.status == .readyToPlay {
let time : Float64 = CMTimeGetSeconds(self.player!.currentTime());
self.playbackSlider.value = Float ( time );
self.labelCurrentTime.text = self.stringFromTimeInterval(interval: time)
}
let playbackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp
if playbackLikelyToKeepUp == false{
print("IsBuffering")
self.playButtonOutlet.isHidden = true
} else {
//stop the activity indicator
print("Buffering completed")
self.playButtonOutlet.isHidden = false
}
}
//change the progress value
playbackSlider.addTarget(self, action: #selector(playbackSliderValueChanged(_:)), for: .valueChanged)
//check player has completed playing audio
NotificationCenter.default.addObserver(self, selector: #selector(self.finishedPlaying(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)}
#objc func playbackSliderValueChanged(_ playbackSlider:UISlider) {
let seconds : Int64 = Int64(playbackSlider.value)
let targetTime:CMTime = CMTimeMake(value: seconds, timescale: 1)
player!.seek(to: targetTime)
if player!.rate == 0 {
player?.play()
}
}
#objc func finishedPlaying( _ myNotification:NSNotification) {
playButtonOutlet.setImage(UIImage(named: "play"), for: UIControl.State.normal)
//reset player when finish
playbackSlider.value = 0
let targetTime:CMTime = CMTimeMake(value: 0, timescale: 1)
player!.seek(to: targetTime)
}
#IBAction func playButton(_ sender: Any) {
print("play Button")
if player?.rate == 0
{
player?.play()
self.playButtonOutlet.isHidden = true
playButtonOutlet.setImage(UIImage(systemName: "pause"), for: UIControl.State.normal)
} else {
player?.pause()
playButtonOutlet.setImage(UIImage(systemName: "play"), for: UIControl.State.normal)
}
}
func stringFromTimeInterval(interval: TimeInterval) -> String {
let interval = Int(interval)
let seconds = interval % 60
let minutes = (interval / 60) % 60
let hours = (interval / 3600)
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
#IBAction func seekBackWards(_ sender: Any) {
if player == nil { return }
let playerCurrenTime = CMTimeGetSeconds(player!.currentTime())
var newTime = playerCurrenTime - seekDuration
if newTime < 0 { newTime = 0 }
player?.pause()
let selectedTime: CMTime = CMTimeMake(value: Int64(newTime * 1000 as Float64), timescale: 1000)
player?.seek(to: selectedTime)
player?.play()
}
#IBAction func seekForward(_ sender: Any) {
if player == nil { return }
if let duration = player!.currentItem?.duration {
let playerCurrentTime = CMTimeGetSeconds(player!.currentTime())
let newTime = playerCurrentTime + seekDuration
if newTime < CMTimeGetSeconds(duration)
{
let selectedTime: CMTime = CMTimeMake(value: Int64(newTime * 1000 as Float64), timescale: 1000)
player!.seek(to: selectedTime)
}
player?.pause()
player?.play()
}
}
}
Here is a DEMO:
https://github.com/florentin89/PlaySongFromExternalSource

UISlider as audio seekbar jumping to maximum value when changed

I am using a UISlider as a seek bar for audio and it works great for adjusting to change position in the track if it is not animated. If it's animated it works great and tracks along the bar in time with the track perfectly but if you try and adjust it while the animation is active, it jumps to the maximum value of the bar. I assume there is a conflict between the two processes but I'm struggling to work out a fix.
func changeProgressBar() {
let trackLength = Float(AudioService.shared.playerItem?.duration.seconds ?? 0)
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true){_ in
let currentTime = Float(AudioService.shared.player?.currentTime().seconds ?? 0)
let sliderPosition = currentTime / (trackLength / 100)
self.progressBar.setValue(sliderPosition, animated: true)
print("the current time is", currentTime)
print("the slider position is", sliderPosition)
}
}
#IBAction func progressBarValueChanged(_ sender: UISlider) {
let trackLength = AudioService.shared.playerItem?.duration.seconds ?? 0
let sliderValueFloat = progressBar.value * 100.00
let sliderValueDouble = Double(sliderValueFloat)
let targetTime = (trackLength / 100 * sliderValueDouble)
let targetTimeActual = CMTimeMake(value: Int64(targetTime), timescale: 1)
AudioService.shared.player!.seek(to: targetTimeActual)
}
I have buttons that add or subtract 30 seconds to skip forward or back in the track and they work fine even when the animation is active.
#IBAction func plus30Secs(_ sender: UIButton) {
let currentTime = Float(AudioService.shared.player?.currentTime().seconds ?? 0)
let seekTime = currentTime + 30
let seekTimeActual = CMTimeMake(value: Int64(seekTime), timescale: 1)
AudioService.shared.player!.seek(to: seekTimeActual)
}
#IBAction func minus30Secs(_ sender: UIButton) {
let currentTime = Float(AudioService.shared.player?.currentTime().seconds ?? 0)
let seekTime = currentTime - 30
let seekTimeActual = CMTimeMake(value: Int64(seekTime), timescale: 1)
AudioService.shared.player!.seek(to: seekTimeActual)
}
ok, i have fixed it.
The issue was i had progressBar.maximumValue = 100 meaning that my progressBar.value * 100.00 was giving a value 100 times what it should have been and as such a value beyond the end of the track. I removed the * 100.00 and now it works great.

Swift: Trying to control time in AVAudioPlayerNode using UISlider

I'm using an AVAudioPlayerNode attached to an AVAudioEngine to play a sound.
to get the current time of the player I'm doing this:
extension AVAudioPlayerNode {
var currentTime: TimeInterval {
get {
if let nodeTime: AVAudioTime = self.lastRenderTime, let playerTime: AVAudioTime = self.playerTime(forNodeTime: nodeTime) {
return Double(playerTime.sampleTime) / playerTime.sampleRate
}
return 0
}
}
}
I have a slider that indicates the current time of the audio. When the user changes the slider value, on .ended event I have to change the current time of the player to that indicated in the slider.
To do so:
extension AVAudioPlayerNode {
func seekTo(value: Float, audioFile: AVAudioFile, duration: Float) {
if let nodetime = self.lastRenderTime{
let playerTime: AVAudioTime = self.playerTime(forNodeTime: nodetime)!
let sampleRate = self.outputFormat(forBus: 0).sampleRate
let newsampletime = AVAudioFramePosition(Int(sampleRate * Double(value)))
let length = duration - value
let framestoplay = AVAudioFrameCount(Float(playerTime.sampleRate) * length)
self.stop()
if framestoplay > 1000 {
self.scheduleSegment(audioFile, startingFrame: newsampletime, frameCount: framestoplay, at: nil,completionHandler: nil)
}
}
self.play()
}
However, my function seekTo is not working correctly(I'm printing currentTime before and after the function and it shows always a negative value ~= -0.02). What is the wrong thing I'm doing and can I find a simpler way to change the currentTime of the player?
I ran into same issue. Apparently the framestoplay was always 0, which happened because of sampleRate. The value for playerTime.sampleRate was always 0 in my case.
So,
let framestoplay = AVAudioFrameCount(Float(playerTime.sampleRate) * length)
must be replaced with
let framestoplay = AVAudioFrameCount(Float(sampleRate) * length)

Swift 4 The UISlider with AVPlayer doesn't slide

I am trying to control the audio with the UISlider, the print() statement prints the correct value and the app doesn't crash, however when I try to grab it and move the slider (the thumb tint), the UISlider doesn't slide but just moves a bit when I try to slide it ( like a tap ).
When I comment the 6th row the slider response correctly (But of course nothing happens).
var playerItem : AVPlayerItem?
var player : AVPlayer?
#IBAction func adjustSongProgress(_ sender: UISlider) {
player?.pause()
let floatTime = Float(CMTimeGetSeconds(player!.currentTime()))
sliderProgressOutlet.value = floatTime
print(floatTime)
player?.play()
}
 Fixed it by deleting AVPlayer and changing AVPlayerItem to AVAudioPlayer, then putting the song URL into data : `
//DOWNLOADS THE SONG IN THE URL AND PUTS IT IN DATA
var task: URLSessionTask? = nil
if let songUrl = songUrl {
task = URLSession.shared.dataTask(with: songUrl, completionHandler: { data, response, error in
// SELF.PLAYER IS STRONG PROPERTY
if let data = data {
self.playerItem = try? AVAudioPlayer(data: data)
self.playPause()
DispatchQueue.main.async {
self.sliderProgress()
}
}
})
task?.resume()`
Then I changed UISlider IBAction Value Changed to Touch Down and Touch Up Inside when I connected it to the ViewController:
// TOUCH DOWN
#IBAction func SliderTouchDown(_ sender: UISlider) {
playerItem?.pause()
}
//TOUCH UP INSIDE
#IBAction func SliderTouchUpInside(_ sender: UISlider) {
playerItem?.currentTime = TimeInterval(sliderProgressOutlet.value)
playerItem?.play()
}
iOS Slider takes value between 0 to 1. if CMTimeGetSeconds return value outside from 0 to 1 it will not set properly.
therefor you have to convert your Time range to slider range.
for ex : your video/audio length is 120 second and you want to move slider to 30 second.than you can calculate new value using below function.
OldRange = (OldMax - OldMin)
NewRange = NewMax - NewMin
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
oldRange = 120 - 0
newRange = 1 - 0
New value = (30-0)*1/120+0 = 0.25

Swift - AVPlayer progress via UISlider

I am trying to make video player where I need to show the progress via UISlider and UILabel(for updating time). Here is my code
let videoPlayer = AVPlayer()
var videoPlayerSlider: UISlider = UISlider()
var videoPlayerLabel: UILabel = UILabel()
func updateVideoPlayerSlider() {
guard let currentTime = videoPlayer.currentTime else {
return
}
let mins = currentTime / 60
let secs = currentTime.truncatingRemainder(dividingBy: 60)
let timeformatter = NumberFormatter()
timeformatter.minimumIntegerDigits = 2
timeformatter.minimumFractionDigits = 0
timeformatter.roundingMode = .down
guard let minsStr = timeformatter.string(from: NSNumber(value: mins)), let secsStr = timeformatter.string(from: NSNumber(value: secs)) else {
return
}
videoPlayerLabel.text = "\(minsStr).\(secsStr)"
videoPlayerSlider.value = Float(videoPlayer.currentTime())
}
It shows 2 error.
1.(at very 1st line of the function)Initializer for conditional binding must have optional type, not '() -> CMTime
2.(at last line of the function)Cannot invoke initializer for type 'Float' with an argument list of type '(CMTime)'
Any assistance would be appreciated.
let videoPlayer = AVPlayer()
var videoPlayerSlider: UISlider = UISlider()
var videoPlayerLabel: UILabel = UILabel()
func updateVideoPlayerSlider() {
// 1 . Guard got compile error because `videoPlayer.currentTime()` not returning an optional. So no just remove that.
let currentTimeInSeconds = CMTimeGetSeconds(videoPlayer.currentTime())
// 2 Alternatively, you could able to get current time from `currentItem` - videoPlayer.currentItem.duration
let mins = currentTimeInSeconds / 60
let secs = currentTimeInSeconds.truncatingRemainder(dividingBy: 60)
let timeformatter = NumberFormatter()
timeformatter.minimumIntegerDigits = 2
timeformatter.minimumFractionDigits = 0
timeformatter.roundingMode = .down
guard let minsStr = timeformatter.string(from: NSNumber(value: mins)), let secsStr = timeformatter.string(from: NSNumber(value: secs)) else {
return
}
videoPlayerLabel.text = "\(minsStr).\(secsStr)"
videoPlayerSlider.value = Float(currentTimeInSeconds) // I don't think this is correct to show current progress, however, this update will fix the compile error
// 3 My suggestion is probably to show current progress properly
if let currentItem = videoPlayer.currentItem {
let duration = currentItem.duration
if (CMTIME_IS_INVALID(duration)) {
// Do sth
return;
}
let currentTime = currentItem.currentTime()
videoPlayerSlider.value = Float(CMTimeGetSeconds(currentTime) / CMTimeGetSeconds(duration))
}
}
I hope this would help you

Resources