Skip to content

fix(player): lazy-load and cache real bitrate and sample rate for local songs - #38

Closed
adam-adrian wants to merge 11 commits into
devfrom
fix/local-media-bitrate-info
Closed

fix(player): lazy-load and cache real bitrate and sample rate for local songs#38
adam-adrian wants to merge 11 commits into
devfrom
fix/local-media-bitrate-info

Conversation

@adam-adrian

Copy link
Copy Markdown
Owner

No description provided.

@adam-adrian adam-adrian closed this Jul 6, 2026
@adam-adrian
adam-adrian force-pushed the fix/local-media-bitrate-info branch from 581ed30 to cdba7d0 Compare July 6, 2026 07:29
@adam-adrian adam-adrian reopened this Jul 6, 2026
@adam-adrian adam-adrian changed the title "fix(player): lazy-load and cache real bitrate and sample rate for local songs fix(player): lazy-load and cache real bitrate and sample rate for local songs Jul 6, 2026

rukamori commented Jul 6, 2026

Copy link
Copy Markdown

I think this needs a bit more cleanup before merge.

main issue: the retry/cache logic can still keep re-running MediaExtractor. if bitrate extraction fails but sampleRate is found, bitrate stays 0, so the storedFormat.bitrate == 0 check will trigger extraction again next time. better to use a proper “metadata checked” flag, or set a failure sentinel whenever bitrate can’t be extracted.

also, cached bitrate/sampleRate should probably be reset when the local file changes (size, dateModified, or mimeType), otherwise stale metadata can stay around. and getFormatById() inside the scan loop is an N+1 query, better batch-load formats like the other entities.

@adam-adrian
adam-adrian force-pushed the fix/local-media-bitrate-info branch from cdc22b5 to 53e9800 Compare July 7, 2026 04:24
…al songs

Implements an on-demand background extraction of real audio metadata (bitrate and sample rate) for local files to replace the default "Unknown" representation on the player screen:

1. Dynamic Lazy-Loading: Integrates a Coroutines flow collector inside PlayerConnection to trigger extraction precisely when a local song starts playing. This guarantees that full library scans remain lightning fast without blocking physical I/O.
2. Safe Persistence & Anti-Overwrite: Adds a targeted database update query to save successfully extracted metadata to the FormatEntity. Concurrently refactors LocalSongScanner to preserve these extracted values during full-library rescans.
3. MediaExtractor & Sentinel Defense: Uses Android's native MediaExtractor for universal API level compatibility. Implements a sentinel value of '-1' for corrupted or unreadable local files to prevent wasteful and battery-draining repeated extraction loops.
4. Robust Concurrency: Employs collectLatest combined with Coroutine's native isActive check to prevent race conditions or out-of-order database writes during fast-skipping.
…erformance

Refactors and optimizes the local audio metadata lazy-loading and database preservation flow to prevent visual regressions, eliminate N+1 queries, and secure cache invalidation:

1. Optimize Scanner Performance (Batch Load): Replaced the N+1 single-row "getFormatById" database query inside the LocalSongScanner loop with a single targeted batch query "getFormatsByIds" executed at scan start, reducing database roundtrips to O(1) via an in-memory Map.
2. Automatic Cache Invalidation: Introduced a file size comparison check (contentLength vs sizeBytes) during device scans. If a local file has changed in size (e.g. via re-encoding), the cached bitrate/sampleRate is safely reset to force an updated extraction on the next play.
3. Perfect Sentinel & Retry Defense: Refactored the player evaluation block to check both 'bitrate == 0' and 'sampleRate == null'. This guarantees that if metadata is only partially successfully extracted, or falls back to a failed sentinel of '-1', we permanently prevent repetitive and battery-draining MediaExtractor execution.
…n failures

Formally declares the targeted 'getFormatsByIds(ids: List<String>)' batch query within DatabaseDao.kt. This resolves the unresolved reference compilation errors on the scanner side ('LocalSongScanner.kt') introduced in the previous optimization commit.
… bitrate is missing

Optimizes CodecInfoRow invocation logic across player styles V1, V3, V4, V7, and V9. When the local audio file's bitrate is unavailable ("Unknown"), the player dynamically substitutes the missing bitrate field with the extracted high-resolution sample rate (e.g. "FLAC • 44.1 kHz • 25 MB"). Player V2 is safely untouched as it natively renders all fields by default. This guarantees high-fidelity Lossless flexing is always displayed beautifully, regardless of the chosen player style.
@adam-adrian
adam-adrian force-pushed the fix/local-media-bitrate-info branch 2 times, most recently from 1de9c79 to a9644cf Compare July 7, 2026 04:56
…y helper

Refactors player codec information rows (V1, V3, V4, V7, and V9 player styles) by consolidating the duplicate fallback evaluation block into a single, clean extension function inside FormatEntity.kt:

1. Unify Codec Evaluation: Added 'RatePriority' enum and 'FormatEntity.autoRateDisplay(priority)' helper in FormatEntity.kt. This unified function gracefully manages sample rate and bitrate fallback priorities, while ensuring absolute robust handling of empty string or null conversions.
2. Clean Up UI Duplication: Replaced over 60 lines of redundant and duplicated Kotlin evaluation logic scattered across 5 collapsed player styles inside QueueComponents.kt with a single, highly readable O(1) invocation call 'currentFormat.autoRateDisplay()'.
@adam-adrian
adam-adrian force-pushed the fix/local-media-bitrate-info branch from 6dd3fd2 to 749be02 Compare July 7, 2026 05:33
adam-adrian and others added 6 commits July 7, 2026 21:01
…SQL variable limit

Hardens PlayerConnection and LocalSongScanner against critical production-level issues:

1. Fix Coroutine Cancellation Swallowing: Converted 'extractLocalAudioProperties' into a suspend function running on 'withContext(Dispatchers.IO)'. Propagates 'CancellationException' by invoking 'ensureActive()' inside the try-catch block to prevent leaking background thread I/O during skip transitions.
2. Safe SQLite Batch Chunking: Implemented '.chunked(SqlBatchSize)' grouping inside the scanner on scannedIds before executing 'database.getFormatsByIds' query. This ensures large local music libraries (>999 songs) are 100% immune to SQLiteException variables limit crash.
3. Idiomatic Cancellation Check: Replaced manual 'isActive' check in PlayerConnection with explicit 'ensureActive()' precisely before updating SQLite database records.
- Store metadata extraction Job reference, cancel on dispose()
- Reset all file-derived metadata when file changes, not just bitrate/sampleRate
- Check dateModified in addition to size for cache invalidation
- Distinguish temporary vs permanent failures in metadata extraction
- Confirm global autoRateDisplay() behavior is intentional
- Fix string-based business logic in autoRateDisplay()
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.

2 participants