3

I'm making a simple audio player app, and it allows a user to change where they are in a track using the UISlider.

What it doesn't do: the slider doesn't update based on the track's progress. I found a similar question here, and the second answer was responsive to AVAudioPlayer -- suggesting using currentTime to update the slider. I want to try to do that.

Here's my code:

import UIKit
import AVFoundation

class MusicViewController: UIViewController {

    ....

    @IBOutlet var Slider: UISlider!

    @IBAction func changeAudioTime(_ sender: Any) {
        Slider.maximumValue = Float(audioPlayer.duration)
        audioPlayer.stop()
        audioPlayer.currentTime = TimeInterval(Slider.value)
        audioPlayer.prepareToPlay()
        audioPlayer.play()
    }

    func updateSlider(){

        Slider.value = Float(audioPlayer.currentTime)
        print("Changing works")
    }

The first function works fine -- the user can change where they are. However, the second function to me should change the Slider.value based on the currentTime of the AudioPlayer. It doesn't do that.

It's also not printing, so my belief is it isn't firing.

I'm new to Swift so I am likely missing something. Any ideas?

darkginger
  • 652
  • 1
  • 10
  • 38

3 Answers3

4

Tested code:-

Add timer variable

var timer: Timer?

in a function where you start playing your audio:

    slidderrr.value = 0.0
    slidderrr.maximumValue = Float((player?.duration)!)
    audioPlayer.play()
    timer = Timer.scheduledTimer(timeInterval: 0.0001, target: self, selector: #selector(self.updateSlider), userInfo: nil, repeats: true)

Don't forget to invalidate timer when you don't want to update slider

func stopPlayer() {
    player?.stop()
    timer?.invalidate()
    print("Player and timer stopped")
  }
Akash More
  • 143
  • 1
  • 2
  • 13
1

You need to set up a CADisplayLink and update the position of the slider on each invocation of its selector. Here's how the whole AVAudioPlayer-UISlider binding can be done:

// 1
@IBOutlet weak var slider: UISlider! {
    didSet {
        slider.isContinuous = false

        slider.addTarget(self, action: #selector(didBeginDraggingSlider), for: .touchDown)
        slider.addTarget(self, action: #selector(didEndDraggingSlider), for: .valueChanged)
    }
}

// 2
var player: AVAudioPlayer

// 3
lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: #selector(updatePlaybackStatus))

// 4
func startUpdatingPlaybackStatus() {
    displayLink.add(to: .main, forMode: .common)
}

func stopUpdatingPlaybackStatus() {
    displayLink.invalidate()
}

// 5
@objc
func updatePlaybackStatus() {
    let playbackProgress = Float(player.currentTime / player.duration)

    slider.setValue(playbackProgress, animated: true)
}

// 6
@objc
func didBeginDraggingSlider() {
    displayLink.isPaused = true
}

@objc
func didEndDraggingSlider() {
    let newPosition = player.duration * Double(slider.value)
    player.currentTime = newPosition

    displayLink.isPaused = false
}
  1. An outlet to the UISlider. Make sure that its min and max values are set to 0 and 1 respectively and add actions for its touchDown and valueChanged events to get notified when the user begins and ends dragging the slider respectively. Also, set one's isContinuous property to false, so the valueChanged event is reported only when the user releases the slider. This setup can also be done with Interface Builder and IBActions;
  2. A reference to the AVAudioPlayer, just for clarity;
  3. A property storing an instance of CADisplayLink. Since we use self as the target, the initialization of CADisplayLink should be done after initializing the parent class to be able to correctly reference it. For that purpose I made it lazy;
  4. Methods that start and stop updating the UI. You should call them when the playback status changes.
  5. The method that gets called during screen refresh, the logic for updating the UI is stored here.
  6. The methods for handling slider's events with fairly straightforward logic - pause display link when the user begins dragging the slider, so its value isn't updated during user interaction; seek the audio to the selected position and resume display link when the user released the slider.

Source - my blog post

Artem Garmash
  • 154
  • 10
0

Have you tried func setValue(Float, animated: Bool) Sets the slider’s current value, allowing you to animate the change visually.

So something like slider.setValue(index, animated: true)

can be found here

https://developer.apple.com/documentation/uikit/uislider

let me know if that works!

matt
  • 1
  • Thanks for the response, Matt. Tried a couple things. First I changed the function to .setValue of 'Float' like so: `func updateSlider(){ Slider.setValue(Float, animated: true)}` but this gave me an error "Cannot convert value of Float.type to expected argument." I tried to change this argument to a value so tried again by creating a variable for `currentTime` of the AudioPlayer like so: `func updateSlider(){ let updater = Float(audioPlayer.currentTime) Slider.setValue(updater, animated: true) }`. This would run but the slider isn't animated yet. Will keep trying. – darkginger Jan 10 '18 at 07:57