217 lines
6.8 KiB
Swift
217 lines
6.8 KiB
Swift
//
|
|
// PigeonMessageHistoryVC.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import UIKit
|
|
import AVFoundation
|
|
import RxSwift
|
|
|
|
final class PigeonMessageHistoryVC: BaseViewController {
|
|
|
|
private let groupKey: String
|
|
private var items: [PigeonHistoryItem] = []
|
|
private var rootView: PigeonMessageHistoryView!
|
|
|
|
private var playingItemId: String?
|
|
private var isVoicePlaying = false
|
|
private var voiceElapsed: TimeInterval = 0
|
|
private var voiceDuration: TimeInterval = 0
|
|
private var voiceTimer: Timer?
|
|
private var audioPlayer: AVAudioPlayer?
|
|
|
|
init(groupKey: String) {
|
|
self.groupKey = groupKey
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func loadView() {
|
|
rootView = PigeonMessageHistoryView(frame: UIScreen.main.bounds)
|
|
view = rootView
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
rootView.tableView.dataSource = self
|
|
rootView.tableView.delegate = self
|
|
rootView.tableView.register(PigeonHistoryCell.self, forCellReuseIdentifier: PigeonHistoryCell.reuseId)
|
|
loadHistory()
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
stopVoicePlayback(reset: true)
|
|
}
|
|
|
|
@objc private func goBack() {
|
|
navigationController?.popViewController(animated: true)
|
|
}
|
|
|
|
private func loadHistory() {
|
|
DLToast.showLoading()
|
|
PigeonService.sent(groupKey: groupKey)
|
|
.subscribe(onNext: { [weak self] response in
|
|
DLToast.dismiss()
|
|
guard let self else { return }
|
|
self.items = response.list.map { $0.toHistoryItem() }
|
|
self.rootView.tableView.reloadData()
|
|
}, onError: { _ in
|
|
DLToast.dismiss()
|
|
})
|
|
.disposed(by: disposeBag)
|
|
}
|
|
|
|
private func togglePlayback(for item: PigeonHistoryItem) {
|
|
guard item.kind == .voice else { return }
|
|
guard item.mediaURL != nil else {
|
|
DLToast.show(text: "无法播放")
|
|
return
|
|
}
|
|
if playingItemId == item.id {
|
|
if isVoicePlaying {
|
|
pauseVoicePlayback()
|
|
} else {
|
|
startVoicePlayback(item: item)
|
|
}
|
|
return
|
|
}
|
|
|
|
stopVoicePlayback(reset: true)
|
|
startVoicePlayback(item: item)
|
|
}
|
|
|
|
private func startVoicePlayback(item: PigeonHistoryItem) {
|
|
playingItemId = item.id
|
|
if audioPlayer == nil || playingItemId != item.id {
|
|
preparePlayer(for: item)
|
|
return
|
|
}
|
|
audioPlayer?.play()
|
|
isVoicePlaying = true
|
|
startTimer()
|
|
refreshVisiblePlayback()
|
|
}
|
|
|
|
private func preparePlayer(for item: PigeonHistoryItem) {
|
|
guard let url = item.mediaURL else { return }
|
|
DLToast.showLoading()
|
|
URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
|
|
DispatchQueue.main.async {
|
|
DLToast.dismiss()
|
|
guard let self, self.playingItemId == item.id else { return }
|
|
guard let data, error == nil else {
|
|
DLToast.show(text: "无法播放")
|
|
self.playingItemId = nil
|
|
return
|
|
}
|
|
do {
|
|
let player = try AVAudioPlayer(data: data)
|
|
player.prepareToPlay()
|
|
self.audioPlayer = player
|
|
self.voiceDuration = max(1, player.duration)
|
|
self.voiceElapsed = 0
|
|
player.play()
|
|
self.isVoicePlaying = true
|
|
self.startTimer()
|
|
self.refreshVisiblePlayback()
|
|
} catch {
|
|
DLToast.show(text: "无法播放")
|
|
self.playingItemId = nil
|
|
}
|
|
}
|
|
}.resume()
|
|
}
|
|
|
|
private func startTimer() {
|
|
voiceTimer?.invalidate()
|
|
let timer = Timer(timeInterval: 0.05, repeats: true) { [weak self] _ in
|
|
self?.tickVoicePlayback()
|
|
}
|
|
RunLoop.main.add(timer, forMode: .common)
|
|
voiceTimer = timer
|
|
}
|
|
|
|
private func pauseVoicePlayback() {
|
|
isVoicePlaying = false
|
|
audioPlayer?.pause()
|
|
voiceTimer?.invalidate()
|
|
voiceTimer = nil
|
|
refreshVisiblePlayback()
|
|
}
|
|
|
|
private func stopVoicePlayback(reset: Bool) {
|
|
isVoicePlaying = false
|
|
voiceTimer?.invalidate()
|
|
voiceTimer = nil
|
|
audioPlayer?.stop()
|
|
audioPlayer = nil
|
|
if reset {
|
|
voiceElapsed = 0
|
|
playingItemId = nil
|
|
}
|
|
refreshVisiblePlayback()
|
|
}
|
|
|
|
private func tickVoicePlayback() {
|
|
if let player = audioPlayer {
|
|
voiceElapsed = player.currentTime
|
|
voiceDuration = max(player.duration, voiceElapsed)
|
|
} else {
|
|
voiceElapsed = min(voiceDuration, voiceElapsed + 0.05)
|
|
}
|
|
refreshVisiblePlayback()
|
|
guard voiceElapsed >= voiceDuration, voiceDuration > 0 else { return }
|
|
stopVoicePlayback(reset: true)
|
|
}
|
|
|
|
private func refreshVisiblePlayback() {
|
|
for cell in rootView.tableView.visibleCells {
|
|
guard let historyCell = cell as? PigeonHistoryCell,
|
|
let indexPath = rootView.tableView.indexPath(for: historyCell) else {
|
|
continue
|
|
}
|
|
let item = items[indexPath.row]
|
|
let isCurrent = item.id == playingItemId
|
|
let duration = isCurrent && voiceDuration > 0 ? voiceDuration : item.duration
|
|
historyCell.applyPlayback(
|
|
isPlaying: isCurrent && isVoicePlaying,
|
|
elapsed: isCurrent ? voiceElapsed : 0,
|
|
duration: duration
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension PigeonMessageHistoryVC: UITableViewDataSource, UITableViewDelegate {
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
items.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let cell = tableView.dequeueReusableCell(
|
|
withIdentifier: PigeonHistoryCell.reuseId,
|
|
for: indexPath
|
|
) as! PigeonHistoryCell
|
|
let item = items[indexPath.row]
|
|
let isCurrent = item.id == playingItemId
|
|
cell.configure(
|
|
item: item,
|
|
isPlaying: isCurrent && isVoicePlaying,
|
|
elapsed: isCurrent ? voiceElapsed : 0
|
|
)
|
|
cell.onTogglePlay = { [weak self] in
|
|
self?.togglePlayback(for: item)
|
|
}
|
|
return cell
|
|
}
|
|
|
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
|
guard isVoicePlaying else { return }
|
|
stopVoicePlayback(reset: true)
|
|
}
|
|
}
|