Okay I'm trying to do something similar to iTunes not sure if it's still the same. It's when you click a song and it gives a sample of the audio file. This is my code looks.
The music file is like 2-3min long. I got the start time to start at 42sec seconds. However, the song finishes to the end. I'm trying to make the audio file a sample of 30sec. So it should start at 42sec and end at 1min and 12sec.
Would appreciate any help thanks.
Every time your audio sample starts playing, you can create a Timer object which will make your player stop in a given amount of time.
var audioPlayer = AVAudioPlayer()
var timer: Timer?
func prepareMusic() {
....
// Your code to start playing sample
audioPlayer.currentTime = 42
audioPlayer.play()
// Here we are stopping previous timer if there was any, and creating new one for 30 seconds. It will make player stop.
timer?.invalidate()
timer = Timer(fire: Date.init(timeIntervalSinceNow: 30), interval: 0, repeats: false) { (timer) in
if self.audioPlayer.isPlaying {
self.audioPlayer.stop()
}
}
RunLoop.main.add(timer!, forMode: .defaultRunLoopMode)
}
func musicButton(sender: UIButton) {
....
// If sample is stopped by user — stop timer as well
if audioPlayer.isPlaying {
audioPlayer.stop()
timer?.invalidate()
}
}
There is one more edge case I can think of — if you hide/close view controller, you might also want to stop that timer.
override func viewWillDisappear(_ animated: Bool) {
timer?.invalidate()
}
Related
I'm working on a music player in Swift. The user can play a track, pause the track, or move a slider to choose a place to start. These three functions work.
However, if a user plays a track, listens for 1:00, and hits pause: when they click play again, it will restart the track from 0:00 instead of respecting where they paused.
I am using AVAudioPlayer as a media player -- according to the documentation, .play() allows you to specify a start-point using atTime.
So I tried to use atTime and have it start where the slider is -- if a listener listens for 1:00, I want atTime to dynamically pick that up. I try to establish this time value in a var.
class MusicViewController: UIViewController {
// removed imports and viewdidload to condense
#IBAction func playDownload(_ sender: Any) {
// removed file download code to condense
do {
audioPlayer = try AVAudioPlayer(contentsOf: destinationUrl)
guard let player = audioPlayer else { return }
//here's where I try to capture a time value from the slider's progress
let checkTime = TimeInterval(Slider.value)
player.prepareToPlay()
player.play(atTime: checkTime)
} catch let error {
print(error.localizedDescription)
}
// updates slider with progress
Slider.value = 0.0
Slider.maximumValue = Float((audioPlayer?.duration)!)
audioPlayer.play()
timer = Timer.scheduledTimer(timeInterval: 0.0001, target: self, selector: #selector(self.updateSlider), userInfo: nil, repeats: true)
}
#IBAction func pause(_ sender: Any) {
if audioPlayer.isPlaying {
audioPlayer.pause()
} else {
audioPlayer.play()
}
}
#IBOutlet var Slider: UISlider!
#objc func updateSlider() {
Slider.value = Float(audioPlayer.currentTime)
print("Changing works")
}
}
So I am trying to capture progress and have .play respect that -- this code will run, but it does not work. No errors but after hitting play, it will go to the end of the track immediately now. No good.
I am new to Swift - any idea where I went wrong?
Something really odd is happening with my code.
I made a rather simple Timer function that is triggered by button.
The button calls a first method, then the method use another function to do the counting, then it triggers something when the time's up.
And everything works fine.
here's the code.
// This is the declaration of the launch button.
#IBAction func playLater(_ sender: Any) {
if isTimerRunning == false {
runTimer()
}
}
var seconds = 10
var timer = Timer()
var isTimerRunning = false
var resumeTapped = false
//the timer function that sets up the duration and launches the counting.
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
isTimerRunning = true
}
//this part is making sure that you go from 10 to 0 and when it's at 0, something happens. In this very case, it plays a song.
#objc func updateTimer() {
if seconds < 1 {
timer.invalidate()
isTimerRunning = false
playNow((Any).self)
} else {
seconds -= 1
timerLabel.text = timeString(time: TimeInterval(seconds))
timerLabel.text = String(seconds)
}
}
The thing is that I also want to be able to triger that same function from the Apple Watch.
I made a WCSession that is working well, the messages are passing, it's ok.
So I'm using this code to launch the same function when the user pushes a button on the apple Watch. That part of the code is on the same iOS swift file.
#available(iOS 9.0, *)
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
// do something
isTimerRunning = false
playLater(Any.self)
}
As you can see, I'm not even trying to add some code, I just call the same function used with the iOS button.
But this time, it's not working. The first part of the method is responding, I saw that runTimer() is working, but it's not going to updateTimer().
Maybe I'm missing something here, but, what is the difference ? Why if it comes from the push of a button it's working, and if it's called directly from "the inside" nothing happens ?
If you have any educated guesses, or even, clues, I'd be grateful.
Thanks !
I have an NSTimer set up that fires every 0.1 seconds, in the callback I fetch currentTime() and use it to update the label with the duration of the video.
When I am seeking forwards, by setting the rate to 3, this timer keeps up, but when I set the rate to -3, the video keeps up, but the currentTime() still returns the same value from when I started seeking. This occurs until I stop seeking and then currentTime() returns the correct time
How can I fetch the current time the video is at, which will work when seeking backwards?
Edit: Here is the code I use (translated from Xamarin C#):
class VideoPlayer: UIView {
var player: AVPlayer!
var wasPaused: Bool!
func play(url: String) {
// set the URL to the Video Player
let streamingURL: NSURL = NSURL(string: url)!
player = AVPlayer(URL: streamingURL)
let playerLayer = AVPlayerLayer(layer: player)
layer.insertSublayer(playerLayer, atIndex: 0)
// Reset the state
player.seekToTime(CMTime(seconds: 0, preferredTimescale: 600))
// Start a timer to move the scrub label
NSTimer(timeInterval: 0.1, target: self, selector: #selector(playbackTimeUpdated), userInfo: nil, repeats: true)
}
func playbackTimeUpdated() {
// This one is not correct when seeking backwards
let time = player.currentTime().seconds;
// Use the time to adjust a UIProgressView
}
// Gets called when the reverse button is released
func reverseTouchUp() {
player.rate = 1
}
// Gets called when the reverse button is pressed
func reverseTouchDown()
{
player.rate = -3;
}
}
Try CMTimeGetSeconds(player.currentTime()) instead of player.currentTime().seconds. It works for me.
Also check that you timer is actually running (add NSLog calls to it for example), maybe you just blocking its thread.
I am trying to play a clip of sound that can be paused, resumed or stopped. My pause button works, and the action function for this button is shown below. However I cannot get my resume function to work. When I press the resume button no audio is played. I have read around online and the only tips I can find are to use the prepareToPlay() function and to set shortStartTimeDelay to a value greater than 0.0. I have tried both of these to no avail.
timeAtPause is a global variable of type NSTimeInterval
The action function for the pause button is as follows:
#IBAction func pauseAllAudio(sender: UIButton) {
timeAtPause = audioPlayer.currentTime
audioPlayer.pause()
}
The action function for the resume button is as follows:
#IBAction func resumeAllAudio(sender: UIButton) {
let shortStartDelay = 0.01
audioPlayer.prepareToPlay()
audioPlayer.playAtTime(timeAtPause + shortStartDelay)
}
Any tips on how to resume the audio would be really appreciated.
Thank you for your time.
You should not use playAtTime() to resume the AVAudioPlayer, as documentation states:
Plays a sound asynchronously, starting at a specified point in the
audio output device’s timeline.
and
Use this method to precisely synchronize the playback of two or more
AVAudioPlayer objects.
And, even if you use it, it should be used in conjunction with deviceCurrentTime plus the time in seconds to have the delay. In one word, it's not meant to be used to resume the paused player. Instead, just use play() to resume the playback.
You probably did what I did. Upon calling my play() function, I created a new player every time:
// The Wrong Way
#IBAction func playAction(sender: AnyObject) {
// do and catch omitted for brevity
player = try AVAudioPlayer(contentsOf: goodURL)
player.play()
}
However this should be done at some other initialization time, such as when you have the user choose the file to play.
Or, if you're hard-coding the file to play, you could initialize at an earlier time, such as viewDidLoad().
Then, once your AVAudioPlayer is initialized, you can call .play() and pause() on it and they will work correctly. And .play() will resume after a pause.
To simply pause and restart at the same point in the audio file, the following works:
#IBAction func playAction(sender: AnyObject) {
player.play()
}
#IBAction func pauseAction(sender: AnyObject) {
player.pause()
}
I found these to be helpful. If you are doing this in Swift, I did this a little differently. I created an AudioPlayer class after importing AVFoundation and used these two functions to pause and resume the music. In the SwiftUI call, I used an #State boolean that toggled between the button states. Here is the code that I used:
import AVFoundation
class MusicPlayer {
static let shared = MusicPlayer()
var audioPlayer: AVAudioPlayer?
// Pause music
func pauseBackgroundMusic() {
guard let audioPlayer = audioPlayer else { return }
audioPlayer.pause()
}
// Resume music
func resumeBackgroundMusic() {
guard let audioPlayer = audioPlayer else { return }
audioPlayer.play()
}
}
And in the View:
Define an #State variable:
#State var pauseMode = false
And call the music player as follows in the Button action code:
Button(action: {
if pauseMode {
MusicPlayer.shared.resumeBackgroundMusic()
self.pauseMode.toggle()
} else {
MusicPlayer.shared.pauseBackgroundMusic()
self.pauseMode.toggle()
}
})
{
Image(systemName: "playpause")
}
FYI - I posted this in SwiftUI since newer programmers (and Apple) seem to be moving in this direction. I figured this might help someone (like me!) who came to this question seeking a Swift / SwiftUI answer.
I have a pulsing rectangle animated with animateWithDuration and setAnimationRepeatCount().
I'm trying to add a sound effect in the animations block witch is "clicking" synchronously. But the sound effect is only playing once. I can't find any hint on this anywhere.
UIView.animateWithDuration(0.5,
delay: 0,
options: UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.Repeat,
animations: {
UIView.setAnimationRepeatCount(4)
self.audioPlayer.play()
self.img_MotronomLight.alpha = 0.1
}, completion: nil)
The sound effect should play four times but doesn't.
Audio Implementation:
//global:
var metronomClickSample = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("metronomeClick", ofType: "mp3")!)
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
audioPlayer = AVAudioPlayer(contentsOfURL: metronomClickSample, error: nil)
audioPlayer.prepareToPlay()
....
}
#IBAction func act_toggleStartTapped(sender: AnyObject) {
....
UIView.animateWithDuration(....
animations: {
UIView.setAnimationRepeatCount(4)
self.audioPlayer.play()
self.img_MotronomLight.alpha = 0.1
}, completion: nil)
}
Providing the UIViewAnimationOptions.Repeat option does NOT cause the animation block to be called repeatedly. The animation is repeated on a CoreAnimation level. If you place a breakpoint in the animation block, you'll notice that it is only executed once.
If you want the animation to execute in a loop alongside the sound, create a repeating NSTimer and call the animation / sound from there. Keep in mind that the timer will retain the target, so don't forget to invalidate the timer to prevent retain cycle.
EDIT: Added implementation below
First, we'll need to create the timer, assuming we have an instance variable called timer. This can be done in viewDidLoad: or init method of a view. Once initialized, we schedule for execution with the run loop, otherwise it will not fire repeatedly.
self.timesFired = 0
self.timer = NSTimer(timeInterval: 0.5, target: self, selector:"timerDidFire:", userInfo: nil, repeats: true)
if let timer = self.timer {
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
}
The following is the method fired by the timer every interval (in this case 0.5 seconds). Here, you can run your animations and audio playback. Note that UIViewAnimationOptions.Repeat option has been removed since the timer is now responsible for handling the repeating animation and audio. If you only the timer to fire a specific number of times, you can add an instance variable to keep track of times fired and invalidate the timer if the count is above the threshold.
func timerDidFire(timer: NSTimer) {
/*
* If limited number of repeats is required
* and assuming there's an instance variable
* called `timesFired`
*/
if self.timesFired > 5 {
if let timer = self.timer {
timer.invalidate()
}
self.timer = nil
return
}
++self.timesFired
self.audioPlayer.play()
var options = UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.CurveEaseOut;
UIView.animateWithDuration(0.5, delay: 0, options: options, animations: {
self.img_MotronomLight.alpha = 0.1
}, completion: nil)
}
Without seeing the implementation of the audio player some things I can think of include:
The audio file is too long and perhaps has silence on the end of it so it is not playing the first part of the file which has the sound in it
the audio file needs to be set to the beginning of the file every time (it could just be playing the end of the file the other 3 times leading to no audio output)
The animation is happening to quickly and the audio doesn't have time to buffer
Hopefully these suggestions help you narrow down the problem but without seeing the implementation of the player, it is really hard to say what the actual problem is.