ArchiveTune now races all available Tidal streaming instances concurrently instead of trying them sequentially. This dramatically improves stream resolution latency and robustness, especially on networks where some instances are slow or unreachable.
The implementation mirrors echo-tidal-extension's successful architecture and eliminates the "try one, wait for timeout, then try the next" bottleneck.
- User override (highest priority): Custom instance URL set in Settings → Tidal
- Live remote list: Fetched from
monochrome.tf/instances.jsonwith 1-hour cache - Hardcoded fallback defaults: Built-in known-good instances for offline use
When resolving a stream:
// Fire all instances at the same time on Dispatchers.IO
val tasks = endpoints.map { endpoint ->
async(Dispatchers.IO) {
// Try this endpoint
requestDirectFlacFromEndpoint(endpoint, ...)
}
}
// Wait for all and return the first success
val results = tasks.awaitAll()
results.forEach { result ->
result.getOrNull()?.let { return it } // First success wins
}Key benefit: If instance A is slow (500ms) and instance B is fast (50ms), you get the B response in ~50ms instead of waiting 500ms+ to try the next one.
TidalInstanceManager.kt: Fetches live instance list from remote JSON, manages cache, provides fallback chain- Preference key:
TidalInstanceUrlKey(added toPreferenceKeys.kt)
-
TidalAudioProvider.kt:resolve()→suspend fun resolve()(now async)requestDirectFlac()→private suspend fun requestDirectFlac()(now async)- Replaced sequential for-loop with
async { ... }.awaitAll()racing pattern - Added coroutine imports
-
MusicService.kt:- Wrapped
TidalAudioProvider.resolve()call withrunBlocking(Dispatchers.IO) { ... }to call suspending function
- Wrapped
-
TidalSettings.kt(existing):- Already has comprehensive instance management UI (add/remove/health-check)
- Now automatically uses concurrent racing when resolving streams
- Try user-configured custom instance first (if set)
- Try all live list instances in parallel
- If live fetch failed, use cached list from previous run
- If no cache, use hardcoded defaults
- Healthy instance: Used first in next stream resolution
- Soft cooldown (15 min): Transient errors (5xx, timeout, missing track)
- Hard cooldown (60 min): Connection refused, DNS failed
- Instances always available as last resort when all are cooling down
- Rate-limited response: All instances rate-limited → throw
TidalRateLimitedExceptionwith retry delay - All instances failed: Throw exception with aggregated error messages from all attempts
- One succeeds: Return immediately, mark instance as healthy
Try instance 1... timeout (5s) ❌
Try instance 2... timeout (5s) ❌
Try instance 3... success ✓ (15s total)
Try instances 1, 2, 3... in parallel
Instance 2 responds first ✓ (200ms total)
- Open Settings → Tidal
- Scroll to Instance Configuration
- Enter custom HiFi instance URL (e.g.,
https://api.example.com) - Leave blank to use public list
- Settings → Tidal → Check Instances
- Pings all configured instances, shows latency + status
- Dead instances shown as "Unreachable"
- Settings → Tidal → Discover
- Searches public discovery sources for new working mirrors
To verify racing is working:
-
Settings → Tidal → Check Instances
- All instances should show latency (not just first one)
- Multiple instances responding = racing working correctly
-
Play a Tidal track
- Should resolve faster than before (especially if a second instance is quicker)
- Watch logcat: Should see all instances attempted in parallel
[TidalAudio] resolver <name> failedtags indicate which instances responded/failed
- ✅ Concurrent racing of streaming instances
- ✅ User-configurable custom instance
- ✅ Live instance list fetching with cache
- ✅ Health tracking and cooldowns
- Public instances return previews only (30s clips): Because every reachable public instance is unsubscribed. A subscribed instance (or your own Tidal account via the account path) is required for full streams.
- Your ISP blocks Tidal DNS: The
auth.tidal.comresolution fails at your network level. DoH fallback is present but may also be blocked. Workaround: VPN or Private DNS with a DNS provider your ISP allows.
✅ Robustness: One slow/dead instance no longer blocks the entire stream
✅ Speed: Faster stream resolution (race semantics)
✅ Configurability: Easy to point at any working instance
✅ Mirrors dead instances quickly: Health tracking puts bad instances on cooldown
❌ Public instances being unsubscribed: Still only PREVIEW quality
❌ ISP-level DNS blocking: Requires VPN or network-level fix
- Coroutine scope:
Dispatchers.IO(unbounded thread pool for I/O operations) - No race condition on shared state: All health/cooldown updates are atomic via
ConcurrentHashMap - Backwards compatible: Existing code paths unchanged, just faster + more resilient
- Code matches echo-tidal-extension patterns (proven working on many networks)
- echo-tidal-extension: https://github.com/brahmkshatriya/echo-tidal-extension (racing pattern source)
- monochrome.tf: Public Tidal instance discovery list
- Kotlin coroutines:
async,awaitAllfor concurrent operations