Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update jellyfin-sdk-kotlin to v1.6.1 #1506

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions app/src/main/java/org/jellyfin/mobile/app/ApiClientController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.jellyfin.mobile.data.entity.ServerEntity
import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.DeviceInfo
import org.jellyfin.sdk.model.serializer.toUUID

class ApiClientController(
private val appPreferences: AppPreferences,
Expand All @@ -27,7 +26,7 @@ class ApiClientController(
appPreferences.currentServerId = withContext(Dispatchers.IO) {
serverDao.getServerByHostname(hostname)?.id ?: serverDao.insert(hostname)
}
apiClient.baseUrl = hostname
apiClient.update(baseUrl = hostname)
}

suspend fun setupUser(serverId: Long, userId: String, accessToken: String) {
Expand Down Expand Up @@ -69,19 +68,21 @@ class ApiClientController(
}

private fun configureApiClientServer(server: ServerEntity?) {
apiClient.baseUrl = server?.hostname
apiClient.update(baseUrl = server?.hostname)
}

private fun configureApiClientUser(userId: String, accessToken: String) {
apiClient.userId = userId.toUUID()
apiClient.accessToken = accessToken
// Append user id to device id to ensure uniqueness across sessions
apiClient.deviceInfo = baseDeviceInfo.copy(id = baseDeviceInfo.id + userId)
apiClient.update(
accessToken = accessToken,
// Append user id to device id to ensure uniqueness across sessions
deviceInfo = baseDeviceInfo.copy(id = baseDeviceInfo.id + userId),
)
}

private fun resetApiClientUser() {
apiClient.userId = null
apiClient.accessToken = null
apiClient.deviceInfo = baseDeviceInfo
apiClient.update(
accessToken = null,
deviceInfo = baseDeviceInfo,
)
}
}
28 changes: 27 additions & 1 deletion app/src/main/java/org/jellyfin/mobile/app/AppModule.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jellyfin.mobile.app

import android.content.Context
import androidx.core.net.toUri
import coil.ImageLoader
import com.google.android.exoplayer2.ext.cronet.CronetDataSource
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
Expand All @@ -11,8 +12,10 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.SingleSampleMediaSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DataSpec
import com.google.android.exoplayer2.upstream.DefaultDataSource
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.ResolvingDataSource
import com.google.android.exoplayer2.util.Util
import kotlinx.coroutines.channels.Channel
import okhttp3.OkHttpClient
Expand All @@ -34,6 +37,8 @@ import org.jellyfin.mobile.utils.isLowRamDevice
import org.jellyfin.mobile.webapp.RemoteVolumeProvider
import org.jellyfin.mobile.webapp.WebViewFragment
import org.jellyfin.mobile.webapp.WebappFunctionChannel
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.util.AuthorizationHeaderBuilder
import org.koin.android.ext.koin.androidApplication
import org.koin.androidx.fragment.dsl.fragment
import org.koin.androidx.viewmodel.dsl.viewModel
Expand Down Expand Up @@ -81,6 +86,7 @@ val applicationModule = module {
// ExoPlayer factories
single<DataSource.Factory> {
val context: Context = get()
val apiClient: ApiClient = get()

val provider = CronetProvider.getAllProviders(context).firstOrNull { provider: CronetProvider ->
(provider.name == CronetProvider.PROVIDER_NAME_APP_PACKAGED) && provider.isEnabled
Expand All @@ -102,7 +108,27 @@ val applicationModule = module {
}
}

DefaultDataSource.Factory(context, baseDataSourceFactory)
val dataSourceFactory = DefaultDataSource.Factory(context, baseDataSourceFactory)

// Add authorization header. This is needed as we don't pass the
// access token in the URL for Android Auto.
ResolvingDataSource.Factory(dataSourceFactory) { dataSpec: DataSpec ->
// Only send authorization header if URI matches the jellyfin server
val baseUrlAuthority = apiClient.baseUrl?.toUri()?.authority

if (dataSpec.uri.authority == baseUrlAuthority) {
val authorizationHeaderString = AuthorizationHeaderBuilder.buildHeader(
clientName = apiClient.clientInfo.name,
clientVersion = apiClient.clientInfo.version,
deviceId = apiClient.deviceInfo.id,
deviceName = apiClient.deviceInfo.name,
accessToken = apiClient.accessToken,
)

dataSpec.withRequestHeaders(hashMapOf("Authorization" to authorizationHeaderString))
nielsvanvelzen marked this conversation as resolved.
Show resolved Hide resolved
} else
dataSpec
}
}
single<MediaSource.Factory> {
val context: Context = get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import org.jellyfin.sdk.api.operations.HlsSegmentApi
import org.jellyfin.sdk.api.operations.PlayStateApi
import org.jellyfin.sdk.api.operations.UserApi
import org.jellyfin.sdk.model.api.PlayMethod
import org.jellyfin.sdk.model.api.PlaybackOrder
import org.jellyfin.sdk.model.api.PlaybackProgressInfo
import org.jellyfin.sdk.model.api.PlaybackStartInfo
import org.jellyfin.sdk.model.api.PlaybackStopInfo
Expand Down Expand Up @@ -319,6 +320,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
positionTicks = mediaSource.startTimeMs * Constants.TICKS_PER_MILLISECOND,
volumeLevel = audioManager.getVolumeLevelPercent(),
repeatMode = RepeatMode.REPEAT_NONE,
playbackOrder = PlaybackOrder.DEFAULT,
),
)
} catch (e: ApiClientException) {
Expand Down Expand Up @@ -347,6 +349,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
positionTicks = playbackPositionMillis * Constants.TICKS_PER_MILLISECOND,
volumeLevel = (currentVolume - volumeRange.first) * Constants.PERCENT_MAX / volumeRange.width,
repeatMode = RepeatMode.REPEAT_NONE,
playbackOrder = PlaybackOrder.DEFAULT,
),
)
} catch (e: ApiClientException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import org.jellyfin.mobile.player.cast.ICastPlayerProvider
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.extensions.mediaUri
import org.jellyfin.mobile.utils.toast
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
Expand All @@ -53,7 +52,6 @@ import com.google.android.exoplayer2.MediaItem as ExoPlayerMediaItem

class MediaService : MediaBrowserServiceCompat() {
private val apiClientController: ApiClientController by inject()
private val apiClient: ApiClient by inject()
private val libraryBrowser: LibraryBrowser by inject()

private val serviceScope = MainScope()
Expand Down Expand Up @@ -177,12 +175,7 @@ class MediaService : MediaBrowserServiceCompat() {
loadingJob.join()

val items = try {
if (apiClient.userId != null) {
libraryBrowser.loadLibrary(parentId)
} else {
Timber.e("Missing userId in ApiClient")
null
}
libraryBrowser.loadLibrary(parentId)
} catch (e: ApiClientException) {
Timber.e(e)
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ import org.jellyfin.sdk.api.operations.UserViewsApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemDtoQueryResult
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.ItemFilter
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.MediaStreamProtocol
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.serializer.toUUID
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
Expand Down Expand Up @@ -178,10 +180,10 @@ class LibraryBrowser(
Timber.d("Searching for artist $artistQuery")
searchItems(artistQuery, BaseItemKind.MUSIC_ARTIST)
}?.let { artistId ->
itemsApi.getItemsByUserId(
itemsApi.getItems(
artistIds = listOf(artistId),
includeItemTypes = listOf(BaseItemKind.AUDIO),
sortBy = listOf("Random"),
sortBy = listOf(ItemSortBy.RANDOM),
recursive = true,
imageTypeLimit = 1,
enableImageTypes = listOf(ImageType.PRIMARY),
Expand All @@ -197,7 +199,7 @@ class LibraryBrowser(

// Fallback to generic search
Timber.d("Searching for '$searchQuery'")
val result by itemsApi.getItemsByUserId(
val result by itemsApi.getItems(
searchTerm = searchQuery,
includeItemTypes = listOf(BaseItemKind.AUDIO),
recursive = true,
Expand All @@ -214,7 +216,7 @@ class LibraryBrowser(
* Find a single specific item for the given [searchQuery] with a specific [type]
*/
private suspend fun searchItems(searchQuery: String, type: BaseItemKind): UUID? {
val result by itemsApi.getItemsByUserId(
val result by itemsApi.getItems(
searchTerm = searchQuery,
includeItemTypes = listOf(type),
recursive = true,
Expand All @@ -223,7 +225,7 @@ class LibraryBrowser(
limit = 1,
)

return result.items?.firstOrNull()?.id
return result.items.firstOrNull()?.id
}

suspend fun getDefaultRecents(): List<MediaMetadataCompat>? = getLibraries().firstOrNull()?.mediaId?.let { defaultLibrary ->
Expand All @@ -235,8 +237,8 @@ class LibraryBrowser(
private suspend fun getLibraries(): List<MediaBrowserCompat.MediaItem> {
val userViews by userViewsApi.getUserViews()

return userViews.items.orEmpty()
.filter { item -> item.collectionType.equals("music", ignoreCase = true) }
return userViews.items
.filter { item -> item.collectionType == CollectionType.MUSIC }
.map { item ->
val itemImageUrl = imageApi.getItemImageUrl(
itemId = item.id,
Expand Down Expand Up @@ -286,11 +288,11 @@ class LibraryBrowser(
}

private suspend fun getRecents(libraryId: UUID): List<MediaMetadataCompat>? {
val result by itemsApi.getItemsByUserId(
val result by itemsApi.getItems(
parentId = libraryId,
includeItemTypes = listOf(BaseItemKind.AUDIO),
filters = listOf(ItemFilter.IS_PLAYED),
sortBy = listOf("DatePlayed"),
sortBy = listOf(ItemSortBy.DATE_PLAYED),
sortOrder = listOf(SortOrder.DESCENDING),
recursive = true,
imageTypeLimit = 1,
Expand All @@ -307,12 +309,12 @@ class LibraryBrowser(
filterArtist: UUID? = null,
filterGenre: UUID? = null,
): List<MediaBrowserCompat.MediaItem>? {
val result by itemsApi.getItemsByUserId(
val result by itemsApi.getItems(
parentId = libraryId,
artistIds = filterArtist?.let(::listOf),
genreIds = filterGenre?.let(::listOf),
includeItemTypes = listOf(BaseItemKind.MUSIC_ALBUM),
sortBy = listOf(ItemFields.SORT_NAME.serialName),
sortBy = listOf(ItemSortBy.SORT_NAME),
recursive = true,
imageTypeLimit = 1,
enableImageTypes = listOf(ImageType.PRIMARY),
Expand All @@ -323,10 +325,10 @@ class LibraryBrowser(
}

private suspend fun getArtists(libraryId: UUID): List<MediaBrowserCompat.MediaItem>? {
val result by itemsApi.getItemsByUserId(
val result by itemsApi.getItems(
parentId = libraryId,
includeItemTypes = listOf(BaseItemKind.MUSIC_ARTIST),
sortBy = listOf(ItemFields.SORT_NAME.serialName),
sortBy = listOf(ItemSortBy.SORT_NAME),
recursive = true,
imageTypeLimit = 1,
enableImageTypes = listOf(ImageType.PRIMARY),
Expand All @@ -338,7 +340,6 @@ class LibraryBrowser(

private suspend fun getGenres(libraryId: UUID): List<MediaBrowserCompat.MediaItem>? {
val result by genresApi.getGenres(
userId = apiClient.userId,
parentId = libraryId,
imageTypeLimit = 1,
enableImageTypes = listOf(ImageType.PRIMARY),
Expand All @@ -349,10 +350,10 @@ class LibraryBrowser(
}

private suspend fun getPlaylists(libraryId: UUID): List<MediaBrowserCompat.MediaItem>? {
val result by itemsApi.getItemsByUserId(
val result by itemsApi.getItems(
parentId = libraryId,
includeItemTypes = listOf(BaseItemKind.PLAYLIST),
sortBy = listOf(ItemFields.SORT_NAME.serialName),
sortBy = listOf(ItemSortBy.SORT_NAME),
recursive = true,
imageTypeLimit = 1,
enableImageTypes = listOf(ImageType.PRIMARY),
Expand All @@ -363,9 +364,9 @@ class LibraryBrowser(
}

private suspend fun getAlbum(albumId: UUID): List<MediaMetadataCompat>? {
val result by itemsApi.getItemsByUserId(
val result by itemsApi.getItems(
parentId = albumId,
sortBy = listOf(ItemFields.SORT_NAME.serialName),
sortBy = listOf(ItemSortBy.SORT_NAME),
)

return result.extractItems("${LibraryPage.ALBUM}|$albumId")
Expand Down Expand Up @@ -404,7 +405,6 @@ class LibraryBrowser(
if (item.type == BaseItemKind.AUDIO) {
val uri = universalAudioApi.getUniversalAudioStreamUrl(
itemId = item.id,
userId = apiClient.userId,
deviceId = apiClient.deviceInfo.id,
maxStreamingBitrate = 140000000,
container = listOf(
Expand All @@ -419,11 +419,10 @@ class LibraryBrowser(
"wav",
"ogg",
),
transcodingProtocol = "hls",
transcodingProtocol = MediaStreamProtocol.HLS,
transcodingContainer = "ts",
audioCodec = "aac",
enableRemoteMedia = true,
includeCredentials = true,
)

builder.setMediaUri(uri)
Expand Down
Loading
Loading