fix: crash at startup from a Koin cycle in DeviceAudioPlayerQueue

#3170 made DeviceAudioPlayerQueue resolve StreamingUrlRepository eagerly in
a property initializer, but StreamingUrlRepository is constructed with the
AudioPlayerQueue (this queue). Koin recursed between the two definitions
until the stack overflowed, crashing the app at launch (SIGSEGV in
Koin's SingleInstanceFactory). The queue is common code, so every
platform was affected.

The dependency only existed for an iOS-only path that resolved every
queue entry to a direct stream URL, bypassing the local stream proxy.
That was an AVPlayer workaround; iOS now plays through VLC, which plays
the proxied streams like the other platforms, so the path is removed and
the queue is back to how it was before #3170. The iOS media session from
#3170 is kept.
This commit is contained in:
Kingkor Roy Tirtho 2026-11-03 18:52:06 +06:00
parent 3a856d6d03
commit fa2183ed18

View File

@ -18,10 +18,7 @@
package org.spotube.spotube.core.audioplayer package org.spotube.spotube.core.audioplayer
import org.spotube.plugin_interfaces.plugin_apis.audio.StreamProtocol import org.spotube.plugin_interfaces.plugin_apis.audio.StreamProtocol
import org.spotube.spotube.PlatformType
import org.spotube.spotube.getPlatform
import org.spotube.spotube.core.di.injectLogger import org.spotube.spotube.core.di.injectLogger
import org.spotube.spotube.core.server.StreamingUrlRepository
import org.spotube.spotube.modules.blacklist.BlacklistRepository import org.spotube.spotube.modules.blacklist.BlacklistRepository
import org.spotube.spotube.modules.library.local_tracks.media.LocalMediaCacheRepository import org.spotube.spotube.modules.library.local_tracks.media.LocalMediaCacheRepository
import org.spotube.spotube.modules.plugin.PluginProvider import org.spotube.spotube.modules.plugin.PluginProvider
@ -54,7 +51,6 @@ class DeviceAudioPlayerQueue(
private val blacklistRepository: BlacklistRepository) : AudioPlayerQueue, KoinComponent { private val blacklistRepository: BlacklistRepository) : AudioPlayerQueue, KoinComponent {
private val logger by injectLogger<DeviceAudioPlayerQueue>() private val logger by injectLogger<DeviceAudioPlayerQueue>()
private val streamingUrlRepository = get<StreamingUrlRepository>()
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
@ -519,25 +515,7 @@ class DeviceAudioPlayerQueue(
private suspend fun QueueEntry.toMediaItem(): MediaItem? { private suspend fun QueueEntry.toMediaItem(): MediaItem? {
return when (this) { return when (this) {
is QueueEntry.StreamingTrack -> { is QueueEntry.StreamingTrack -> {
val isIos = getPlatform().type == PlatformType.IOS val resolvedUrl = url.ifBlank { buildStreamingUrl(track.id, protocol) }
val requiresIosResolution = isIos && (
url.isBlank() ||
url.startsWith("http://127.0.0.1") ||
url.startsWith("https://127.0.0.1") ||
url.startsWith("http://[::1]") ||
url.startsWith("https://[::1]") ||
url.startsWith("http://localhost") ||
url.startsWith("https://localhost"))
val resolvedStream = if (requiresIosResolution) {
streamingUrlRepository.resolveStreamInfo(track)
} else {
null
}
val resolvedUrl = if (requiresIosResolution) {
resolvedStream?.url ?: buildStreamingUrl(track.id, protocol)
} else {
url.ifBlank { buildStreamingUrl(track.id, protocol) }
}
if (resolvedUrl.isBlank()) return null if (resolvedUrl.isBlank()) return null
MediaItem( MediaItem(
title = track.title, title = track.title,
@ -546,7 +524,7 @@ class DeviceAudioPlayerQueue(
duration = track.durationMs.milliseconds, duration = track.durationMs.milliseconds,
coverURL = artworkUrl.orEmpty(), coverURL = artworkUrl.orEmpty(),
url = resolvedUrl, url = resolvedUrl,
protocol = resolvedStream?.protocol ?: protocol) protocol = protocol)
} }
is QueueEntry.LocalTrack -> { is QueueEntry.LocalTrack -> {