LNVoicePlayer.swift 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. //
  2. // LNVoicePlayer.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/5.
  6. //
  7. import Foundation
  8. import AVFAudio
  9. import AVFoundation
  10. protocol LNVoicePlayerNotify {
  11. func onAudioStartPlay(path: String)
  12. func onAudioUpdateDuration(path: String, cur: TimeInterval, total: TimeInterval)
  13. func onAudioStopPlay(path: String)
  14. }
  15. extension LNVoicePlayerNotify {
  16. func onAudioStartPlay(path: String) { }
  17. func onAudioUpdateDuration(path: String, cur: TimeInterval, total: TimeInterval) { }
  18. func onAudioStopPlay(path: String) { }
  19. }
  20. enum LNVoiceSource: Codable {
  21. case unknown
  22. case im
  23. case game
  24. var folderName: String {
  25. switch self {
  26. case .unknown: "common"
  27. case .im: "im"
  28. case .game: "game"
  29. }
  30. }
  31. }
  32. class LNVoicePlayer: NSObject {
  33. static let shared = LNVoicePlayer()
  34. private var curPlayer: AVAudioPlayer?
  35. private(set) var playingUrl: String?
  36. var isPlaying: Bool {
  37. curPlayer?.isPlaying == true
  38. }
  39. private var durationTimer: Timer?
  40. private(set) var currentTime: TimeInterval = 0.0
  41. private(set) var duration: TimeInterval = 0.0
  42. var curSpeed: Float {
  43. curPlayer?.rate ?? 1.0
  44. }
  45. private override init() { super.init() }
  46. func play(path: String) {
  47. stop()
  48. playingUrl = path
  49. playVoice(path)
  50. }
  51. func play(_ url: String, type: LNVoiceSource = .unknown) {
  52. stop()
  53. playingUrl = url
  54. LNVoiceResourceManager.shared.voiceResourceFor(url: url, type: type) { [weak self] path in
  55. guard let self else { return }
  56. guard playingUrl == url, curPlayer == nil else { return }
  57. playVoice(path)
  58. }
  59. }
  60. func playVoiceMessage(message: LNIMMessageData) {
  61. stop()
  62. let uuid = message.imMessage.soundElem?.uuid
  63. playingUrl = uuid
  64. if let path = message.content,
  65. FileManager.default.fileExists(atPath: path) {
  66. playVoice(path)
  67. return
  68. }
  69. let cachePath: URL? = if let uuid {
  70. LNVoiceResourceManager.shared.voicePath(uuid, type: .im)
  71. } else {
  72. nil
  73. }
  74. if let cachePath {
  75. if FileManager.default.fileExists(atPath: cachePath.path) {
  76. playVoice(cachePath.path)
  77. return
  78. }
  79. }
  80. message.imMessage.soundElem?.getUrl { url in
  81. guard let url else { return }
  82. LNVoiceResourceManager.shared.voiceResourceFor(
  83. url: url, type: .im, cachePath: cachePath)
  84. { [weak self] path in
  85. guard let self else { return }
  86. guard playingUrl == uuid, curPlayer == nil else { return }
  87. playVoice(path)
  88. }
  89. }
  90. }
  91. func stop() {
  92. curPlayer?.stop()
  93. curPlayer = nil
  94. stopTimer()
  95. duration = 0.0
  96. currentTime = 0.0
  97. notifyStopPlay()
  98. playingUrl = nil
  99. }
  100. func setSpeed(speed: Float) {
  101. curPlayer?.rate = speed
  102. }
  103. }
  104. extension LNVoicePlayer {
  105. private func playVoice(_ path: String) {
  106. guard FileManager.default.fileExists(atPath: path) else { return }
  107. try? AVAudioSession.sharedInstance().setCategory(.playback)
  108. guard let player = try? AVAudioPlayer.init(contentsOf: URL(fileURLWithPath: path)) else { return }
  109. player.delegate = self
  110. player.enableRate = true
  111. if player.play() {
  112. duration = player.duration
  113. curPlayer = player
  114. notifyStartPlay()
  115. startTimer()
  116. } else {
  117. stop()
  118. }
  119. }
  120. }
  121. extension LNVoicePlayer {
  122. private func startTimer() {
  123. stopTimer()
  124. currentTime = 0.0
  125. let timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: { [weak self] _ in
  126. guard let self else { return }
  127. currentTime = curPlayer?.currentTime ?? 0
  128. notifyDuration(cur: currentTime, total: curPlayer?.duration ?? 0)
  129. })
  130. RunLoop.main.add(timer, forMode: .common)
  131. durationTimer = timer
  132. }
  133. private func stopTimer() {
  134. durationTimer?.invalidate()
  135. durationTimer = nil
  136. }
  137. }
  138. extension LNVoicePlayer: AVAudioPlayerDelegate {
  139. func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
  140. stop()
  141. notifyStopPlay()
  142. }
  143. }
  144. extension LNVoicePlayer {
  145. private func notifyStartPlay() {
  146. guard let url = playingUrl, !url.isEmpty else { return }
  147. LNEventDeliver.notifyEvent {
  148. ($0 as? LNVoicePlayerNotify)?.onAudioStartPlay(path: url)
  149. }
  150. }
  151. private func notifyStopPlay() {
  152. guard let url = playingUrl, !url.isEmpty else { return }
  153. LNEventDeliver.notifyEvent {
  154. ($0 as? LNVoicePlayerNotify)?.onAudioStopPlay(path: url)
  155. }
  156. }
  157. private func notifyDuration(cur: TimeInterval, total: TimeInterval) {
  158. guard let url = playingUrl, !url.isEmpty else { return }
  159. LNEventDeliver.notifyEvent {
  160. ($0 as? LNVoicePlayerNotify)?.onAudioUpdateDuration(path: url, cur: cur, total: total)
  161. }
  162. }
  163. }