From 92eaefe6e138d3ad58b51ca20ba5f2d0f9795753 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 13:40:45 +0100 Subject: [PATCH 01/24] skip credits --- .../dev/jdtech/jellyfin/PlayerActivity.kt | 12 +++-- .../src/main/res/layout/exo_player_view.xml | 1 - .../jdtech/jellyfin/utils/DownloaderImpl.kt | 8 ++++ core/src/main/res/values-it/strings.xml | 2 + core/src/main/res/values/strings.xml | 6 ++- .../3.json | 48 ++++++++++++++++++- .../jellyfin/database/ServerDatabase.kt | 3 +- .../jellyfin/database/ServerDatabaseDao.kt | 10 ++++ .../java/dev/jdtech/jellyfin/models/Credit.kt | 33 +++++++++++++ .../dev/jdtech/jellyfin/models/CreditDto.kt | 25 ++++++++++ .../jellyfin/repository/JellyfinRepository.kt | 3 ++ .../repository/JellyfinRepositoryImpl.kt | 24 ++++++++++ .../JellyfinRepositoryOfflineImpl.kt | 7 +++ .../viewmodels/PlayerActivityViewModel.kt | 23 ++++++++- .../src/main/res/values-b+es+419/strings.xml | 1 - .../video/src/main/res/values-bg/strings.xml | 1 - .../src/main/res/values-cs-rCZ/strings.xml | 1 - .../video/src/main/res/values-de/strings.xml | 1 - .../src/main/res/values-es-rMX/strings.xml | 1 - .../video/src/main/res/values-es/strings.xml | 1 - .../video/src/main/res/values-fr/strings.xml | 1 - .../video/src/main/res/values-hu/strings.xml | 1 - .../video/src/main/res/values-it/strings.xml | 1 - .../video/src/main/res/values-iw/strings.xml | 1 - .../video/src/main/res/values-ko/strings.xml | 1 - .../video/src/main/res/values-nl/strings.xml | 1 - .../video/src/main/res/values-pl/strings.xml | 1 - .../src/main/res/values-pt-rBR/strings.xml | 1 - .../video/src/main/res/values-pt/strings.xml | 1 - .../video/src/main/res/values-ru/strings.xml | 1 - .../video/src/main/res/values-sk/strings.xml | 1 - .../video/src/main/res/values-sl/strings.xml | 1 - .../video/src/main/res/values-sv/strings.xml | 1 - .../video/src/main/res/values-uk/strings.xml | 1 - .../video/src/main/res/values-vi/strings.xml | 1 - .../src/main/res/values-zh-rCN/strings.xml | 1 - .../src/main/res/values-zh-rTW/strings.xml | 1 - player/video/src/main/res/values/strings.xml | 1 - 38 files changed, 194 insertions(+), 35 deletions(-) create mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt create mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/CreditDto.kt diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index 84982dbb..1b7b8099 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -32,6 +32,7 @@ import androidx.media3.ui.DefaultTimeBar import androidx.media3.ui.PlayerView import androidx.navigation.navArgs import dagger.hilt.android.AndroidEntryPoint +import dev.jdtech.jellyfin.core.R as CoreR import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment @@ -136,10 +137,15 @@ class PlayerActivity : BasePlayerActivity() { videoNameTextView.text = currentItemTitle // Skip Intro button - skipIntroButton.isVisible = !isInPictureInPictureMode && currentIntro != null + skipIntroButton.isVisible = !isInPictureInPictureMode && (currentIntro != null || currentCredit != null) + skipIntroButton.text = if (currentCredit != null) getString(CoreR.string.skip_credit_button) else getString(CoreR.string.skip_intro_button) skipIntroButton.setOnClickListener { - currentIntro?.let { - binding.playerView.player?.seekTo((it.introEnd * 1000).toLong()) + if (currentIntro != null) { + currentIntro?.let { + binding.playerView.player?.seekTo((it.introEnd * 1000).toLong()) + } + } else if (currentCredit != null) { + binding.playerView.player?.seekToNext() } } diff --git a/app/phone/src/main/res/layout/exo_player_view.xml b/app/phone/src/main/res/layout/exo_player_view.xml index 03b28bbd..16b91058 100644 --- a/app/phone/src/main/res/layout/exo_player_view.xml +++ b/app/phone/src/main/res/layout/exo_player_view.xml @@ -55,7 +55,6 @@ android:layout_gravity="end|bottom" android:layout_marginEnd="24dp" android:layout_marginBottom="64dp" - android:text="@string/player_controls_skip_intro" android:textColor="@android:color/white" android:visibility="gone" app:backgroundTint="@color/player_background" diff --git a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt index 0b670493..66630ed8 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt @@ -15,6 +15,7 @@ import dev.jdtech.jellyfin.models.FindroidMovie import dev.jdtech.jellyfin.models.FindroidSource import dev.jdtech.jellyfin.models.TrickPlayManifest import dev.jdtech.jellyfin.models.UiText +import dev.jdtech.jellyfin.models.toCreditDto import dev.jdtech.jellyfin.models.toFindroidEpisodeDto import dev.jdtech.jellyfin.models.toFindroidMediaStreamDto import dev.jdtech.jellyfin.models.toFindroidMovieDto @@ -46,6 +47,7 @@ class DownloaderImpl( try { val source = jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId } val intro = jellyfinRepository.getIntroTimestamps(item.id) + val credit = jellyfinRepository.getCreditTimestamps(item.id) val trickPlayManifest = jellyfinRepository.getTrickPlayManifest(item.id) val trickPlayData = if (trickPlayManifest != null) { jellyfinRepository.getTrickPlayData( @@ -81,6 +83,9 @@ class DownloaderImpl( if (intro != null) { database.insertIntro(intro.toIntroDto(item.id)) } + if (credit != null) { + database.insertCredit(credit.toCreditDto(item.id)) + } if (trickPlayManifest != null && trickPlayData != null) { downloadTrickPlay(item, trickPlayManifest, trickPlayData) } @@ -110,6 +115,9 @@ class DownloaderImpl( if (intro != null) { database.insertIntro(intro.toIntroDto(item.id)) } + if (credit != null) { + database.insertCredit(credit.toCreditDto(item.id)) + } if (trickPlayManifest != null && trickPlayData != null) { downloadTrickPlay(item, trickPlayManifest, trickPlayData) } diff --git a/core/src/main/res/values-it/strings.xml b/core/src/main/res/values-it/strings.xml index 5a961d89..c5303760 100644 --- a/core/src/main/res/values-it/strings.xml +++ b/core/src/main/res/values-it/strings.xml @@ -185,4 +185,6 @@ Diretta TV Riproduci Rimuovi dai preferiti + Salta intro + Prossimo episodio \ No newline at end of file diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index ec8684c0..87846ec4 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -140,9 +140,9 @@ Video output Audio output Intro Skipper - Requires ConfusedPolarBear\'s Intro Skipper plugin to be installed on the server + Requires ConfusedPolarBear\'s Intro Skipper plugin to be installed on the server.\nInstall jumoog\'s Intro Skipper v0.1.8 or higher to skip end credits. Trick Play - Requires nicknsy\'s Jellyscrub plugin to be installed on the server + Requires nicknsy\'s Jellyscrub plugin to be installed on the server Addresses Add address Add server address @@ -185,4 +185,6 @@ Unmark as played Add to favorites Remove from favorites + Skip Intro + Next episode diff --git a/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/3.json b/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/3.json index 22b5646d..6742f17d 100644 --- a/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/3.json +++ b/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/3.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 3, - "identityHash": "3cb9aaa3295b9e461cb94dfc708258ed", + "identityHash": "2611f255654b3d481be40f080a8b5401", "entities": [ { "tableName": "servers", @@ -758,6 +758,50 @@ "indices": [], "foreignKeys": [] }, + { + "tableName": "credits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` TEXT NOT NULL, `start` REAL NOT NULL, `end` REAL NOT NULL, `showAt` REAL NOT NULL, `hideAt` REAL NOT NULL, PRIMARY KEY(`itemId`))", + "fields": [ + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "showAt", + "columnName": "showAt", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "hideAt", + "columnName": "hideAt", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "itemId" + ] + }, + "indices": [], + "foreignKeys": [] + }, { "tableName": "userdata", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` TEXT NOT NULL, `itemId` TEXT NOT NULL, `played` INTEGER NOT NULL, `favorite` INTEGER NOT NULL, `playbackPositionTicks` INTEGER NOT NULL, `toBeSynced` INTEGER NOT NULL, PRIMARY KEY(`userId`, `itemId`))", @@ -813,7 +857,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3cb9aaa3295b9e461cb94dfc708258ed')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2611f255654b3d481be40f080a8b5401')" ] } } \ No newline at end of file diff --git a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt index 1d84c004..f63625cf 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt @@ -4,6 +4,7 @@ import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import dev.jdtech.jellyfin.models.CreditDto import dev.jdtech.jellyfin.models.FindroidEpisodeDto import dev.jdtech.jellyfin.models.FindroidMediaStreamDto import dev.jdtech.jellyfin.models.FindroidMovieDto @@ -18,7 +19,7 @@ import dev.jdtech.jellyfin.models.TrickPlayManifestDto import dev.jdtech.jellyfin.models.User @Database( - entities = [Server::class, ServerAddress::class, User::class, FindroidMovieDto::class, FindroidShowDto::class, FindroidSeasonDto::class, FindroidEpisodeDto::class, FindroidSourceDto::class, FindroidMediaStreamDto::class, TrickPlayManifestDto::class, IntroDto::class, FindroidUserDataDto::class], + entities = [Server::class, ServerAddress::class, User::class, FindroidMovieDto::class, FindroidShowDto::class, FindroidSeasonDto::class, FindroidEpisodeDto::class, FindroidSourceDto::class, FindroidMediaStreamDto::class, TrickPlayManifestDto::class, IntroDto::class, CreditDto::class, FindroidUserDataDto::class], version = 3, autoMigrations = [ AutoMigration(from = 2, to = 3), diff --git a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt index 1d78c789..b6d2c271 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt @@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction import androidx.room.Update +import dev.jdtech.jellyfin.models.CreditDto import dev.jdtech.jellyfin.models.FindroidEpisodeDto import dev.jdtech.jellyfin.models.FindroidMediaStreamDto import dev.jdtech.jellyfin.models.FindroidMovieDto @@ -222,6 +223,15 @@ interface ServerDatabaseDao { @Query("DELETE FROM intros WHERE itemId = :itemId") fun deleteIntro(itemId: UUID) + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertCredit(credit: CreditDto) + + @Query("SELECT * FROM credits WHERE itemId = :itemId") + fun getCredit(itemId: UUID): CreditDto? + + @Query("DELETE FROM credits WHERE itemId = :itemId") + fun deleteCredit(itemId: UUID) + @Query("SELECT * FROM seasons") fun getSeasons(): List diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt b/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt new file mode 100644 index 00000000..1f4311b6 --- /dev/null +++ b/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt @@ -0,0 +1,33 @@ +package dev.jdtech.jellyfin.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Credit( + @SerialName("Credits") + val credit: Credits, +) + +@Serializable +data class Credits( + @SerialName("IntroStart") + val introStart: Double, + @SerialName("IntroEnd") + val introEnd: Double, + @SerialName("ShowSkipPromptAt") + val showSkipPromptAt: Double, + @SerialName("HideSkipPromptAt") + val hideSkipPromptAt: Double, +) + +fun CreditDto.toCredit(): Credit { + return Credit( + credit = Credits( + introStart = start, + introEnd = end, + showSkipPromptAt = showAt, + hideSkipPromptAt = hideAt, + ) + ) +} diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/CreditDto.kt b/data/src/main/java/dev/jdtech/jellyfin/models/CreditDto.kt new file mode 100644 index 00000000..4f24bf68 --- /dev/null +++ b/data/src/main/java/dev/jdtech/jellyfin/models/CreditDto.kt @@ -0,0 +1,25 @@ +package dev.jdtech.jellyfin.models + +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.util.UUID + +@Entity(tableName = "credits") +data class CreditDto( + @PrimaryKey + val itemId: UUID, + val start: Double, + val end: Double, + val showAt: Double, + val hideAt: Double, +) + +fun Credit.toCreditDto(itemId: UUID): CreditDto { + return CreditDto( + itemId = itemId, + start = credit.introStart, + end = credit.introEnd, + showAt = credit.showSkipPromptAt, + hideAt = credit.hideSkipPromptAt, + ) +} diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt index 8b902f55..7f31e281 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt @@ -1,6 +1,7 @@ package dev.jdtech.jellyfin.repository import androidx.paging.PagingData +import dev.jdtech.jellyfin.models.Credit import dev.jdtech.jellyfin.models.FindroidCollection import dev.jdtech.jellyfin.models.FindroidEpisode import dev.jdtech.jellyfin.models.FindroidItem @@ -86,6 +87,8 @@ interface JellyfinRepository { suspend fun getIntroTimestamps(itemId: UUID): Intro? + suspend fun getCreditTimestamps(itemId: UUID): Credit? + suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest? suspend fun getTrickPlayData(itemId: UUID, width: Int): ByteArray? diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt index f5019f0c..8c887036 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt @@ -7,6 +7,7 @@ import androidx.paging.PagingData import dev.jdtech.jellyfin.AppPreferences import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.database.ServerDatabaseDao +import dev.jdtech.jellyfin.models.Credit import dev.jdtech.jellyfin.models.FindroidCollection import dev.jdtech.jellyfin.models.FindroidEpisode import dev.jdtech.jellyfin.models.FindroidItem @@ -17,6 +18,7 @@ import dev.jdtech.jellyfin.models.FindroidSource import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.SortBy import dev.jdtech.jellyfin.models.TrickPlayManifest +import dev.jdtech.jellyfin.models.toCredit import dev.jdtech.jellyfin.models.toFindroidCollection import dev.jdtech.jellyfin.models.toFindroidEpisode import dev.jdtech.jellyfin.models.toFindroidItem @@ -372,6 +374,28 @@ class JellyfinRepositoryImpl( } } + override suspend fun getCreditTimestamps(itemId: UUID): Credit? = + withContext(Dispatchers.IO) { + val credit = database.getCredit(itemId)?.toCredit() + + if (credit != null) { + return@withContext credit + } + + // https://github.com/ConfusedPolarBear/intro-skipper/blob/master/docs/api.md + val pathParameters = mutableMapOf() + pathParameters["itemId"] = itemId + + try { + return@withContext jellyfinApi.api.get( + "/Episode/{itemId}/IntroSkipperSegments", + pathParameters, + ).content + } catch (e: Exception) { + return@withContext null + } + } + override suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest? = withContext(Dispatchers.IO) { val trickPlayManifest = database.getTrickPlayManifest(itemId) diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt index 2fb4a399..389749a1 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt @@ -5,6 +5,7 @@ import androidx.paging.PagingData import dev.jdtech.jellyfin.AppPreferences import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.database.ServerDatabaseDao +import dev.jdtech.jellyfin.models.Credit import dev.jdtech.jellyfin.models.FindroidCollection import dev.jdtech.jellyfin.models.FindroidEpisode import dev.jdtech.jellyfin.models.FindroidItem @@ -15,6 +16,7 @@ import dev.jdtech.jellyfin.models.FindroidSource import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.SortBy import dev.jdtech.jellyfin.models.TrickPlayManifest +import dev.jdtech.jellyfin.models.toCredit import dev.jdtech.jellyfin.models.toFindroidEpisode import dev.jdtech.jellyfin.models.toFindroidMovie import dev.jdtech.jellyfin.models.toFindroidSeason @@ -184,6 +186,11 @@ class JellyfinRepositoryOfflineImpl( database.getIntro(itemId)?.toIntro() } + override suspend fun getCreditTimestamps(itemId: UUID): Credit? = + withContext(Dispatchers.IO) { + database.getCredit(itemId)?.toCredit() + } + override suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest? = withContext(Dispatchers.IO) { database.getTrickPlayManifest(itemId)?.toTrickPlayManifest() diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index b2ee5e9d..377c9d9d 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -18,6 +18,8 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import dagger.hilt.android.lifecycle.HiltViewModel import dev.jdtech.jellyfin.AppPreferences +import dev.jdtech.jellyfin.models.Credit +import dev.jdtech.jellyfin.models.Credits import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.mpv.MPVPlayer @@ -55,6 +57,7 @@ constructor( UiState( currentItemTitle = "", currentIntro = null, + currentCredit = null, currentTrickPlay = null, fileLoaded = false, ), @@ -65,12 +68,14 @@ constructor( val eventsChannelFlow = eventsChannel.receiveAsFlow() private val intros: MutableMap = mutableMapOf() + private val credits: MutableMap = mutableMapOf() private val trickPlays: MutableMap = mutableMapOf() data class UiState( val currentItemTitle: String, val currentIntro: Intro?, + val currentCredit: Credits?, val currentTrickPlay: BifData?, val fileLoaded: Boolean, ) @@ -152,6 +157,9 @@ constructor( jellyfinRepository.getIntroTimestamps(item.itemId)?.let { intro -> intros[item.itemId] = intro } + jellyfinRepository.getCreditTimestamps(item.itemId)?.let { credit -> + credits[item.itemId] = credit.credit + } } Timber.d("Stream url: $streamUrl") @@ -241,10 +249,11 @@ constructor( handler.postDelayed(this, 5000L) } } - val introCheckRunnable = object : Runnable { + val skipCheckRunnable = object : Runnable { override fun run() { if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) { val itemId = UUID.fromString(player.currentMediaItem!!.mediaId) + intros[itemId]?.let { intro -> val seconds = player.currentPosition / 1000.0 if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) { @@ -253,12 +262,22 @@ constructor( } _uiState.update { it.copy(currentIntro = null) } } + + credits[itemId]?.let { credit -> + val seconds = player.currentPosition / 1000.0 + if (seconds > credit.showSkipPromptAt && seconds < credit.hideSkipPromptAt) { + _uiState.update { it.copy(currentCredit = credit) } + return@let + } + _uiState.update { it.copy(currentCredit = null) } + } } + handler.postDelayed(this, 1000L) } } handler.post(playbackProgressRunnable) - if (intros.isNotEmpty()) handler.post(introCheckRunnable) + if (intros.isNotEmpty()) handler.post(skipCheckRunnable) } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { diff --git a/player/video/src/main/res/values-b+es+419/strings.xml b/player/video/src/main/res/values-b+es+419/strings.xml index b76a8749..3a11a2b3 100644 --- a/player/video/src/main/res/values-b+es+419/strings.xml +++ b/player/video/src/main/res/values-b+es+419/strings.xml @@ -7,7 +7,6 @@ Rebobinar> Salir de reproductor Avanzar - Saltar intro Externo Saltar atrás Reproducir pausar diff --git a/player/video/src/main/res/values-bg/strings.xml b/player/video/src/main/res/values-bg/strings.xml index 55bf2fa1..7fc902d3 100644 --- a/player/video/src/main/res/values-bg/strings.xml +++ b/player/video/src/main/res/values-bg/strings.xml @@ -14,6 +14,5 @@ Truques Pausa na reprodução Sair do reprodutor - Pular introdução Insira imagem em imagem \ No newline at end of file diff --git a/player/video/src/main/res/values-cs-rCZ/strings.xml b/player/video/src/main/res/values-cs-rCZ/strings.xml index daae75dd..f033c9bf 100644 --- a/player/video/src/main/res/values-cs-rCZ/strings.xml +++ b/player/video/src/main/res/values-cs-rCZ/strings.xml @@ -9,7 +9,6 @@ Přeskočit zpět Přehrát pauza Přetočit - Přeskočit úvod Ukončit přehrávač Rychlý posun vpřed Přeskočit vpřed diff --git a/player/video/src/main/res/values-de/strings.xml b/player/video/src/main/res/values-de/strings.xml index 37ba16d3..b0e68dc9 100644 --- a/player/video/src/main/res/values-de/strings.xml +++ b/player/video/src/main/res/values-de/strings.xml @@ -8,5 +8,4 @@ Player verlassen Wiederholen Vorspulen - Intro überspringen diff --git a/player/video/src/main/res/values-es-rMX/strings.xml b/player/video/src/main/res/values-es-rMX/strings.xml index a3da9e7f..ff19a455 100644 --- a/player/video/src/main/res/values-es-rMX/strings.xml +++ b/player/video/src/main/res/values-es-rMX/strings.xml @@ -8,7 +8,6 @@ Salir de reproductor Atrasar Avanzar - Saltar intro Saltar adelante Avance Bloquea el reproductor diff --git a/player/video/src/main/res/values-es/strings.xml b/player/video/src/main/res/values-es/strings.xml index fa5bc468..6b101382 100644 --- a/player/video/src/main/res/values-es/strings.xml +++ b/player/video/src/main/res/values-es/strings.xml @@ -8,7 +8,6 @@ Salir del reproductor Rebobinar Avanzar - Saltar introducción Saltar adelante Bloquea el reproductor Reproducir pausar diff --git a/player/video/src/main/res/values-fr/strings.xml b/player/video/src/main/res/values-fr/strings.xml index c4f13ee4..94ad3382 100644 --- a/player/video/src/main/res/values-fr/strings.xml +++ b/player/video/src/main/res/values-fr/strings.xml @@ -8,7 +8,6 @@ Quitter le lecteur Rembobiner Avance rapide - Ignorer l\'introduction Verrouille le lecteur Lecture / Pause Retour en arrière diff --git a/player/video/src/main/res/values-hu/strings.xml b/player/video/src/main/res/values-hu/strings.xml index 0d87f497..a1a2c227 100644 --- a/player/video/src/main/res/values-hu/strings.xml +++ b/player/video/src/main/res/values-hu/strings.xml @@ -8,7 +8,6 @@ Kilépés a lejátszóból Visszatekerés Előrepörgetés - Intro kihagyása Zárolja a lejátszót Ugrás vissza Ugrás előre diff --git a/player/video/src/main/res/values-it/strings.xml b/player/video/src/main/res/values-it/strings.xml index caf23db6..083bfe92 100644 --- a/player/video/src/main/res/values-it/strings.xml +++ b/player/video/src/main/res/values-it/strings.xml @@ -14,7 +14,6 @@ Riavvolgi Blocca il player Esci dal player - Salta intro Attiva picture in picture Nessuno \ No newline at end of file diff --git a/player/video/src/main/res/values-iw/strings.xml b/player/video/src/main/res/values-iw/strings.xml index c2fa91a4..dae114b9 100644 --- a/player/video/src/main/res/values-iw/strings.xml +++ b/player/video/src/main/res/values-iw/strings.xml @@ -8,7 +8,6 @@ צא מהנגן הרצה אחורה הרצה קדימה - דלג פתיח דלג קדימה נועל את הנגן דלג אחורה diff --git a/player/video/src/main/res/values-ko/strings.xml b/player/video/src/main/res/values-ko/strings.xml index 8993e36d..c6ee35dd 100644 --- a/player/video/src/main/res/values-ko/strings.xml +++ b/player/video/src/main/res/values-ko/strings.xml @@ -8,7 +8,6 @@ 되감기 빨리 감기 플레이어 나가기 - 오프닝 스킵 플레이어 잠금 Trickplay 뒤로 건너뛰기 diff --git a/player/video/src/main/res/values-nl/strings.xml b/player/video/src/main/res/values-nl/strings.xml index 6b69e563..c021b257 100644 --- a/player/video/src/main/res/values-nl/strings.xml +++ b/player/video/src/main/res/values-nl/strings.xml @@ -8,5 +8,4 @@ Sluit speler Terugspoelen Snel vooruit - Intro overslaan diff --git a/player/video/src/main/res/values-pl/strings.xml b/player/video/src/main/res/values-pl/strings.xml index ee45bc6f..fba463a4 100644 --- a/player/video/src/main/res/values-pl/strings.xml +++ b/player/video/src/main/res/values-pl/strings.xml @@ -8,7 +8,6 @@ Zamknij odtwarzacz Przewiń Przewiń do przodu - Pomiń czołówkę Zablokuj odtwarzacz Skocz do tyłu Trickplay diff --git a/player/video/src/main/res/values-pt-rBR/strings.xml b/player/video/src/main/res/values-pt-rBR/strings.xml index d394cac9..6eb53b13 100644 --- a/player/video/src/main/res/values-pt-rBR/strings.xml +++ b/player/video/src/main/res/values-pt-rBR/strings.xml @@ -8,7 +8,6 @@ Sair do reprodutor Retroceder Avanço rápido - Pular introdução Bloqueia o reprodutor Saltar para trás Miniatura de pré-visualização diff --git a/player/video/src/main/res/values-pt/strings.xml b/player/video/src/main/res/values-pt/strings.xml index 0cc14bd2..b07f00b0 100644 --- a/player/video/src/main/res/values-pt/strings.xml +++ b/player/video/src/main/res/values-pt/strings.xml @@ -14,6 +14,5 @@ Barra de progresso Avançar Pular para trás - Pular introdução Insira imagem em imagem \ No newline at end of file diff --git a/player/video/src/main/res/values-ru/strings.xml b/player/video/src/main/res/values-ru/strings.xml index f4e97c9d..fd12b06a 100644 --- a/player/video/src/main/res/values-ru/strings.xml +++ b/player/video/src/main/res/values-ru/strings.xml @@ -8,7 +8,6 @@ Выйти из проигрывателя Перемотка Быстрая перемотка - Пропустить заставку Блокировка Перейти назад Плей пауза diff --git a/player/video/src/main/res/values-sk/strings.xml b/player/video/src/main/res/values-sk/strings.xml index ccb936f2..2dc95d79 100644 --- a/player/video/src/main/res/values-sk/strings.xml +++ b/player/video/src/main/res/values-sk/strings.xml @@ -8,7 +8,6 @@ Zavrieť prehrávač Pretočiť dozadu Pretočiť dopredu - Preskočiť úvodnú zvučku Zamkne prehrávač Preskočiť späť Preskočiť dopredu diff --git a/player/video/src/main/res/values-sl/strings.xml b/player/video/src/main/res/values-sl/strings.xml index 72b604ef..a5e04c51 100644 --- a/player/video/src/main/res/values-sl/strings.xml +++ b/player/video/src/main/res/values-sl/strings.xml @@ -8,7 +8,6 @@ Izhod iz predvajalnika Previj nazaj Navijaj naprej - Preskoči uvod Zaklene predvajalnik Preskoči nazaj Predvajaj ustavi diff --git a/player/video/src/main/res/values-sv/strings.xml b/player/video/src/main/res/values-sv/strings.xml index 3e9998a1..c7086524 100644 --- a/player/video/src/main/res/values-sv/strings.xml +++ b/player/video/src/main/res/values-sv/strings.xml @@ -8,5 +8,4 @@ Avsluta spelare Spola tillbaka Spola framåt - Hoppa över intro \ No newline at end of file diff --git a/player/video/src/main/res/values-uk/strings.xml b/player/video/src/main/res/values-uk/strings.xml index 8b54d3f5..41c42b80 100644 --- a/player/video/src/main/res/values-uk/strings.xml +++ b/player/video/src/main/res/values-uk/strings.xml @@ -8,5 +8,4 @@ Відмотка Швидке перемотування Вийти з плеєра - Пропустити вступ \ No newline at end of file diff --git a/player/video/src/main/res/values-vi/strings.xml b/player/video/src/main/res/values-vi/strings.xml index f3061da1..44964e2c 100644 --- a/player/video/src/main/res/values-vi/strings.xml +++ b/player/video/src/main/res/values-vi/strings.xml @@ -8,7 +8,6 @@ Thoát khỏi trình xem Tua lùi Tua tới - Bỏ qua đoạn mở đầu Bỏ qua / Trở về Khoá trình phát Phát / Tạm dừng diff --git a/player/video/src/main/res/values-zh-rCN/strings.xml b/player/video/src/main/res/values-zh-rCN/strings.xml index 4d329a21..59cd4ed1 100644 --- a/player/video/src/main/res/values-zh-rCN/strings.xml +++ b/player/video/src/main/res/values-zh-rCN/strings.xml @@ -8,7 +8,6 @@ 退出播放器 快退 快进 - 跳过片头 锁定播放器 跳回 播放暂停 diff --git a/player/video/src/main/res/values-zh-rTW/strings.xml b/player/video/src/main/res/values-zh-rTW/strings.xml index 0c81db38..c4bf81c9 100644 --- a/player/video/src/main/res/values-zh-rTW/strings.xml +++ b/player/video/src/main/res/values-zh-rTW/strings.xml @@ -8,7 +8,6 @@ 關閉播放器 倒帶 快轉 - 跳過片頭 鎖定播放器 跳回 播放暫停 diff --git a/player/video/src/main/res/values/strings.xml b/player/video/src/main/res/values/strings.xml index fe54ebe6..eb6df272 100644 --- a/player/video/src/main/res/values/strings.xml +++ b/player/video/src/main/res/values/strings.xml @@ -11,7 +11,6 @@ Play pause Rewind Exit player - Skip Intro Fast forward Skip forward Trickplay From 85ff16d84306a4b8623f8de4943a1608c0b13593 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 13:41:40 +0100 Subject: [PATCH 02/24] skip credits --- .../dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index 377c9d9d..417333da 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -18,7 +18,6 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import dagger.hilt.android.lifecycle.HiltViewModel import dev.jdtech.jellyfin.AppPreferences -import dev.jdtech.jellyfin.models.Credit import dev.jdtech.jellyfin.models.Credits import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.PlayerItem From 14eb313b1e506c12a96ede949f957e55a812ae8a Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 13:45:41 +0100 Subject: [PATCH 03/24] fix lint --- data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt b/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt index 1f4311b6..f19f607f 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt @@ -28,6 +28,6 @@ fun CreditDto.toCredit(): Credit { introEnd = end, showSkipPromptAt = showAt, hideSkipPromptAt = hideAt, - ) + ), ) } From 7f02f3de0a5cd00ae81a2e0656fb2e4e470800c2 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 14:14:28 +0100 Subject: [PATCH 04/24] fix lint --- app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index 1b7b8099..a366fd48 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -32,7 +32,6 @@ import androidx.media3.ui.DefaultTimeBar import androidx.media3.ui.PlayerView import androidx.navigation.navArgs import dagger.hilt.android.AndroidEntryPoint -import dev.jdtech.jellyfin.core.R as CoreR import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment @@ -44,6 +43,7 @@ import dev.jdtech.jellyfin.viewmodels.PlayerEvents import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +import dev.jdtech.jellyfin.core.R as CoreR var isControlsLocked: Boolean = false From 4a3a22de37454d0ff859190929042a977fd4a1e1 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 15:24:49 +0100 Subject: [PATCH 05/24] fix PlayerActivityViewModel.kt --- .../dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index 417333da..a9854612 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -277,6 +277,7 @@ constructor( } handler.post(playbackProgressRunnable) if (intros.isNotEmpty()) handler.post(skipCheckRunnable) + if (credits.isNotEmpty()) handler.post(skipCheckRunnable) } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { From 916d71a08514e37b9de3b89d73c98bf930dadc04 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 15:40:53 +0100 Subject: [PATCH 06/24] fix PlayerActivityViewModel.kt --- .../viewmodels/PlayerActivityViewModel.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index a9854612..a5351a76 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -248,11 +248,10 @@ constructor( handler.postDelayed(this, 5000L) } } - val skipCheckRunnable = object : Runnable { + val introCheckRunnable = object : Runnable { override fun run() { if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) { val itemId = UUID.fromString(player.currentMediaItem!!.mediaId) - intros[itemId]?.let { intro -> val seconds = player.currentPosition / 1000.0 if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) { @@ -261,7 +260,14 @@ constructor( } _uiState.update { it.copy(currentIntro = null) } } - + handler.postDelayed(this, 1000L) + } + } + } + val creditCheckRunnable = object : Runnable { + override fun run() { + if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) { + val itemId = UUID.fromString(player.currentMediaItem!!.mediaId) credits[itemId]?.let { credit -> val seconds = player.currentPosition / 1000.0 if (seconds > credit.showSkipPromptAt && seconds < credit.hideSkipPromptAt) { @@ -271,13 +277,12 @@ constructor( _uiState.update { it.copy(currentCredit = null) } } } - handler.postDelayed(this, 1000L) } } handler.post(playbackProgressRunnable) - if (intros.isNotEmpty()) handler.post(skipCheckRunnable) - if (credits.isNotEmpty()) handler.post(skipCheckRunnable) + if (intros.isNotEmpty()) handler.post(introCheckRunnable) + if (credits.isNotEmpty()) handler.post(creditCheckRunnable) } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { From 9711f4c4fba6cdd093faa00b85d3869a9b29f202 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 17:25:12 +0100 Subject: [PATCH 07/24] Close player on the last episode of a series --- app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index a366fd48..575d31ee 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -145,7 +145,8 @@ class PlayerActivity : BasePlayerActivity() { binding.playerView.player?.seekTo((it.introEnd * 1000).toLong()) } } else if (currentCredit != null) { - binding.playerView.player?.seekToNext() + if (binding.playerView.player?.hasNextMediaItem() == true) binding.playerView.player?.seekToNext() + else finish() } } From a740d3fc717476d76388a7834c3a941908f59581 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 17:29:44 +0100 Subject: [PATCH 08/24] fix lint --- .../src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index 575d31ee..d0c398bc 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -145,8 +145,11 @@ class PlayerActivity : BasePlayerActivity() { binding.playerView.player?.seekTo((it.introEnd * 1000).toLong()) } } else if (currentCredit != null) { - if (binding.playerView.player?.hasNextMediaItem() == true) binding.playerView.player?.seekToNext() - else finish() + if (binding.playerView.player?.hasNextMediaItem() == true) { + binding.playerView.player?.seekToNext() + } else { + finish() + } } } From 6402a6a0c4c59e28bf51c6686f35b75571df0a17 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 19:16:44 +0100 Subject: [PATCH 09/24] fix and change pref_player_intro_skipper_summary --- core/src/main/res/values-it/strings.xml | 4 ++-- core/src/main/res/values/strings.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/res/values-it/strings.xml b/core/src/main/res/values-it/strings.xml index c5303760..28fff6fd 100644 --- a/core/src/main/res/values-it/strings.xml +++ b/core/src/main/res/values-it/strings.xml @@ -135,7 +135,7 @@ Aggiungi Connessione Rapida Salta intro - Richiede il plugin Intro Skipper di ConfusedPolarBear installato sul server + Richiede il plugin Intro Skipper di ConfusedPolarBear installato sul server.\nInstalla Intro Skipper v0.1.8.0 o maggiore di jumoog per saltare anche i titoli di coda Scorri orizzontalmente per posizionarti avanti o indietro Gesto posizionamento Audio @@ -148,7 +148,7 @@ Tema scuro AMOLED Usa il tema AMOLED con lo sfondo nero Anteprima - Richiede il plugin Jellyscrub di nicknsy installato sul server + Richiede il plugin Jellyscrub di nicknsy installato sul server Dimensione Utilizzando Findroid accetti l\'informativa sulla privacy che afferma che non raccogliamo alcun dato %1$d-%2$d. %3$s diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 87846ec4..c7224682 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -140,7 +140,7 @@ Video output Audio output Intro Skipper - Requires ConfusedPolarBear\'s Intro Skipper plugin to be installed on the server.\nInstall jumoog\'s Intro Skipper v0.1.8 or higher to skip end credits. + Requires ConfusedPolarBear\'s Intro Skipper plugin to be installed on the server.\nInstall jumoog\'s Intro Skipper v0.1.8.0 or higher to skip end credits. Trick Play Requires nicknsy\'s Jellyscrub plugin to be installed on the server Addresses From 2b9831af56f74f98dc1e6ff111636e900c6f789c Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 19:59:01 +0100 Subject: [PATCH 10/24] fix next episode no credits --- .../dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index a5351a76..970c3c49 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -303,6 +303,8 @@ constructor( } _uiState.update { it.copy(currentItemTitle = itemTitle) } + _uiState.update { it.copy(currentCredit = null) } + jellyfinRepository.postPlaybackStart(item.itemId) if (appPreferences.playerTrickPlay) { From f9454029f7315448585313517204e0e09cb07ae6 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 22 Jan 2024 22:59:04 +0100 Subject: [PATCH 11/24] clean code --- .../viewmodels/PlayerActivityViewModel.kt | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index 970c3c49..64329cae 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -248,41 +248,31 @@ constructor( handler.postDelayed(this, 5000L) } } - val introCheckRunnable = object : Runnable { + val skipCheckRunnable = object : Runnable { override fun run() { if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) { val itemId = UUID.fromString(player.currentMediaItem!!.mediaId) + val seconds = player.currentPosition / 1000.0 intros[itemId]?.let { intro -> - val seconds = player.currentPosition / 1000.0 if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) { _uiState.update { it.copy(currentIntro = intro) } return@let } _uiState.update { it.copy(currentIntro = null) } } - handler.postDelayed(this, 1000L) - } - } - } - val creditCheckRunnable = object : Runnable { - override fun run() { - if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) { - val itemId = UUID.fromString(player.currentMediaItem!!.mediaId) credits[itemId]?.let { credit -> - val seconds = player.currentPosition / 1000.0 if (seconds > credit.showSkipPromptAt && seconds < credit.hideSkipPromptAt) { _uiState.update { it.copy(currentCredit = credit) } return@let } _uiState.update { it.copy(currentCredit = null) } } + handler.postDelayed(this, 1000L) } - handler.postDelayed(this, 1000L) } } handler.post(playbackProgressRunnable) - if (intros.isNotEmpty()) handler.post(introCheckRunnable) - if (credits.isNotEmpty()) handler.post(creditCheckRunnable) + if (intros.isNotEmpty() || credits.isNotEmpty()) handler.post(skipCheckRunnable) } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { From 674699aeabd831406de031449275e692bd7ba57c Mon Sep 17 00:00:00 2001 From: cd16b Date: Tue, 23 Jan 2024 09:58:26 +0100 Subject: [PATCH 12/24] fix code --- .../viewmodels/PlayerActivityViewModel.kt | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index 64329cae..5d95532b 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -253,22 +253,26 @@ constructor( if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) { val itemId = UUID.fromString(player.currentMediaItem!!.mediaId) val seconds = player.currentPosition / 1000.0 - intros[itemId]?.let { intro -> - if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) { - _uiState.update { it.copy(currentIntro = intro) } - return@let + if (intros.isNotEmpty()) { + intros[itemId]?.let { intro -> + if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) { + _uiState.update { it.copy(currentIntro = intro) } + return@let + } + _uiState.update { it.copy(currentIntro = null) } } - _uiState.update { it.copy(currentIntro = null) } } - credits[itemId]?.let { credit -> - if (seconds > credit.showSkipPromptAt && seconds < credit.hideSkipPromptAt) { - _uiState.update { it.copy(currentCredit = credit) } - return@let + if (credits.isNotEmpty()) { + credits[itemId]?.let { credit -> + if (seconds > credit.showSkipPromptAt && seconds < credit.hideSkipPromptAt) { + _uiState.update { it.copy(currentCredit = credit) } + return@let + } + _uiState.update { it.copy(currentCredit = null) } } - _uiState.update { it.copy(currentCredit = null) } } - handler.postDelayed(this, 1000L) } + handler.postDelayed(this, 1000L) } } handler.post(playbackProgressRunnable) From 05730a513c1c4c6fd5113f96c5422e3a348c58da Mon Sep 17 00:00:00 2001 From: cd16b Date: Tue, 23 Jan 2024 11:51:45 +0100 Subject: [PATCH 13/24] change text hasNextMediaItem() false --- .../main/java/dev/jdtech/jellyfin/PlayerActivity.kt | 10 +++++++++- core/src/main/res/values-it/strings.xml | 1 + core/src/main/res/values/strings.xml | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index d0c398bc..6045cb2c 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -138,7 +138,15 @@ class PlayerActivity : BasePlayerActivity() { // Skip Intro button skipIntroButton.isVisible = !isInPictureInPictureMode && (currentIntro != null || currentCredit != null) - skipIntroButton.text = if (currentCredit != null) getString(CoreR.string.skip_credit_button) else getString(CoreR.string.skip_intro_button) + skipIntroButton.text = if (currentCredit != null) { + if (binding.playerView.player?.hasNextMediaItem() == true) { + getString(CoreR.string.skip_credit_button) + } else { + getString(CoreR.string.skip_credit_button_last) + } + } else { + getString(CoreR.string.skip_intro_button) + } skipIntroButton.setOnClickListener { if (currentIntro != null) { currentIntro?.let { diff --git a/core/src/main/res/values-it/strings.xml b/core/src/main/res/values-it/strings.xml index 28fff6fd..f374fad4 100644 --- a/core/src/main/res/values-it/strings.xml +++ b/core/src/main/res/values-it/strings.xml @@ -187,4 +187,5 @@ Rimuovi dai preferiti Salta intro Prossimo episodio + Chiudi player \ No newline at end of file diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index c7224682..19e2df36 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -187,4 +187,5 @@ Remove from favorites Skip Intro Next episode + Close player From 50b39d6658d0a0d7aabe902fd252a38c1e980bda Mon Sep 17 00:00:00 2001 From: Cd16d <98320806+cd16b@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:36:21 +0100 Subject: [PATCH 14/24] Update strings.xml --- core/src/main/res/values/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index a2ceab5a..3e1dc584 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -147,7 +147,6 @@ Requires ConfusedPolarBear\'s Intro Skipper plugin to be installed on the server.\nInstall jumoog\'s Intro Skipper v0.1.8.0 or higher to skip end credits. Trick Play Requires nicknsy\'s Jellyscrub plugin to be installed on the server - Requires nicknsy\'s Jellyscrub plugin to be installed on the server Chapter markers Display chapter markers on the timebar Addresses From 4a3afe62effae244fc734a53fc10558319d17e25 Mon Sep 17 00:00:00 2001 From: Cd16d <98320806+cd16b@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:45:36 +0100 Subject: [PATCH 15/24] Update strings-da --- player/video/src/main/res/values-da/strings.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/player/video/src/main/res/values-da/strings.xml b/player/video/src/main/res/values-da/strings.xml index b022c4a0..6dfacea9 100644 --- a/player/video/src/main/res/values-da/strings.xml +++ b/player/video/src/main/res/values-da/strings.xml @@ -10,11 +10,10 @@ Låser afspilleren Hop tilbage Stop afspiller - Spring over intro Spol frem Spring frem Spole afspille Ingen Process indikator Spol tilbage - \ No newline at end of file + From f75079f72045e68d7cb85ca5a5b6ca728cb26630 Mon Sep 17 00:00:00 2001 From: cd16b Date: Mon, 4 Mar 2024 00:28:26 +0100 Subject: [PATCH 16/24] fix skipButton still visible after intro end --- .../dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index 2cc1218d..d0c9b7cc 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -258,7 +258,7 @@ constructor( val seconds = player.currentPosition / 1000.0 if (intros.isNotEmpty()) { intros[itemId]?.let { intro -> - if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) { + if (seconds > intro.showSkipPromptAt && seconds < (intro.hideSkipPromptAt - 1)) { _uiState.update { it.copy(currentIntro = intro) } return@let } From ce9eed63446993596127e3a0a37b8a785708b398 Mon Sep 17 00:00:00 2001 From: cd16b Date: Tue, 5 Mar 2024 16:27:21 +0100 Subject: [PATCH 17/24] fix skipButton hide on click --- app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt | 1 + .../dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index 1eb0daae..d8aa8293 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -150,6 +150,7 @@ class PlayerActivity : BasePlayerActivity() { currentIntro?.let { binding.playerView.player?.seekTo((it.introEnd * 1000).toLong()) } + skipIntroButton.isVisible = false } else if (currentCredit != null) { if (binding.playerView.player?.hasNextMediaItem() == true) { binding.playerView.player?.seekToNext() diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index d0c9b7cc..2cc1218d 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -258,7 +258,7 @@ constructor( val seconds = player.currentPosition / 1000.0 if (intros.isNotEmpty()) { intros[itemId]?.let { intro -> - if (seconds > intro.showSkipPromptAt && seconds < (intro.hideSkipPromptAt - 1)) { + if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) { _uiState.update { it.copy(currentIntro = intro) } return@let } From df984fb24b3a2026e26ef2f3195282f3f13cbe6e Mon Sep 17 00:00:00 2001 From: cd16b Date: Thu, 20 Jun 2024 23:59:24 +0200 Subject: [PATCH 18/24] FindroidSegment --- .../dev/jdtech/jellyfin/PlayerActivity.kt | 47 +- .../jdtech/jellyfin/utils/DownloaderImpl.kt | 23 +- core/src/main/res/values-it/strings.xml | 2 +- core/src/main/res/values/strings.xml | 2 +- .../3.json | 48 +- .../5.json | 813 ++++++++++++++++++ .../jdtech/jellyfin/database/Converters.kt | 11 + .../jellyfin/database/ServerDatabase.kt | 17 +- .../jellyfin/database/ServerDatabaseDao.kt | 22 +- .../java/dev/jdtech/jellyfin/models/Credit.kt | 33 - .../dev/jdtech/jellyfin/models/CreditDto.kt | 25 - .../jdtech/jellyfin/models/FindroidSegment.kt | 39 + .../jellyfin/models/FindroidSegmentDto.kt | 19 + .../java/dev/jdtech/jellyfin/models/Intro.kt | 25 - .../dev/jdtech/jellyfin/models/IntroDto.kt | 25 - .../jellyfin/repository/JellyfinRepository.kt | 7 +- .../repository/JellyfinRepositoryImpl.kt | 66 +- .../JellyfinRepositoryOfflineImpl.kt | 15 +- .../viewmodels/PlayerActivityViewModel.kt | 67 +- .../video/src/main/res/values-da/strings.xml | 2 +- 20 files changed, 1011 insertions(+), 297 deletions(-) create mode 100644 data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/5.json delete mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt delete mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/CreditDto.kt create mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/FindroidSegment.kt create mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/FindroidSegmentDto.kt delete mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/Intro.kt delete mode 100644 data/src/main/java/dev/jdtech/jellyfin/models/IntroDto.kt diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index 0e6d8463..3dd2c133 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -135,30 +135,39 @@ class PlayerActivity : BasePlayerActivity() { videoNameTextView.text = currentItemTitle // Skip Intro button - skipIntroButton.isVisible = !isInPictureInPictureMode && (currentIntro != null || currentCredit != null) - skipIntroButton.text = if (currentCredit != null) { - if (binding.playerView.player?.hasNextMediaItem() == true) { - getString(CoreR.string.skip_credit_button) - } else { - getString(CoreR.string.skip_credit_button_last) + // Visibility + skipIntroButton.isVisible = !isInPictureInPictureMode && showSkip == true + // Text + when (currentSegment?.type) { + "intro" -> { + skipIntroButton.text = getString(CoreR.string.skip_intro_button) } - } else { - getString(CoreR.string.skip_intro_button) - } - skipIntroButton.setOnClickListener { - if (currentIntro != null) { - currentIntro?.let { - binding.playerView.player?.seekTo((it.introEnd * 1000).toLong()) - } - skipIntroButton.isVisible = false - } else if (currentCredit != null) { - if (binding.playerView.player?.hasNextMediaItem() == true) { - binding.playerView.player?.seekToNext() + "credit" -> { + skipIntroButton.text = if (binding.playerView.player?.hasNextMediaItem() == true) { + getString(CoreR.string.skip_credit_button) } else { - finish() + getString(CoreR.string.skip_credit_button_last) } } } + // onClick + skipIntroButton.setOnClickListener { + when (currentSegment?.type) { + "intro" -> { + currentSegment?.let { + binding.playerView.player?.seekTo((it.endTime * 1000).toLong()) + } + } + "credit" -> { + if (binding.playerView.player?.hasNextMediaItem() == true) { + binding.playerView.player?.seekToNext() + } else { + finish() + } + } + } + skipIntroButton.isVisible = false + } // Trick Play previewScrubListener?.let { diff --git a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt index 66630ed8..f4f8527f 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt @@ -15,20 +15,18 @@ import dev.jdtech.jellyfin.models.FindroidMovie import dev.jdtech.jellyfin.models.FindroidSource import dev.jdtech.jellyfin.models.TrickPlayManifest import dev.jdtech.jellyfin.models.UiText -import dev.jdtech.jellyfin.models.toCreditDto import dev.jdtech.jellyfin.models.toFindroidEpisodeDto import dev.jdtech.jellyfin.models.toFindroidMediaStreamDto import dev.jdtech.jellyfin.models.toFindroidMovieDto import dev.jdtech.jellyfin.models.toFindroidSeasonDto +import dev.jdtech.jellyfin.models.toFindroidSegmentsDto import dev.jdtech.jellyfin.models.toFindroidShowDto import dev.jdtech.jellyfin.models.toFindroidSourceDto import dev.jdtech.jellyfin.models.toFindroidUserDataDto -import dev.jdtech.jellyfin.models.toIntroDto import dev.jdtech.jellyfin.models.toTrickPlayManifestDto import dev.jdtech.jellyfin.repository.JellyfinRepository import java.io.File import java.util.UUID -import kotlin.Exception import dev.jdtech.jellyfin.core.R as CoreR class DownloaderImpl( @@ -46,8 +44,7 @@ class DownloaderImpl( ): Pair { try { val source = jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId } - val intro = jellyfinRepository.getIntroTimestamps(item.id) - val credit = jellyfinRepository.getCreditTimestamps(item.id) + val segments = jellyfinRepository.getSegmentsTimestamps(item.id) val trickPlayManifest = jellyfinRepository.getTrickPlayManifest(item.id) val trickPlayData = if (trickPlayManifest != null) { jellyfinRepository.getTrickPlayData( @@ -80,11 +77,8 @@ class DownloaderImpl( database.insertSource(source.toFindroidSourceDto(item.id, path.path.orEmpty())) database.insertUserData(item.toFindroidUserDataDto(jellyfinRepository.getUserId())) downloadExternalMediaStreams(item, source, storageIndex) - if (intro != null) { - database.insertIntro(intro.toIntroDto(item.id)) - } - if (credit != null) { - database.insertCredit(credit.toCreditDto(item.id)) + if (segments != null) { + database.insertSegments(segments.toFindroidSegmentsDto(item.id)) } if (trickPlayManifest != null && trickPlayData != null) { downloadTrickPlay(item, trickPlayManifest, trickPlayData) @@ -112,11 +106,8 @@ class DownloaderImpl( database.insertSource(source.toFindroidSourceDto(item.id, path.path.orEmpty())) database.insertUserData(item.toFindroidUserDataDto(jellyfinRepository.getUserId())) downloadExternalMediaStreams(item, source, storageIndex) - if (intro != null) { - database.insertIntro(intro.toIntroDto(item.id)) - } - if (credit != null) { - database.insertCredit(credit.toCreditDto(item.id)) + if (segments != null) { + database.insertSegments(segments.toFindroidSegmentsDto(item.id)) } if (trickPlayManifest != null && trickPlayData != null) { downloadTrickPlay(item, trickPlayManifest, trickPlayData) @@ -181,7 +172,7 @@ class DownloaderImpl( database.deleteUserData(item.id) - database.deleteIntro(item.id) + database.deleteSegments(item.id) database.deleteTrickPlayManifest(item.id) File(context.filesDir, "trickplay/${item.id}.bif").delete() diff --git a/core/src/main/res/values-it/strings.xml b/core/src/main/res/values-it/strings.xml index dd31be9a..51f4537d 100644 --- a/core/src/main/res/values-it/strings.xml +++ b/core/src/main/res/values-it/strings.xml @@ -135,7 +135,7 @@ Aggiungi Connessione Rapida Salta intro - Richiede il plugin Intro Skipper di ConfusedPolarBear installato sul server.\nInstalla Intro Skipper v0.1.8.0 o maggiore di jumoog per saltare anche i titoli di coda + Richiede il plugin Intro Skipper di jumoog installato sul server. Scorri orizzontalmente per posizionarti avanti o indietro Gesto posizionamento Audio diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 056aa8e6..b1628fb1 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -145,7 +145,7 @@ Video output Audio output Intro Skipper - Requires ConfusedPolarBear\'s Intro Skipper plugin to be installed on the server.\nInstall jumoog\'s Intro Skipper v0.1.8.0 or higher to skip end credits. + Requires jumoog\'s Intro Skipper plugin to be installed on the server. Trick Play Requires nicknsy\'s Jellyscrub plugin to be installed on the server Chapter markers diff --git a/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/3.json b/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/3.json index 6742f17d..22b5646d 100644 --- a/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/3.json +++ b/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/3.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 3, - "identityHash": "2611f255654b3d481be40f080a8b5401", + "identityHash": "3cb9aaa3295b9e461cb94dfc708258ed", "entities": [ { "tableName": "servers", @@ -758,50 +758,6 @@ "indices": [], "foreignKeys": [] }, - { - "tableName": "credits", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` TEXT NOT NULL, `start` REAL NOT NULL, `end` REAL NOT NULL, `showAt` REAL NOT NULL, `hideAt` REAL NOT NULL, PRIMARY KEY(`itemId`))", - "fields": [ - { - "fieldPath": "itemId", - "columnName": "itemId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "start", - "columnName": "start", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "end", - "columnName": "end", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "showAt", - "columnName": "showAt", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "hideAt", - "columnName": "hideAt", - "affinity": "REAL", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "itemId" - ] - }, - "indices": [], - "foreignKeys": [] - }, { "tableName": "userdata", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` TEXT NOT NULL, `itemId` TEXT NOT NULL, `played` INTEGER NOT NULL, `favorite` INTEGER NOT NULL, `playbackPositionTicks` INTEGER NOT NULL, `toBeSynced` INTEGER NOT NULL, PRIMARY KEY(`userId`, `itemId`))", @@ -857,7 +813,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2611f255654b3d481be40f080a8b5401')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3cb9aaa3295b9e461cb94dfc708258ed')" ] } } \ No newline at end of file diff --git a/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/5.json b/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/5.json new file mode 100644 index 00000000..d6a144bb --- /dev/null +++ b/data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/5.json @@ -0,0 +1,813 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "98335303560b91843defc6f7631873ab", + "entities": [ + { + "tableName": "servers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `currentServerAddressId` TEXT, `currentUserId` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentServerAddressId", + "columnName": "currentServerAddressId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "currentUserId", + "columnName": "currentUserId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "serverAddresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` TEXT NOT NULL, `address` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`serverId`) REFERENCES `servers`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_serverAddresses_serverId", + "unique": false, + "columnNames": [ + "serverId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_serverAddresses_serverId` ON `${TABLE_NAME}` (`serverId`)" + } + ], + "foreignKeys": [ + { + "table": "servers", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serverId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "users", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `serverId` TEXT NOT NULL, `accessToken` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`serverId`) REFERENCES `servers`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessToken", + "columnName": "accessToken", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_users_serverId", + "unique": false, + "columnNames": [ + "serverId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_users_serverId` ON `${TABLE_NAME}` (`serverId`)" + } + ], + "foreignKeys": [ + { + "table": "servers", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serverId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "movies", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` TEXT, `name` TEXT NOT NULL, `originalTitle` TEXT, `overview` TEXT NOT NULL, `runtimeTicks` INTEGER NOT NULL, `premiereDate` INTEGER, `communityRating` REAL, `officialRating` TEXT, `status` TEXT NOT NULL, `productionYear` INTEGER, `endDate` INTEGER, `chapters` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalTitle", + "columnName": "originalTitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "overview", + "columnName": "overview", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "runtimeTicks", + "columnName": "runtimeTicks", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "premiereDate", + "columnName": "premiereDate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "communityRating", + "columnName": "communityRating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "officialRating", + "columnName": "officialRating", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "productionYear", + "columnName": "productionYear", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "endDate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "chapters", + "columnName": "chapters", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "shows", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` TEXT, `name` TEXT NOT NULL, `originalTitle` TEXT, `overview` TEXT NOT NULL, `runtimeTicks` INTEGER NOT NULL, `communityRating` REAL, `officialRating` TEXT, `status` TEXT NOT NULL, `productionYear` INTEGER, `endDate` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalTitle", + "columnName": "originalTitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "overview", + "columnName": "overview", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "runtimeTicks", + "columnName": "runtimeTicks", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "communityRating", + "columnName": "communityRating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "officialRating", + "columnName": "officialRating", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "productionYear", + "columnName": "productionYear", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "endDate", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "seasons", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `seriesId` TEXT NOT NULL, `name` TEXT NOT NULL, `seriesName` TEXT NOT NULL, `overview` TEXT NOT NULL, `indexNumber` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`seriesId`) REFERENCES `shows`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seriesId", + "columnName": "seriesId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seriesName", + "columnName": "seriesName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "overview", + "columnName": "overview", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "indexNumber", + "columnName": "indexNumber", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_seasons_seriesId", + "unique": false, + "columnNames": [ + "seriesId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_seasons_seriesId` ON `${TABLE_NAME}` (`seriesId`)" + } + ], + "foreignKeys": [ + { + "table": "shows", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "seriesId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "episodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serverId` TEXT, `seasonId` TEXT NOT NULL, `seriesId` TEXT NOT NULL, `name` TEXT NOT NULL, `seriesName` TEXT NOT NULL, `overview` TEXT NOT NULL, `indexNumber` INTEGER NOT NULL, `indexNumberEnd` INTEGER, `parentIndexNumber` INTEGER NOT NULL, `runtimeTicks` INTEGER NOT NULL, `premiereDate` INTEGER, `communityRating` REAL, `chapters` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`seasonId`) REFERENCES `seasons`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`seriesId`) REFERENCES `shows`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "seasonId", + "columnName": "seasonId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seriesId", + "columnName": "seriesId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seriesName", + "columnName": "seriesName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "overview", + "columnName": "overview", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "indexNumber", + "columnName": "indexNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "indexNumberEnd", + "columnName": "indexNumberEnd", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "parentIndexNumber", + "columnName": "parentIndexNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "runtimeTicks", + "columnName": "runtimeTicks", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "premiereDate", + "columnName": "premiereDate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "communityRating", + "columnName": "communityRating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "chapters", + "columnName": "chapters", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_episodes_seasonId", + "unique": false, + "columnNames": [ + "seasonId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_episodes_seasonId` ON `${TABLE_NAME}` (`seasonId`)" + }, + { + "name": "index_episodes_seriesId", + "unique": false, + "columnNames": [ + "seriesId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_episodes_seriesId` ON `${TABLE_NAME}` (`seriesId`)" + } + ], + "foreignKeys": [ + { + "table": "seasons", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "seasonId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "shows", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "seriesId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "sources", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `itemId` TEXT NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `path` TEXT NOT NULL, `downloadId` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadId", + "columnName": "downloadId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "mediastreams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `sourceId` TEXT NOT NULL, `title` TEXT NOT NULL, `displayTitle` TEXT, `language` TEXT NOT NULL, `type` TEXT NOT NULL, `codec` TEXT NOT NULL, `isExternal` INTEGER NOT NULL, `path` TEXT NOT NULL, `channelLayout` TEXT, `videoRangeType` TEXT, `height` INTEGER, `width` INTEGER, `videoDoViTitle` TEXT, `downloadId` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sourceId", + "columnName": "sourceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayTitle", + "columnName": "displayTitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "codec", + "columnName": "codec", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isExternal", + "columnName": "isExternal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "channelLayout", + "columnName": "channelLayout", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "videoRangeType", + "columnName": "videoRangeType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "videoDoViTitle", + "columnName": "videoDoViTitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadId", + "columnName": "downloadId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "trickPlayManifests", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` TEXT NOT NULL, `version` TEXT NOT NULL, `resolution` INTEGER NOT NULL, PRIMARY KEY(`itemId`))", + "fields": [ + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resolution", + "columnName": "resolution", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "itemId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "segments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` TEXT NOT NULL, `segments` TEXT NOT NULL, PRIMARY KEY(`itemId`))", + "fields": [ + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "segments", + "columnName": "segments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "itemId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "userdata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` TEXT NOT NULL, `itemId` TEXT NOT NULL, `played` INTEGER NOT NULL, `favorite` INTEGER NOT NULL, `playbackPositionTicks` INTEGER NOT NULL, `toBeSynced` INTEGER NOT NULL, PRIMARY KEY(`userId`, `itemId`))", + "fields": [ + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "played", + "columnName": "played", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favorite", + "columnName": "favorite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playbackPositionTicks", + "columnName": "playbackPositionTicks", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "toBeSynced", + "columnName": "toBeSynced", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "userId", + "itemId" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '98335303560b91843defc6f7631873ab')" + ] + } +} \ No newline at end of file diff --git a/data/src/main/java/dev/jdtech/jellyfin/database/Converters.kt b/data/src/main/java/dev/jdtech/jellyfin/database/Converters.kt index 0d6c112e..8eed546f 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/database/Converters.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/database/Converters.kt @@ -2,6 +2,7 @@ package dev.jdtech.jellyfin.database import androidx.room.TypeConverter import dev.jdtech.jellyfin.models.FindroidChapter +import dev.jdtech.jellyfin.models.FindroidSegment import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.jellyfin.sdk.model.DateTime @@ -38,4 +39,14 @@ class Converters { fun fromStringToFindroidChapters(value: String?): List? { return value?.let { Json.decodeFromString(value) } } + + @TypeConverter + fun fromFindroidSegmentsToString(value: List?): String? { + return value?.let { Json.encodeToString(value) } + } + + @TypeConverter + fun fromStringToFindroidSegments(value: String?): List? { + return value?.let { Json.decodeFromString(value) } + } } diff --git a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt index fdfa8fc4..90e3a3e2 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt @@ -2,31 +2,40 @@ package dev.jdtech.jellyfin.database import androidx.room.AutoMigration import androidx.room.Database +import androidx.room.DeleteTable import androidx.room.RoomDatabase import androidx.room.TypeConverters -import dev.jdtech.jellyfin.models.CreditDto +import androidx.room.migration.AutoMigrationSpec import dev.jdtech.jellyfin.models.FindroidEpisodeDto import dev.jdtech.jellyfin.models.FindroidMediaStreamDto import dev.jdtech.jellyfin.models.FindroidMovieDto import dev.jdtech.jellyfin.models.FindroidSeasonDto +import dev.jdtech.jellyfin.models.FindroidSegmentsDto import dev.jdtech.jellyfin.models.FindroidShowDto import dev.jdtech.jellyfin.models.FindroidSourceDto import dev.jdtech.jellyfin.models.FindroidUserDataDto -import dev.jdtech.jellyfin.models.IntroDto import dev.jdtech.jellyfin.models.Server import dev.jdtech.jellyfin.models.ServerAddress import dev.jdtech.jellyfin.models.TrickPlayManifestDto import dev.jdtech.jellyfin.models.User @Database( - entities = [Server::class, ServerAddress::class, User::class, FindroidMovieDto::class, FindroidShowDto::class, FindroidSeasonDto::class, FindroidEpisodeDto::class, FindroidSourceDto::class, FindroidMediaStreamDto::class, TrickPlayManifestDto::class, IntroDto::class, CreditDto::class, FindroidUserDataDto::class], - version = 4, + entities = [Server::class, ServerAddress::class, User::class, FindroidMovieDto::class, FindroidShowDto::class, FindroidSeasonDto::class, FindroidEpisodeDto::class, FindroidSourceDto::class, FindroidMediaStreamDto::class, TrickPlayManifestDto::class, FindroidSegmentsDto::class, FindroidUserDataDto::class], + version = 5, autoMigrations = [ AutoMigration(from = 2, to = 3), AutoMigration(from = 3, to = 4), + AutoMigration( + from = 4, + to = 5, + spec = ServerDatabase.IntrosAutoMigration::class, + ), ], ) @TypeConverters(Converters::class) abstract class ServerDatabase : RoomDatabase() { abstract fun getServerDatabaseDao(): ServerDatabaseDao + + @DeleteTable(tableName = "intros") + class IntrosAutoMigration : AutoMigrationSpec } diff --git a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt index cb12df6a..5b5ce974 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt @@ -6,15 +6,14 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction import androidx.room.Update -import dev.jdtech.jellyfin.models.CreditDto import dev.jdtech.jellyfin.models.FindroidEpisodeDto import dev.jdtech.jellyfin.models.FindroidMediaStreamDto import dev.jdtech.jellyfin.models.FindroidMovieDto import dev.jdtech.jellyfin.models.FindroidSeasonDto +import dev.jdtech.jellyfin.models.FindroidSegmentsDto import dev.jdtech.jellyfin.models.FindroidShowDto import dev.jdtech.jellyfin.models.FindroidSourceDto import dev.jdtech.jellyfin.models.FindroidUserDataDto -import dev.jdtech.jellyfin.models.IntroDto import dev.jdtech.jellyfin.models.Server import dev.jdtech.jellyfin.models.ServerAddress import dev.jdtech.jellyfin.models.ServerWithAddressAndUser @@ -215,22 +214,13 @@ interface ServerDatabaseDao { fun deleteEpisodesBySeasonId(seasonId: UUID) @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertIntro(intro: IntroDto) + fun insertSegments(segment: FindroidSegmentsDto) - @Query("SELECT * FROM intros WHERE itemId = :itemId") - fun getIntro(itemId: UUID): IntroDto? + @Query("SELECT * FROM segments WHERE itemId = :itemId") + fun getSegments(itemId: UUID): FindroidSegmentsDto? - @Query("DELETE FROM intros WHERE itemId = :itemId") - fun deleteIntro(itemId: UUID) - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertCredit(credit: CreditDto) - - @Query("SELECT * FROM credits WHERE itemId = :itemId") - fun getCredit(itemId: UUID): CreditDto? - - @Query("DELETE FROM credits WHERE itemId = :itemId") - fun deleteCredit(itemId: UUID) + @Query("DELETE FROM segments WHERE itemId = :itemId") + fun deleteSegments(itemId: UUID) @Query("SELECT * FROM seasons") fun getSeasons(): List diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt b/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt deleted file mode 100644 index f19f607f..00000000 --- a/data/src/main/java/dev/jdtech/jellyfin/models/Credit.kt +++ /dev/null @@ -1,33 +0,0 @@ -package dev.jdtech.jellyfin.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class Credit( - @SerialName("Credits") - val credit: Credits, -) - -@Serializable -data class Credits( - @SerialName("IntroStart") - val introStart: Double, - @SerialName("IntroEnd") - val introEnd: Double, - @SerialName("ShowSkipPromptAt") - val showSkipPromptAt: Double, - @SerialName("HideSkipPromptAt") - val hideSkipPromptAt: Double, -) - -fun CreditDto.toCredit(): Credit { - return Credit( - credit = Credits( - introStart = start, - introEnd = end, - showSkipPromptAt = showAt, - hideSkipPromptAt = hideAt, - ), - ) -} diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/CreditDto.kt b/data/src/main/java/dev/jdtech/jellyfin/models/CreditDto.kt deleted file mode 100644 index 4f24bf68..00000000 --- a/data/src/main/java/dev/jdtech/jellyfin/models/CreditDto.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.jdtech.jellyfin.models - -import androidx.room.Entity -import androidx.room.PrimaryKey -import java.util.UUID - -@Entity(tableName = "credits") -data class CreditDto( - @PrimaryKey - val itemId: UUID, - val start: Double, - val end: Double, - val showAt: Double, - val hideAt: Double, -) - -fun Credit.toCreditDto(itemId: UUID): CreditDto { - return CreditDto( - itemId = itemId, - start = credit.introStart, - end = credit.introEnd, - showAt = credit.showSkipPromptAt, - hideAt = credit.hideSkipPromptAt, - ) -} diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/FindroidSegment.kt b/data/src/main/java/dev/jdtech/jellyfin/models/FindroidSegment.kt new file mode 100644 index 00000000..ca3a182c --- /dev/null +++ b/data/src/main/java/dev/jdtech/jellyfin/models/FindroidSegment.kt @@ -0,0 +1,39 @@ +package dev.jdtech.jellyfin.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class FindroidSegments( + @SerialName("Introduction") + val intro: FindroidSegment?, + @SerialName("Credits") + val credit: FindroidSegment?, +) + +@Serializable +data class FindroidSegment( + val type: String = "none", + val skip: Boolean = false, + @SerialName("IntroStart") + val startTime: Double, + @SerialName("IntroEnd") + val endTime: Double, + @SerialName("ShowSkipPromptAt") + val showAt: Double, + @SerialName("HideSkipPromptAt") + val hideAt: Double, +) + +fun FindroidSegmentsDto.toFindroidSegments(): List { + return segments.map { segment -> + FindroidSegment( + type = segment.type, + skip = segment.skip, + startTime = segment.startTime, + endTime = segment.endTime, + showAt = segment.showAt, + hideAt = segment.hideAt, + ) + } +} diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/FindroidSegmentDto.kt b/data/src/main/java/dev/jdtech/jellyfin/models/FindroidSegmentDto.kt new file mode 100644 index 00000000..a07650ce --- /dev/null +++ b/data/src/main/java/dev/jdtech/jellyfin/models/FindroidSegmentDto.kt @@ -0,0 +1,19 @@ +package dev.jdtech.jellyfin.models + +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.util.UUID + +@Entity(tableName = "segments") +data class FindroidSegmentsDto( + @PrimaryKey + val itemId: UUID, + val segments: List, +) + +fun List.toFindroidSegmentsDto(itemId: UUID): FindroidSegmentsDto { + return FindroidSegmentsDto( + itemId = itemId, + segments = this, + ) +} diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/Intro.kt b/data/src/main/java/dev/jdtech/jellyfin/models/Intro.kt deleted file mode 100644 index 31193e73..00000000 --- a/data/src/main/java/dev/jdtech/jellyfin/models/Intro.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.jdtech.jellyfin.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class Intro( - @SerialName("IntroStart") - val introStart: Double, - @SerialName("IntroEnd") - val introEnd: Double, - @SerialName("ShowSkipPromptAt") - val showSkipPromptAt: Double, - @SerialName("HideSkipPromptAt") - val hideSkipPromptAt: Double, -) - -fun IntroDto.toIntro(): Intro { - return Intro( - introStart = start, - introEnd = end, - showSkipPromptAt = showAt, - hideSkipPromptAt = hideAt, - ) -} diff --git a/data/src/main/java/dev/jdtech/jellyfin/models/IntroDto.kt b/data/src/main/java/dev/jdtech/jellyfin/models/IntroDto.kt deleted file mode 100644 index 735fabf4..00000000 --- a/data/src/main/java/dev/jdtech/jellyfin/models/IntroDto.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.jdtech.jellyfin.models - -import androidx.room.Entity -import androidx.room.PrimaryKey -import java.util.UUID - -@Entity(tableName = "intros") -data class IntroDto( - @PrimaryKey - val itemId: UUID, - val start: Double, - val end: Double, - val showAt: Double, - val hideAt: Double, -) - -fun Intro.toIntroDto(itemId: UUID): IntroDto { - return IntroDto( - itemId = itemId, - start = introStart, - end = introEnd, - showAt = showSkipPromptAt, - hideAt = hideSkipPromptAt, - ) -} diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt index 7f31e281..8bd82e94 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepository.kt @@ -1,15 +1,14 @@ package dev.jdtech.jellyfin.repository import androidx.paging.PagingData -import dev.jdtech.jellyfin.models.Credit import dev.jdtech.jellyfin.models.FindroidCollection import dev.jdtech.jellyfin.models.FindroidEpisode import dev.jdtech.jellyfin.models.FindroidItem import dev.jdtech.jellyfin.models.FindroidMovie import dev.jdtech.jellyfin.models.FindroidSeason +import dev.jdtech.jellyfin.models.FindroidSegment import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidSource -import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.SortBy import dev.jdtech.jellyfin.models.TrickPlayManifest import kotlinx.coroutines.flow.Flow @@ -85,9 +84,7 @@ interface JellyfinRepository { suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String - suspend fun getIntroTimestamps(itemId: UUID): Intro? - - suspend fun getCreditTimestamps(itemId: UUID): Credit? + suspend fun getSegmentsTimestamps(itemId: UUID): List? suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest? diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt index 6a79b74c..c1fee495 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt @@ -7,26 +7,25 @@ import androidx.paging.PagingData import dev.jdtech.jellyfin.AppPreferences import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.database.ServerDatabaseDao -import dev.jdtech.jellyfin.models.Credit import dev.jdtech.jellyfin.models.FindroidCollection import dev.jdtech.jellyfin.models.FindroidEpisode import dev.jdtech.jellyfin.models.FindroidItem import dev.jdtech.jellyfin.models.FindroidMovie import dev.jdtech.jellyfin.models.FindroidSeason +import dev.jdtech.jellyfin.models.FindroidSegment +import dev.jdtech.jellyfin.models.FindroidSegments import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidSource -import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.SortBy import dev.jdtech.jellyfin.models.TrickPlayManifest -import dev.jdtech.jellyfin.models.toCredit import dev.jdtech.jellyfin.models.toFindroidCollection import dev.jdtech.jellyfin.models.toFindroidEpisode import dev.jdtech.jellyfin.models.toFindroidItem import dev.jdtech.jellyfin.models.toFindroidMovie import dev.jdtech.jellyfin.models.toFindroidSeason +import dev.jdtech.jellyfin.models.toFindroidSegments import dev.jdtech.jellyfin.models.toFindroidShow import dev.jdtech.jellyfin.models.toFindroidSource -import dev.jdtech.jellyfin.models.toIntro import dev.jdtech.jellyfin.models.toTrickPlayManifest import io.ktor.util.cio.toByteArray import io.ktor.utils.io.ByteReadChannel @@ -341,12 +340,12 @@ class JellyfinRepositoryImpl( } } - override suspend fun getIntroTimestamps(itemId: UUID): Intro? = + override suspend fun getSegmentsTimestamps(itemId: UUID): List? = withContext(Dispatchers.IO) { - val intro = database.getIntro(itemId)?.toIntro() + val segments = database.getSegments(itemId)?.toFindroidSegments() - if (intro != null) { - return@withContext intro + if (segments != null) { + return@withContext segments } // https://github.com/ConfusedPolarBear/intro-skipper/blob/master/docs/api.md @@ -354,32 +353,37 @@ class JellyfinRepositoryImpl( pathParameters["itemId"] = itemId try { - return@withContext jellyfinApi.api.get( - "/Episode/{itemId}/IntroTimestamps/v1", - pathParameters, - ).content - } catch (e: Exception) { - return@withContext null - } - } - - override suspend fun getCreditTimestamps(itemId: UUID): Credit? = - withContext(Dispatchers.IO) { - val credit = database.getCredit(itemId)?.toCredit() - - if (credit != null) { - return@withContext credit - } - - // https://github.com/ConfusedPolarBear/intro-skipper/blob/master/docs/api.md - val pathParameters = mutableMapOf() - pathParameters["itemId"] = itemId - - try { - return@withContext jellyfinApi.api.get( + val segmentToConvert = jellyfinApi.api.get( "/Episode/{itemId}/IntroSkipperSegments", pathParameters, ).content + + val segmentConverted = mutableListOf( + segmentToConvert.intro!!.let { + FindroidSegment( + type = "intro", + skip = true, + startTime = it.startTime, + endTime = it.endTime, + showAt = it.showAt, + hideAt = it.hideAt, + ) + }, + segmentToConvert.credit!!.let { + FindroidSegment( + type = "credit", + skip = true, + startTime = it.startTime, + endTime = it.endTime, + showAt = it.showAt, + hideAt = it.hideAt, + ) + }, + ) + Timber.tag("SegmentInfo").d("segmentToConvert: %s", segmentToConvert) + Timber.tag("SegmentInfo").d("segmentConverted: %s", segmentConverted) + + return@withContext segmentConverted.toList() } catch (e: Exception) { return@withContext null } diff --git a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt index 389749a1..9a10c3c4 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt @@ -5,24 +5,22 @@ import androidx.paging.PagingData import dev.jdtech.jellyfin.AppPreferences import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.database.ServerDatabaseDao -import dev.jdtech.jellyfin.models.Credit import dev.jdtech.jellyfin.models.FindroidCollection import dev.jdtech.jellyfin.models.FindroidEpisode import dev.jdtech.jellyfin.models.FindroidItem import dev.jdtech.jellyfin.models.FindroidMovie import dev.jdtech.jellyfin.models.FindroidSeason +import dev.jdtech.jellyfin.models.FindroidSegment import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidSource -import dev.jdtech.jellyfin.models.Intro import dev.jdtech.jellyfin.models.SortBy import dev.jdtech.jellyfin.models.TrickPlayManifest -import dev.jdtech.jellyfin.models.toCredit import dev.jdtech.jellyfin.models.toFindroidEpisode import dev.jdtech.jellyfin.models.toFindroidMovie import dev.jdtech.jellyfin.models.toFindroidSeason +import dev.jdtech.jellyfin.models.toFindroidSegments import dev.jdtech.jellyfin.models.toFindroidShow import dev.jdtech.jellyfin.models.toFindroidSource -import dev.jdtech.jellyfin.models.toIntro import dev.jdtech.jellyfin.models.toTrickPlayManifest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -181,14 +179,9 @@ class JellyfinRepositoryOfflineImpl( TODO("Not yet implemented") } - override suspend fun getIntroTimestamps(itemId: UUID): Intro? = + override suspend fun getSegmentsTimestamps(itemId: UUID): List? = withContext(Dispatchers.IO) { - database.getIntro(itemId)?.toIntro() - } - - override suspend fun getCreditTimestamps(itemId: UUID): Credit? = - withContext(Dispatchers.IO) { - database.getCredit(itemId)?.toCredit() + database.getSegments(itemId)?.toFindroidSegments() } override suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest? = diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index 5fc1ce1b..9fc9b233 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -18,8 +18,7 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import dagger.hilt.android.lifecycle.HiltViewModel import dev.jdtech.jellyfin.AppPreferences -import dev.jdtech.jellyfin.models.Credits -import dev.jdtech.jellyfin.models.Intro +import dev.jdtech.jellyfin.models.FindroidSegment import dev.jdtech.jellyfin.models.PlayerChapter import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.mpv.MPVPlayer @@ -54,8 +53,8 @@ constructor( private val _uiState = MutableStateFlow( UiState( currentItemTitle = "", - currentIntro = null, - currentCredit = null, + currentSegment = null, + showSkip = false, currentTrickPlay = null, currentChapters = null, fileLoaded = false, @@ -66,15 +65,14 @@ constructor( private val eventsChannel = Channel() val eventsChannelFlow = eventsChannel.receiveAsFlow() - private val intros: MutableMap = mutableMapOf() - private val credits: MutableMap = mutableMapOf() + private val segments: MutableMap> = mutableMapOf() private val trickPlays: MutableMap = mutableMapOf() data class UiState( val currentItemTitle: String, - val currentIntro: Intro?, - val currentCredit: Credits?, + val currentSegment: FindroidSegment?, + val showSkip: Boolean?, val currentTrickPlay: BifData?, val currentChapters: List?, val fileLoaded: Boolean, @@ -154,12 +152,10 @@ constructor( } if (appPreferences.playerIntroSkipper) { - jellyfinRepository.getIntroTimestamps(item.itemId)?.let { intro -> - intros[item.itemId] = intro - } - jellyfinRepository.getCreditTimestamps(item.itemId)?.let { credit -> - credits[item.itemId] = credit.credit + jellyfinRepository.getSegmentsTimestamps(item.itemId)?.let { segment -> + segments[item.itemId] = segment } + Timber.tag("SegmentInfo").d("Segments: %s", segments) } Timber.d("Stream url: $streamUrl") @@ -244,35 +240,25 @@ constructor( handler.postDelayed(this, 5000L) } } - val skipCheckRunnable = object : Runnable { + val segmentCheckRunnable = object : Runnable { override fun run() { - if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) { - val itemId = UUID.fromString(player.currentMediaItem!!.mediaId) + val currentMediaItem = player.currentMediaItem + if (currentMediaItem != null && currentMediaItem.mediaId.isNotEmpty()) { + val itemId = UUID.fromString(currentMediaItem.mediaId) val seconds = player.currentPosition / 1000.0 - if (intros.isNotEmpty()) { - intros[itemId]?.let { intro -> - if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) { - _uiState.update { it.copy(currentIntro = intro) } - return@let - } - _uiState.update { it.copy(currentIntro = null) } - } - } - if (credits.isNotEmpty()) { - credits[itemId]?.let { credit -> - if (seconds > credit.showSkipPromptAt && seconds < credit.hideSkipPromptAt) { - _uiState.update { it.copy(currentCredit = credit) } - return@let - } - _uiState.update { it.copy(currentCredit = null) } - } - } + + val currentSegment = segments[itemId]?.find { segment -> seconds in segment.startTime..segment.endTime } + _uiState.update { it.copy(currentSegment = currentSegment) } + Timber.tag("SegmentInfo").d("currentSegment: %s", currentSegment) + + val showSkip = currentSegment?.let { it.skip && seconds in it.showAt..it.hideAt } ?: false + _uiState.update { it.copy(showSkip = showSkip) } } handler.postDelayed(this, 1000L) } } handler.post(playbackProgressRunnable) - if (intros.isNotEmpty() || credits.isNotEmpty()) handler.post(skipCheckRunnable) + if (segments.isNotEmpty()) handler.post(segmentCheckRunnable) } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { @@ -291,9 +277,14 @@ constructor( } else { item.name } - _uiState.update { it.copy(currentItemTitle = itemTitle, currentChapters = item.chapters, fileLoaded = false) } - - _uiState.update { it.copy(currentCredit = null) } + _uiState.update { + it.copy( + currentItemTitle = itemTitle, + currentSegment = null, + currentChapters = item.chapters, + fileLoaded = false, + ) + } jellyfinRepository.postPlaybackStart(item.itemId) diff --git a/player/video/src/main/res/values-da/strings.xml b/player/video/src/main/res/values-da/strings.xml index 6dfacea9..4cb768c0 100644 --- a/player/video/src/main/res/values-da/strings.xml +++ b/player/video/src/main/res/values-da/strings.xml @@ -16,4 +16,4 @@ Ingen Process indikator Spol tilbage - + \ No newline at end of file From 91cccc55a7b9a4e053c5309e4de74aec7b19f787 Mon Sep 17 00:00:00 2001 From: cd16b Date: Fri, 21 Jun 2024 14:36:11 +0200 Subject: [PATCH 19/24] Improve skipButton visibility/usability --- .../dev/jdtech/jellyfin/PlayerActivity.kt | 43 +++++++++++++------ .../res/layout/exo_player_skip_control.xml | 25 +++++++++++ .../src/main/res/layout/exo_player_view.xml | 23 +++------- .../viewmodels/PlayerActivityViewModel.kt | 4 +- 4 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 app/phone/src/main/res/layout/exo_player_skip_control.xml diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index 3dd2c133..3add87ab 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -37,6 +37,7 @@ import dagger.hilt.android.AndroidEntryPoint import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment +import dev.jdtech.jellyfin.models.FindroidSegment import dev.jdtech.jellyfin.utils.PlayerGestureHelper import dev.jdtech.jellyfin.utils.PreviewScrubListener import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel @@ -59,6 +60,8 @@ class PlayerActivity : BasePlayerActivity() { override val viewModel: PlayerActivityViewModel by viewModels() private var previewScrubListener: PreviewScrubListener? = null private var wasZoom: Boolean = false + private var oldSegment: FindroidSegment? = null + private var buttonPressed: Boolean = false private val isPipSupported by lazy { // Check if device has PiP feature @@ -120,7 +123,7 @@ class PlayerActivity : BasePlayerActivity() { val audioButton = binding.playerView.findViewById(R.id.btn_audio_track) val subtitleButton = binding.playerView.findViewById(R.id.btn_subtitle) val speedButton = binding.playerView.findViewById(R.id.btn_speed) - val skipIntroButton = binding.playerView.findViewById