Where to Place Code for Audio Playback in a Swiftui App

Where to place code for audio playback in a SwiftUI app

Correct, the View shouldn't take care of data. And SwiftUI enforces this logic.

You could create a Player class which takes care of playing the audio. And add it as an EnvironmentObject, so you can control it with your button in the View. You can then do sweet things like binding your play button to show different images depending on whether the player is... playing :)

Update
Code has been updated for Xcode 11 beta 4 (willChange instead of didChange)

struct PlayerView : View {
@EnvironmentObject private var player: Player

var body: some View {
Button(action: {
self.player.pauseOrPlay()
}) {
Image(systemName: player.isPlaying ? "pause.circle.fill" : "play.circle.fill").font(.title).frame(minWidth: 44, minHeight: 44)
}
}
}

class Player: BindableObject {
let willChange = PassthroughSubject<Player, Never>()

var isPlaying: Bool = false {
willSet {
willChange.send(self)
}
}

func pauseOrPlay() {
// Code that toggles the audio on and off
}
}

Audio not playing in SwiftUI

I'm pretty new myself, but I'll do my best. What happens if you move the declaration of AVAudioPlayer outside the function? For example:

import Combine

class Player: ObservableObject {

var sound: AVAudioPlayer!

let willChange = PassthroughSubject<Player, Never>()

var isPlaying: Bool = false {
willSet {
willChange.send(self)
}
}

func playSound() {

if let path = Bundle.main.path(forResource: "TryAgainWav", ofType: "wav") {
do {
sound = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
print("Playing sound")
sound.play()
} catch {
print( "Could not find file")
}
}
}
}

Try this out and let me know if it works or not! Thanks.

How to play audio from an http data stream in swiftUI?

Use https instead of the http you are using, because:

"iOS 9.0 and macOS 10.11 and later use App Transport Security (ATS)
for all HTTP connections made with URLSession. ATS requires that HTTP connections use HTTPS (RFC 2818)."

however;

"You can circumvent or augment these protections by adding the
NSAppTransportSecurity key to your app’s Information Property List
file and providing an ATS configuration dictionary as the value."

That is adding exceptions in the Info.plist file.

see "https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity"

How to play a sound using Swift?

Most preferably you might want to use AVFoundation.
It provides all the essentials for working with audiovisual media.

Update: Compatible with Swift 2, Swift 3 and Swift 4 as suggested by some of you in the comments.


Swift 2.3

import AVFoundation

var player: AVAudioPlayer?

func playSound() {
let url = NSBundle.mainBundle().URLForResource("soundName", withExtension: "mp3")!

do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }

player.prepareToPlay()
player.play()

} catch let error as NSError {
print(error.description)
}
}

Swift 3

import AVFoundation

var player: AVAudioPlayer?

func playSound() {
guard let url = Bundle.main.url(forResource: "soundName", withExtension: "mp3") else { return }

do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)

let player = try AVAudioPlayer(contentsOf: url)

player.play()

} catch let error {
print(error.localizedDescription)
}
}

Swift 4 (iOS 13 compatible)

import AVFoundation

var player: AVAudioPlayer?

func playSound() {
guard let url = Bundle.main.url(forResource: "soundName", withExtension: "mp3") else { return }

do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)

/* The following line is required for the player to work on iOS 11. Change the file type accordingly*/
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)

/* iOS 10 and earlier require the following line:
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3) */

guard let player = player else { return }

player.play()

} catch let error {
print(error.localizedDescription)
}
}

Make sure to change the name of your tune as well as the extension.
The file needs to be properly imported (Project Build Phases > Copy Bundle Resources). You might want to place it in assets.xcassets for
greater convenience.

For short sound files you might want to go for non-compressed audio formats such as .wav since they have the best quality and a low cpu impact. The higher disk-space consumption should not be a big deal for short sound files. The longer the files are, you might want to go for a compressed format such as .mp3 etc. pp. Check the compatible audio formats of CoreAudio.


Fun-fact: There are neat little libraries which make playing sounds even easier. :)

For example: SwiftySound

playing a url link instead of a .mp3 file in swiftui

You can use AVPlayer to stream resources from a URL like this. I'd also move the streaming/async logic into an ObservableObject:


class SoundManager : ObservableObject {
var audioPlayer: AVPlayer?

func playSound(sound: String){
if let url = URL(string: sound) {
self.audioPlayer = AVPlayer(url: url)
}
}
}

struct ContentView: View {
@State var song1 = false
@StateObject private var soundManager = SoundManager()

var body: some View {
Image(systemName: song1 ? "pause.circle.fill": "play.circle.fill")
.font(.system(size: 25))
.padding(.trailing)
.onTapGesture {
soundManager.playSound(sound: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3")
song1.toggle()

if song1{
soundManager.audioPlayer?.play()
} else {
soundManager.audioPlayer?.pause()
}
}
}
}

How to control AVPlayer playback in SwiftUI

The issue here is you are initializing your Viewmodel twice. So you have 2 different sources of truth. So there are 2 different AVAudioPlayer.

Solution: Create one single instance in the top View and pass this on to the views that need this.

As you decided to omit how SettingsView correlate with the other Views I can only give a more general solution.

Let´s asume the SettingsView is used in AppName:

@StateObject private var player = MusicPlayer()

WindowGroup {
....(ContentView stuff)
SettingsView()
// pass the observableObject on to the SettingsView and its children
.environmentObject(player)
}

Then in SettingsView:

struct SettingsView: View {
// get the observableObject from the environment
@EnvironmentObject private var player: MusicPlayer

var body: some View {
Toggle("Music", isOn: $player.isPlaying)
.onChange(of: player.isPlaying, perform: { _ in
AppDefaults.shared.setMusic(player.isPlaying)
if player.isPlaying {
player.music?.stop()
} else {
player.music?.play()
}
})
}
}

How to play audio using AVAudioPlayer in SwiftUI project

Is the audiofile there? Please select the project, go to Build Phases tab and under 'Copy Bundle Resources' you must see the audio file. If it is there then the problem is this.

I tried your code, it played the sound and then crashed. i changed it like this to make it work

 @State var audioPlayer:AVAudioPlayer?

@State var isPlaying : Bool = false

var body: some View {

Button(action: {

if let path = Bundle.main.path(forResource: "a", ofType: ".mp3") {

self.audioPlayer = AVAudioPlayer()

self.isPlaying.toggle()

let url = URL(fileURLWithPath: path)

do {
self.audioPlayer = try AVAudioPlayer(contentsOf: url)
self.audioPlayer?.prepareToPlay()
self.audioPlayer?.play()
}catch {
print("Error")
}
}

}, label: {

----

Have you considered separating your Audio model from your UI? It would make your code much clearer if you put it into separate Swift file

import AVFoundation

class Sounds {

static var audioPlayer:AVAudioPlayer?

static func playSounds(soundfile: String) {

if let path = Bundle.main.path(forResource: soundfile, ofType: nil){

do{

audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
audioPlayer?.prepareToPlay()
audioPlayer?.play()

}catch {
print("Error")
}
}
}
}

And just one line to use it in UI

var body: some View {
Button(action: {
self.isPlaying.toggle()
Sounds.playSounds(soundfile: "0.wav")

}, label: {

In Swift, How can I make music keep playing while user is using my app? (swiftUI)

Put the below code before self.bombSoundEffect = try AVAudioPlayer(contentsOf: url)

_ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: .mixWithOthers)

For reusability I found that creating a class is quite helpful in order to not repeat code in other views.

class Sounds {
static var audioPlayer: AVAudioPlayer!

static func play(sound: String, type: String) {
if let path = Bundle.main.path(forResource: sound, ofType: type) {
do {
//Doesn't stop background music
_ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: .mixWithOthers)
//Load & play sound
audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
audioPlayer?.play()
} catch {
print("Error playing sound")
}
}
}
}

Then just use it in your view via a Button like so

Button("Play Sound") {
Sounds.play(sound: "bombSoundEffect", type: "mp3") //Local file example
}


Related Topics



Leave a reply



Submit