Zorgan
Zorgan

Reputation: 9143

Audio file stops playing after a couple seconds

I have an audio file (its length is > 60 seconds) that is played in the onAppear() method of 2 of my views (shown below).

When I play the audio file in GamesList.swift, it plays the whole length. But if I play it in ActiveGame.swift, it cuts off after a couple seconds. I don't know what the difference is between these views, but it does not work for the latter.

GamesList.swift (successfully plays whole file)

struct GamesList: View {
    @State var showCreateGameSheet = false
    let diameter: CGFloat = 25.0
    @State var games: [Game] = []
    @EnvironmentObject var gameManager: GameManager
    @State var stayPressed = false
    @ObservedObject var soundManager = SoundManager()
    
    var body: some View {
        NavigationView {
            VStack {
                // Create game
                HStack {
                    Spacer()
                    Button(action: { showCreateGameSheet.toggle() }){
                        Text("+ Create game")
                            .font(.custom("Seravek-Medium", size: 20))
                            .foregroundColor(Color.white)
                    }.buttonStyle(GTButton(color: Color("primary"), stayPressed: stayPressed))
                }
                .frame(
                    maxWidth: .infinity,
                    maxHeight: 80,
                    alignment: .top
                )
                ScrollView(.vertical, showsIndicators: true) {
                    ForEach(self.games.sorted(by: { $0.players.count > $1.players.count }), id: \.id){ game in
                        AvailableGame(game: game)
                            .environmentObject(gameManager)
                        Divider()
                    }
                } // Scrollview
            } // VStack
            .padding(25)
//            .navigationBarHidden(true)
            .navigationTitle("Games").foregroundColor(Color("primary"))
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    HStack {
                        Button(action: {
                            self.games = []
                            Database().fetchGames(){ game in
                                if let game = game {
                                    self.games.append(contentsOf: game)
                                }
                            }
                        }){
                            Image(systemName: "arrow.clockwise")
                                .resizable()
                                .aspectRatio(contentMode: .fill)
                                .frame(width: diameter, height: diameter)
                                .foregroundColor(.gray)
                                .padding(.horizontal, 30)
                        }
                        NavigationLink(destination: Settings()){
                            Image("settings")
                                .resizable()
                                .aspectRatio(contentMode: .fill)
                                .frame(width: diameter, height: diameter)
                                .foregroundColor(.gray)
                        }
                    }.padding(.top, 10)
                }
            }
        } // NavigationView
        .sheet(isPresented: $showCreateGameSheet, onDismiss: {  }) {
            CreateGame()
                .environmentObject(self.gameManager)
        }
        .onAppear {
            self.soundManager.playSound(name: "duringGame.aiff", loop: false)
            Database().fetchGames(){ game in
                if let game = game {
                    self.games.append(contentsOf: game)
                }
            }
        }
        .accentColor(Color("primary"))
    }
} 

ActiveGame.swift (cuts out after a couple of seconds)

struct ActiveGame: View {
    @EnvironmentObject var gameManager: GameManager
    let diameter: CGFloat = 50.0
    @State var image = Image(systemName: "rectangle")
    @ObservedObject var soundManager = SoundManager()
    
    var body: some View {
        ZStack {
            PlayerBlock()
                .padding(.horizontal, 10)
                .position(x: (UIScreen.main.bounds.size.width / 2), y: 30)
            VStack(spacing: 15) {
                ZStack { // Question and answers block
                    if self.gameManager.game?.question == nil {
                        ProgressView()
                            .progressViewStyle(CircularProgressViewStyle())
    //                        .scaleEffect(2.0, anchor: .center)
                    }
                    VStack {
                        Text("Round \(self.gameManager.game?.currentRound ?? 1)")
                            .font(.system(size: 22))
                            .foregroundColor(Color.gray)
                            .multilineTextAlignment(.center)
                            .padding(.bottom, 5)
                        // QUESTION
                        Text("\(self.gameManager.game?.question?.text ?? "")")
                            .font(.custom("Seravek-Bold", size: 28))
                            .foregroundColor(Color("primary"))
                            .multilineTextAlignment(.center)
                            .padding(.horizontal, 30)
                            .fixedSize(horizontal: false, vertical: true)
                        // QUESTION IMAGE
                        (self.gameManager.cachedQuestionImage ?? Image(systemName: "rectangle"))
                            .resizable()
                            .scaledToFit()
                            .aspectRatio(contentMode: .fill)
                            .frame(height: 200)
            //                .frame(width: 280, height: 180)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                            .padding(.horizontal, 30)
                            .opacity(self.gameManager.cachedQuestionImage == nil ? 0.0 : 1.0) // Hide image while it is loading
                            .onTapGesture {
                                self.gameManager.deleteGame()
                            }
                        // ANSWERS
                        if 1 > 0 {
                            MultipleChoice(options: self.gameManager.game?.question?.options ?? [])
                        } else if (self.gameManager.game?.question?.type == QuestionType.textInput){
                            TextInput()
                        }
                    }.opacity(self.gameManager.game?.question == nil ? 0.0 : 1.0)
                    .disabled(self.gameManager.game?.question == nil)
                    // Hide and disable the question block when the next question is loading
                }
            }.transition(.fadeTransition)
            .padding(.top, 40)
        }
        .onAppear {
            print("onAppear")
            self.soundManager.playSound(name: "duringGame.aiff", loop: false)
        }
        .onDisappear {
            print("onDisappear")
            self.soundManager.player?.stop()
        }
    }
}

SoundManager.swift - this is the viewmodel that plays the audio

class SoundManager: ObservableObject {
    @Published var player: AVAudioPlayer?
    
    func playSound(name: String, loop: Bool){
        let path = Bundle.main.path(forResource: name, ofType: nil)
        let url = URL(fileURLWithPath: path!)
        print("Play URL from name: \(name)")
        do {
            player = try AVAudioPlayer(contentsOf: url)
            if loop {
                player?.numberOfLoops = -1 // Loop forever
            }
            player?.play()
            print("Played sound") // Both views print this when the audio plays
        } catch {
            print("Error playing \(name) sound")
        }
    }
}

Any idea what the problem is? The SoundManager is stored as an observed object in both views, so it should live as long as the view is still alive. In ActiveGame, onDisappear() does not get called, so the view is still alive and should be playing the audio until the end.

Upvotes: 1

Views: 173

Answers (1)

BJ Beecher
BJ Beecher

Reputation: 205

The first thing I would fix is changing your @ObservedObject wrapper to a @StateObject wrapper. This will prevent deallocation if the view updates at some point in the process of playing the sound. Let me know if that works...

Upvotes: 2

Related Questions