| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- //
- // LNVoicePlayer.swift
- // Lanu
- //
- // Created by OneeChan on 2025/12/5.
- //
- import Foundation
- import AVFAudio
- import AVFoundation
- protocol LNVoicePlayerNotify {
- func onAudioStartPlay(path: String)
- func onAudioUpdateDuration(path: String, cur: TimeInterval, total: TimeInterval)
- func onAudioStopPlay(path: String)
- }
- extension LNVoicePlayerNotify {
- func onAudioStartPlay(path: String) { }
- func onAudioUpdateDuration(path: String, cur: TimeInterval, total: TimeInterval) { }
- func onAudioStopPlay(path: String) { }
- }
- enum LNVoiceSource: Codable {
- case unknown
- case im
- case game
-
- var folderName: String {
- switch self {
- case .unknown: "common"
- case .im: "im"
- case .game: "game"
- }
- }
- }
- class LNVoicePlayer: NSObject {
- static let shared = LNVoicePlayer()
-
- private var curPlayer: AVAudioPlayer?
- private(set) var playingUrl: String?
- var isPlaying: Bool {
- curPlayer?.isPlaying == true
- }
-
- private var durationTimer: Timer?
- private(set) var currentTime: TimeInterval = 0.0
- private(set) var duration: TimeInterval = 0.0
- var curSpeed: Float {
- curPlayer?.rate ?? 1.0
- }
-
- private override init() { super.init() }
-
- func play(path: String) {
- stop()
-
- playingUrl = path
-
- playVoice(path)
- }
-
- func play(_ url: String, type: LNVoiceSource = .unknown) {
- stop()
-
- playingUrl = url
-
- LNVoiceResourceManager.shared.voiceResourceFor(url: url, type: type) { [weak self] path in
- guard let self else { return }
- guard playingUrl == url, curPlayer == nil else { return }
- playVoice(path)
- }
- }
-
- func playVoiceMessage(message: LNIMMessageData) {
- stop()
-
- let uuid = message.imMessage.soundElem?.uuid
-
- playingUrl = uuid
-
- if let path = message.content,
- FileManager.default.fileExists(atPath: path) {
- playVoice(path)
- return
- }
-
- let cachePath: URL? = if let uuid {
- LNVoiceResourceManager.shared.voicePath(uuid, type: .im)
- } else {
- nil
- }
- if let cachePath {
- if FileManager.default.fileExists(atPath: cachePath.path) {
- playVoice(cachePath.path)
- return
- }
- }
-
- message.imMessage.soundElem?.getUrl { url in
- guard let url else { return }
- LNVoiceResourceManager.shared.voiceResourceFor(
- url: url, type: .im, cachePath: cachePath)
- { [weak self] path in
- guard let self else { return }
- guard playingUrl == uuid, curPlayer == nil else { return }
- playVoice(path)
- }
- }
- }
-
- func stop() {
- curPlayer?.stop()
- curPlayer = nil
- stopTimer()
- duration = 0.0
- currentTime = 0.0
- notifyStopPlay()
- playingUrl = nil
- }
-
- func setSpeed(speed: Float) {
- curPlayer?.rate = speed
- }
- }
- extension LNVoicePlayer {
- private func playVoice(_ path: String) {
- guard FileManager.default.fileExists(atPath: path) else { return }
-
- try? AVAudioSession.sharedInstance().setCategory(.playback)
-
- guard let player = try? AVAudioPlayer.init(contentsOf: URL(fileURLWithPath: path)) else { return }
-
- player.delegate = self
- player.enableRate = true
-
- if player.play() {
- duration = player.duration
- curPlayer = player
- notifyStartPlay()
- startTimer()
- } else {
- stop()
- }
- }
- }
- extension LNVoicePlayer {
- private func startTimer() {
- stopTimer()
- currentTime = 0.0
-
- let timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: { [weak self] _ in
- guard let self else { return }
- currentTime = curPlayer?.currentTime ?? 0
- notifyDuration(cur: currentTime, total: curPlayer?.duration ?? 0)
- })
- RunLoop.main.add(timer, forMode: .common)
- durationTimer = timer
- }
-
- private func stopTimer() {
- durationTimer?.invalidate()
- durationTimer = nil
- }
- }
- extension LNVoicePlayer: AVAudioPlayerDelegate {
- func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
- stop()
- notifyStopPlay()
- }
- }
- extension LNVoicePlayer {
- private func notifyStartPlay() {
- guard let url = playingUrl, !url.isEmpty else { return }
- LNEventDeliver.notifyEvent {
- ($0 as? LNVoicePlayerNotify)?.onAudioStartPlay(path: url)
- }
- }
-
- private func notifyStopPlay() {
- guard let url = playingUrl, !url.isEmpty else { return }
- LNEventDeliver.notifyEvent {
- ($0 as? LNVoicePlayerNotify)?.onAudioStopPlay(path: url)
- }
- }
-
- private func notifyDuration(cur: TimeInterval, total: TimeInterval) {
- guard let url = playingUrl, !url.isEmpty else { return }
- LNEventDeliver.notifyEvent {
- ($0 as? LNVoicePlayerNotify)?.onAudioUpdateDuration(path: url, cur: cur, total: total)
- }
- }
- }
|