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

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

Related

Swift - Re-add time back into Timer

I have a countdown Timer that shows seconds and milliseconds. The user can start/stop recording multiple times until the timer hits zero. The user can also delete a previous recording at which point I have to re-add that deleted time back into the initial 20 secs. There are 2 issues.
The first issue is when the timer is stopped, the remaining time that shows on the timer label doesn't match the time culmination of the recordings. From my understanding this might be a RunLoop issue and I don't think there is anything that I can do about the inaccuracies.
let initialTime = 20.0
var cumulativeTimeForAllAssests = 0.0
for asset in arrOfAssets {
let assetDuration = CMTimeGetSeconds(asset.duration)
print("assetDuration: ", assetDuration)
cumulativeTimeForAllAssests += assetDuration
}
print("\ncumulativeTimeForAllAssests: ", cumulativeTimeForAllAssests)
After starting/stopping 5 times, the remaining time on the timer label says 16.5 but the culmination of the assets time is 4.196666.... The timer label should say 15.8, it's 0.7 milli off. The more I start/stop the recording, the more inaccurate/further off the culmination time - the initial time and the timer label time is.
assetDuration: 0.7666666666666667
assetDuration: 0.9666666666666667
assetDuration: 0.7983333333333333
assetDuration: 0.7333333333333333
assetDuration: 0.9316666666666666
cumulativeTimeForAllAssests: 4.196666666666667
The second issue is because I'm using seconds and milliseconds in my timerLabel, when I add re-add the subtracted time back in via deleteAssetAndUpdateTimer(...), I use the parts of modf() to update the seconds and milliseconds. I couldn't think of another way to update the timer. I know there has to be a more accurate way to do it.
Timer code:
weak var timer: Timer?
var seconds = 20
var milliseconds = 0
let initialTime = 20.0
func startTimer() {
invalidateTimer()
if seconds == Int(initalTime) && milliseconds == 0 {
timerIsRunning()
}
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
self?.timerIsRunning()
})
}
func timerIsRunning() {
updateTimerLabel()
if milliseconds == 0 {
seconds -= 1
}
milliseconds -= 1
if milliseconds < 0 {
milliseconds = 9
}
if seconds == 0 && milliseconds == 0 {
invalidateTimer()
updateTimerLabel()
}
}
func invalidateTimer() {
timer?.invalidate()
timer = nil
}
func updateTimerLabel() {
let milisecStr = "\(milliseconds)"
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
timerLabel.text = "\(secondsStr).\(milisecStr)"
}
Delete asset and update timer code:
// the timer is stopped when this is called
func deleteAssetAndUpdateTimer(_ assetToDelete: AVURLAsset) {
var cumulativeTimeForAllAssests = 0.0
for asset in arrOfAssets {
let assetDuration = CMTimeGetSeconds(asset.duration)
cumulativeTimeForAllAssests += assetDuration
}
let timeFromAssetToDelete = CMTimeGetSeconds(assetToDelete.duration)
let remainingTime = self.initialTime - cumulativeTimeForAllAssests
let updatedTime = remainingTime + timeFromAssetToDelete
let mod = modf(updatedTime)
self.seconds = Int(mod.0)
self.milliseconds = Int(mod.1 * 10)
updateTimerLabel()
// remove assetToDelete from array
}
The big issue here was I was using a Timer to countdown which was incorrect. Following #LeoDabus' comments, I instead used CACurrentMediaTime():
let timerLabel = UILabel()
let maxRecordingTime = 30.0
lazy var elapsedTime = maxRecordingTime
var startTime: CFTimeInterval?
var endTime: CFTimeInterval?
weak var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
updateTimerLabel(with: Int(maxRecordingTime))
}
#IBAction func recordButtonPressed(_ sender: UIButton) {
if startTime == nil {
startTimer()
} else {
stopTimer(updateElapsed: true)
}
}
func startTimer() {
if elapsedTime == 0 { return }
stopTimer()
startTime = CACurrentMediaTime()
endTime = startTime! + elapsedTime
print("startTime: \(startTime!) | endTime: \(endTime!)")
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self] _ in
self?.timerIsRunning()
}
}
func timerIsRunning() {
guard let startTime = startTime, let endTime = endTime else { return }
let currentTime = CACurrentMediaTime()
let remainingTime = currentTime - startTime
print("%2d %.3lf", elapsedTime, remainingTime)
if currentTime >= endTime {
print("stopped at - currentTime: \(currentTime) | endTime: \(endTime)")
stopTimer(updateElapsed: true, currentTime: currentTime)
return
}
let countDownTime: Double = elapsedTime - remainingTime
let seconds = Int(countDownTime)
updateTimerLabel(with: seconds)
}
func updateTimerLabel(with seconds: Int) {
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
timerLabel.text = secondsStr
}
func stopTimer(updateElapsed: Bool = false, currentTime: Double? = nil) {
timer?.invalidate()
timer = nil
if updateElapsed {
updateElapsedTime(using: currentTime)
}
startTime = nil
endTime = nil
}
func updateElapsedTime(using currentTime: Double? = nil) {
guard let startTime = startTime else { return }
var timeNow = CACurrentMediaTime()
if let currentTime = currentTime {
timeNow = currentTime
}
var updatedTime = elapsedTime - (timeNow - startTime)
if updatedTime < 0 {
updatedTime = 0
}
elapsedTime = updatedTime
}
func resetElapsedTime() { // This is for a resetButton not shown here
elapsedTime = maxRecordingTime
}

Audio files won't play with AirPods Pro on iOS 15

I am using this code to play audio. My code works fine on iOS 14 with all headphones model, but when customers have updated their devices to iOS 15 and are using AirPods Pro, no audio files play. On other AirPods models and when playing audio files through the iPhone speaker, everything works. What's happened. How to fix it?
Update:
After a long wait, I was given AirPods Pro. And at first I removed that line setupMediaPlayerNotificationView(true) and the app played a sound fine. But some of the functions on Lock Screen were removed. And with this line in the app there was no sound. In the App Store, I had 3 apps with the same code. And after ios 15 only one worked. And I did not understand in any way what is the reason if the code is the same. Why aren't the others working? But it turned out that the app that worked had 1 word in its name - Build Settings -> Product Name -> "myAppName". And the rest had a few words. And when I renamed them to 1 word everything working fine. Sound play fine. What was it? I still don't understand? If anyone has a version, share it.
code:
let url = Bundle.main.url(forResource: "\(masterIndex)0", withExtension: "m4a")!
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
play(sender:AnyObject.self as AnyObject)
setupMediaPlayerNotificationView(true)
lockScreen()
} catch {
}
other code:
func lockScreen() {
var albumArtwork : MPMediaItemArtwork!
let image:UIImage = UIImage(named: "infoImage")!
albumArtwork = MPMediaItemArtwork.init(boundsSize: image.size, requestHandler: { (size) -> UIImage in
return image
})
let infotitle = "\(firstArray[index])"
MPNowPlayingInfoCenter.default().nowPlayingInfo = [
MPMediaItemPropertyArtist : "",
MPMediaItemPropertyTitle : infotitle,
MPMediaItemPropertyArtwork : albumArtwork,
MPMediaItemPropertyAlbumTitle : "",
MPNowPlayingInfoPropertyElapsedPlaybackTime : Int(audioPlayer.currentTime),
MPMediaItemPropertyPlaybackDuration: Int(audioPlayer.duration)]
}
#objc func lockScreenPlay(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
self.audioPlayer.play()
self.lockScreen()
self.playButton.setImage(UIImage(named: "pause.png"), for: UIControlState.normal)
return .success
}
#objc func lockScreenPause(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
self.audioPlayer.pause()
self.lockScreen()
self.playButton.setImage(UIImage(named: "play.png"), for: UIControlState.normal)
return .success
}
#objc func lockScreenFastForward(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
var time: TimeInterval = audioPlayer.currentTime
time += 15.0
if time > audioPlayer.duration {
audioPlayerDidFinishPlaying(audioPlayer, successfully: true)
} else {
audioPlayer.currentTime = time
updateTime()
}
self.lockScreen()
return .success
}
#objc func lockScreenFastBackward(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
var time: TimeInterval = audioPlayer.currentTime
time -= 15.0
if time < 0 {
audioPlayer.currentTime = 0
updateTime()
} else {
audioPlayer.currentTime = time
updateTime()
}
self.lockScreen()
return .success
}
#objc func changedThumbSlider(_ event: MPChangePlaybackPositionCommandEvent) -> MPRemoteCommandHandlerStatus {
let time = event.positionTime
audioPlayer.currentTime = TimeInterval(time)
self.lockScreen()
return .success
}
func setupMediaPlayerNotificationView(_ enable: Bool) {
let commandCenter = MPRemoteCommandCenter.shared()
if enable {
commandCenter.playCommand.addTarget(self, action: #selector(lockScreenPlay))
commandCenter.pauseCommand.addTarget(self, action: #selector(lockScreenPause))
commandCenter.skipForwardCommand.preferredIntervals = [15]
commandCenter.skipForwardCommand.addTarget(self, action: #selector(lockScreenFastForward))
commandCenter.skipBackwardCommand.preferredIntervals = [15]
commandCenter.skipBackwardCommand.addTarget(self, action: #selector(lockScreenFastBackward))
commandCenter.changePlaybackPositionCommand.addTarget(self, action: #selector(self.changedThumbSlider(_:)))
} else {
commandCenter.playCommand.removeTarget(self, action: #selector(lockScreenPlay))
commandCenter.pauseCommand.removeTarget(self, action: #selector(lockScreenPause))
commandCenter.skipForwardCommand.removeTarget(self, action: #selector(lockScreenFastForward))
commandCenter.skipBackwardCommand.removeTarget(self, action: #selector(lockScreenFastBackward))
commandCenter.changePlaybackPositionCommand.removeTarget(self, action: #selector(self.changedThumbSlider(_:)))
}
}
#IBAction func play(sender: AnyObject) {
if !audioPlayer.isPlaying{
animationStatus()
audioPlayer.play()
slider.maximumValue = Float(audioPlayer.duration)
timer = Timer(timeInterval: 0.1, target: self, selector: #selector(self.updateTime), userInfo: nil, repeats: true)
RunLoop.main.add(timer!, forMode: .commonModes)
restorePlayerCurrentTime()
playButton.setImage(UIImage(named: "pause.png"), for: UIControlState.normal)
} else {
animationStatus()
audioPlayer.pause()
playButton.setImage(UIImage(named: "play.png"), for: UIControlState.normal)
timer?.invalidate()
}
}
#IBAction func fastForward(sender: AnyObject) {
var time: TimeInterval = audioPlayer.currentTime
time += 15.0 // Go Forward by 15 Seconds
if time > audioPlayer.duration {
audioPlayerDidFinishPlaying(audioPlayer, successfully: true)
} else {
audioPlayer.currentTime = time
updateTime()
}
self.lockScreen()
}
#IBAction func fastBackward(sender: AnyObject) {
var time: TimeInterval = audioPlayer.currentTime
time -= 15.0 // Go Back by 15 Seconds
if time < 0 {
audioPlayer.currentTime = 0
updateTime()
} else {
audioPlayer.currentTime = time
updateTime()
}
self.lockScreen()
}
private func restorePlayerCurrentTime() {
let currentTimeFromUserDefaults : Double? = UserDefaults.standard.value(forKey: "currentTime\(masterIndex)\(index)") as! Double?
if let currentTimeFromUserDefaultsValue = currentTimeFromUserDefaults {
audioPlayer.currentTime = currentTimeFromUserDefaultsValue
slider.value = Float.init(audioPlayer.currentTime)
}
}
#objc func updateTime() {
let currentTime = Int(audioPlayer.currentTime)
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
let durationTime = Int(audioPlayer.duration) - Int(audioPlayer.currentTime)
let minutes1 = durationTime/60
let seconds1 = durationTime - minutes1 * 60
timeElapsed.text = NSString(format: "%02d:%02d", minutes,seconds) as String
timeDuration.text = NSString(format: "-%02d:%02d", minutes1,seconds1) as String
UserDefaults.standard.set(currentTime, forKey: "currentTime\(masterIndex)\(index)")
UserDefaults.standard.set(durationTime, forKey: "durationTime\(masterIndex)\(index)")
slider.value = Float.init(audioPlayer.currentTime)
}
func audioPlayerDidFinishPlaying(_ audioPlayer: AVAudioPlayer, successfully flag: Bool) {
let currentTime = 0
let durationTime = 0.1
UserDefaults.standard.set(currentTime, forKey: "currentTime\(masterIndex)\(index)")
UserDefaults.standard.set(durationTime, forKey: "durationTime\(masterIndex)\(index)")
slider.value = Float.init(audioPlayer.currentTime)
timer?.invalidate()
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentDirectoryPath:String = path[0]
let fileManager = FileManager()
let destinationURLForFile = URL(fileURLWithPath: documentDirectoryPath.appendingFormat("/\(masterIndex)/\(index+1).mp3"))
if fileManager.fileExists(atPath: destinationURLForFile.path){
if endOfChapterSleepTimer == true {
endOfChapterSleepTimer = false
} else {
index = index + 1
viewDidLoad()
}
} else {
}
}
func animationStatus() {
let vinylLayer = vinylView.layer
pause = !pause
if pause {
pauseLayer(layer: vinylLayer)
} else {
if vinylStatus == "true" {
resumeLayer(layer: vinylLayer)
} else {
rotateImageView()
resumeLayer(layer: vinylLayer)
}
}
}
private func rotateImageView() {
vinylStatus = "true"
UIView.animate(withDuration: 3, delay: 0, options: .curveLinear, animations: {
self.vinylView.transform = self.vinylView.transform.rotated(by: .pi / 2)
}) { (finished) in
if finished {
self.rotateImageView()
}
}
}
May be it is a problem of iOS 15?
I've found some papers for search query: "sound doesn't play AirPods Pro on ios 15": one, tow, three .
As I understand, this problem was fixed in iOS 15.1.
Sometimes Apple makes mistakes (for example, every updating of Xcode :) )

how to run progress bar until the background plying audio stoped

i'm working on a audio player where i have to allow the audio to play in background, i have done with audio sessions but i stuck in progress bar when i play audio in background and again open my app the progress bar and timer stoped, i need to run them also in background. i mean progress bar should be updated with timer if i move to other views or minimise the app.
#IBAction func btnPlayMusicAction(_ sender: Any)
{
if isPlaying == false {
self.btnPlayMusicPro.setImage(#imageLiteral(resourceName: "PauseIcon"), for: .normal)
self.isPlaying = true
self.audioPlayer?.play()
timer = Timer.scheduledTimer(timeInterval:1.0, target: self,selector: (#selector(updateRemainingTime)), userInfo: nil, repeats:true)
}else{
self.btnPlayMusicPro.setImage(#imageLiteral(resourceName: "PlayIcon"), for: .normal)
self.isPlaying = false
if (audioPlayer?.isPlaying)!{
self.audioPlayer?.pause()
}
self.timer?.invalidate()
}
}
her is the method updateRemainingTime()
#objc func updateRemainingTime(){
let currentTime = Int((audioPlayer?.currentTime)!)
let duration = Int((audioPlayer?.duration)!)
let seconds = currentTime % 60
let minutes = (currentTime / 60) % 60
let hours = (currentTime / 3600)
var remainingTime:String = ""
if currentTime >= 3600{
remainingTime = String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}else{
remainingTime = String(format: "%02d:%02d", minutes, seconds)
}
self.lblPlayMusicTimePro.text = remainingTime
Tray.remainingAudioTime = remainingTime
if currentTime == duration-1{
timer?.invalidate()
if (audioPlayer?.isPlaying)!{
self.audioPlayer?.currentTime = 0
self.audioPlayer?.stop()
}
self.btnPlayMusicPro.setImage(#imageLiteral(resourceName: "PlayIcon"), for: .normal)
self.isPlaying = false
self.musicProgressViewPro.setProgress(0.0, animated: true)
self.lblPlayMusicTimePro.text = "00:00"
self.lblMusicTimePro.text = "00:00"
}else {
let currentProgress = Float((audioPlayer?.currentTime)! / (audioPlayer?.duration)!)
Tray.progressTime = currentProgress
self.musicProgressViewPro.setProgress(currentProgress, animated: true)
}
}

Count-Up and Count-Down Timer with Date() in Swift 4 should stop after a certain time period

I am quite new to Swift and already learned a lot using the questions here.
In one of my first projects I try to write a soccer playtime timer app. The first timer is counting up after the whistle button is pressed showing the minutes played and the second timer is counting down to zero showing the minutes left to play. This works so far.
Now both timers should stop automatically when the halftime is over, so that I can start a third overtime timer. So far the invalidate statement of the timer is not working - both timers keep running. There seems to be something wrong with my if-statements, but at the moment I have no clue what. So any help would be very appreciated.
var countUpClock: Timer?
var countDownClock: Timer?
private var formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()
func runPlaytimeClocks() {
let startTime = Date()
let countTime = Date() + 2700 //45min of playtime
if startTime <= countTime {
countUpClock = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.timePlayed.text = self?.formatter.string(from: startTime, to: Date())
}
}
else {
countUpClock?.invalidate()
}
if startTime <= countTime {
countDownClock = Timer.scheduledTimer(withTimeInterval: -1.0, repeats: true) { [weak self] _ in
self?.timetoPlay.text = self?.formatter.string(from: Date(), to: countTime)
}
}
else {
countDownClock?.invalidate()
}
Thank you very much for the replies.
I found exactly what I was looking for here: http://ioscake.com/swift-nstimer-in-background.html
I adapted the solution to my for clocks (CountUpClock, CountDownClock, OvertimeClock, HalftimeClock).
Do you have any suggestions what would be the best solution to start the second halftime of the soccer game?
So far the CountUpClock starts again at 0:00 when I press the whistle button after the halftime break. But it should keep running from minute 45:00 to 90:00 - while the CountDownClock should counting down from 45:00 to 0:00 again.
What would be the best solution for such a behaviour?
import UIKit
import UserNotifications
private let stopTimeKey = "stopTimeKey"
class ViewController: UIViewController {
//Outlets
#IBOutlet weak var timePlayed: UILabel!
#IBOutlet weak var timeToPlay: UILabel!
#IBOutlet weak var overtime: UILabel!
#IBOutlet weak var halftime: UILabel!
#IBOutlet weak var halftimeButton: UIButton!
private var stopTime: Date?
override func viewDidLoad() {
super.viewDidLoad()
registerForLocalNotifications()
stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
if let time = stopTime {
if time > Date() {
startTimers(time, includeNotification: false)
} else {
notifyTimerCompleted()
}
}
}
private func registerForLocalNotifications() {
if #available(iOS 10, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
guard granted && error == nil else {
// display error
print("\(String(describing: error))")
return
}
}
} else {
let types: UIUserNotificationType = [.badge, .sound, .alert]
let settings = UIUserNotificationSettings(types: types, categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
}
//Actions
#IBAction func whistleButtonTapped(_ sender: UIButton) {
overtimeClock?.invalidate()
overtimeClock = nil
halftimeClock?.invalidate()
halftimeClock = nil
overtime.text = "00:00"
halftime.text = "00:00"
halftimeButton.isHidden = true
//add 10 seconds per halftime to try out
let time = Date() + 10
if time > Date() {
startTimers(time)
} else {
timeToPlay.text = "error"
}
}
#IBAction func halftimeButton(_ sender: UIButton) {
halftimeButtoPressed()
}
// Code for different Timers
private var countDownClock: Timer?
private var countUpClock: Timer?
var overtimeClock: Timer?
var halftimeClock: Timer?
private func startTimers(_ stopTime: Date, includeNotification: Bool = true) {
// save `stopTime` in case app is terminated
UserDefaults.standard.set(stopTime, forKey: stopTimeKey)
self.stopTime = stopTime
// start Timer
countDownClock = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleCountDownTimer(_:)), userInfo: nil, repeats: true)
countUpClock = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleCountUpTimer(_:)), userInfo: nil, repeats: true)
guard includeNotification else { return }
// start local notification (so we're notified if timer expires while app is not running)
if #available(iOS 10, *) {
let content = UNMutableNotificationContent()
content.title = "Overtime is starting soon"
content.body = "In 5 seconds the overtime will start"
content.sound = UNNotificationSound.default()
//5 seconds warning before overtime starts
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: stopTime.timeIntervalSinceNow - 5, repeats: false)
let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
} else {
let notification = UILocalNotification()
//5 seconds warning before overtime starts
notification.fireDate = stopTime - 5
notification.alertBody = "Overtime is starting soon"
UIApplication.shared.scheduleLocalNotification(notification)
}
}
private func stopTimer() {
countDownClock?.invalidate()
countDownClock = nil
countUpClock?.invalidate()
countUpClock = nil
}
private func halftimeButtoPressed() {
overtimeClock?.invalidate()
overtimeClock = nil
startHalftimeClock()
halftimeButton.isHidden = true
}
private let dateComponentsFormatter: DateComponentsFormatter = {
let _formatter = DateComponentsFormatter()
_formatter.allowedUnits = [.minute, .second]
_formatter.unitsStyle = .positional
_formatter.zeroFormattingBehavior = .pad
return _formatter
}()
#objc func handleCountDownTimer(_ timer: Timer) {
let now = Date()
if stopTime! > now {
timeToPlay.text = dateComponentsFormatter.string(from: now, to: stopTime!)
} else {
stopTimer()
notifyTimerCompleted()
startOvertimeClock()
halftimeButton.isHidden = false
}
}
#objc func handleCountUpTimer(_ timer: Timer) {
//add 10 seconds per halftime to try out
let now = Date() + 10
if now > stopTime! {
timePlayed.text = dateComponentsFormatter.string(from: stopTime!, to: now)
} else {
stopTimer()
notifyTimerCompleted()
}
}
//Overtime Clock
#objc func startOvertimeClock() {
let startOvertime = Date()
overtimeClock = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.overtime.text = self?.dateComponentsFormatter.string(from: startOvertime, to: Date())
}
}
//Halftime Clock
#objc func startHalftimeClock() {
let startHalftime = Date()
halftimeClock = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.halftime.text = self?.dateComponentsFormatter.string(from: startHalftime, to: Date())
}
}
private func notifyTimerCompleted() {
timeToPlay.text = "End"
timePlayed.text = "End"
}
}

Get valid Float value to use in AVPlayer from CMTime

Im trying to set a slider to the current value of an audio file which is playing. But I'm getting problems when setting the values for the slider.
when i use a timer from ViewDidLoad:
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(PlayerViewController.audioSliderUpdate), userInfo: nil, repeats: true)
which calls my function:
func audioSliderUpdate() {
var currentTime : CMTime = (self.audioPlayerItem?.currentTime())!
var seconds : Float64 = CMTimeGetSeconds(currentTime)
var time : Float = Float(seconds)
self.audioSlider.value = time
}
The audio track starts skipping and the slider is jumping about and it returns this to the console:
[aqme] 255: AQDefaultDevice (173): skipping input stream 0 0 0x0
and when i try set the max value with this code:
let duration : CMTime = (self.audioPlayerItem?.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
let maxTime : Float = Float(seconds)
self.audioSlider.valueMaximum = maxTime
i get this error:
<Error>: Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem.
Jan 20 07:24:23 ParseStarterProject-Swift[2441] <Error>: If you want to see the backtrace, please set CG_NUMERICS_SHOW_BACKTRACE environmental variable.
I've read all over SO and they all have simple solutions like
let floatTime = Float(player.currentTime)
which don't work. as you cant convert CMT directly to float in swift 3????
So my question is how do i set the values for the slider?
this is the entire class to shed light if my explanation was unclear:
import UIKit
import Parse
import AVFoundation
import AVKit
class PlayerViewController: UIViewController, AVAudioPlayerDelegate {
#IBOutlet var audioSlider: MTCircularSlider!
// passed objectID from PackViewController
var selectedAudio: String!
var audioPlayer: AVPlayer?
var audioPlayerItem: AVPlayerItem?
fileprivate var timer: Timer?
fileprivate var direction: Float = 0.01
// query parse to get the file and stream it
func getAudio() {
let query = PFQuery(className: "Part")
query.whereKey("objectId", equalTo: selectedAudio)
query.getFirstObjectInBackground { (object, error) in
if error != nil || object == nil {
print("The getFirstObject request failed.")
} else {
print("There is an object now get the Audio. ")
if let audioFileURL = (object?.object(forKey: "partAudio") as! PFFile).url {
// create an item for use by notification center
self.audioPlayerItem = AVPlayerItem(url: NSURL(string: audioFileURL) as! URL)
self.audioPlayer = AVPlayer(playerItem: self.audioPlayerItem)
self.audioPlayer?.play()
var duration : CMTime = (self.audioPlayerItem?.duration)!
var seconds : Float64 = CMTimeGetSeconds(duration)
var maxTime : Float = Float(seconds)
self.audioSlider.valueMaximum = maxTime
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(PlayerViewController.audioSliderUpdate), userInfo: nil, repeats: true)
}
}
}
}
#IBOutlet var playerButton: UIButton!
func playerButtonTapped() {
// if the player is not playing
if audioPlayer?.rate == 0 {
audioPlayer?.play()
self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
} else {
audioPlayer?.pause()
self.playerButton.setImage(UIImage(named: "pause"), for: UIControlState.normal)
}
}
func finishedPlaying (myNotification: NSNotification) {
self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
let stoppedPlayerItem: AVPlayerItem = myNotification.object as! AVPlayerItem
//create a CMTime for zero seconds so we can go back to the beginning
let seconds : Int64 = 0
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
stoppedPlayerItem.seek(to: seekTime)
}
override func viewDidLoad() {
super.viewDidLoad()
self.playerButton.addTarget(self, action: #selector(PlayerViewController.playerButtonTapped), for: UIControlEvents.touchUpInside)
getAudio()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(audioPlayerItem as Any, selector: #selector(PlayerViewController.finishedPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: audioPlayer)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
// remove the observer when leaving page
NotificationCenter.default.removeObserver(audioPlayer?.currentItem! as Any)
}
#IBAction func audioSliderActioned(_ sender: Any) {
audioPlayer?.pause()
//audioPlayer.currentTime() = TimeInterval(audioSlider.value)
//audioPlayer.prepareToPlay()
audioPlayer?.play()
}
func audioSliderUpdate() {
var currentTime : CMTime = (self.audioPlayerItem?.currentTime())!
var seconds : Float64 = CMTimeGetSeconds(currentTime)
var time : Float = Float(seconds)
self.audioSlider.value = time
}
}

Resources