You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中AVAudioPlayer滑块值实时更新实现问题咨询

解决SwiftUI音频播放时滑块实时更新的问题

嘿,我看了你的代码,问题的核心其实很明确——你的audioSettings里的关键变量没有被标记为可观察的,所以SwiftUI不知道playValue在播放过程中变化了,自然不会实时更新滑块!咱们一步步来修复:

第一步:修正audioSettings的ObservableObject属性

ObservableObject只有当属性被@Published标记时,才会把变化通知给视图。你现在的playing、playValue都没加这个标记,这就是滑块不实时动的根本原因。另外,硬编码playerDuration也不太靠谱,最好从音频文件里读取真实时长。

修改后的audioSettings代码:

import Foundation
import AVFoundation
import Combine

class AudioSettings: ObservableObject { // 类名首字母大写,符合Swift规范
    @Published var audioPlayer: AVAudioPlayer?
    @Published var playing = false
    @Published var playValue: TimeInterval = 0.0
    @Published var playerDuration: TimeInterval = 0.0 // 不再硬编码
    private var timerCancellable: AnyCancellable? // 用Cancellable管理timer生命周期

    func playSound(sound: String, type: String) {
        guard let path = Bundle.main.path(forResource: sound, ofType: type) else {
            print("Could not find the sound file.")
            return
        }
        let url = URL(fileURLWithPath: path)
        
        do {
            if audioPlayer == nil {
                audioPlayer = try AVAudioPlayer(contentsOf: url)
                audioPlayer?.prepareToPlay()
                playerDuration = audioPlayer?.duration ?? 0.0 // 读取真实时长
            }
            
            if !playing {
                audioPlayer?.play()
                playing = true
                startTimer() // 播放时启动timer
            }
        } catch {
            print("Could not play the sound file: \(error.localizedDescription)")
        }
    }
    
    func stopSound() {
        audioPlayer?.stop()
        audioPlayer = nil
        playing = false
        playValue = 0.0
        stopTimer() // 停止timer
    }
    
    func pauseSound() {
        if playing {
            audioPlayer?.pause()
            playing = false
            stopTimer() // 暂停时停止timer
        }
    }
    
    func changeSliderValue() {
        guard let player = audioPlayer else { return }
        
        if playing {
            pauseSound()
        }
        player.currentTime = playValue
        player.play()
        playing = true
        startTimer() // 跳转后重新启动timer
    }
    
    // 封装timer的启动逻辑
    private func startTimer() {
        stopTimer() // 先停止之前的timer,避免重复
        timerCancellable = Timer.publish(every: 0.1, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                guard let self = self, let player = self.audioPlayer, self.playing else {
                    self?.stopTimer()
                    return
                }
                self.playValue = player.currentTime
                // 检测播放结束
                if player.currentTime >= player.duration {
                    self.stopSound()
                }
            }
    }
    
    // 封装timer的停止逻辑
    private func stopTimer() {
        timerCancellable?.cancel()
        timerCancellable = nil
    }
}

第二步:简化视图代码,移除冗余逻辑

原来的视图里手动管理playButton状态、重新创建timer都是多余的,现在可以直接依赖AudioSettings的@Published属性来驱动UI,让代码更简洁可靠:

修改后的视图代码:

import SwiftUI

struct MyExperienceFearChunk: View { // 视图名首字母大写,符合Swift规范
    @ObservedObject var audioSettings = AudioSettings()
    // 不再手动维护按钮状态,直接根据playing判断
    private var playButtonImage: Image {
        audioSettings.playing ? Image(systemName: "pause.circle") : Image(systemName: "play.circle")
    }

    var body: some View {
        VStack { // 用VStack把按钮和滑块垂直排列,更合理
            HStack {
                Button(action: {
                    if audioSettings.playing {
                        audioSettings.pauseSound()
                    } else {
                        audioSettings.playSound(sound: "audio file", type: "mp3")
                    }
                }) {
                    playButtonImage
                        .foregroundColor(.white)
                        .font(.system(size: 44))
                }

                Button(action: {
                    audioSettings.stopSound()
                }) {
                    Image(systemName: "stop.circle")
                        .foregroundColor(.white)
                        .font(.system(size: 44))
                }
            }

            Slider(
                value: $audioSettings.playValue,
                in: 0...audioSettings.playerDuration,
                onEditingChanged: { isEditing in
                    if !isEditing { // 只有当用户结束拖动时才跳转
                        audioSettings.changeSliderValue()
                    }
                }
            )
            .disabled(audioSettings.playerDuration == 0) // 音频未加载时禁用滑块
        }
    }
}

关键修复点说明

  • @Published标记:给playing、playValue、playerDuration加上@Published,让SwiftUI能感知到这些值的变化,自动刷新视图(包括滑块)。
  • Timer生命周期管理:在audioSettings内部封装timer的启动和停止,避免在视图里重复创建和销毁,逻辑更内聚。
  • 移除冗余状态:视图不再手动维护playButton的状态,直接根据audioSettings.playing动态生成,减少状态不一致的风险。
  • 真实时长获取:从AVAudioPlayer读取音频的真实时长,代替硬编码的146,适配不同音频文件。

这样修改后,播放音频时滑块就会实时跟随playValue的变化更新了!

内容的提问来源于stack exchange,提问作者Joshua Browne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 19:27:49