Swift 4 - UISlider with AVAudioPlayer - ios

I am using AVAudioPlayer to play audio, now I am trying to user UISlider as the audio timeline
#IBOutlet var audioSlider: UISlider!
override func viewDidLoad() {
audioSlider.setValue(0, animated: false)
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: (#selector(PlayerController.updateTimer)), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
if(audio != nil)
{
audioSlider.setValue(Float(Int((self.audio?.currentTime)!)), animated: false)
}
}
But I don't think its working, my UISlider goes from the start to the end right away. I am expected a smooth transition with my UISlider
Here is how I am playing audio:
#IBAction func playButtonPressed(_ sender: Any) {
if(playButton.titleLabel?.text == "Play")
{
postPlay(postid: self.postid) { results in
DispatchQueue.main.async {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
}
catch {
}
self.audio?.delegate = self
self.audio?.numberOfLoops = 0
self.playButton.titleLabel?.text = "Stop"
self.audio?.play()
self.playCount = self.playCount + 1
self.plays.text = "Total Plays: " + self.playCount.description
}
}
}
else
{
playButton.titleLabel?.text = "Resume"
audio?.stop()
}
}

The UISlider expects a value between 0.0 and 1.0. To achieve this you must divide the current progress by the total time.
For Example
audioSlider.setValue(Float(self.audio?.currentTime! / self.audio?.duration!), animated: false)

Related

How to get Scrubbing to work on UISlider using AVAudioPlayer

So I'm using a rather unique version of audio player and everything works except for track scrubbing. The tracks, it should be noted, are buffered and collected from a website. I thought it might have to do with the observers I'm using. But I don't know if it's that really. I'm able to call the duration of the track and I'm able to get the actual UISlider to animate in conjunction with the track progression. But I can't scrub forward or backward. What ends up happening is I can move the slider but the track's progress doesn't move along with it. Here's what I've got:
override func viewDidLoad() {
audio.initialize(config: ["loop": true]) { (playerItem, change) in
var duration = CMTimeGetSeconds(playerItem.duration)
self.scrubSlider.maximumValue = Float(duration)
}
scrubSlider.setThumbImage(UIImage(named: "position_knob"), for: .normal)
}
override func viewDidAppear(_ animated: Bool) {
scrubSlider.value = 0.0
print(audio.seekDuration())
self.scrubSlider.maximumValue = audio.seekDuration()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.trackAudio), userInfo: nil, repeats: true)
print(self.scrubSlider.maximumValue)
}
#objc func trackAudio() {
var currentTime = audio.seekCurrentTime()
print(currentTime)
scrubSlider.value = currentTime
var duration = audio.seekDuration()
let remainingTimeInSeconds = duration - currentTime
timeElapsedLabel.text = audio.createTimeString(time: currentTime)
timeRemainingLabel.text = audio.createTimeString(time: remainingTimeInSeconds)
}
#IBAction func scrubberValueChanged(_ sender: UISlider) {
print("Entro")
var currentTime = (CMTimeGetSeconds((audio.playerQueue?.currentItem!.currentTime())!))
currentTime = (Float64)((scrubSlider.value))
}
Here's the observer where I collect the duration:
/**
Setup observers to monitor playback flow
*/
private func setupObservers(completion: #escaping (AVPlayerItem, Any) -> ()) {
// listening for current item change
self.audioQueueObserver = self.playerQueue?.observe(\.currentItem, options: [.new]) { [weak self] (player, _) in
print("media item changed...")
print("media number ", self?.playerQueue?.items() as Any, self?.playerQueue?.items().count as Any, self?.playerQueue?.currentItem as Any)
// loop here if needed //
if self?.audioPlayerConfig["loop"] as! Bool == true && self?.playerQueue?.items().count == 0 && self?.playerQueue?.currentItem == nil {
self?.playerQueue?.removeAllItems()
self?.playerQueue?.replaceCurrentItem(with: nil)
for item:AVPlayerItem in (self?.AVItemPool)! {
item.seek(to: CMTime.zero)
self?.playerQueue?.insert(item, after: nil)
}
self?.playerQueue?.play()
}
}
// listening for current item status change
self.audioQueueStatusObserver = self.playerQueue?.currentItem?.observe(\.status, options: [.new, .old], changeHandler: { (playerItem, change) in
guard let currentItemDuration = self.playerQueue?.currentItem?.duration else { return }
if playerItem.status == .readyToPlay {
print("current item status is ready")
print("media Queue ", self.playerQueue?.items() as Any, self.playerQueue?.items().count as Any)
print("item duration ", CMTimeGetSeconds(playerItem.duration))
print("itemD ", CMTimeGetSeconds(currentItemDuration))
print(currentItemDuration)
completion(playerItem, change)
}
})

Autopan in iOS with AVAudioPlayer smoothly

I am trying to build autopan(move the audio between left, middle and right channel) with AVaudioPlayer to play my music files. I could get it through AVAudioPlayer.pan method. Implemented to get AUTO pan by using timer in my swift code. Now the issue is ,audio is not playing smoothly and it breaks in between.
Here is my present code ,
class AudioPlayer: UIViewController {
var player = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
prepareAudioSession()
}
func prepareAudioSession() {
let audioFileName = "LPNumb"
let audioFileExtension = "mp3"
guard let filePath = Bundle.main.path(forResource: audioFileName, ofType: audioFileExtension) else {
print("Audio file not found at specified path")
return
}
do {
let alertSound = URL(fileURLWithPath: filePath)
try? player = AVAudioPlayer(contentsOf: alertSound)
} catch {
print(error)
}
}
#IBAction func play(_ sender: AnyObject){
player.play()
if player.isPlaying{
_ = Timer.scheduledTimer(timeInterval: 0.50, target: self, selector: #selector(self.update1), userInfo: nil, repeats: true)
_ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.update2), userInfo: nil, repeats: true)
_ = Timer.scheduledTimer(timeInterval: 1.50, target: self, selector: #selector(self.update3), userInfo: nil, repeats: true)
}
}
#objc func update1() {
player.pan = 0
}
#objc func update2() {
player.pan = 1
}
#objc func update3() {
player.pan = -1
}
}
I want to make the output audio as MONO and require audio to be played AUTOPANNED smoothly.
I think, you need make just one Timer for this task.Take a look through the code:
import UIKit
import AVFoundation
enum PanState {
case up
case down
}
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer?
var timerForPan: Timer?
var pan: Double = 0.0
var panState: PanState = .up
override func viewDidLoad() {
super.viewDidLoad()
if let path = Bundle.main.path(forResource: "LPNumb", ofType: "mp3") {
let record = URL(fileURLWithPath: path)
setupRecordToPlayer(from: record)
}
setTimers()
}
func setTimers() {
timerForPan = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updatePan), userInfo: nil, repeats: true)
}
func setupRecordToPlayer(from url: URL) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
} catch let error {
debugPrint(error.localizedDescription)
}
}
#IBAction func playButtonPressed(_ sender: UIButton) {
audioPlayerToPlay()
}
#objc func updatePan() {
if audioPlayer?.isPlaying ?? false {
switch panState {
case .up:
self.pan += 0.1
if pan >= 1 {
self.panState = .down
}
case .down:
self.pan -= 0.1
if pan <= -1 {
self.panState = .up
}
}
audioPlayer?.pan = Float(self.pan)
}
}
/// Prepare Audio player to play
func audioPlayerToPlay() {
audioPlayer?.prepareToPlay()
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
} catch {
print(error.localizedDescription)
}
audioPlayer?.play()
}
}

Swift timer won't work after invalidating and reinitiating

I'm using Timer() for indicating current audio position in audio player
func startTimer() {
print("PlayerController: startTimer()")
if itemPlayTimer != nil {
return
}
itemPlayTimer = Timer.scheduledTimer(timeInterval: 0.001,
target: self,
selector: #selector(updateItemPlayerTimer),
userInfo: nil,
repeats: true)
}
#objc func updateItemPlayerTimer() {
guard let currentTime = player?.currentTime else {
return
}
updateTimeDescription?(currentTime)
}
when user pause player app invalidating timer
func stopTimer() {
itemPlayTimer?.invalidate()
itemPlayTimer = nil
}
But after calling startTimer() again selector won't call
The reason was that the timer starts executing in other thread, not in main thread.
change your functions in this way:
func startTimer() {
print("PlayerController: startTimer()")
if itemPlayTimer == nil {
itemPlayTimer = Timer.scheduledTimer(timeInterval: 0.001,
target: self,
selector: #selector(updateItemPlayerTimer),
userInfo: nil,
repeats: true)
}
}
#objc func updateItemPlayerTimer() {
guard let currentTime = player?.currentTime else {
return
}
updateTimeDescription?(currentTime)
}
func stopTimer() {
if itemPlayTimer != nil {
itemPlayTimer?.invalidate()
itemPlayTimer = nil
}
}
Use the following code. I have used An AVPlayer with a sample Video to demonstrate pausing/playing with Timer. Logic is same for audio player.
Just replace AVPlayer with your audioPlayer.
Key logic here is to properly manage the state of Playing/not playing and checking timer for nil etc.
As indicated in this Answer
startTimer() starts the timer only if it's nil and stopTimer() stops
it only if it's not nil.
You have only to take care of stopping the timer before
creating/starting a new one.
I have implemented this in a sample project and is working 100%.
Carefully See the function pauseTapped(_ sender: UIButton)
See sample gif At end of Code
import UIKit
import AVKit
class TimerVC: UIViewController {
///A container view for displaying the AVPlayer.
#IBOutlet weak var playerView: UIView!
/// A button to play and pause the video
#IBOutlet weak var btnPause: UIButton!
///to maintain the status of AVPlayer playing or not
var flagPlaying = true
///An AVPlayer for displaying and playing the video
var player: AVPlayer?
///to show the current time to user
#IBOutlet weak var lblCurrentTime: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//add an AVPlayer with sample URL link
addVideoPlayer( playerView: playerView)
}
///Timer
var itemPlayTimer: Timer?
#objc func startTimer() {
if itemPlayTimer != nil {
return
}
itemPlayTimer = Timer.scheduledTimer(timeInterval: 0.001,
target: self,
selector: #selector(updateItemPlayerTimer),
userInfo: nil,
repeats: true)
}
///update time label
#objc func updateItemPlayerTimer() {
guard let currentTime = player?.currentTime else {
return
}
updateTimeDescription(currentTime())
}
func updateTimeDescription( _ currentTime :CMTime ) {
self.lblCurrentTime.text = "\(currentTime.seconds)"
}
///To Pause and play the video
#IBAction func pauseTapped(_ sender: UIButton) {
if flagPlaying {
//pause
if itemPlayTimer != nil {
player?.pause()
itemPlayTimer?.invalidate()
itemPlayTimer = nil
flagPlaying = false
DispatchQueue.main.async {
self.btnPause.setTitle("Play", for: .normal)
}
}
}else {
//not playing
if itemPlayTimer == nil {
player?.play()
startTimer()
flagPlaying = true
DispatchQueue.main.async {
self.btnPause.setTitle("Pause", for: .normal)
}
}
}
}
private func addVideoPlayer(playerView: UIView) {
//let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer.init(url: URL.init(string: "http://techslides.com/demos/sample-videos/small.mp4")!)
let layer: AVPlayerLayer = AVPlayerLayer(player: player)
layer.backgroundColor = UIColor.white.cgColor
layer.frame = CGRect(x: 0, y: 0, width: playerView.frame.width, height: playerView.frame.height)
layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerView.layer.sublayers?.forEach({$0.removeFromSuperlayer()})
playerView.layer.addSublayer(layer)
flagPlaying = true
player?.play()
startTimer()
}
}
Working Example
Let me know if you need any help

Loop vibration in Swift

I'm playing a sound using AVPlayer. I'm trying to let the iPhone vibrate during this sound.
I'm able to create a vibration with:
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
But I'm having problems trying to loop this vibration if I use:
vibrationTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ViewController.loopVibration), userInfo: nil, repeats: true)
#objc func loopVibration() {
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
}
The func loopVibration() gets called every two seconds but it doesn't vibrate.
Not sure why this isn't working for you--the following sample works for me on my test device (iPhone X, iOS 11.2).
The ViewController sample below includes outlets for playing single sounds, playing single vibrations, looping sounds and looping vibrations. The "Chirp" sound is a 1s wav file.
Note that since this is a sample, the code below doesn't dispose of the system sounds, nor invalidate timers if the ViewController is hidden. Make sure to manage your system resources appropriately if you adapt this.
//
// ViewController.swift
//
import UIKit
import AudioToolbox
class ViewController: UIViewController {
static let soundId: SystemSoundID? = {
guard let soundURL = Bundle.main.url(forResource: "Chirp", withExtension: "wav") else {
return nil
}
var soundId: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL as CFURL, &soundId)
return soundId
}()
static let timerInterval: TimeInterval = 2
var soundTimer: Timer?
var vibrationTimer: Timer?
var playCount = 0 {
didSet {
playCountLabel.text = String(playCount)
}
}
func invalidateTimers() {
if let vibrationTimer = vibrationTimer {
vibrationTimer.invalidate()
}
if let soundTimer = soundTimer {
soundTimer.invalidate()
}
}
#IBOutlet weak var playCountLabel: UILabel!
#IBAction func playSound(_ sender: Any) {
if let soundId = ViewController.soundId {
AudioServicesPlaySystemSound(soundId)
playCount += 1
}
}
#IBAction func loopSound(_ sender: Any) {
invalidateTimers()
soundTimer = Timer.scheduledTimer(timeInterval: ViewController.timerInterval, target: self, selector: #selector(playSound(_:)), userInfo: nil, repeats: true)
}
#IBAction func vibrate(_ sender: Any) {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
playCount += 1
}
#IBAction func loopVibrate(_ sender: Any) {
invalidateTimers()
soundTimer = Timer.scheduledTimer(timeInterval: ViewController.timerInterval, target: self, selector: #selector(vibrate(_:)), userInfo: nil, repeats: true)
}
#IBAction func cancelAll(_ sender: Any) {
invalidateTimers()
}
}
It's not permitted to use continuous vibration, your app might be rejected. If you still want to do it you can try something like this:
func vibrate() {
for i in 0...13 {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(Double(i) * 0.1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
self.vibrate()
})
}
}

Stop watch working fine only first time in Swift

I made a stopwatch in swift. It has three buttons- play, stop, reset. it has a display in the middle where I display the seconds passed.
My code runs fine. But the watch runs very fast after the stop or reset.
I am not able to figure out what is the fuss.
please help!
var time = 0
var timer = NSTimer()
var pause = Bool(false)
#IBOutlet var result: UILabel!
func displayResult() {
if pause != true
{
time++
result.text = String(time)
}
}
#IBAction func reset(sender: AnyObject) {
time = 0
result.text = String(time)
}
#IBAction func pause(sender: AnyObject) {
pause = true
}
#IBAction func play(sender: AnyObject) {
pause = false
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("displayResult") , userInfo: nil, repeats: true)
}
Your pause action should be:
#IBAction func pause(sender: AnyObject) {
timer.invalidate()
pause = true
}
UPD:
var time = 0
var timer = NSTimer()
var pause = true // 1
func displayResult() {
if pause != true
{
time++
label.text = String(time)
}
}
#IBAction func reset(sender: AnyObject) {
time = 0
label.text = String(time)
timer.invalidate() // 2
pause = true // 3
}
#IBAction func pause(sender: AnyObject) {
timer.invalidate() // 4
pause = true
}
#IBAction func play(sender: AnyObject) {
// 5
if pause == true {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("displayResult") , userInfo: nil, repeats: true)
pause = false
}
}

Resources