Skip to content

Fix VoiceOver performance lag with large libraries#1489

Open
matalvernaz wants to merge 1 commit intoTortugaPower:developfrom
matalvernaz:fix/voiceover-performance
Open

Fix VoiceOver performance lag with large libraries#1489
matalvernaz wants to merge 1 commit intoTortugaPower:developfrom
matalvernaz:fix/voiceover-performance

Conversation

@matalvernaz
Copy link
Contributor

Problem

When VoiceOver is enabled and a large number of books are loaded, the library list becomes noticeably laggy. Three separate issues compound to cause this:

  1. DynamicAccessibilityLabelModifier recomputes labels for every item on track change — the onChange(of: playerManager.currentItem?.relativePath) handler calls setupObserver() unconditionally on every item in the list. For non-playing items this synchronously calls VoiceOverService.getAccessibilityLabel(), which does time arithmetic and localizedStringWithFormat with multiple arguments. With 100+ books this fires O(n) times on the main thread every time playback starts/stops/changes.

  2. Custom accessibility rotors call getAccessibilityLabel for all items on every rebuildcustomBookRotor and customFolderRotor pass VoiceOverService.getAccessibilityLabel(for: item) as the rotor entry label for every item. Rotors are rebuilt whenever filteredResults changes, and VoiceOver triggers these rebuilds frequently during navigation.

  3. immediateProgressUpdatePublisher in ItemProgressView is unthrottled — the folderProgressUpdated subscription is throttled to 1s but immediateProgressUpdatePublisher is not, so rapid-fire progress writes hit every visible cell immediately.

Changes

  • DynamicAccessibilityLabelModifier: skip setupObserver() in the onChange handler for items that are neither currently playing nor have an active subscription. The expensive label recomputation now only runs for the one item that just started or stopped playing.

  • ItemListView custom rotors: use item.title as the rotor entry label instead of calling getAccessibilityLabel(). The full rich label (with remaining time, progress %) is already applied by dynamicAccessibilityLabel() on each row — the rotor list only needs the title for navigation purposes.

  • ItemProgressView: add .throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true) to the immediateProgressUpdatePublisher subscription, consistent with the existing throttle on folderProgressUpdated.

Testing

  • Load 50+ books in the library
  • Enable VoiceOver
  • Navigate the list with swipe gestures — scrolling and focus changes should be noticeably smoother
  • Start/pause/change playback — verify the playing item's label still updates with remaining time and the non-playing items are unaffected
  • Use the Books and Folders rotors — verify items are correctly navigable by title

Three changes to reduce main-thread work when VoiceOver is active:

1. DynamicAccessibilityLabelModifier: skip setupObserver() for items
   that are neither currently playing nor have an active subscription.
   Previously every item in the library called getAccessibilityLabel()
   (string formatting + time math) whenever the playing track changed.

2. ItemProgressView: throttle immediateProgressUpdatePublisher at 1s,
   matching the existing throttle on folderProgressUpdated. Rapid-fire
   progress events no longer cause per-frame state writes on all cells.

3. ItemListView custom rotors: use item.title instead of calling
   getAccessibilityLabel() for every item on each rotor rebuild.
   The rich label is already applied by dynamicAccessibilityLabel();
   the rotor entry only needs the title for navigation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant