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
}
}
Related
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
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 :) )
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"
}
}
The high score in my game is time based and I am having trouble setting comparing it with the current score. This is my code so far which doesn't work:
class GameScene: SKScene, SKPhysicsContactDelegate {
// initialise value for current time
var currentTime = NSDate()
var bestTime = NSDate()
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
// set the current time to 0 seconds
var date0 = NSDate();
let timeInterval = floor(date0 .timeIntervalSinceReferenceDate / 60.0) * 60.0
date0 = NSDate(timeIntervalSinceReferenceDate: timeInterval)
currentTime = date0
// call to start timer
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "printDuration:", userInfo: NSDate(), repeats: true)
Then
func printDuration(timer: NSTimer) {
if self.view?.paused == false {
guard let userInfo = timer.userInfo else {
return
}
guard let startDate = userInfo as? NSDate else {
return
}
let duration = NSDate().timeIntervalSinceDate(startDate)
currentTime = NSDate(timeIntervalSinceReferenceDate: duration)
currentTimeValueLabel.text = "\(NSString(format:"%3.2f", duration))"
}
}
I want to be able to do something like below where I am able to compare the time in both variables and set the higher one accordingly:
if (currentTime > highScore) {
highScore = currentTime
highScoreLabel.text = "\(NSString(format:"%3.2f", highScore))"
}
You can set startDate = NSDate() in didMoveToView() at the beginning and then compare the two to see if duration is higher by typing
if duration > highScore {
highScore = duration
}
I need to develop an iOS App in swift which detects a blow in the microphone from a user. This has to be a Challenge-Game where two players have to blow into the iPhone mic one after the other. The decibel values should be measured and converted in meter or kilometer so I can determine a winner. The player which "blows further" (player1: 50km, player2: 70km) wins.
Is this a possible implementation?
I have this code in swift and I don't know how to proceed:
import Foundation
import UIKit
import AVFoundation
import CoreAudio
class ViewController: UIViewController {
// #IBOutlet weak var mainImage: UIImageView!
var recorder: AVAudioRecorder!
var levelTimer = NSTimer()
var lowPassResults: Double = 0.0
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL.fileURLWithPath("dev/null")
//numbers are automatically wrapped into NSNumber objects, so I simplified that to [NSString : NSNumber]
var settings : [NSString : NSNumber] = [AVSampleRateKey: 44100.0, AVFormatIDKey: kAudioFormatAppleLossless, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.Max.rawValue]
var error: NSError?
// mainImage?.image = UIImage(named: "flyForReal.png");
recorder = AVAudioRecorder(URL:url, settings:settings, error:&error)
if((recorder) != nil){
recorder.prepareToRecord()
recorder.meteringEnabled = true
recorder.record()
levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.05, target: self, selector: Selector("levelTimerCallback"), userInfo: nil, repeats: true)
}
else{
NSLog("%#", "Error");
}
}
func levelTimerCallback(timer:NSTimer) {
recorder.updateMeters()
let ALPHA: Double = 0.05
var peakPowerForChannel = pow(Double(10), (0.05 * Double(recorder.peakPowerForChannel(0))))
lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;
if(lowPassResults > 0.95){
NSLog("#Mic blow detected");
}
NSLog("#Average input: %f Peak input: %f Low pass results: %f", recorder.averagePowerForChannel(0), recorder.peakPowerForChannel(0), lowPassResults);
}
}
Thanks ahead!
I converted Andrew's answer to Swift 4:
import Foundation
import UIKit
import AVFoundation
import CoreAudio
class ViewController: UIViewController {
var recorder: AVAudioRecorder!
var levelTimer = Timer()
let LEVEL_THRESHOLD: Float = -10.0
override func viewDidLoad() {
super.viewDidLoad()
let documents = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0])
let url = documents.appendingPathComponent("record.caf")
let recordSettings: [String: Any] = [
AVFormatIDKey: kAudioFormatAppleIMA4,
AVSampleRateKey: 44100.0,
AVNumberOfChannelsKey: 2,
AVEncoderBitRateKey: 12800,
AVLinearPCMBitDepthKey: 16,
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue
]
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioSession.setActive(true)
try recorder = AVAudioRecorder(url:url, settings: recordSettings)
} catch {
return
}
recorder.prepareToRecord()
recorder.isMeteringEnabled = true
recorder.record()
levelTimer = Timer.scheduledTimer(timeInterval: 0.02, target: self, selector: #selector(levelTimerCallback), userInfo: nil, repeats: true)
}
#objc func levelTimerCallback() {
recorder.updateMeters()
let level = recorder.averagePower(forChannel: 0)
let isLoud = level > LEVEL_THRESHOLD
// do whatever you want with isLoud
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Close. You have a couple of issues. Your selector call crashes the app because you're not passing an argument and levelTimerCallback() expects one.
averagePowerPerChannel seems to give me a more real-time metering so I used that instead of peakPowerPerChannel
Also, you need to set up an audio session. I wasn't really sure what all that math was about, so I just got rid of it here. I've pasted the entire view controller below for basic mic detection.
import Foundation
import UIKit
import AVFoundation
import CoreAudio
class ViewController: UIViewController {
var recorder: AVAudioRecorder!
var levelTimer = NSTimer()
var lowPassResults: Double = 0.0
override func viewDidLoad() {
super.viewDidLoad()
//make an AudioSession, set it to PlayAndRecord and make it active
var audioSession:AVAudioSession = AVAudioSession.sharedInstance()
audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
audioSession.setActive(true, error: nil)
//set up the URL for the audio file
var documents: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
var str = documents.stringByAppendingPathComponent("recordTest.caf")
var url = NSURL.fileURLWithPath(str as String)
// make a dictionary to hold the recording settings so we can instantiate our AVAudioRecorder
var recordSettings: [NSObject : AnyObject] = [AVFormatIDKey:kAudioFormatAppleIMA4,
AVSampleRateKey:44100.0,
AVNumberOfChannelsKey:2,AVEncoderBitRateKey:12800,
AVLinearPCMBitDepthKey:16,
AVEncoderAudioQualityKey:AVAudioQuality.Max.rawValue
]
//declare a variable to store the returned error if we have a problem instantiating our AVAudioRecorder
var error: NSError?
//Instantiate an AVAudioRecorder
recorder = AVAudioRecorder(URL:url, settings: recordSettings, error: &error)
//If there's an error, print that shit - otherwise, run prepareToRecord and meteringEnabled to turn on metering (must be run in that order)
if let e = error {
println(e.localizedDescription)
} else {
recorder.prepareToRecord()
recorder.meteringEnabled = true
//start recording
recorder.record()
//instantiate a timer to be called with whatever frequency we want to grab metering values
self.levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: Selector("levelTimerCallback"), userInfo: nil, repeats: true)
}
}
//This selector/function is called every time our timer (levelTime) fires
func levelTimerCallback() {
//we have to update meters before we can get the metering values
recorder.updateMeters()
//print to the console if we are beyond a threshold value. Here I've used -7
if recorder.averagePowerForChannel(0) > -7 {
print("Dis be da level I'm hearin' you in dat mic ")
println(recorder.averagePowerForChannel(0))
println("Do the thing I want, mofo")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}