Add tone to Countdown Timer in XCODE 8 Swift 3 - ios

I am new to coding. I am trying to learn iOS development and I have created a simple countdown timer, that uses a slider to select the amount of time in seconds. I would like to add a tone to the final 9 secs of the countdown. I know how to add audio files, but I have "NO IDEA" how to add a countdown tone to the last 9 seconds of the timer.
#IBAction func startBtnTapped(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.counter), userInfo: nil, repeats: true)
SoundPlayer4.play()
//Hide Start and Slider Buttons
startBtn.isHidden = true
sliderCtrl.isHidden = true
stopBtn.isHidden = false
}
#IBAction func stopBtnTapped(_ sender: Any) {
timer.invalidate()
seconds = 180
sliderCtrl.setValue(180, animated: true)
timerLbl.text = "180"
SoundPlayer3.play()
//Show Start and Slider Buttons
sliderCtrl.isHidden = false
startBtn.isHidden = false
stopBtn.isHidden = true
}
#IBAction func sliderCrtlUsed(_ sender: UISlider) {
seconds = Int(sender.value)
timerLbl.text = String(seconds)
}
func counter(){
seconds -= 1
timerLbl.text = String(seconds)
if (seconds == 0){
timer.invalidate()
//Show Start and Slider Buttons
startBtn.isHidden = false
sliderCtrl.isHidden = false
stopBtn.isHidden = true
//Play Audio
SoundPlayer1.play()
}
}
So can anyone help me add a countdown tone to my project?
Thank you

Put this code in counter function:
if seconds < 10 {
var AudioURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Dog bark", ofType: "mp3")!)
var AudioPlayer = AVAudioPlayer()
do {
AudioPlayer = try AVAudioPlayer(contentsOfURL: AudioURL)
} catch _ as NSError {
fatalError()
}
AudioPlayer.play()
}
Then put the file here:
And that's it!
You can read more on this question here.

Related

Swift 4 - UISlider with AVAudioPlayer

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)

Jumpy UISlider when scrubbing - Using UISlider with AVPlayer

I am using AvPlayer and am trying to set up a slider to allow scrubbing of audio files. Im having a problem with the slider jumping all over the place when its selected. It then goes back to the origin position for a second before going back to the location it was dragged to.
You cant see my cursor on the Gif, but the smooth elongated drags are me moving the knob, then the quick whips are the slider misbehaving.
Ive spent hours googling and combing through Stack Overflow and cant figure out what I'm doing wrong here, a lot of similar questions are quite old and in ObjC.
This is the section of code i think is responsible for the problem, it does handle the event of the slider being moved: Ive tried it without the if statement also and didn't see a different result.
#IBAction func horizontalSliderActioned(_ sender: Any) {
horizontalSlider.isContinuous = true
if self.horizontalSlider.isTouchInside {
audioPlayer?.pause()
let seconds : Int64 = Int64(horizontalSlider.value)
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
audioPlayerItem?.seek(to: seekTime)
audioPlayer?.play()
} else {
let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
self.horizontalSlider.value = Float(seconds)
}
}
I will include my entire class below for reference.
import UIKit
import Parse
import AVFoundation
import AVKit
class PlayerViewController: UIViewController, AVAudioPlayerDelegate {
#IBOutlet var horizontalSlider: UISlider!
var selectedAudio: String!
var audioPlayer: AVPlayer?
var audioPlayerItem: AVPlayerItem?
var timer: Timer?
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. ")
let audioFileURL = (object?.object(forKey: "partAudio") as! PFFile).url
self.audioPlayerItem = AVPlayerItem(url: NSURL(string: audioFileURL!) as! URL)
self.audioPlayer = AVPlayer(playerItem: self.audioPlayerItem)
let playerLayer = AVPlayerLayer(player: self.audioPlayer)
playerLayer.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
self.view.layer.addSublayer(playerLayer)
let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
let maxTime : Float = Float(seconds)
self.horizontalSlider.maximumValue = maxTime
self.audioPlayer?.play()
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PlayerViewController.audioSliderUpdate), userInfo: nil, repeats: true)
}
}
}
#IBOutlet var playerButton: UIButton!
func playerButtonTapped() {
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)
}
}
override func viewDidLoad() {
super.viewDidLoad()
horizontalSlider.minimumValue = 0
horizontalSlider.value = 0
self.playerButton.addTarget(self, action: #selector(PlayerViewController.playerButtonTapped), for: UIControlEvents.touchUpInside)
getAudio()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(PlayerViewController.finishedPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.audioPlayerItem)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
// remove the timer
self.timer?.invalidate()
// remove the observer when leaving page
NotificationCenter.default.removeObserver(audioPlayer?.currentItem! as Any)
}
func finishedPlaying() {
// need option to play next track
self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
let seconds : Int64 = 0
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
audioPlayerItem!.seek(to: seekTime)
}
#IBAction func horizontalSliderActioned(_ sender: Any) {
horizontalSlider.isContinuous = true
if self.horizontalSlider.isTouchInside {
audioPlayer?.pause()
let seconds : Int64 = Int64(horizontalSlider.value)
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
audioPlayerItem?.seek(to: seekTime)
audioPlayer?.play()
} else {
let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
self.horizontalSlider.value = Float(seconds)
}
}
func audioSliderUpdate() {
let currentTime : CMTime = (self.audioPlayerItem?.currentTime())!
let seconds : Float64 = CMTimeGetSeconds(currentTime)
let time : Float = Float(seconds)
self.horizontalSlider.value = time
}
}
Swift 5, Xcode 11
I faced the same issue, it was apparently periodicTimeObserver which was causing to return incorrect time which caused lag or jump in the slider. I solved it by removing periodic time observer when the slider was changing and adding it back when seeking completion handler was called.
#objc func sliderValueChanged(_ playbackSlider: UISlider, event: UIEvent){
let seconds : Float = Float(playbackSlider.value)
let targetTime:CMTime = CMTimeMake(value: Int64(seconds), timescale: 1)
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
// handle drag began
//Remove observer when dragging is in progress
self.removePeriodicTimeObserver()
break
case .moved:
// handle drag moved
break
case .ended:
// handle drag ended
//Add Observer back when seeking got completed
player.seek(to: targetTime, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] (value) in
self?.addTimeObserver()
}
break
default:
break
}
}
}
you need to remove observers and invalidate timers as soon as user selects the thumb on slider and add them back again when dragging is done
to do add targets like this where you load your player:
mySlider.addTarget(self,
action: #selector(PlayerViewController.mySliderBeganTracking(_:)),
forControlEvents:.TouchDown)
mySlider.addTarget(self,
action: #selector(PlayerViewController.mySliderEndedTracking(_:)),
forControlEvents: .TouchUpInside)
mySlider.addTarget(self,
action: #selector(PlayerViewController.mySliderEndedTracking(_:)),
forControlEvents: .TouchUpOutside )
and remove observers and invalidate timers in mySliderBeganTracking then add observers in mySliderEndedTracking
for better control on what happens in your player write 2 functions : addObservers and removeObservers and call them when needed
Make sure to do the following:
isContinuous for the slider is NOT set to false.
Pause the player before seeking.
Seek to the position and use the completion handler to resume playing.
Example code:
#objc func sliderValueChanged(sender: UISlider, event: UIEvent) {
let roundedValue = sender.value.rounded()
guard let touchEvent = event.allTouches?.first else { return }
switch touchEvent.phase {
case .began:
PlayerManager.shared.pause()
case .moved:
print("Slider moved")
case .ended:
PlayerManager.shared.seek(to: roundedValue, playAfterSeeking: true)
default: ()
}
}
And here is the function for seeking:
func seek(to: Float, playAfterSeeking: Bool) {
player?.seek(to: CMTime(value: CMTimeValue(to), timescale: 1), completionHandler: { [weak self] (status) in
if playAfterSeeking {
self?.play()
}
})
}
Try using the time slider value like below:
#IBAction func timeSliderDidChange(_ sender: UISlider) {
AVPlayerManager.sharedInstance.currentTime = Double(sender.value)
}
var currentTime: Double {
get {
return CMTimeGetSeconds(player.currentTime())
}
set {
if self.player.status == .readyToPlay {
let newTime = CMTimeMakeWithSeconds(newValue, 1)
player.seek(to: newTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) { ( _ ) in
self.updatePlayerInfo()
}
}
}
}
and pass the value of slider when user release the slider, also don't update the slider value of current playing while user interaction happening on the slider
This is a temporary solution for me, I observed that the rebound is only once, so I set an int value isSeekInProgress:
When sliderDidFinish, isSeekInProgress = 0
In reply to avplayer time change:
if (self.isSeekInProgress > 1) {
float sliderValue = 1.f / (self.slider.maximumValue - self.slider.minimumValue) * progress;
// if (sliderValue > self.slider.value ) {
self.slider.value = sliderValue;
}else {
self.isSeekInProgress += 1;
}

AVPlayer layer showing blank screen when application running or idle for 15 minutes

I would like to create a custom video player using AVPlayer() and AVPlayerLayer() classes.
My code working fine for application fresh start but goes wrong and showing blank screen when the application running or idle for more than 15 minutes.
When the occurrences this issue all classes of my application having AVPlayerLayer showing blank screen. I don't know why this happens.
AVPlayer() and AVPlayerlayer() instances are initialized as below.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
dispatch_async(dispatch_get_main_queue(), {
let playerItem = AVPlayerItem(URL: self.itemUrl)
self.avPlayer = AVPlayer(playerItem: playerItem)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(VideoPreviewView.restartVideoFromBeginning),
name: AVPlayerItemDidPlayToEndTimeNotification,
object: self.avPlayer.currentItem)
self.avPlayerLayer = AVPlayerLayer(player: self.avPlayer)
self.view.layer.addSublayer(self.avPlayerLayer)
self.avPlayerLayer.frame = self.view.bounds
self.avPlayer.pause()
})
}
}
Play function
func playVideo(sender: AnyObject) {
avPlayer.play()
if (avPlayer.rate != 0 && avPlayer.error == nil) {
print("playing")
}
slider.hidden=false
myTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: #selector(VideoPreviewView.updateSlider), userInfo: nil, repeats: true)
slider.addTarget(self, action: #selector(VideoPreviewView.sliderValueDidChange(_: )), forControlEvents: UIControlEvents.ValueChanged)
slider.minimumValue = 0.0
slider.continuous = true
pauseButton.hidden=false
playButton.hidden=true
closeViewButton.hidden = false
}
Restart video
func restartVideoFromBeginning() {
let seconds: Int64 = 0
let preferredTimeScale: Int32 = 1
let seekTime: CMTime = CMTimeMake(seconds, preferredTimeScale)
avPlayer?.seekToTime(seekTime)
avPlayer?.pause()
pauseButton.hidden=true
playButton.hidden = false
closeViewButton.hidden=false
}
func updateSlider() {
if (avPlayer.rate != 0 && avPlayer.error == nil) {
print("playing")
}
else{
print("Not playing.")
}
currentTime = Float(CMTimeGetSeconds(avPlayer.currentTime()))
duration = avPlayer.currentItem!.asset.duration
totalDuration = Float(CMTimeGetSeconds(duration))
slider.value = currentTime // Setting slider value as current time
slider.maximumValue = totalDuration // Setting maximum value as total duration of the video
}
func sliderValueDidChange(sender: UISlider!) {
timeInSecond=slider.value
newtime = CMTimeMakeWithSeconds(Double(timeInSecond), 1)// Setting new time using slider value
avPlayer.seekToTime(newtime)
}
#IBAction func closeViewAction(sender: AnyObject) {
pauseButton.hidden=true
self.avPlayer.pause()
self.avPlayerLayer.removeFromSuperlayer()
self.dismissViewControllerAnimated(true, completion: nil)
}

Swift: Sound plays on simulator but not on device

I'm building an app for iPhone/iPad, and I'm trying to solve a sound problem:
The app comes with a four minute countdown timer. When the coundown reaches 00:00, it triggers a short sound effect. This works fine on the different simulators, but my phone remains silent. The app is built for 7.1. I'm wondering if it's because of the code or if my phone isn't working (it does have sound issues). Does my code look okay? I hope someone can help with this.
Here it is:
import Foundation
import UIKit
import AVFoundation
class VC11 : UIViewController {
#IBOutlet weak var timerLabel: UILabel!
var timer = NSTimer()
var count = 240
var timerRunning = false
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
func nextPage(sender:UISwipeGestureRecognizer) {
switch sender.direction {
case UISwipeGestureRecognizerDirection.Left:
print("SWIPED LEFT")
self.performSegueWithIdentifier("seg11", sender: nil)
default:
break
}
var leftSwipe = UISwipeGestureRecognizer (target: self, action: Selector("nextPage"))
var rightSwipe = UISwipeGestureRecognizer (target: self, action: Selector("nextPage"))
leftSwipe.direction = .Left
rightSwipe.direction = .Right
view.addGestureRecognizer(leftSwipe)
view.addGestureRecognizer(rightSwipe)
}
}
func updateTime() {
count--
let seconds = count % 60
let minutes = (count / 60) % 60
let hours = count / 3600
let strHours = hours > 9 ? String(hours) : "0" + String(hours)
let strMinutes = minutes > 9 ? String(minutes) : "0" + String(minutes)
let strSeconds = seconds > 9 ? String(seconds) : "0" + String(seconds)
if hours > 0 {
timerLabel.text = "\(strHours):\(strMinutes):\(strSeconds)"
}
else {
timerLabel.text = "\(strMinutes):\(strSeconds)"
}
stopTimer()
}
#IBAction func startTimer(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("updateTime"), userInfo: nil, repeats: true)
sender.setTitle("Running...", forState: .Normal)
}
func stopTimer()
{
if count == 0 {
timer.invalidate()
timerRunning = false
timerLabel.text = "04:00"
playSound()
count = 240
}
}
func playSound() {
var soundPath = NSBundle.mainBundle().pathForResource("Metal_Gong", ofType: "wav")
var soundURL = NSURL.fileURLWithPath(soundPath!)
self.audioPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
self.audioPlayer.play()
}
}
Make sure that your device is not triggered into silent mode.
Also, for short system sounds you can use System Sounds from AudioToolbox (https://developer.apple.com/library/ios/documentation/AudioToolbox/Reference/SystemSoundServicesReference/):
if let soundURL = NSBundle.mainBundle().URLForResource("Metal_Gong", withExtension: "wav") {
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL, &mySound)
AudioServicesPlaySystemSound(mySound);
}
I just found out that my code does work - it's only because of my broken phone that no sound is played. Thank you for your help!

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