mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 19:05:20 +00:00
Make voice note loading completely lazy with deferred initialization
Aggressive performance optimization to prevent UI freezes: Problem: Even with async loading, creating 10+ VoiceNotePlaybackController instances simultaneously (when scrolling past multiple voice notes) spawned 20+ concurrent background tasks, potentially starving main thread. Solution - Ultra-lazy loading: 1. VoiceNotePlaybackController.init() now does ZERO work - No duration loading - No player creation - Instant initialization 2. Duration loaded on-demand via public loadDuration() method - Called from VoiceNoteView.onAppear after 150ms delay - Reduced priority: .utility instead of .userInitiated - Guard prevents duplicate loading 3. Waveform loading also deferred 150ms - Gives UI time to settle after message appears - Prevents task storms when multiple voice notes appear This spreads the work over time instead of all at once.
This commit is contained in:
@@ -16,12 +16,14 @@ final class VoiceNotePlaybackController: NSObject, ObservableObject, AVAudioPlay
|
|||||||
init(url: URL) {
|
init(url: URL) {
|
||||||
self.url = url
|
self.url = url
|
||||||
super.init()
|
super.init()
|
||||||
// Load duration asynchronously to avoid blocking main thread
|
// Don't load anything eagerly - wait until user interaction or view is fully displayed
|
||||||
loadDurationAsync()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadDurationAsync() {
|
func loadDuration() {
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
// Only load if not already loaded
|
||||||
|
guard duration == 0 else { return }
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .utility).async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
do {
|
do {
|
||||||
let player = try AVAudioPlayer(contentsOf: self.url)
|
let player = try AVAudioPlayer(contentsOf: self.url)
|
||||||
@@ -44,7 +46,8 @@ final class VoiceNotePlaybackController: NSObject, ObservableObject, AVAudioPlay
|
|||||||
stop()
|
stop()
|
||||||
self.url = url
|
self.url = url
|
||||||
player = nil
|
player = nil
|
||||||
loadDurationAsync()
|
duration = 0
|
||||||
|
// Duration will be loaded on demand when needed
|
||||||
}
|
}
|
||||||
|
|
||||||
func togglePlayback() {
|
func togglePlayback() {
|
||||||
|
|||||||
@@ -100,10 +100,13 @@ struct VoiceNoteView: View {
|
|||||||
.stroke(borderColor, lineWidth: 1)
|
.stroke(borderColor, lineWidth: 1)
|
||||||
)
|
)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
// Defer both duration and waveform loading to let UI settle after message appears
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||||
|
playback.loadDuration()
|
||||||
WaveformCache.shared.waveform(for: url, completion: { bins in
|
WaveformCache.shared.waveform(for: url, completion: { bins in
|
||||||
self.waveform = bins
|
self.waveform = bins
|
||||||
})
|
})
|
||||||
// No need to call playback.replaceURL - already initialized with correct URL
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: url) { newValue in
|
.onChange(of: url) { newValue in
|
||||||
WaveformCache.shared.waveform(for: newValue, completion: { bins in
|
WaveformCache.shared.waveform(for: newValue, completion: { bins in
|
||||||
|
|||||||
Reference in New Issue
Block a user