feat: merged Skip Credits
This commit is contained in:
commit
44b6e915ba
46 changed files with 1148 additions and 158 deletions
|
@ -37,13 +37,15 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import com.nomadics9.ananas.databinding.ActivityPlayerBinding
|
||||
import com.nomadics9.ananas.dialogs.SpeedSelectionDialogFragment
|
||||
import com.nomadics9.ananas.dialogs.TrackSelectionDialogFragment
|
||||
import com.nomadics9.ananas.models.FindroidSegment
|
||||
import com.nomadics9.ananas.utils.PlayerGestureHelper
|
||||
import com.nomadics9.ananas.utils.PreviewScrubListener
|
||||
import com.nomadics9.ananas.viewmodels.PlayerActivityViewModel
|
||||
import com.nomadics9.ananas.viewmodels.PlayerEvents
|
||||
import kotlinx.coroutines.launch
|
||||
import com.nomadics9.ananas.utils.PlayerGestureHelper
|
||||
import com.nomadics9.ananas.utils.PreviewScrubListener
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import com.nomadics9.ananas.core.R as CoreR
|
||||
|
||||
var isControlsLocked: Boolean = false
|
||||
|
||||
|
@ -58,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
|
||||
|
@ -119,7 +123,8 @@ class PlayerActivity : BasePlayerActivity() {
|
|||
val audioButton = binding.playerView.findViewById<ImageButton>(R.id.btn_audio_track)
|
||||
val subtitleButton = binding.playerView.findViewById<ImageButton>(R.id.btn_subtitle)
|
||||
val speedButton = binding.playerView.findViewById<ImageButton>(R.id.btn_speed)
|
||||
val skipIntroButton = binding.playerView.findViewById<Button>(R.id.btn_skip_intro)
|
||||
val skipButton = binding.playerView.findViewById<Button>(R.id.btn_skip_intro)
|
||||
val watchCreditsButton = binding.playerView.findViewById<Button>(R.id.btn_watch_credits)
|
||||
val pipButton = binding.playerView.findViewById<ImageButton>(R.id.btn_pip)
|
||||
val lockButton = binding.playerView.findViewById<ImageButton>(R.id.btn_lockview)
|
||||
val unlockButton = binding.playerView.findViewById<ImageButton>(R.id.btn_unlock)
|
||||
|
@ -133,13 +138,80 @@ class PlayerActivity : BasePlayerActivity() {
|
|||
// Title
|
||||
videoNameTextView.text = currentItemTitle
|
||||
|
||||
// Skip Intro button
|
||||
skipIntroButton.isVisible = !isInPictureInPictureMode && currentIntro != null
|
||||
skipIntroButton.setOnClickListener {
|
||||
currentIntro?.let {
|
||||
binding.playerView.player?.seekTo((it.introEnd * 1000).toLong())
|
||||
// Skip Button
|
||||
if (currentSegment != oldSegment) buttonPressed = false
|
||||
// Button Visibility and Text
|
||||
when (currentSegment?.type) {
|
||||
"intro" -> {
|
||||
skipButton.text =
|
||||
getString(CoreR.string.skip_intro_button)
|
||||
skipButton.isVisible =
|
||||
!isInPictureInPictureMode && !buttonPressed && (showSkip == true || (binding.playerView.isControllerFullyVisible && currentSegment?.skip == true))
|
||||
watchCreditsButton.isVisible = false
|
||||
}
|
||||
|
||||
"credit" -> {
|
||||
skipButton.text =
|
||||
if (binding.playerView.player?.hasNextMediaItem() == true) {
|
||||
getString(CoreR.string.skip_credit_button)
|
||||
} else {
|
||||
getString(CoreR.string.skip_credit_button_last)
|
||||
}
|
||||
skipButton.isVisible =
|
||||
!isInPictureInPictureMode && !buttonPressed && currentSegment?.skip == true && !binding.playerView.isControllerFullyVisible
|
||||
watchCreditsButton.isVisible = skipButton.isVisible
|
||||
}
|
||||
|
||||
else -> {
|
||||
skipButton.isVisible = false
|
||||
watchCreditsButton.isVisible = false
|
||||
}
|
||||
}
|
||||
binding.playerView.setControllerVisibilityListener(
|
||||
PlayerView.ControllerVisibilityListener { visibility ->
|
||||
when (currentSegment?.type) {
|
||||
"intro" -> {
|
||||
skipButton.isVisible =
|
||||
!buttonPressed && (showSkip == true || (visibility == View.VISIBLE && currentSegment?.skip == true))
|
||||
}
|
||||
|
||||
"credit" -> {
|
||||
skipButton.isVisible =
|
||||
!buttonPressed && currentSegment?.skip == true && visibility == View.GONE
|
||||
watchCreditsButton.isVisible = skipButton.isVisible
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
// onClick
|
||||
if (currentSegment?.type == "credit") {
|
||||
watchCreditsButton.setOnClickListener {
|
||||
buttonPressed = true
|
||||
skipButton.isVisible = false
|
||||
watchCreditsButton.isVisible = false
|
||||
}
|
||||
}
|
||||
skipButton.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
buttonPressed = true
|
||||
skipButton.isVisible = false
|
||||
watchCreditsButton.isVisible = false
|
||||
}
|
||||
oldSegment = currentSegment
|
||||
|
||||
// Trickplay
|
||||
previewScrubListener?.let {
|
||||
|
|
|
@ -9,12 +9,12 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.nomadics9.ananas.bindCardItemImage
|
||||
import com.nomadics9.ananas.core.R
|
||||
import com.nomadics9.ananas.databinding.HomeEpisodeItemBinding
|
||||
import com.nomadics9.ananas.models.FindroidEpisode
|
||||
import com.nomadics9.ananas.models.FindroidItem
|
||||
import com.nomadics9.ananas.models.FindroidMovie
|
||||
import com.nomadics9.ananas.models.isDownloaded
|
||||
import com.nomadics9.ananas.core.R as CoreR
|
||||
|
||||
class HomeEpisodeListAdapter(private val onClickListener: (item: FindroidItem) -> Unit) : ListAdapter<FindroidItem, HomeEpisodeListAdapter.EpisodeViewHolder>(DiffCallback) {
|
||||
class EpisodeViewHolder(
|
||||
|
@ -42,9 +42,9 @@ class HomeEpisodeListAdapter(private val onClickListener: (item: FindroidItem) -
|
|||
is FindroidEpisode -> {
|
||||
binding.primaryName.text = item.seriesName
|
||||
binding.secondaryName.text = if (item.indexNumberEnd == null) {
|
||||
parent.resources.getString(R.string.episode_name_extended, item.parentIndexNumber, item.indexNumber, item.name)
|
||||
parent.resources.getString(CoreR.string.episode_name_extended, item.parentIndexNumber, item.indexNumber, item.name)
|
||||
} else {
|
||||
parent.resources.getString(R.string.episode_name_extended_with_end, item.parentIndexNumber, item.indexNumber, item.indexNumberEnd, item.name)
|
||||
parent.resources.getString(CoreR.string.episode_name_extended_with_end, item.parentIndexNumber, item.indexNumber, item.indexNumberEnd, item.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.nomadics9.ananas.bindItemImage
|
||||
import com.nomadics9.ananas.core.R
|
||||
import com.nomadics9.ananas.databinding.BaseItemBinding
|
||||
import com.nomadics9.ananas.models.FindroidEpisode
|
||||
import com.nomadics9.ananas.models.FindroidItem
|
||||
import com.nomadics9.ananas.models.isDownloaded
|
||||
import com.nomadics9.ananas.core.R as CoreR
|
||||
|
||||
class ViewItemListAdapter(
|
||||
private val onClickListener: (item: FindroidItem) -> Unit,
|
||||
|
@ -27,7 +27,7 @@ class ViewItemListAdapter(
|
|||
if (item.unplayedItemCount != null && item.unplayedItemCount!! > 0) View.VISIBLE else View.GONE
|
||||
if (fixedWidth) {
|
||||
binding.itemLayout.layoutParams.width =
|
||||
parent.resources.getDimension(R.dimen.overview_media_width).toInt()
|
||||
parent.resources.getDimension(CoreR.dimen.overview_media_width).toInt()
|
||||
(binding.itemLayout.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = 0
|
||||
}
|
||||
|
||||
|
@ -67,4 +67,4 @@ class ViewItemListAdapter(
|
|||
}
|
||||
holder.bind(item, fixedWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import androidx.paging.PagingDataAdapter
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.nomadics9.ananas.bindItemImage
|
||||
import com.nomadics9.ananas.core.R
|
||||
import com.nomadics9.ananas.databinding.BaseItemBinding
|
||||
import com.nomadics9.ananas.models.FindroidEpisode
|
||||
import com.nomadics9.ananas.models.FindroidItem
|
||||
import com.nomadics9.ananas.models.isDownloaded
|
||||
import com.nomadics9.ananas.core.R as CoreR
|
||||
|
||||
class ViewItemPagingAdapter(
|
||||
private val onClickListener: (item: FindroidItem) -> Unit,
|
||||
|
@ -28,7 +28,7 @@ class ViewItemPagingAdapter(
|
|||
if (item.unplayedItemCount != null && item.unplayedItemCount!! > 0) View.VISIBLE else View.GONE
|
||||
if (fixedWidth) {
|
||||
binding.itemLayout.layoutParams.width =
|
||||
parent.resources.getDimension(R.dimen.overview_media_width).toInt()
|
||||
parent.resources.getDimension(CoreR.dimen.overview_media_width).toInt()
|
||||
(binding.itemLayout.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = 0
|
||||
}
|
||||
|
||||
|
|
|
@ -48,24 +48,6 @@
|
|||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_skip_intro"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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"
|
||||
app:icon="@drawable/ic_skip_forward"
|
||||
app:iconGravity="end"
|
||||
app:iconTint="@android:color/white"
|
||||
app:strokeColor="@android:color/white"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.media3.ui.AspectRatioFrameLayout>
|
||||
|
||||
<androidx.media3.ui.SubtitleView
|
||||
|
@ -89,4 +71,33 @@
|
|||
android:layout_height="match_parent"
|
||||
app:animation_enabled="false"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="64dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_watch_credits"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/watch_credits"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_skip_intro"
|
||||
style="@style/Widget.Material3.Button.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_skip_forward"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
4
app/phone/src/main/res/values/strings.xml
Normal file
4
app/phone/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="watch_credits">Watch credits</string>
|
||||
</resources>
|
|
@ -20,11 +20,11 @@ import com.nomadics9.ananas.models.toFindroidEpisodeDto
|
|||
import com.nomadics9.ananas.models.toFindroidMediaStreamDto
|
||||
import com.nomadics9.ananas.models.toFindroidMovieDto
|
||||
import com.nomadics9.ananas.models.toFindroidSeasonDto
|
||||
import com.nomadics9.ananas.models.toFindroidSegmentsDto
|
||||
import com.nomadics9.ananas.models.toFindroidShowDto
|
||||
import com.nomadics9.ananas.models.toFindroidSourceDto
|
||||
import com.nomadics9.ananas.models.toFindroidTrickplayInfoDto
|
||||
import com.nomadics9.ananas.models.toFindroidUserDataDto
|
||||
import com.nomadics9.ananas.models.toIntroDto
|
||||
import com.nomadics9.ananas.repository.JellyfinRepository
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
@ -47,7 +47,7 @@ class DownloaderImpl(
|
|||
): Pair<Long, UiText?> {
|
||||
try {
|
||||
val source = jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId }
|
||||
val intro = jellyfinRepository.getIntroTimestamps(item.id)
|
||||
val segments = jellyfinRepository.getSegmentsTimestamps(item.id)
|
||||
val trickplayInfo = if (item is FindroidSources) {
|
||||
item.trickplayInfo?.get(sourceId)
|
||||
} else {
|
||||
|
@ -79,8 +79,8 @@ class DownloaderImpl(
|
|||
if (trickplayInfo != null) {
|
||||
downloadTrickplayData(item.id, sourceId, trickplayInfo)
|
||||
}
|
||||
if (intro != null) {
|
||||
database.insertIntro(intro.toIntroDto(item.id))
|
||||
if (segments != null) {
|
||||
database.insertSegments(segments.toFindroidSegmentsDto(item.id))
|
||||
}
|
||||
val request = DownloadManager.Request(source.path.toUri())
|
||||
.setTitle(item.name)
|
||||
|
@ -108,8 +108,8 @@ class DownloaderImpl(
|
|||
if (trickplayInfo != null) {
|
||||
downloadTrickplayData(item.id, sourceId, trickplayInfo)
|
||||
}
|
||||
if (intro != null) {
|
||||
database.insertIntro(intro.toIntroDto(item.id))
|
||||
if (segments != null) {
|
||||
database.insertSegments(segments.toFindroidSegmentsDto(item.id))
|
||||
}
|
||||
val request = DownloadManager.Request(source.path.toUri())
|
||||
.setTitle(item.name)
|
||||
|
@ -171,7 +171,7 @@ class DownloaderImpl(
|
|||
|
||||
database.deleteUserData(item.id)
|
||||
|
||||
database.deleteIntro(item.id)
|
||||
database.deleteSegments(item.id)
|
||||
|
||||
File(context.filesDir, "trickplay/${item.id}").deleteRecursively()
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
<string name="add">Aggiungi</string>
|
||||
<string name="quick_connect">Connessione Rapida</string>
|
||||
<string name="pref_player_intro_skipper">Salta intro</string>
|
||||
<string name="pref_player_intro_skipper_summary">Richiede il plugin Intro Skipper di ConfusedPolarBear installato sul server</string>
|
||||
<string name="pref_player_intro_skipper_summary">Richiede il plugin <b>Intro Skipper</b> di <i>jumoog</i> installato sul server</string>
|
||||
<string name="player_gestures_seek_summary">Scorri orizzontalmente per posizionarti avanti o indietro</string>
|
||||
<string name="player_gestures_seek">Gesto posizionamento</string>
|
||||
<string name="audio">Audio</string>
|
||||
|
@ -147,6 +147,8 @@
|
|||
<string name="extra_info">Mostra più informazioni</string>
|
||||
<string name="amoled_theme">Tema scuro AMOLED</string>
|
||||
<string name="amoled_theme_summary">Usa il tema AMOLED con lo sfondo nero</string>
|
||||
<string name="pref_player_trickplay">Anteprima</string>
|
||||
<string name="pref_player_trickplay_summary">Richiede il plugin Jellyscrub di nicknsy installato sul server</string>
|
||||
<string name="size">Dimensione</string>
|
||||
<string name="privacy_policy_notice">Utilizzando Findroid accetti l\'<a href="https://raw.githubusercontent.com/nomadics9/ananas/main/PRIVACY">informativa sulla privacy</a> che afferma che non raccogliamo alcun dato</string>
|
||||
<string name="episode_name_with_end">%1$d-%2$d. %3$s</string>
|
||||
|
@ -183,6 +185,9 @@
|
|||
<string name="live_tv">Diretta TV</string>
|
||||
<string name="play">Riproduci</string>
|
||||
<string name="remove_from_favorites">Rimuovi dai preferiti</string>
|
||||
<string name="skip_intro_button">Salta intro</string>
|
||||
<string name="skip_credit_button">Prossimo episodio</string>
|
||||
<string name="skip_credit_button_last">Chiudi player</string>
|
||||
<string name="player_gestures_chapter_skip">Gesto per le scene</string>
|
||||
<string name="pref_player_chapter_markers">Marcatori delle scene</string>
|
||||
<string name="pref_player_chapter_markers_summary">Mostra i marcatori delle scene sulla timebar</string>
|
||||
|
|
|
@ -200,4 +200,7 @@
|
|||
<string name="download_season_dialog_question">Which episodes do you want to download?</string>
|
||||
<string name="download_season_dialog_download_all">All Episodes</string>
|
||||
<string name="download_season_dialog_download_unwatched">Unwatched Episodes</string>
|
||||
<string name="skip_intro_button">Skip Intro</string>
|
||||
<string name="skip_credit_button">Next episode</string>
|
||||
<string name="skip_credit_button_last">Close player</string>
|
||||
</resources>
|
||||
|
|
855
data/schemas/com.nomadics9.ananas.database.ServerDatabase/6.json
Normal file
855
data/schemas/com.nomadics9.ananas.database.ServerDatabase/6.json
Normal file
|
@ -0,0 +1,855 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 6,
|
||||
"identityHash": "8b765f00961c1833893fc376339db699",
|
||||
"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": "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": []
|
||||
},
|
||||
{
|
||||
"tableName": "trickplayInfos",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceId` TEXT NOT NULL, `width` INTEGER NOT NULL, `height` INTEGER NOT NULL, `tileWidth` INTEGER NOT NULL, `tileHeight` INTEGER NOT NULL, `thumbnailCount` INTEGER NOT NULL, `interval` INTEGER NOT NULL, `bandwidth` INTEGER NOT NULL, PRIMARY KEY(`sourceId`), FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "sourceId",
|
||||
"columnName": "sourceId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "width",
|
||||
"columnName": "width",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "height",
|
||||
"columnName": "height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tileWidth",
|
||||
"columnName": "tileWidth",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tileHeight",
|
||||
"columnName": "tileHeight",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbnailCount",
|
||||
"columnName": "thumbnailCount",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "interval",
|
||||
"columnName": "interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "bandwidth",
|
||||
"columnName": "bandwidth",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"sourceId"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "sources",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"sourceId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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, '8b765f00961c1833893fc376339db699')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.nomadics9.ananas.database
|
|||
|
||||
import androidx.room.TypeConverter
|
||||
import com.nomadics9.ananas.models.FindroidChapter
|
||||
import com.nomadics9.ananas.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<FindroidChapter>? {
|
||||
return value?.let { Json.decodeFromString(value) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromFindroidSegmentsToString(value: List<FindroidSegment>?): String? {
|
||||
return value?.let { Json.encodeToString(value) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToFindroidSegments(value: String?): List<FindroidSegment>? {
|
||||
return value?.let { Json.decodeFromString(value) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,22 +10,23 @@ import com.nomadics9.ananas.models.FindroidEpisodeDto
|
|||
import com.nomadics9.ananas.models.FindroidMediaStreamDto
|
||||
import com.nomadics9.ananas.models.FindroidMovieDto
|
||||
import com.nomadics9.ananas.models.FindroidSeasonDto
|
||||
import com.nomadics9.ananas.models.FindroidSegmentsDto
|
||||
import com.nomadics9.ananas.models.FindroidShowDto
|
||||
import com.nomadics9.ananas.models.FindroidSourceDto
|
||||
import com.nomadics9.ananas.models.FindroidTrickplayInfoDto
|
||||
import com.nomadics9.ananas.models.FindroidUserDataDto
|
||||
import com.nomadics9.ananas.models.IntroDto
|
||||
import com.nomadics9.ananas.models.Server
|
||||
import com.nomadics9.ananas.models.ServerAddress
|
||||
import com.nomadics9.ananas.models.User
|
||||
|
||||
@Database(
|
||||
entities = [Server::class, ServerAddress::class, User::class, FindroidMovieDto::class, FindroidShowDto::class, FindroidSeasonDto::class, FindroidEpisodeDto::class, FindroidSourceDto::class, FindroidMediaStreamDto::class, IntroDto::class, FindroidUserDataDto::class, FindroidTrickplayInfoDto::class],
|
||||
version = 5,
|
||||
entities = [Server::class, ServerAddress::class, User::class, FindroidMovieDto::class, FindroidShowDto::class, FindroidSeasonDto::class, FindroidEpisodeDto::class, FindroidSourceDto::class, FindroidMediaStreamDto::class, FindroidSegmentsDto::class, FindroidUserDataDto::class, FindroidTrickplayInfoDto::class],
|
||||
version = 6,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 2, to = 3),
|
||||
AutoMigration(from = 3, to = 4),
|
||||
AutoMigration(from = 4, to = 5, spec = ServerDatabase.TrickplayMigration::class),
|
||||
AutoMigration(from = 5, to = 6, spec = ServerDatabase.IntrosMigration::class),
|
||||
],
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
@ -34,4 +35,7 @@ abstract class ServerDatabase : RoomDatabase() {
|
|||
|
||||
@DeleteTable(tableName = "trickPlayManifests")
|
||||
class TrickplayMigration : AutoMigrationSpec
|
||||
|
||||
@DeleteTable(tableName = "intros")
|
||||
class IntrosMigration : AutoMigrationSpec
|
||||
}
|
||||
|
|
|
@ -10,11 +10,11 @@ import com.nomadics9.ananas.models.FindroidEpisodeDto
|
|||
import com.nomadics9.ananas.models.FindroidMediaStreamDto
|
||||
import com.nomadics9.ananas.models.FindroidMovieDto
|
||||
import com.nomadics9.ananas.models.FindroidSeasonDto
|
||||
import com.nomadics9.ananas.models.FindroidSegmentsDto
|
||||
import com.nomadics9.ananas.models.FindroidShowDto
|
||||
import com.nomadics9.ananas.models.FindroidSourceDto
|
||||
import com.nomadics9.ananas.models.FindroidTrickplayInfoDto
|
||||
import com.nomadics9.ananas.models.FindroidUserDataDto
|
||||
import com.nomadics9.ananas.models.IntroDto
|
||||
import com.nomadics9.ananas.models.Server
|
||||
import com.nomadics9.ananas.models.ServerAddress
|
||||
import com.nomadics9.ananas.models.ServerWithAddressAndUser
|
||||
|
@ -205,13 +205,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)
|
||||
@Query("DELETE FROM segments WHERE itemId = :itemId")
|
||||
fun deleteSegments(itemId: UUID)
|
||||
|
||||
@Query("SELECT * FROM seasons")
|
||||
fun getSeasons(): List<FindroidSeasonDto>
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package com.nomadics9.ananas.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<FindroidSegment> {
|
||||
return segments.map { segment ->
|
||||
FindroidSegment(
|
||||
type = segment.type,
|
||||
skip = segment.skip,
|
||||
startTime = segment.startTime,
|
||||
endTime = segment.endTime,
|
||||
showAt = segment.showAt,
|
||||
hideAt = segment.hideAt,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.nomadics9.ananas.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<FindroidSegment>,
|
||||
)
|
||||
|
||||
fun List<FindroidSegment>.toFindroidSegmentsDto(itemId: UUID): FindroidSegmentsDto {
|
||||
return FindroidSegmentsDto(
|
||||
itemId = itemId,
|
||||
segments = this,
|
||||
)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package com.nomadics9.ananas.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,
|
||||
)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package com.nomadics9.ananas.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,
|
||||
)
|
||||
}
|
|
@ -6,9 +6,9 @@ import com.nomadics9.ananas.models.FindroidEpisode
|
|||
import com.nomadics9.ananas.models.FindroidItem
|
||||
import com.nomadics9.ananas.models.FindroidMovie
|
||||
import com.nomadics9.ananas.models.FindroidSeason
|
||||
import com.nomadics9.ananas.models.FindroidSegment
|
||||
import com.nomadics9.ananas.models.FindroidShow
|
||||
import com.nomadics9.ananas.models.FindroidSource
|
||||
import com.nomadics9.ananas.models.Intro
|
||||
import com.nomadics9.ananas.models.SortBy
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
|
@ -83,7 +83,7 @@ interface JellyfinRepository {
|
|||
|
||||
suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String
|
||||
|
||||
suspend fun getIntroTimestamps(itemId: UUID): Intro?
|
||||
suspend fun getSegmentsTimestamps(itemId: UUID): List<FindroidSegment>?
|
||||
|
||||
suspend fun getTrickplayData(itemId: UUID, width: Int, index: Int): ByteArray?
|
||||
|
||||
|
|
|
@ -12,18 +12,19 @@ import com.nomadics9.ananas.models.FindroidEpisode
|
|||
import com.nomadics9.ananas.models.FindroidItem
|
||||
import com.nomadics9.ananas.models.FindroidMovie
|
||||
import com.nomadics9.ananas.models.FindroidSeason
|
||||
import com.nomadics9.ananas.models.FindroidSegment
|
||||
import com.nomadics9.ananas.models.FindroidSegments
|
||||
import com.nomadics9.ananas.models.FindroidShow
|
||||
import com.nomadics9.ananas.models.FindroidSource
|
||||
import com.nomadics9.ananas.models.Intro
|
||||
import com.nomadics9.ananas.models.SortBy
|
||||
import com.nomadics9.ananas.models.toFindroidCollection
|
||||
import com.nomadics9.ananas.models.toFindroidEpisode
|
||||
import com.nomadics9.ananas.models.toFindroidItem
|
||||
import com.nomadics9.ananas.models.toFindroidMovie
|
||||
import com.nomadics9.ananas.models.toFindroidSeason
|
||||
import com.nomadics9.ananas.models.toFindroidSegments
|
||||
import com.nomadics9.ananas.models.toFindroidShow
|
||||
import com.nomadics9.ananas.models.toFindroidSource
|
||||
import com.nomadics9.ananas.models.toIntro
|
||||
import io.ktor.util.toByteArray
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -336,12 +337,12 @@ class JellyfinRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getIntroTimestamps(itemId: UUID): Intro? =
|
||||
override suspend fun getSegmentsTimestamps(itemId: UUID): List<FindroidSegment>? =
|
||||
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
|
||||
|
@ -349,10 +350,37 @@ class JellyfinRepositoryImpl(
|
|||
pathParameters["itemId"] = itemId
|
||||
|
||||
try {
|
||||
return@withContext jellyfinApi.api.get<Intro>(
|
||||
"/Episode/{itemId}/IntroTimestamps/v1",
|
||||
val segmentToConvert = jellyfinApi.api.get<FindroidSegments>(
|
||||
"/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
|
||||
}
|
||||
|
|
|
@ -10,16 +10,16 @@ import com.nomadics9.ananas.models.FindroidEpisode
|
|||
import com.nomadics9.ananas.models.FindroidItem
|
||||
import com.nomadics9.ananas.models.FindroidMovie
|
||||
import com.nomadics9.ananas.models.FindroidSeason
|
||||
import com.nomadics9.ananas.models.FindroidSegment
|
||||
import com.nomadics9.ananas.models.FindroidShow
|
||||
import com.nomadics9.ananas.models.FindroidSource
|
||||
import com.nomadics9.ananas.models.Intro
|
||||
import com.nomadics9.ananas.models.SortBy
|
||||
import com.nomadics9.ananas.models.toFindroidEpisode
|
||||
import com.nomadics9.ananas.models.toFindroidMovie
|
||||
import com.nomadics9.ananas.models.toFindroidSeason
|
||||
import com.nomadics9.ananas.models.toFindroidSegments
|
||||
import com.nomadics9.ananas.models.toFindroidShow
|
||||
import com.nomadics9.ananas.models.toFindroidSource
|
||||
import com.nomadics9.ananas.models.toIntro
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -177,9 +177,9 @@ class JellyfinRepositoryOfflineImpl(
|
|||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getIntroTimestamps(itemId: UUID): Intro? =
|
||||
override suspend fun getSegmentsTimestamps(itemId: UUID): List<FindroidSegment>? =
|
||||
withContext(Dispatchers.IO) {
|
||||
database.getIntro(itemId)?.toIntro()
|
||||
database.getSegments(itemId)?.toFindroidSegments()
|
||||
}
|
||||
|
||||
override suspend fun getTrickplayData(itemId: UUID, width: Int, index: Int): ByteArray? =
|
||||
|
|
|
@ -20,7 +20,7 @@ import androidx.media3.exoplayer.ExoPlayer
|
|||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import com.nomadics9.ananas.AppPreferences
|
||||
import com.nomadics9.ananas.models.Intro
|
||||
import com.nomadics9.ananas.models.FindroidSegment
|
||||
import com.nomadics9.ananas.models.PlayerChapter
|
||||
import com.nomadics9.ananas.models.PlayerItem
|
||||
import com.nomadics9.ananas.models.Trickplay
|
||||
|
@ -57,7 +57,8 @@ constructor(
|
|||
private val _uiState = MutableStateFlow(
|
||||
UiState(
|
||||
currentItemTitle = "",
|
||||
currentIntro = null,
|
||||
currentSegment = null,
|
||||
showSkip = false,
|
||||
currentTrickplay = null,
|
||||
currentChapters = null,
|
||||
fileLoaded = false,
|
||||
|
@ -68,11 +69,12 @@ constructor(
|
|||
private val eventsChannel = Channel<PlayerEvents>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
private val intros: MutableMap<UUID, Intro> = mutableMapOf()
|
||||
private val segments: MutableMap<UUID, List<FindroidSegment>> = mutableMapOf()
|
||||
|
||||
data class UiState(
|
||||
val currentItemTitle: String,
|
||||
val currentIntro: Intro?,
|
||||
val currentSegment: FindroidSegment?,
|
||||
val showSkip: Boolean?,
|
||||
val currentTrickplay: Trickplay?,
|
||||
val currentChapters: List<PlayerChapter>?,
|
||||
val fileLoaded: Boolean,
|
||||
|
@ -152,9 +154,10 @@ constructor(
|
|||
}
|
||||
|
||||
if (appPreferences.playerIntroSkipper) {
|
||||
jellyfinRepository.getIntroTimestamps(item.itemId)?.let { intro ->
|
||||
intros[item.itemId] = intro
|
||||
jellyfinRepository.getSegmentsTimestamps(item.itemId)?.let { segment ->
|
||||
segments[item.itemId] = segment
|
||||
}
|
||||
Timber.tag("SegmentInfo").d("Segments: %s", segments)
|
||||
}
|
||||
|
||||
Timber.d("Stream url: $streamUrl")
|
||||
|
@ -239,24 +242,28 @@ constructor(
|
|||
handler.postDelayed(this, 5000L)
|
||||
}
|
||||
}
|
||||
val introCheckRunnable = object : Runnable {
|
||||
val segmentCheckRunnable = 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) {
|
||||
_uiState.update { it.copy(currentIntro = intro) }
|
||||
return@let
|
||||
}
|
||||
_uiState.update { it.copy(currentIntro = null) }
|
||||
val currentMediaItem = player.currentMediaItem
|
||||
if (currentMediaItem != null && currentMediaItem.mediaId.isNotEmpty()) {
|
||||
val itemId = UUID.fromString(currentMediaItem.mediaId)
|
||||
val seconds = player.currentPosition / 1000.0
|
||||
|
||||
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)
|
||||
|
||||
if (currentSegment?.type == "intro") {
|
||||
val showSkip =
|
||||
currentSegment.let { it.skip && seconds in it.showAt..<it.hideAt }
|
||||
_uiState.update { it.copy(showSkip = showSkip) }
|
||||
}
|
||||
}
|
||||
handler.postDelayed(this, 1000L)
|
||||
}
|
||||
}
|
||||
handler.post(playbackProgressRunnable)
|
||||
if (intros.isNotEmpty()) handler.post(introCheckRunnable)
|
||||
if (segments.isNotEmpty()) handler.post(segmentCheckRunnable)
|
||||
}
|
||||
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
|
@ -275,7 +282,14 @@ constructor(
|
|||
} else {
|
||||
item.name
|
||||
}
|
||||
_uiState.update { it.copy(currentItemTitle = itemTitle, currentChapters = item.chapters, fileLoaded = false) }
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
currentItemTitle = itemTitle,
|
||||
currentSegment = null,
|
||||
currentChapters = item.chapters,
|
||||
fileLoaded = false,
|
||||
)
|
||||
}
|
||||
|
||||
jellyfinRepository.postPlaybackStart(item.itemId)
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
<string name="player_controls_rewind">Rebobinar</string>>
|
||||
<string name="player_controls_exit">Salir de reproductor</string>
|
||||
<string name="player_controls_fast_forward">Avanzar</string>
|
||||
<string name="player_controls_skip_intro">Saltar intro</string>
|
||||
<string name="external">Externo</string>
|
||||
<string name="player_controls_skip_back">Saltar atrás</string>
|
||||
<string name="player_controls_play_pause">Reproducir pausar</string>
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
<string name="player_trickplay">Trickplay</string>
|
||||
<string name="player_controls_play_pause">Начало пауза</string>
|
||||
<string name="player_controls_exit">Излез от плейъра</string>
|
||||
<string name="player_controls_skip_intro">Пропусни интро</string>
|
||||
<string name="player_controls_picture_in_picture">Влез в режим картина-в-картина</string>
|
||||
<string name="none">Нищо</string>
|
||||
</resources>
|
|
@ -9,7 +9,6 @@
|
|||
<string name="player_controls_skip_back">Přeskočit zpět</string>
|
||||
<string name="player_controls_play_pause">Přehrát pauza</string>
|
||||
<string name="player_controls_rewind">Přetočit</string>
|
||||
<string name="player_controls_skip_intro">Přeskočit úvod</string>
|
||||
<string name="player_controls_exit">Ukončit přehrávač</string>
|
||||
<string name="player_controls_fast_forward">Rychlý posun vpřed</string>
|
||||
<string name="player_controls_skip_forward">Přeskočit vpřed</string>
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<string name="player_controls_lock">Låser afspilleren</string>
|
||||
<string name="player_controls_skip_back">Hop tilbage</string>
|
||||
<string name="player_controls_exit">Stop afspiller</string>
|
||||
<string name="player_controls_skip_intro">Spring over intro</string>
|
||||
<string name="player_controls_fast_forward">Spol frem</string>
|
||||
<string name="player_controls_skip_forward">Spring frem</string>
|
||||
<string name="player_trickplay">Spole afspille</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Player verlassen</string>
|
||||
<string name="player_controls_rewind">Wiederholen</string>
|
||||
<string name="player_controls_fast_forward">Vorspulen</string>
|
||||
<string name="player_controls_skip_intro">Intro überspringen</string>
|
||||
<string name="player_controls_lock">Player sperren</string>
|
||||
<string name="player_controls_skip_back">Zurückspulen</string>
|
||||
<string name="player_controls_play_pause">Starten/Anhalten</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Salir de reproductor</string>
|
||||
<string name="player_controls_rewind">Atrasar</string>
|
||||
<string name="player_controls_fast_forward">Avanzar</string>
|
||||
<string name="player_controls_skip_intro">Saltar intro</string>
|
||||
<string name="player_controls_skip_forward">Saltar adelante</string>
|
||||
<string name="player_trickplay">Avance</string>
|
||||
<string name="player_controls_lock">Bloquea el reproductor</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Salir del reproductor</string>
|
||||
<string name="player_controls_rewind">Rebobinar</string>
|
||||
<string name="player_controls_fast_forward">Avanzar</string>
|
||||
<string name="player_controls_skip_intro">Saltar introducción</string>
|
||||
<string name="player_controls_skip_forward">Saltar adelante</string>
|
||||
<string name="player_controls_lock">Bloquea el reproductor</string>
|
||||
<string name="player_controls_play_pause">Reproducir pausar</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Quitter le lecteur</string>
|
||||
<string name="player_controls_rewind">Rembobiner</string>
|
||||
<string name="player_controls_fast_forward">Avance rapide</string>
|
||||
<string name="player_controls_skip_intro">Ignorer l\'introduction</string>
|
||||
<string name="player_controls_lock">Verrouille le lecteur</string>
|
||||
<string name="player_controls_play_pause">Lecture / Pause</string>
|
||||
<string name="player_controls_skip_back">Retour en arrière</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Kilépés a lejátszóból</string>
|
||||
<string name="player_controls_rewind">Visszatekerés</string>
|
||||
<string name="player_controls_fast_forward">Előrepörgetés</string>
|
||||
<string name="player_controls_skip_intro">Intro kihagyása</string>
|
||||
<string name="player_controls_lock">Zárolja a lejátszót</string>
|
||||
<string name="player_controls_skip_back">Ugrás vissza</string>
|
||||
<string name="player_controls_skip_forward">Ugrás előre</string>
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
<string name="player_controls_rewind">Riavvolgi</string>
|
||||
<string name="player_controls_lock">Blocca il player</string>
|
||||
<string name="player_controls_exit">Esci dal player</string>
|
||||
<string name="player_controls_skip_intro">Salta intro</string>
|
||||
<string name="player_controls_picture_in_picture">Attiva picture in picture</string>
|
||||
<string name="none">Nessuno</string>
|
||||
</resources>
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">צא מהנגן</string>
|
||||
<string name="player_controls_rewind">הרצה אחורה</string>
|
||||
<string name="player_controls_fast_forward">הרצה קדימה</string>
|
||||
<string name="player_controls_skip_intro">דלג פתיח</string>
|
||||
<string name="player_controls_skip_forward">דלג קדימה</string>
|
||||
<string name="player_controls_lock">נועל את הנגן</string>
|
||||
<string name="player_controls_skip_back">דלג אחורה</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_rewind">되감기</string>
|
||||
<string name="player_controls_fast_forward">빨리 감기</string>
|
||||
<string name="player_controls_exit">플레이어 나가기</string>
|
||||
<string name="player_controls_skip_intro">오프닝 스킵</string>
|
||||
<string name="player_controls_lock">플레이어 잠금</string>
|
||||
<string name="player_trickplay">Trickplay</string>
|
||||
<string name="player_controls_skip_back">뒤로 건너뛰기</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Sluit speler</string>
|
||||
<string name="player_controls_rewind">Terugspoelen</string>
|
||||
<string name="player_controls_fast_forward">Snel vooruit</string>
|
||||
<string name="player_controls_skip_intro">Intro overslaan</string>
|
||||
<string name="player_controls_progress">Voortgangsbalk</string>
|
||||
<string name="none">Geen</string>
|
||||
<string name="player_controls_picture_in_picture">Scherm-in-scherm openen</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Zamknij odtwarzacz</string>
|
||||
<string name="player_controls_rewind">Przewiń</string>
|
||||
<string name="player_controls_fast_forward">Przewiń do przodu</string>
|
||||
<string name="player_controls_skip_intro">Pomiń czołówkę</string>
|
||||
<string name="player_controls_lock">Zablokuj odtwarzacz</string>
|
||||
<string name="player_controls_skip_back">Skocz do tyłu</string>
|
||||
<string name="player_trickplay">Trickplay</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Sair do reprodutor</string>
|
||||
<string name="player_controls_rewind">Retroceder</string>
|
||||
<string name="player_controls_fast_forward">Avanço rápido</string>
|
||||
<string name="player_controls_skip_intro">Pular introdução</string>
|
||||
<string name="player_controls_lock">Bloqueia o reprodutor</string>
|
||||
<string name="player_controls_skip_back">Saltar para trás</string>
|
||||
<string name="player_trickplay">Miniatura de pré-visualização</string>
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
<string name="player_controls_progress">Barra de progresso</string>
|
||||
<string name="player_controls_skip_forward">Avançar</string>
|
||||
<string name="player_controls_skip_back">Pular para trás</string>
|
||||
<string name="player_controls_skip_intro">Pular introdução</string>
|
||||
<string name="player_controls_picture_in_picture">Insira imagem em imagem</string>
|
||||
<string name="none">Nenhum</string>
|
||||
</resources>
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Выйти из проигрывателя</string>
|
||||
<string name="player_controls_rewind">Перемотка</string>
|
||||
<string name="player_controls_fast_forward">Быстрая перемотка</string>
|
||||
<string name="player_controls_skip_intro">Пропустить заставку</string>
|
||||
<string name="player_controls_lock">Блокировка</string>
|
||||
<string name="player_controls_skip_back">Перейти назад</string>
|
||||
<string name="player_controls_play_pause">Плей пауза</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Zavrieť prehrávač</string>
|
||||
<string name="player_controls_rewind">Pretočiť dozadu</string>
|
||||
<string name="player_controls_fast_forward">Pretočiť dopredu</string>
|
||||
<string name="player_controls_skip_intro">Preskočiť úvodnú zvučku</string>
|
||||
<string name="player_controls_lock">Zamkne prehrávač</string>
|
||||
<string name="player_controls_skip_back">Preskočiť späť</string>
|
||||
<string name="player_controls_skip_forward">Preskočiť dopredu</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Izhod iz predvajalnika</string>
|
||||
<string name="player_controls_rewind">Previj nazaj</string>
|
||||
<string name="player_controls_fast_forward">Navijaj naprej</string>
|
||||
<string name="player_controls_skip_intro">Preskoči uvod</string>
|
||||
<string name="player_controls_lock">Zaklene predvajalnik</string>
|
||||
<string name="player_controls_skip_back">Preskoči nazaj</string>
|
||||
<string name="player_controls_play_pause">Predvajaj ustavi</string>
|
||||
|
|
|
@ -8,5 +8,4 @@
|
|||
<string name="player_controls_exit">Avsluta spelare</string>
|
||||
<string name="player_controls_rewind">Spola tillbaka</string>
|
||||
<string name="player_controls_fast_forward">Spola framåt</string>
|
||||
<string name="player_controls_skip_intro">Hoppa över intro</string>
|
||||
</resources>
|
|
@ -8,5 +8,4 @@
|
|||
<string name="player_controls_rewind">Відмотка</string>
|
||||
<string name="player_controls_fast_forward">Швидке перемотування</string>
|
||||
<string name="player_controls_exit">Вийти з плеєра</string>
|
||||
<string name="player_controls_skip_intro">Пропустити вступ</string>
|
||||
</resources>
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">Thoát khỏi trình xem</string>
|
||||
<string name="player_controls_rewind">Tua lùi</string>
|
||||
<string name="player_controls_fast_forward">Tua tới</string>
|
||||
<string name="player_controls_skip_intro">Bỏ qua đoạn mở đầu</string>
|
||||
<string name="player_controls_skip_back">Bỏ qua / Trở về</string>
|
||||
<string name="player_controls_lock">Khoá trình phát</string>
|
||||
<string name="player_controls_play_pause">Phát / Tạm dừng</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">退出播放器</string>
|
||||
<string name="player_controls_rewind">快退</string>
|
||||
<string name="player_controls_fast_forward">快进</string>
|
||||
<string name="player_controls_skip_intro">跳过片头</string>
|
||||
<string name="player_controls_lock">锁定播放器</string>
|
||||
<string name="player_controls_skip_back">跳回</string>
|
||||
<string name="player_controls_play_pause">播放暂停</string>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<string name="player_controls_exit">關閉播放器</string>
|
||||
<string name="player_controls_rewind">倒帶</string>
|
||||
<string name="player_controls_fast_forward">快轉</string>
|
||||
<string name="player_controls_skip_intro">跳過片頭</string>
|
||||
<string name="player_controls_lock">鎖定播放器</string>
|
||||
<string name="player_controls_skip_back">跳回</string>
|
||||
<string name="player_controls_play_pause">播放暫停</string>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
<string name="player_controls_play_pause">Play pause</string>
|
||||
<string name="player_controls_rewind">Rewind</string>
|
||||
<string name="player_controls_exit">Exit player</string>
|
||||
<string name="player_controls_skip_intro">Skip Intro</string>
|
||||
<string name="player_controls_fast_forward">Fast forward</string>
|
||||
<string name="player_controls_skip_forward">Skip forward</string>
|
||||
<string name="player_trickplay">Trickplay</string>
|
||||
|
|
Loading…
Reference in a new issue