如何从已经关闭的视图中删除背景音频
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
的多个实例可能存在的假设是错误的,请在评论中告诉我
我的应用程序的主视图是一个歌曲列表。
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
的多个实例可能存在的假设是错误的,请在评论中告诉我