refactor: Enhance error handling in API calls with logging

This commit is contained in:
2026-04-29 23:07:48 +02:00
parent 1fc291d2d8
commit 522b93ff06
2 changed files with 288 additions and 219 deletions

View File

@@ -11,6 +11,7 @@ import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient
import hu.bbara.purefin.model.Episode import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Movie import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Series import hu.bbara.purefin.model.Series
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@@ -105,7 +106,14 @@ class InMemoryMediaRepository @Inject constructor(
override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> { override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> {
scope.launch { scope.launch {
ensureSeriesContentLoaded(seriesId) try {
ensureSeriesContentLoaded(seriesId)
} catch (error: CancellationException) {
throw error
} catch (error: Exception) {
Log.e("InMemoryMediaRepository", "Failed to load content for series $seriesId", error)
throw error
}
} }
return seriesState.map { it[seriesId] } return seriesState.map { it[seriesId] }
} }

View File

@@ -6,6 +6,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import hu.bbara.purefin.data.PlaybackMethod import hu.bbara.purefin.data.PlaybackMethod
import hu.bbara.purefin.data.PlaybackReportContext import hu.bbara.purefin.data.PlaybackReportContext
import hu.bbara.purefin.data.UserSessionRepository import hu.bbara.purefin.data.UserSessionRepository
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -85,7 +86,9 @@ class JellyfinApiClient @Inject constructor(
} }
suspend fun configureFromSession(): Boolean = withContext(Dispatchers.IO) { suspend fun configureFromSession(): Boolean = withContext(Dispatchers.IO) {
ensureConfigured() logApiFailure("configureFromSession") {
ensureConfigured()
}
} }
suspend fun authenticate( suspend fun authenticate(
@@ -93,224 +96,254 @@ class JellyfinApiClient @Inject constructor(
username: String, username: String,
password: String, password: String,
): AuthenticationResult? = withContext(Dispatchers.IO) { ): AuthenticationResult? = withContext(Dispatchers.IO) {
val trimmedUrl = url.trim() logApiFailure("authenticate") {
if (trimmedUrl.isBlank()) { val trimmedUrl = url.trim()
return@withContext null if (trimmedUrl.isBlank()) {
} return@logApiFailure null
}
api.update(baseUrl = trimmedUrl) api.update(baseUrl = trimmedUrl)
api.userApi.authenticateUserByName(username = username, password = password).content api.userApi.authenticateUserByName(username = username, password = password).content
}
} }
suspend fun getLibraries(): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getLibraries(): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getLibraries") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val response = api.userViewsApi.getUserViews(
userId = getUserId(),
presetViews = listOf(CollectionType.MOVIES, CollectionType.TVSHOWS),
includeHidden = false,
)
Log.d("getLibraries", response.content.toString())
response.content.items
} }
val response = api.userViewsApi.getUserViews(
userId = getUserId(),
presetViews = listOf(CollectionType.MOVIES, CollectionType.TVSHOWS),
includeHidden = false,
)
Log.d("getLibraries", response.content.toString())
response.content.items
} }
suspend fun getLibraryContent(libraryId: UUID): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getLibraryContent(libraryId: UUID): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getLibraryContent($libraryId)") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val getItemsRequest = GetItemsRequest(
userId = getUserId(),
enableImages = true,
parentId = libraryId,
fields = itemFields,
enableUserData = true,
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
recursive = true,
)
val response = api.itemsApi.getItems(getItemsRequest)
Log.d("getLibraryContent", response.content.toString())
response.content.items
} }
val getItemsRequest = GetItemsRequest(
userId = getUserId(),
enableImages = true,
parentId = libraryId,
fields = itemFields,
enableUserData = true,
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
recursive = true,
)
val response = api.itemsApi.getItems(getItemsRequest)
Log.d("getLibraryContent", response.content.toString())
response.content.items
} }
suspend fun getSuggestions(): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getSuggestions(): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getSuggestions") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val userId = getUserId() ?: return@logApiFailure emptyList()
val response = api.suggestionsApi.getSuggestions(
userId = userId,
mediaType = listOf(MediaType.VIDEO),
type = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
limit = 8,
enableTotalRecordCount = true,
)
Log.d("getSuggestions", response.content.toString())
response.content.items
} }
val userId = getUserId() ?: return@withContext emptyList()
val response = api.suggestionsApi.getSuggestions(
userId = userId,
mediaType = listOf(MediaType.VIDEO),
type = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
limit = 8,
enableTotalRecordCount = true,
)
Log.d("getSuggestions", response.content.toString())
response.content.items
} }
suspend fun getContinueWatching(): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getContinueWatching(): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getContinueWatching") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val userId = getUserId() ?: return@logApiFailure emptyList()
val getResumeItemsRequest = GetResumeItemsRequest(
userId = userId,
fields = itemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE),
enableUserData = true,
startIndex = 0,
)
val response: Response<BaseItemDtoQueryResult> = api.itemsApi.getResumeItems(getResumeItemsRequest)
Log.d("getContinueWatching", response.content.toString())
response.content.items
} }
val userId = getUserId() ?: return@withContext emptyList()
val getResumeItemsRequest = GetResumeItemsRequest(
userId = userId,
fields = itemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE),
enableUserData = true,
startIndex = 0,
)
val response: Response<BaseItemDtoQueryResult> = api.itemsApi.getResumeItems(getResumeItemsRequest)
Log.d("getContinueWatching", response.content.toString())
response.content.items
} }
suspend fun getNextUpEpisodes(): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getNextUpEpisodes(): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getNextUpEpisodes") {
throw IllegalStateException("Not configured") if (!ensureConfigured()) {
throw IllegalStateException("Not configured")
}
val getNextUpRequest = GetNextUpRequest(
userId = getUserId(),
fields = itemFields,
enableResumable = false,
)
val result = api.tvShowsApi.getNextUp(getNextUpRequest)
Log.d("getNextUpEpisodes", result.content.toString())
result.content.items
} }
val getNextUpRequest = GetNextUpRequest(
userId = getUserId(),
fields = itemFields,
enableResumable = false,
)
val result = api.tvShowsApi.getNextUp(getNextUpRequest)
Log.d("getNextUpEpisodes", result.content.toString())
result.content.items
} }
suspend fun getLatestFromLibrary(libraryId: UUID): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getLatestFromLibrary(libraryId: UUID): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getLatestFromLibrary($libraryId)") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val response = api.userLibraryApi.getLatestMedia(
userId = getUserId(),
parentId = libraryId,
fields = itemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE, BaseItemKind.SEASON),
limit = 10,
)
Log.d("getLatestFromLibrary", response.content.toString())
response.content
} }
val response = api.userLibraryApi.getLatestMedia(
userId = getUserId(),
parentId = libraryId,
fields = itemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE, BaseItemKind.SEASON),
limit = 10,
)
Log.d("getLatestFromLibrary", response.content.toString())
response.content
} }
suspend fun getItemInfo(mediaId: UUID): BaseItemDto? = withContext(Dispatchers.IO) { suspend fun getItemInfo(mediaId: UUID): BaseItemDto? = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getItemInfo($mediaId)") {
return@withContext null if (!ensureConfigured()) {
return@logApiFailure null
}
val result = api.userLibraryApi.getItem(itemId = mediaId, userId = getUserId())
Log.d("getItemInfo", result.content.toString())
result.content
} }
val result = api.userLibraryApi.getItem(itemId = mediaId, userId = getUserId())
Log.d("getItemInfo", result.content.toString())
result.content
} }
suspend fun getSeasons(seriesId: UUID): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getSeasons(seriesId: UUID): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getSeasons($seriesId)") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val result = api.tvShowsApi.getSeasons(
userId = getUserId(),
seriesId = seriesId,
fields = itemFields,
enableUserData = true,
)
Log.d("getSeasons", result.content.toString())
result.content.items
} }
val result = api.tvShowsApi.getSeasons(
userId = getUserId(),
seriesId = seriesId,
fields = itemFields,
enableUserData = true,
)
Log.d("getSeasons", result.content.toString())
result.content.items
} }
suspend fun getEpisodesInSeason(seriesId: UUID, seasonId: UUID): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getEpisodesInSeason(seriesId: UUID, seasonId: UUID): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getEpisodesInSeason(series=$seriesId, season=$seasonId)") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val result = api.tvShowsApi.getEpisodes(
userId = getUserId(),
seriesId = seriesId,
seasonId = seasonId,
fields = itemFields,
enableUserData = true,
)
Log.d("getEpisodesInSeason", result.content.toString())
result.content.items
} }
val result = api.tvShowsApi.getEpisodes(
userId = getUserId(),
seriesId = seriesId,
seasonId = seasonId,
fields = itemFields,
enableUserData = true,
)
Log.d("getEpisodesInSeason", result.content.toString())
result.content.items
} }
suspend fun getNextEpisodes(episodeId: UUID, count: Int): List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getNextEpisodes(episodeId: UUID, count: Int): List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getNextEpisodes($episodeId, count=$count)") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val episodeInfo = getItemInfo(episodeId) ?: return@logApiFailure emptyList()
val seriesId = episodeInfo.seriesId ?: return@logApiFailure emptyList()
val nextUpEpisodesResult = api.tvShowsApi.getEpisodes(
userId = getUserId(),
seriesId = seriesId,
enableUserData = true,
startItemId = episodeId,
limit = count,
)
val nextUpEpisodes = nextUpEpisodesResult.content.items
Log.d("getNextEpisodes", nextUpEpisodes.toString())
nextUpEpisodes
} }
val episodeInfo = getItemInfo(episodeId) ?: return@withContext emptyList()
val seriesId = episodeInfo.seriesId ?: return@withContext emptyList()
val nextUpEpisodesResult = api.tvShowsApi.getEpisodes(
userId = getUserId(),
seriesId = seriesId,
enableUserData = true,
startItemId = episodeId,
limit = count,
)
val nextUpEpisodes = nextUpEpisodesResult.content.items
Log.d("getNextEpisodes", nextUpEpisodes.toString())
nextUpEpisodes
} }
suspend fun getGenres(id: UUID? = null) : List<BaseItemDto> = withContext(Dispatchers.IO) { suspend fun getGenres(id: UUID? = null) : List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getGenres($id)") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val result = api.genresApi.getGenres(
userId = getUserId(),
parentId = id,
)
Log.d("getGenres", result.toString())
result.content.items
} }
val result = api.genresApi.getGenres(
userId = getUserId(),
parentId = id,
)
Log.d("getGenres", result.toString())
result.content.items
} }
suspend fun getMediaSources(mediaId: UUID): List<MediaSourceInfo> = withContext(Dispatchers.IO) { suspend fun getMediaSources(mediaId: UUID): List<MediaSourceInfo> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getMediaSources($mediaId)") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val result = api.mediaInfoApi.getPostedPlaybackInfo(
mediaId,
PlaybackInfoDto(
userId = getUserId(),
deviceProfile = null,
maxStreamingBitrate = 100_000_000,
),
)
Log.d("getMediaSources", result.toString())
result.content.mediaSources
} }
val result = api.mediaInfoApi.getPostedPlaybackInfo(
mediaId,
PlaybackInfoDto(
userId = getUserId(),
deviceProfile = null,
maxStreamingBitrate = 100_000_000,
),
)
Log.d("getMediaSources", result.toString())
result.content.mediaSources
} }
suspend fun getMediaSegments(mediaId: UUID) : List<MediaSegmentDto> = withContext(Dispatchers.IO) { suspend fun getMediaSegments(mediaId: UUID) : List<MediaSegmentDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getMediaSegments($mediaId)") {
return@withContext emptyList() if (!ensureConfigured()) {
return@logApiFailure emptyList()
}
val result = api.mediaSegmentsApi.getItemSegments(
itemId = mediaId,
//includeSegmentTypes = listOf(MediaSegmentType.INTRO)
)
Log.d("getMediaSegments", result.toString())
result.content.items
} }
val result = api.mediaSegmentsApi.getItemSegments(
itemId = mediaId,
//includeSegmentTypes = listOf(MediaSegmentType.INTRO)
)
Log.d("getMediaSegments", result.toString())
result.content.items
} }
suspend fun getPlaybackInfo( suspend fun getPlaybackInfo(
mediaId: UUID, mediaId: UUID,
deviceProfile: DeviceProfile, deviceProfile: DeviceProfile,
): PlaybackInfoResponse? = withContext(Dispatchers.IO) { ): PlaybackInfoResponse? = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getPlaybackInfo($mediaId)") {
return@withContext null if (!ensureConfigured()) {
return@logApiFailure null
}
api.mediaInfoApi.getPostedPlaybackInfo(
mediaId,
PlaybackInfoDto(
userId = getUserId(),
deviceProfile = deviceProfile,
enableDirectPlay = true,
enableDirectStream = true,
enableTranscoding = true,
allowVideoStreamCopy = true,
allowAudioStreamCopy = true,
autoOpenLiveStream = false,
),
).content
} }
api.mediaInfoApi.getPostedPlaybackInfo(
mediaId,
PlaybackInfoDto(
userId = getUserId(),
deviceProfile = deviceProfile,
enableDirectPlay = true,
enableDirectStream = true,
enableTranscoding = true,
allowVideoStreamCopy = true,
allowAudioStreamCopy = true,
autoOpenLiveStream = false,
),
).content
} }
fun getVideoStreamUrl( fun getVideoStreamUrl(
@@ -320,21 +353,28 @@ class JellyfinApiClient @Inject constructor(
tag: String? = null, tag: String? = null,
playSessionId: String? = null, playSessionId: String? = null,
liveStreamId: String? = null, liveStreamId: String? = null,
): String = api.videosApi.getVideoStreamUrl( ): String = try {
itemId = itemId, api.videosApi.getVideoStreamUrl(
container = container, itemId = itemId,
mediaSourceId = mediaSourceId, container = container,
static = true, mediaSourceId = mediaSourceId,
tag = tag, static = true,
playSessionId = playSessionId, tag = tag,
liveStreamId = liveStreamId, playSessionId = playSessionId,
) liveStreamId = liveStreamId,
)
} catch (error: Exception) {
Log.e(TAG, "getVideoStreamUrl($itemId) failed", error)
throw error
}
suspend fun getPublicSystemInfoVersion(): String? = withContext(Dispatchers.IO) { suspend fun getPublicSystemInfoVersion(): String? = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { logApiFailure("getPublicSystemInfoVersion") {
return@withContext null if (!ensureConfigured()) {
return@logApiFailure null
}
SystemApi(api).getPublicSystemInfo().content.version
} }
SystemApi(api).getPublicSystemInfo().content.version
} }
suspend fun reportPlaybackStart( suspend fun reportPlaybackStart(
@@ -342,24 +382,26 @@ class JellyfinApiClient @Inject constructor(
positionTicks: Long = 0L, positionTicks: Long = 0L,
reportContext: PlaybackReportContext, reportContext: PlaybackReportContext,
) = withContext(Dispatchers.IO) { ) = withContext(Dispatchers.IO) {
if (!ensureConfigured()) return@withContext logApiFailure("reportPlaybackStart($itemId)") {
api.playStateApi.reportPlaybackStart( if (!ensureConfigured()) return@logApiFailure
PlaybackStartInfo( api.playStateApi.reportPlaybackStart(
itemId = itemId, PlaybackStartInfo(
positionTicks = positionTicks, itemId = itemId,
canSeek = true, positionTicks = positionTicks,
isPaused = false, canSeek = true,
isMuted = false, isPaused = false,
mediaSourceId = reportContext.mediaSourceId, isMuted = false,
audioStreamIndex = reportContext.audioStreamIndex, mediaSourceId = reportContext.mediaSourceId,
subtitleStreamIndex = reportContext.subtitleStreamIndex, audioStreamIndex = reportContext.audioStreamIndex,
liveStreamId = reportContext.liveStreamId, subtitleStreamIndex = reportContext.subtitleStreamIndex,
playSessionId = reportContext.playSessionId, liveStreamId = reportContext.liveStreamId,
playMethod = reportContext.playMethod.toJellyfinPlayMethod(), playSessionId = reportContext.playSessionId,
repeatMode = RepeatMode.REPEAT_NONE, playMethod = reportContext.playMethod.toJellyfinPlayMethod(),
playbackOrder = PlaybackOrder.DEFAULT, repeatMode = RepeatMode.REPEAT_NONE,
), playbackOrder = PlaybackOrder.DEFAULT,
) ),
)
}
} }
suspend fun reportPlaybackProgress( suspend fun reportPlaybackProgress(
@@ -368,24 +410,26 @@ class JellyfinApiClient @Inject constructor(
isPaused: Boolean, isPaused: Boolean,
reportContext: PlaybackReportContext, reportContext: PlaybackReportContext,
) = withContext(Dispatchers.IO) { ) = withContext(Dispatchers.IO) {
if (!ensureConfigured()) return@withContext logApiFailure("reportPlaybackProgress($itemId)") {
api.playStateApi.reportPlaybackProgress( if (!ensureConfigured()) return@logApiFailure
PlaybackProgressInfo( api.playStateApi.reportPlaybackProgress(
itemId = itemId, PlaybackProgressInfo(
positionTicks = positionTicks, itemId = itemId,
canSeek = true, positionTicks = positionTicks,
isPaused = isPaused, canSeek = true,
isMuted = false, isPaused = isPaused,
mediaSourceId = reportContext.mediaSourceId, isMuted = false,
audioStreamIndex = reportContext.audioStreamIndex, mediaSourceId = reportContext.mediaSourceId,
subtitleStreamIndex = reportContext.subtitleStreamIndex, audioStreamIndex = reportContext.audioStreamIndex,
liveStreamId = reportContext.liveStreamId, subtitleStreamIndex = reportContext.subtitleStreamIndex,
playSessionId = reportContext.playSessionId, liveStreamId = reportContext.liveStreamId,
playMethod = reportContext.playMethod.toJellyfinPlayMethod(), playSessionId = reportContext.playSessionId,
repeatMode = RepeatMode.REPEAT_NONE, playMethod = reportContext.playMethod.toJellyfinPlayMethod(),
playbackOrder = PlaybackOrder.DEFAULT, repeatMode = RepeatMode.REPEAT_NONE,
), playbackOrder = PlaybackOrder.DEFAULT,
) ),
)
}
} }
suspend fun reportPlaybackStopped( suspend fun reportPlaybackStopped(
@@ -393,17 +437,30 @@ class JellyfinApiClient @Inject constructor(
positionTicks: Long, positionTicks: Long,
reportContext: PlaybackReportContext, reportContext: PlaybackReportContext,
) = withContext(Dispatchers.IO) { ) = withContext(Dispatchers.IO) {
if (!ensureConfigured()) return@withContext logApiFailure("reportPlaybackStopped($itemId)") {
api.playStateApi.reportPlaybackStopped( if (!ensureConfigured()) return@logApiFailure
PlaybackStopInfo( api.playStateApi.reportPlaybackStopped(
itemId = itemId, PlaybackStopInfo(
positionTicks = positionTicks, itemId = itemId,
mediaSourceId = reportContext.mediaSourceId, positionTicks = positionTicks,
liveStreamId = reportContext.liveStreamId, mediaSourceId = reportContext.mediaSourceId,
playSessionId = reportContext.playSessionId, liveStreamId = reportContext.liveStreamId,
failed = false, playSessionId = reportContext.playSessionId,
), failed = false,
) ),
)
}
}
private suspend fun <T> logApiFailure(operation: String, block: suspend () -> T): T {
return try {
block()
} catch (error: CancellationException) {
throw error
} catch (error: Exception) {
Log.e(TAG, "$operation failed", error)
throw error
}
} }
private fun PlaybackMethod.toJellyfinPlayMethod(): PlayMethod = when (this) { private fun PlaybackMethod.toJellyfinPlayMethod(): PlayMethod = when (this) {
@@ -411,4 +468,8 @@ class JellyfinApiClient @Inject constructor(
PlaybackMethod.DIRECT_STREAM -> PlayMethod.DIRECT_STREAM PlaybackMethod.DIRECT_STREAM -> PlayMethod.DIRECT_STREAM
PlaybackMethod.TRANSCODE -> PlayMethod.TRANSCODE PlaybackMethod.TRANSCODE -> PlayMethod.TRANSCODE
} }
companion object {
private const val TAG = "JellyfinApiClient"
}
} }