如何从已经关闭的视图中删除背景音频

How to kill background audio from view that was already dismissed

我的应用程序的主视图是一个歌曲列表。

import SwiftUI

struct ContentView: View {
        var body: some View {
        NavigationView {
            List (fileURLs, id: \.path) { song in
                NavigationLink(destination: PlayView(songURL: song)) {
                    SongRow(songURL: song)
                }
            }
            .padding()
            .navigationBarTitle(Text("Songs"))
                .navigationBarItems(trailing: addButton)
        }

    }

}

您点击一首歌曲,它会导航到一个 PlayView,它会实例化一个音频播放器:

import SwiftUI
import AVKit

struct PlayView: View {

    var songURL: URL

    @State var player : AVAudioPlayer!

    var body: some View {
        VStack {
            Button(action: {
                self.playPause()
            }) {
                Text("Play/Pause")
            }
        }.onAppear {
            self.player = try! AVAudioPlayer(contentsOf: self.songURL)
            self.playPause()
        }
    }

    func playPause() {
        if self.player.isPlaying {
            self.player.pause()
        } else {
            self.player.play()
        }
    }
}

问题是当我导航到另一首歌曲的 PlayView 时,它会在 第一首歌曲的顶部 播放音频(即同时播放)。第二首歌播放的时候第一首歌怎么杀?

在我的项目中,我在应用程序的任何地方都只使用了一个 final class 实例。我认为你的问题是你在子视图(PlayView)中创建了很多 AVPlayer 的实例,但只能控制其中一个。这是我的解决方案示例:

import AVKit
import Combine

final class AudioPlayer: AVPlayer, ObservableObject {

    @Published var currentPlaylist: [Song]? = nil
    @Published var currentSong: Song? = nil
    // ...
    // MARK: player controls
    func playPausePlayer(withSong song: Song, forPlaylist playlist: [Song]? = nil) {

        setCurrentPlaylist(newPlaylist: playlist, newSong: song)
        if currentSong == song {
            continueOrPauseCurrentSong()
        } else {
            changeSong(with: song)
        }

        self.controlPanel?.updateNowPlaying()

    }
    // simplified func (I leave only what you need)
     private func changeSong(with newSong: Song) {

        if let url = URL.init(string: newSong.url) {

            let nextPlayerItem = AVPlayerItem.init(url: url)
            self.replaceCurrentItem(with: nextPlayerItem) // here you change the song
            self.play()

            currentSong = newSong
        } else {...}
    }
    // ...
}

其中 Song 只是一个包含歌曲数据的结构,例如:

struct Song: Codable, Equatable, Identifiable, Hashable {
    let id: String
    let url: String
    // and so on
}

SceneDelegate 函数中,我将此 AudioPlayer 设置为 environmentObject,以便在 HomeView() 及其所有子视图中使用它。因此,您可以从任何地方 play/pause 您的播放器或更改歌曲(使用 .replaceCurrentItem(with: ... 方法)。在你的情况下,它应该是这样的:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // ...
    let homeView = ContentView()
            .environment(\.managedObjectContext, context)
            .environmentObject(AudioPlayer()) // here I set only one player for main window, so all the children will use it too

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: homeView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

// Based on your code snippet:
struct ContentView: View {

        @EnvironmentObject var player: AudioPlayer // add this for main view
        var body: some View {
        NavigationView {
            List (fileURLs, id: \.path) { song in
                NavigationLink(destination: PlayView(songURL: song)) {
                    SongRow(songURL: song)
                }
            }
            .padding()
            .navigationBarTitle(Text("Songs"))
                .navigationBarItems(trailing: addButton)
        }

    }

}

struct PlayView: View {

    @EnvironmentObject var player: AudioPlayer // and for child view (they all will have one object
    var songURL: URL

    var body: some View {
        VStack {
            Button(action: {
                 self.playPause()
            }) {
                Text("Play/Pause")
            }
        }.onAppear {
            // set or change currentItem of player
        }
    }

    func playPause() {
        if self.player.isPlaying {
            self.player.pause()
        } else {
            self.player.play()
        }
    }
}

P.S. 如果我认为 AVPlayer 的多个实例可能存在的假设是错误的,请在评论中告诉我