Compare commits

...

17 Commits

Author SHA1 Message Date
cameron
3da856ec23 fix seekbar not updating when using ff/rw buttons
(cherry picked from commit 4a4efb2467)
2022-11-01 13:11:18 +01:00
Niels van Velzen
8148850206 Fix setExoPlayerTrack exception when allStreams is empty
(cherry picked from commit a73e04c216)
2022-11-01 13:11:08 +01:00
renovate[bot]
eb676a4ff5 Update dependency org.jellyfin.sdk:jellyfin-core to v1.3.7
(cherry picked from commit 7e386babde)
2022-11-01 13:10:33 +01:00
Niels van Velzen
7a478a77a4 Use MediaStream from SDK in MediaInfoCardView and InfoCardPresenter
(cherry picked from commit acc4533868)
2022-10-04 21:38:39 +02:00
Niels van Velzen
9723da673e Fix crash when media info contains a data stream 2022-10-04 20:25:32 +02:00
renovate[bot]
72e45a7b47 Update dependency org.jellyfin.sdk:jellyfin-core to v1.3.6
(cherry picked from commit 7fc9819b63)
2022-10-04 20:25:08 +02:00
Niels van Velzen
4e82f10f18 Use random device id when initializing QuickConnect
(cherry picked from commit 02b9fb60bb)
2022-09-06 19:15:07 +02:00
Niels van Velzen
c1b103e385 Set defaultFocusHighlightEnabled to false in TextUnderButton
(cherry picked from commit 8713cd5317)
2022-09-06 19:14:56 +02:00
Niels van Velzen
0fc8daf45e Set defaultFocusHighlightEnabled to false in DefaultCardView
Fixes an incompatibility with Android 13

(cherry picked from commit 352f472769)
2022-09-06 19:14:55 +02:00
renovate[bot]
bd98065766 Update dependency org.jellyfin.sdk:jellyfin-core to v1.3.5
(cherry picked from commit 1cafe6c539)
2022-08-20 10:31:03 +02:00
Niels van Velzen
4d68e3d31e Properly handle specials in item naming
(cherry picked from commit 14e7391bc2)
2022-08-20 10:30:53 +02:00
Niels van Velzen
9c33bb34ff Don't open library settings for unsupported types
Only collection types that use the grid should open the DisplayPreferencesScreen. Added a special case to open the Live TV (guide) settings as a bonus.

(cherry picked from commit 5bb1ee7819)
2022-08-20 10:30:41 +02:00
Niels van Velzen
9f7891f129 Fix ActivityLifecycleCallback calls on Android 9 and lower
(cherry picked from commit cd33bea725)
2022-08-20 10:30:32 +02:00
Niels van Velzen
11e5b9526d Migrate DefaultLifecycleObserver setup to androidx.startup
(cherry picked from commit a404b99ab1)
2022-08-20 10:30:13 +02:00
Niels van Velzen
a0fb27491c Allow app installation on external storage
(cherry picked from commit e592017602)
2022-08-20 10:30:04 +02:00
Niels van Velzen
b1babfe477 Fix alignment of ClockUserView in live TV guide
(cherry picked from commit 6960f808f2)
2022-08-20 10:29:54 +02:00
Niels van Velzen
ed3b92df08 Catch decoding exception in AuthenticationStore
Fixes corrupt JSON causing the app to crash

(cherry picked from commit 0074062633)
2022-08-20 10:29:46 +02:00
22 changed files with 169 additions and 77 deletions

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.jellyfin.androidtv">
package="org.jellyfin.androidtv"
android:installLocation="auto">
<!-- Android TV Integration -->
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
@@ -88,6 +89,9 @@
<meta-data
android:name="org.jellyfin.androidtv.SessionInitializer"
android:value="androidx.startup" />
<meta-data
android:name="org.jellyfin.androidtv.ProcessLifecycleInitializer"
android:value="androidx.startup" />
</provider>
<provider

View File

@@ -2,8 +2,6 @@ package org.jellyfin.androidtv
import android.app.Application
import android.content.Context
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.work.BackoffPolicy
@@ -15,16 +13,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.acra.ACRA
import org.jellyfin.androidtv.auth.repository.SessionRepository
import org.jellyfin.androidtv.data.eventhandling.SocketHandler
import org.jellyfin.androidtv.data.repository.NotificationsRepository
import org.jellyfin.androidtv.integration.LeanbackChannelWorker
import org.jellyfin.androidtv.telemetry.TelemetryService
import org.jellyfin.androidtv.util.AutoBitrate
import org.koin.android.ext.android.get
import org.koin.android.ext.android.getKoin
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.util.concurrent.TimeUnit
@Suppress("unused")
@@ -37,28 +31,6 @@ class JellyfinApplication : Application() {
val notificationsRepository by inject<NotificationsRepository>()
notificationsRepository.addDefaultNotifications()
// Register application lifecycle events
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
/**
* Called by the Process Lifecycle when the app is created. It is called after [onCreate].
*/
override fun onCreate(owner: LifecycleOwner) {
// Register activity lifecycle callbacks
getKoin().getAll<ActivityLifecycleCallbacks>().forEach(::registerActivityLifecycleCallbacks)
}
/**
* Called by the Process Lifecycle when the app is activated in the foreground (activity opened).
*/
override fun onStart(owner: LifecycleOwner) {
Timber.i("Process lifecycle started")
owner.lifecycleScope.launch {
get<SessionRepository>().restoreSession()
}
}
})
}
/**

View File

@@ -0,0 +1,50 @@
package org.jellyfin.androidtv
import android.app.Application
import android.content.Context
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.startup.AppInitializer
import androidx.startup.Initializer
import kotlinx.coroutines.launch
import org.jellyfin.androidtv.auth.repository.SessionRepository
import org.jellyfin.androidtv.di.KoinInitializer
import timber.log.Timber
@Suppress("unused")
class ProcessLifecycleInitializer : Initializer<Unit> {
override fun create(context: Context) {
val koin = AppInitializer.getInstance(context)
.initializeComponent(KoinInitializer::class.java)
.koin
// Register application lifecycle events
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
/**
* Called by the Process Lifecycle when the app is created. It is called after [onCreate].
*/
override fun onCreate(owner: LifecycleOwner) {
// Register activity lifecycle callbacks
val callbacks = koin.getAll<Application.ActivityLifecycleCallbacks>()
Timber.i("Registering ${callbacks.size} ActivityLifecycleCallbacks")
val app = context.applicationContext as Application
callbacks.forEach { callback -> app.registerActivityLifecycleCallbacks(callback) }
}
/**
* Called by the Process Lifecycle when the app is activated in the foreground (activity opened).
*/
override fun onStart(owner: LifecycleOwner) {
Timber.i("Process lifecycle started")
owner.lifecycleScope.launch {
koin.get<SessionRepository>().restoreSession()
}
}
})
}
override fun dependencies() = listOf(KoinInitializer::class.java)
}

View File

@@ -1,6 +1,7 @@
package org.jellyfin.androidtv.auth.store
import android.content.Context
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
@@ -47,8 +48,15 @@ class AuthenticationStore(
// No store found
if (!storePath.exists()) return emptyMap()
// Parse JSON document
val root = try {
json.parseToJsonElement(storePath.readText()).jsonObject
} catch (e: SerializationException) {
Timber.e(e, "Unable to read JSON")
JsonObject(emptyMap())
}
// Check for version
val root = json.parseToJsonElement(storePath.readText()).jsonObject
return when (root["version"]?.jsonPrimitive?.intOrNull) {
1 -> json.decodeFromJsonElement<Map<UUID, AuthenticationStoreServer>>(root["servers"]!!)
null -> {

View File

@@ -12,6 +12,7 @@ interface UserViewsRepository {
fun isSupported(collectionType: String?): Boolean
fun allowViewSelection(collectionType: String?): Boolean
fun allowGridView(collectionType: String?): Boolean
}
class UserViewsRepositoryImpl(
@@ -26,7 +27,8 @@ class UserViewsRepositoryImpl(
}
override fun isSupported(collectionType: String?) = collectionType !in unsupportedCollectionTypes
override fun allowViewSelection(collectionType: String?) = collectionType != CollectionType.Music
override fun allowViewSelection(collectionType: String?) = collectionType !in disallowViewSelectionCollectionTypes
override fun allowGridView(collectionType: String?) = collectionType !in disallowGridViewCollectionTypes
private companion object {
private val unsupportedCollectionTypes = arrayOf(
@@ -34,5 +36,16 @@ class UserViewsRepositoryImpl(
CollectionType.Games,
CollectionType.Folders
)
private val disallowViewSelectionCollectionTypes = arrayOf(
CollectionType.livetv,
CollectionType.Music,
CollectionType.Photos,
)
private val disallowGridViewCollectionTypes = arrayOf(
CollectionType.livetv,
CollectionType.Music
)
}
}

View File

@@ -90,7 +90,7 @@ val appModule = module {
single<NotificationsRepository> { NotificationsRepositoryImpl(get(), get()) }
viewModel { StartupViewModel(get(), get(), get(), get()) }
viewModel { UserLoginViewModel(get(), get(), get()) }
viewModel { UserLoginViewModel(get(), get(), get(), get(defaultDeviceInfo)) }
viewModel { ServerAddViewModel(get()) }
viewModel { NextUpViewModel(get(), get(), get(), get()) }

View File

@@ -1,6 +1,7 @@
package org.jellyfin.androidtv.ui
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
@@ -22,6 +23,7 @@ class TextUnderButton @JvmOverloads constructor(
isFocusable = true
isFocusableInTouchMode = true
descendantFocusability = FOCUS_BLOCK_DESCENDANTS
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) defaultFocusHighlightEnabled = false
}
fun setLabel(text: String?) {

View File

@@ -3,6 +3,7 @@ package org.jellyfin.androidtv.ui.card
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Build
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.LayoutInflater
@@ -25,6 +26,7 @@ class DefaultCardView @JvmOverloads constructor(
init {
isFocusable = true
descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) defaultFocusHighlightEnabled = false
}
val binding = ViewCardDefaultBinding.inflate(LayoutInflater.from(context), this, true)

View File

@@ -1,23 +1,15 @@
package org.jellyfin.androidtv.ui.card
import android.content.Context
import android.widget.FrameLayout
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.graphics.Typeface
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.TextUtils
import android.util.AttributeSet
import android.widget.TextView
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.text.bold
import androidx.core.text.toSpannable
import org.jellyfin.androidtv.databinding.ViewCardMediaInfoBinding
import org.jellyfin.androidtv.util.Utils
import org.jellyfin.apiclient.model.entities.MediaStream
import org.jellyfin.apiclient.model.entities.MediaStreamType
import org.jellyfin.sdk.model.api.MediaStream
import org.jellyfin.sdk.model.api.MediaStreamType
import java.text.NumberFormat
import java.util.*
class MediaInfoCardView @JvmOverloads constructor(
context: Context,
@@ -30,13 +22,13 @@ class MediaInfoCardView @JvmOverloads constructor(
fun setMediaStream(mediaStream: MediaStream) {
binding.title.text = mediaStream.type.toString()
binding.entries.text = SpannableStringBuilder().apply {
if (mediaStream.type != MediaStreamType.Video) mediaStream.language?.let { addRow("Language", it) }
if (mediaStream.type != MediaStreamType.VIDEO) mediaStream.language?.let { addRow("Language", it) }
mediaStream.codec?.let { addRow("Codec", it) }
mediaStream.profile?.let { addRow("Profile", it) }
mediaStream.level?.let { addRow("Level", it.toString()) }
mediaStream.channelLayout?.let { addRow("Layout", it) }
if (mediaStream.type == MediaStreamType.Video) {
if (mediaStream.type == MediaStreamType.VIDEO) {
if (mediaStream.width != null && mediaStream.height != null) addRow("Resolution", "${mediaStream.width}x${mediaStream.height}")
if (mediaStream.isAnamorphic == true) addRow("Anamorphic")
if (mediaStream.isInterlaced) addRow("Interlaced")

View File

@@ -756,7 +756,7 @@ public class FullDetailsActivity extends BaseActivity implements RecordingIndica
HeaderItem header = new HeaderItem("Media Details"+(ms.getContainer() != null ? " (" +ms.getContainer()+")" : ""));
ArrayObjectAdapter infoAdapter = new ArrayObjectAdapter(new InfoCardPresenter());
for (MediaStream stream : ms.getMediaStreams()) {
infoAdapter.add(stream);
infoAdapter.add(ModelCompat.asSdk(stream));
}
adapter.add(new ListRow(header, infoAdapter));

View File

@@ -410,7 +410,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
newPos = getRealTimeProgress();
// live tv
} else if (hasInitializedVideoManager()) {
if (!isPlaying() && mSeekPosition != -1) {
if (currentSkipPos != 0 || (!isPlaying() && mSeekPosition != -1)) {
newPos = mSeekPosition;
// use seekPosition until playback starts
} else if (isPlaying()) {
@@ -1270,9 +1270,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
public void skip(int msec) {
if (hasInitializedVideoManager() && (isPlaying() || isPaused()) && spinnerOff && mVideoManager.getCurrentPosition() > 0) { //guard against skipping before playback has truly begun
pause();
mHandler.removeCallbacks(skipRunnable);
stopReportLoop();
refreshCurrentPosition();
currentSkipPos = Utils.getSafeSeekPosition((currentSkipPos == 0 ? mCurrentPosition : currentSkipPos) + msec, getDuration());

View File

@@ -529,7 +529,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
}
public boolean setExoPlayerTrack(int index, @Nullable MediaStreamType streamType, @Nullable List<MediaStream> allStreams) {
if (!nativeMode || !isInitialized() || allStreams == null || streamType != MediaStreamType.Subtitle && streamType != MediaStreamType.Audio)
if (!nativeMode || !isInitialized() || allStreams == null || allStreams.isEmpty() || streamType != MediaStreamType.Subtitle && streamType != MediaStreamType.Audio)
return false;
int chosenTrackType = streamType == MediaStreamType.Subtitle ? C.TRACK_TYPE_TEXT : C.TRACK_TYPE_AUDIO;

View File

@@ -9,9 +9,11 @@ import kotlinx.coroutines.launch
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.data.repository.UserViewsRepository
import org.jellyfin.androidtv.ui.browsing.DisplayPreferencesScreen
import org.jellyfin.androidtv.ui.livetv.GuideOptionsScreen
import org.jellyfin.androidtv.ui.preference.dsl.OptionsFragment
import org.jellyfin.androidtv.ui.preference.dsl.link
import org.jellyfin.androidtv.ui.preference.dsl.optionsScreen
import org.jellyfin.apiclient.model.entities.CollectionType
import org.koin.android.ext.android.inject
class LibrariesPreferencesScreen : OptionsFragment() {
@@ -37,11 +39,20 @@ class LibrariesPreferencesScreen : OptionsFragment() {
link {
title = it.name
if (userViewsRepository.allowGridView(it.collectionType)) {
icon = R.drawable.ic_folder
withFragment<DisplayPreferencesScreen>(bundleOf(
DisplayPreferencesScreen.ARG_ALLOW_VIEW_SELECTION to allowViewSelection,
DisplayPreferencesScreen.ARG_PREFERENCES_ID to it.displayPreferencesId,
))
} else if (it.collectionType == CollectionType.livetv) {
icon = R.drawable.ic_guide
withFragment<GuideOptionsScreen>()
} else {
icon = R.drawable.ic_folder
enabled = false
}
}
}
}

View File

@@ -6,7 +6,7 @@ import android.view.ViewGroup;
import androidx.leanback.widget.Presenter;
import org.jellyfin.androidtv.ui.card.MediaInfoCardView;
import org.jellyfin.apiclient.model.entities.MediaStream;
import org.jellyfin.sdk.model.api.MediaStream;
public class InfoCardPresenter extends Presenter {
class ViewHolder extends Presenter.ViewHolder {
@@ -15,7 +15,6 @@ public class InfoCardPresenter extends Presenter {
public ViewHolder(View view) {
super(view);
mInfoCardView = (MediaInfoCardView) view;
}
public void setItem(MediaStream ms) {
@@ -44,6 +43,5 @@ public class InfoCardPresenter extends Presenter {
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
//Log.d(TAG, "onUnbindViewHolder");
}
}

View File

@@ -1,6 +1,7 @@
package org.jellyfin.androidtv.ui.shared
import android.app.Activity
import android.os.Build
import android.os.Bundle
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.preference.constant.AppTheme
@@ -14,22 +15,38 @@ class AppThemeCallbacks(
private var lastPreferencesTheme: AppTheme? = null
override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) activity.applyThemeOnCreated()
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) activity.applyThemeOnCreated()
}
override fun onActivityPreResumed(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) activity.applyThemeOnResume()
}
override fun onActivityResumed(activity: Activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) activity.applyThemeOnResume()
}
private fun Activity.applyThemeOnCreated() {
userPreferences[UserPreferences.appTheme].let {
Timber.i("Applying theme: %s", it)
activity.setTheme(ThemeManager.getTheme(activity, it))
when (activity) {
setTheme(ThemeManager.getTheme(this, it))
when (this) {
is PreferencesActivity -> lastPreferencesTheme = it
else -> lastTheme = it
}
}
}
override fun onActivityPreResumed(activity: Activity) {
val lastThemeForActivity = if (activity is PreferencesActivity) lastPreferencesTheme else lastTheme
private fun Activity.applyThemeOnResume() {
val lastThemeForActivity = if (this is PreferencesActivity) lastPreferencesTheme else lastTheme
userPreferences[UserPreferences.appTheme].let {
if (lastThemeForActivity != null && lastThemeForActivity != it) {
Timber.i("Recreating activity to apply new theme: %s -> %s", lastThemeForActivity, it)
activity.recreate()
recreate()
}
}
}

View File

@@ -2,6 +2,7 @@ package org.jellyfin.androidtv.ui.shared
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import org.jellyfin.androidtv.auth.repository.SessionRepository
import org.jellyfin.androidtv.ui.preference.PreferencesActivity
@@ -26,14 +27,22 @@ class AuthenticatedUserCallbacks(
}
override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) {
val name = activity::class.qualifiedName
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) activity.checkAuthentication()
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) activity.checkAuthentication()
}
private fun Activity.checkAuthentication() {
val name = this::class.qualifiedName
if (name in ignoredClassNames) {
Timber.i("Activity $name is ignored")
} else if (sessionRepository.currentSession.value == null) {
Timber.w("Activity $name started without a session, bouncing to StartupActivity")
activity.startActivity(Intent(activity, StartupActivity::class.java))
activity.finish()
startActivity(Intent(this, StartupActivity::class.java))
finish()
}
}
}

View File

@@ -22,9 +22,11 @@ import org.jellyfin.androidtv.auth.model.UnknownQuickConnectState
import org.jellyfin.androidtv.auth.model.User
import org.jellyfin.androidtv.auth.repository.AuthenticationRepository
import org.jellyfin.androidtv.auth.repository.ServerRepository
import org.jellyfin.androidtv.util.sdk.forUser
import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.quickConnectApi
import org.jellyfin.sdk.model.DeviceInfo
import timber.log.Timber
import java.util.UUID
import kotlin.time.Duration.Companion.seconds
@@ -33,6 +35,7 @@ class UserLoginViewModel(
jellyfin: Jellyfin,
private val serverRepository: ServerRepository,
private val authenticationRepository: AuthenticationRepository,
private val defaultDeviceInfo: DeviceInfo,
) : ViewModel() {
private val _loginState = MutableStateFlow<LoginState?>(null)
val loginState = _loginState.asStateFlow()
@@ -74,6 +77,7 @@ class UserLoginViewModel(
_quickConnectState.emit(UnknownQuickConnectState)
quickConnectSecret = null
quickConnectApi.baseUrl = server.address
quickConnectApi.deviceInfo = defaultDeviceInfo.forUser(UUID.randomUUID())
try {
val response by quickConnectApi.quickConnectApi.initiate()

View File

@@ -36,14 +36,22 @@ fun BaseItemDto?.canPlay() = this != null
&& (!isFolderItem || childCount == null || childCount > 0)
fun BaseItemDto.getFullName(context: Context): String? = when (baseItemType) {
BaseItemType.Episode -> listOfNotNull(
seriesName,
parentIndexNumber?.let { context.getString(R.string.lbl_season_number, it) },
indexNumber?.let { start ->
indexNumberEnd?.let { end -> context.getString(R.string.lbl_episode_range, start, end) }
?: context.getString(R.string.lbl_episode_number, start)
BaseItemType.Episode -> buildList {
add(seriesName)
if (parentIndexNumber == 0) {
add(context.getString(R.string.episode_name_special))
} else {
if (parentIndexNumber != null)
add(context.getString(R.string.lbl_season_number, parentIndexNumber))
if (indexNumber != null && indexNumberEnd != null)
add(context.getString(R.string.lbl_episode_range, indexNumber, indexNumberEnd))
else if (indexNumber != null)
add(context.getString(R.string.lbl_episode_number, indexNumber))
}
).filter { it.isNotEmpty() }.joinToString(" ")
}.filterNot { it.isNullOrBlank() }.joinToString(" ")
// we actually want the artist name if available
BaseItemType.Audio,
BaseItemType.MusicAlbum -> listOfNotNull(albumArtist, name)

View File

@@ -444,11 +444,13 @@ fun LegacyMediaStream.asSdk(): ModernMediaStream = ModernMediaStream(
isAnamorphic = this.isAnamorphic,
)
fun LegacyMediaStreamType.asSdk(): ModernMediaStreamType = when (this) {
fun LegacyMediaStreamType?.asSdk(): ModernMediaStreamType = when (this) {
LegacyMediaStreamType.Audio -> ModernMediaStreamType.AUDIO
LegacyMediaStreamType.Video -> ModernMediaStreamType.VIDEO
LegacyMediaStreamType.Subtitle -> ModernMediaStreamType.SUBTITLE
LegacyMediaStreamType.EmbeddedImage -> ModernMediaStreamType.EMBEDDED_IMAGE
// Note: The apiclient doesn't have the DATA member and defaults to "null"
null -> ModernMediaStreamType.DATA
}
fun LegacySubtitleDeliveryMethod.asSdk(): ModernSubtitleDeliveryMethod = when (this) {

View File

@@ -92,7 +92,7 @@
android:layout_height="150sp"
android:id="@+id/programImage"
android:layout_gravity="left|top"
android:layout_margin="15sp" />
android:layout_margin="20sp" />
<TextView
android:layout_width="wrap_content"

View File

@@ -495,4 +495,6 @@
<string name="pref_crash_report_logs_disabled">Logs will not be included in crash reports</string>
<string name="crash_report_toast">Oops! Something went wrong, a crash report was sent to your Jellyfin server.</string>
<string name="server_setup_incomplete">The setup of this server has not been completed. Open Jellyfin in a web browser to finish setup before signing in.</string>
<string name="enable_picture_viewer_title">Enable new picture viewer</string>
<string name="episode_name_special">special</string>
</resources>

View File

@@ -24,7 +24,7 @@ glide = "4.13.2"
gson = "2.8.9"
jellyfin-apiclient = "v0.7.10"
jellyfin-exoplayer-ffmpegextension = "2.18.1+1"
jellyfin-sdk = "1.3.4"
jellyfin-sdk = "1.3.7"
junit = "4.13.2"
kenburnsview = "1.0.7"
koin = "3.2.0"