第一首歌曲结束后如何自动播放下一首歌曲?

问题描述 投票:0回答:1

我在 Xcode 15 Beta 中使用 SwiftUI 创建了一个音频播放器,代码如下所示。但是,我当前的目标是让音频播放器在前一首歌曲播放完毕后自动播放下一首曲目(不需要用户触发)。我见过有些人使用 AVAudioPlayerDelegate,但这让我更加困惑,因为我使用的是 Struct 并且 AVAudioPlayerDelegate 不能符合类协议。由于我仍在学习 SwiftUI,哪一个最适合这种情况?


import SwiftUI
import AVFoundation

struct Song: Identifiable {
    let id = UUID()
    let name: String
    let composer: String
    let audioFilename: String
}

struct AudioPlayerView: View {  
    @State var audioPlayer: AVAudioPlayer!
    @State var progress: CGFloat = 0.0
    @State private var playing: Bool = false
    @State var duration: Double = 0.0
    @State var formattedDuration: String = ""
    @State var formattedProgress: String = "00:00"
    @State var currentSong : Song?
    @State var currentIndex : Int?
    
    let songs: [Song] = [
        Song(name: "1", composer: "Composer 1", audioFilename: "1.wav"),
        Song(name: "2", composer: "Composer 2", audioFilename: "2.wav"),
        Song(name: "3", composer: "Composer 3", audioFilename: "3.wav"),
        Song(name: "4", composer: "Composer 4", audioFilename: "4.wav"),
        Song(name: "5", composer: "Composer 5", audioFilename: "5.wav"),
    ]
    
    var body: some View {
        HStack {
            Text(currentSong?.name ?? "Audio Player")
            Text(" - ")
            Text(currentSong?.composer ?? "Composer")
        }
        .fontWeight(.semibold)
        .font(.title2)
        .multilineTextAlignment(.center)
        .minimumScaleFactor(0.75)
        
        // the progress bar
        HStack {
            Text(formattedProgress)
                .font(.caption.monospacedDigit())
            
            // this is a dynamic length progress bar
            GeometryReader { gr in
                Capsule()
                    .stroke(Color.black, lineWidth: 2)
                    .background(
                        Capsule()
                            .foregroundColor(.black)
                            .frame(width: gr.size.width * progress,
                                   height: 8), alignment: .leading)
            }
            .frame( height: 8)
            
            Text(formattedDuration)
                .font(.caption.monospacedDigit())
        }
        .padding()
        .frame(height: 50, alignment: .center)
        
        // the control buttons
        HStack(alignment: .center, spacing: 20) {
            
            Spacer()
            
            Button(action: {
                if audioPlayer.isPlaying {
                    playing = false
                    self.audioPlayer.pause()
                } else if !audioPlayer.isPlaying {
                    playing = true
                    self.audioPlayer.play()
                }
            }) {
                Image(systemName: playing ?
                      "pause.circle.fill" : "play.circle.fill")
                .font(.title)
                .imageScale(.large)
                .tint(.black)
            }
            
            Button(action: {
                shuffle()
            }) {
                Image(systemName: "shuffle")
                    .font(.title)
                    .imageScale(.medium)
                    .tint(.black)
            }
            
            Spacer()
        }
        .onAppear {
            let randomSong = songs.randomElement()!
            initialiseAudioPlayer(for: randomSong)
        }
    }
    
    func initialiseAudioPlayer(for song: Song) {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.minute, .second]
        formatter.unitsStyle = .positional
        formatter.zeroFormattingBehavior = [ .pad ]
        
        // init audioPlayer
        let path = Bundle.main.path(forResource: song.audioFilename, ofType: nil)!
        self.audioPlayer = try! AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
        self.audioPlayer.play()
        currentSong = song
        playing = true
        
        //The formattedDuration is the string to display
        formattedDuration = formatter.string(from:TimeInterval(self.audioPlayer.duration))!
        duration = self.audioPlayer.duration
        
        Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
            if !audioPlayer.isPlaying {
                playing = false
            }
            progress = CGFloat(audioPlayer.currentTime / audioPlayer.duration)
            formattedProgress = formatter.string(from: TimeInterval(self.audioPlayer.currentTime))!
        }
    }
    
    func shuffle() {
        if let currentSong = currentSong {
            let songs = songs.filter { $0.id != currentSong.id}
            if let randomSong = songs.randomElement() {
                audioPlayer.stop()
                initialiseAudioPlayer(for: randomSong)
            }
        }
    }
    
}

#Preview {
    AudioPlayerView()
}
swiftui avaudioplayer xcode15
1个回答
0
投票
  1. 创建一个符合
    ObservableObject
    AVAudioPlayerDelegate
    的类来管理音频播放并在歌曲结束时通知您的视图:
import AVFoundation
import Combine

class AudioPlayerManager: NSObject, ObservableObject, AVAudioPlayerDelegate {
    static let shared = AudioPlayerManager()
    
    private var audioPlayer: AVAudioPlayer?
    private var cancellables: Set<AnyCancellable> = []
    
    @Published var currentSong: Song?
    
    private override init() {
        super.init()
    }
    
    func play(song: Song) {
        guard let path = Bundle.main.path(forResource: song.audioFilename, ofType: nil) else {
            return
        }
        
        do {
            audioPlayer?.stop()
            audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
            audioPlayer?.delegate = self
            audioPlayer?.play()
            currentSong = song
        } catch {
            print("Error playing audio: \(error)")
        }
    }
    
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        if flag, let currentSong = currentSong,
           let index = AudioPlayerView.songs.firstIndex(where: { $0.id == currentSong.id }),
           index < AudioPlayerView.songs.count - 1 {
            play(song: AudioPlayerView.songs[index + 1])
        }
    }
}
  1. 更新您的
    AudioPlayerView
    以使用
    AudioPlayerManager
    及其方法来控制音频播放:
struct AudioPlayerView: View {
    // ... Your existing properties
    
    @ObservedObject private var audioPlayerManager = AudioPlayerManager.shared
    
    // ... Your existing code
    
    var body: some View {
        // ... Your existing UI
        
        HStack(alignment: .center, spacing: 20) {
            Spacer()
            
            Button(action: {
                if audioPlayerManager.currentSong != nil {
                    audioPlayerManager.play(song: audioPlayerManager.currentSong!)
                } else {
                    let randomSong = songs.randomElement()!
                    audioPlayerManager.play(song: randomSong)
                }
            }) {
                Image(systemName: audioPlayerManager.isPlaying ? "pause.circle.fill" : "play.circle.fill")
                    .font(.title)
                    .imageScale(.large)
                    .tint(.black)
            }
            
            // ... Your existing shuffle button and Spacer
            
        }
        .onAppear {
            let randomSong = songs.randomElement()!
            audioPlayerManager.play(song: randomSong)
        }
    }
    
    // ... Your existing functions
    
    static let songs: [Song] = [
        Song(name: "1", composer: "Composer 1", audioFilename: "1.wav"),
        Song(name: "2", composer: "Composer 2", audioFilename: "2.wav"),
        Song(name: "3", composer: "Composer 3", audioFilename: "3.wav"),
        Song(name: "4", composer: "Composer 4", audioFilename: "4.wav"),
        Song(name: "5", composer: "Composer 5", audioFilename: "5.wav"),
    ]
}

确保根据您的特定需求调整此代码并对其进行彻底测试,以确保其按预期工作。

© www.soinside.com 2019 - 2024. All rights reserved.