My background music gets played twice (at the same time) - ios

When i launch my app, a background music starts to play. But when i press start and play the game (in another viewcontroller) and then go back to menu (the first view controller) the song starts again but while the same song is still playing (result = hearing it twice at the same time).Very annoying. This is my viewDidLoad function (probably where my problem is). Can someone help me (by explaining or giving code) playing the music only the first time that the view loads ?
override func viewDidLoad() {
super.viewDidLoad()
// get path of audio file
let myFilePathString = NSBundle.mainBundle().pathForResource("Background Music Loop (Free to Use)", ofType: "mp3")
if let myFilePathString = myFilePathString {
let myFilePathURL = NSURL(fileURLWithPath: myFilePathString)
do { try myAudioPlayer = AVAudioPlayer(contentsOfURL: myFilePathURL)
myAudioPlayer.play()
myAudioPlayer.numberOfLoops = -1
}catch{
print("error")
}
}
}

myAudioPlayer.play will simply be called again, since the VC runs the viewDidLoad() method again.
My idea:
declare a variable
var x = false
next step - make a condition on your audio player to play.
if x == false { // your code here }
when triggering the Segue from the other VC try to use the prepareForSegue method.
So on 2nd ViewController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "yourIdentifier" {
let destinationController = segue.destinationViewController as! // Controller Name here
destinationController.x = true
}
}
This will set the x to true and the music doesn't run again.

It all depends on the functionality you want.
If you only want the background music to play when the menu view controller is on screen you could make the file name and player variables properties on the view controller and then in viewWillAppear do
Player.play
And then when a user navigates away from the page stop it playing in the viewDidDissappear function.
If you want the music to always play in the background then it might be worth creating your own class for it and providing a sharedInstance on the app delegate so that you can manage a single instance of the player throughout the application.

Related

How to implement AVPlayer in SwiftUI in a correct way?

I am having a couple of problems with player. I know there are many player examples but almost all of them are designed to play just one url and does not have the functionalities that I want.
First of all this is my existing code for the player:
struct PlayerViewController: UIViewControllerRepresentable {
var video: Video
private let player = AVPlayer()
func makeUIViewController(context: Context) -> AVPlayerViewController {
let playerVC = AVPlayerViewController()
playerVC.player = self.player
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
} catch(let error) {
print(error.localizedDescription)
}
playerVC.player?.play()
return playerVC
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
guard let url = URL(string: video.url) else { return }
resetPlayer(uiViewController)
let item = AVPlayerItem(url: url)
uiViewController.player?.replaceCurrentItem(with: item)
uiViewController.player?.play()
if((uiViewController.view.window) == nil)
{
uiViewController.player?.pause()
}
print("Player::UpdatePlayer: Player updated")
}
func resetPlayer(_ vc: AVPlayerViewController) {
vc.player?.pause()
vc.player?.replaceCurrentItem(with: nil)
print("Player::ResetPlayer: Player reseted.!")
}
}
struct Video: Identifiable, Equatable {
var id: Int
var url: String
}
And I use it as follows. This is just example I replace the str with button action in the actual project.
struct TestTestView: View {
#State var str: String = ""
var body: some View {
PlayerViewController(video: Video(id: 2, url: (str == "") ? chan.getDVR()[0].getHlsStreamURL() : self.str ))
}
}
Now the code above works up to a point. It plays, you can update the url with button click and it plays the new url. But when it enters the fullscreen the video stops, because it updates the whole player although nothing changed. The following lines makes it pause when entering the fullscreen mode:
if((uiViewController.view.window) == nil)
{
uiViewController.player?.pause()
}
However, If I remove those lines when I navigate to another screen it continues to play you keep hearing the sound. I tried to detect the fullscreen to update my code but I was not able to do it. I tried to detect I the player is actually in the screen but it did not work as well. So what is the correct way to implement it. Basically I want to do:
1 - Be able to update the player and play different streams
2 - Starts auto play
3 - Smoothly continue to play when switching in and out of full screen mode
4 - Stop when navigated to another screen.
By the way if it is not in full screen mode I cannot click on play button or progress bar but somehow I can click on the mute or fullscreen button. I need help!!

Swift and AVFoundation: Playing audio while animating a label

I have an app that I'm adding sounds to. It has a keypad and when the user taps a button, the number animates to show the user that their press went through.
However, since both are happening on the main thread, adding the following code below, the play() function causes a slight delay in the animation. If the user waits ~2 seconds and taps a keypad number again, they see the delay again. So as long as they're hitting keypad numbers under 2s, they don't see another lag.
I tried wrapping the code below in a DispatchQueue.main.async {} block with no luck.
if let sound = CashSound(rawValue: "buttonPressMelody\(count)"),
let url = Bundle.main.url(forResource: sound.rawValue, withExtension: sound.fileType) {
self.audioPlayer = try? AVAudioPlayer(contentsOf: url)
self.audioPlayer?.prepareToPlay()
self.audioPlayer?.play()
}
How can I play this audio and have the animation run without them interfering and with the audio coinciding with the press?
Thanks
I experienced a rather similar problem in my SwiftUI app, and in my case the solution was in proper resource loading / AVAudioPlayer initialization and preparing.
I use the following function to load audio from disk (inspired by ImageStore class from Apple's Landmarks SwiftUI Tutorial)
final class AudioStore {
typealias Resources = [String:AVAudioPlayer]
fileprivate var audios: Resources = [:]
static var shared = AudioStore()
func audio(with name: String) -> AVAudioPlayer {
let index = guaranteeAudio(for: name)
return audios.values[index]
}
static func loadAudio(with name: String) -> AVAudioPlayer {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else { fatalError("Couldn't find audio \(name).mp3 in main bundle.") }
do { return try AVAudioPlayer(contentsOf: url) }
catch { fatalError("Couldn't load audio \(name).mp3 from main bundle.") }
}
fileprivate func guaranteeAudio(for name: String) -> Resources.Index {
if let index = audios.index(forKey: name) { return index }
audios[name] = AudioStore.loadAudio(with: name)
return audios.index(forKey: name)!
}
}
In the view's init I initialize the player's instance by calling audio(with:) with proper resource name.
In onAppear() I call prepareToPlay() on view's already initialized player with proper optional unwrapping, and finally
I play audio when the gesture fires.
Also, in my case I needed to delay the actual playback by some 0.3 seconds, and for that I despatched it to the global queue. I should stress that the animation with the audio playback was smooth even without dispatching it to the background queue, so I concluded the key was in proper initialization and preparation. To delay the playback, however, you can only utilize the background thread, otherwise you will get the lag.
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.3) {
///your audio.play() analog here
}
Hope that will help some soul out there.

Music playing over the song when i return to the original VC

I am using xcode 9 and swift 4 for my app. In my app i have music playing in the viewDidLoad. When i exit the view controller to go to another View, it continues to play like it should. How ever, when i return to that view controller the song starts to play again. This song is overlapping the song that first loaded. Do you guys have any ideas on how to stop this from happening?
do
{
let audioPath = Bundle.main.path(forResource: "APP4", ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
}
catch
{
//catch error
}
let session = AVAudioSession.sharedInstance()
do
{
try session.setCategory(AVAudioSessionCategoryPlayback)
}
catch
{
}
player.numberOfLoops = -1
player.play()
It starts playing again, because your viewDidLoad is called again, which asks it to play it again. A simplest fix would be to keep a static bool variable to keep track if you have already made this call.
static var isMusicPlaying: Bool = false
In your viewDidLoad, you can put code before the code that calls the play.
guard !isMusicPlaying else {
return
}
isMusicPlaying = true

Play sound repeatedly when tapping a button

I am trying to add sound to my app. I would like it for when I tap a button, it plays a quick sound. However, when the button is tapped quickly and repeatedly, the sound does not work as well (It only plays 1 or 2 times when I tap the button 5 or 6 times). Here is my code in the button
player.play()
I have this outside
var player = AVAudioPlayer()
let audioPath = NSBundle.mainBundle().pathForResource("illuminati", ofType: "wav")
Viewdidload:
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath!))
} catch {}
How can I play the sound repeatedly better? Thanks.
The problem is that you are calling the play multiple time before the previous call finished. You need to keep track of how many times the user click the button and the play song one by one.
This is what you can do:
Use an integer in you class to keep track of number of times that the button is clicked
var numClicks = 0
var buttonClickTime:NSDate? = nil // The last time when the button is clicked
#IBAction func yourbuttonclickfunction() {
numClicks++;
buttonClickTime = NSDate()
player.play()
}
Register the delegate of AVAudioPlayerDelegate
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath!))
// Add this
player.delegate = self
} catch {}
In the delegate function, play the song again when the previous one reach the end:
optional func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool)
{
if --numClicks > 0
{
let now = NSDate()
let duration = now.timeIntervalSinceDate(buttonClickTime!)
// If the button click was less than 0.5 seconds before
if duration < 0.5
{
// Play the song again
player.play();
}
}
}
The reason your sound only plays a few time is because the song plays until finished or until you stop it, even if you press the button multiple times in succession.
You could just stop the music manually, so before you press a button that plays a sound you say
player.stop()
and than
player.play()
Is that helping?

AVAudioPlayer playing overtime viewcont. loads [Swift]

In my mainmenuviewcontroller in viewDidLoad, I tell it to play a background song. Problem is, when a user goes to another view and then goes back to the main menu, it starts playing the song again, and the two copies overlap. I tried to put this in the viewDidLoad but it didn't work
if themePlayer.playing == false {
themePlayer.prepareToPlay()
themePlayer.play()
}
It just kind of ignores the if condition and plays anyways. How can I fix this?
-viewDidLoad will only run when the view is initially loaded. Since your player is still playing the song when you navigate away from the mainmenuviewcontroller this indicates that the view controller is never being deallocated. Try putting the if condition in the -viewDidAppear or -viewWillAppear method.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
if themePlayer == nil {
// Either it's the first time this view is loaded, or themePlayer was deallocated
let soundURL = NSBundle.mainBundle().URLForResource("pres song", withExtension: "mp3")
themePlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
themePlayer.numberOfLoops = -1
themePlayer.volume = 0.1
themePlayer.prepareToPlay()
themePlayer.play()
}
if themePlayer.playing == false {
themePlayer.prepareToPlay()
themePlayer.play()
}
}
If in doubt in the future on if something is being called, use breakpoints and step through the code.

Resources