mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
refactor: Enhance error handling in API calls with logging
This commit is contained in:
@@ -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] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user