FindroidSegment
This commit is contained in:
parent
9f3be43eac
commit
df984fb24b
20 changed files with 1011 additions and 297 deletions
|
@ -135,30 +135,39 @@ class PlayerActivity : BasePlayerActivity() {
|
||||||
videoNameTextView.text = currentItemTitle
|
videoNameTextView.text = currentItemTitle
|
||||||
|
|
||||||
// Skip Intro button
|
// Skip Intro button
|
||||||
skipIntroButton.isVisible = !isInPictureInPictureMode && (currentIntro != null || currentCredit != null)
|
// Visibility
|
||||||
skipIntroButton.text = if (currentCredit != null) {
|
skipIntroButton.isVisible = !isInPictureInPictureMode && showSkip == true
|
||||||
if (binding.playerView.player?.hasNextMediaItem() == true) {
|
// Text
|
||||||
getString(CoreR.string.skip_credit_button)
|
when (currentSegment?.type) {
|
||||||
} else {
|
"intro" -> {
|
||||||
getString(CoreR.string.skip_credit_button_last)
|
skipIntroButton.text = getString(CoreR.string.skip_intro_button)
|
||||||
}
|
}
|
||||||
} else {
|
"credit" -> {
|
||||||
getString(CoreR.string.skip_intro_button)
|
skipIntroButton.text = if (binding.playerView.player?.hasNextMediaItem() == true) {
|
||||||
}
|
getString(CoreR.string.skip_credit_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()
|
|
||||||
} else {
|
} 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
|
// Trick Play
|
||||||
previewScrubListener?.let {
|
previewScrubListener?.let {
|
||||||
|
|
|
@ -15,20 +15,18 @@ import dev.jdtech.jellyfin.models.FindroidMovie
|
||||||
import dev.jdtech.jellyfin.models.FindroidSource
|
import dev.jdtech.jellyfin.models.FindroidSource
|
||||||
import dev.jdtech.jellyfin.models.TrickPlayManifest
|
import dev.jdtech.jellyfin.models.TrickPlayManifest
|
||||||
import dev.jdtech.jellyfin.models.UiText
|
import dev.jdtech.jellyfin.models.UiText
|
||||||
import dev.jdtech.jellyfin.models.toCreditDto
|
|
||||||
import dev.jdtech.jellyfin.models.toFindroidEpisodeDto
|
import dev.jdtech.jellyfin.models.toFindroidEpisodeDto
|
||||||
import dev.jdtech.jellyfin.models.toFindroidMediaStreamDto
|
import dev.jdtech.jellyfin.models.toFindroidMediaStreamDto
|
||||||
import dev.jdtech.jellyfin.models.toFindroidMovieDto
|
import dev.jdtech.jellyfin.models.toFindroidMovieDto
|
||||||
import dev.jdtech.jellyfin.models.toFindroidSeasonDto
|
import dev.jdtech.jellyfin.models.toFindroidSeasonDto
|
||||||
|
import dev.jdtech.jellyfin.models.toFindroidSegmentsDto
|
||||||
import dev.jdtech.jellyfin.models.toFindroidShowDto
|
import dev.jdtech.jellyfin.models.toFindroidShowDto
|
||||||
import dev.jdtech.jellyfin.models.toFindroidSourceDto
|
import dev.jdtech.jellyfin.models.toFindroidSourceDto
|
||||||
import dev.jdtech.jellyfin.models.toFindroidUserDataDto
|
import dev.jdtech.jellyfin.models.toFindroidUserDataDto
|
||||||
import dev.jdtech.jellyfin.models.toIntroDto
|
|
||||||
import dev.jdtech.jellyfin.models.toTrickPlayManifestDto
|
import dev.jdtech.jellyfin.models.toTrickPlayManifestDto
|
||||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.Exception
|
|
||||||
import dev.jdtech.jellyfin.core.R as CoreR
|
import dev.jdtech.jellyfin.core.R as CoreR
|
||||||
|
|
||||||
class DownloaderImpl(
|
class DownloaderImpl(
|
||||||
|
@ -46,8 +44,7 @@ class DownloaderImpl(
|
||||||
): Pair<Long, UiText?> {
|
): Pair<Long, UiText?> {
|
||||||
try {
|
try {
|
||||||
val source = jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId }
|
val source = jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId }
|
||||||
val intro = jellyfinRepository.getIntroTimestamps(item.id)
|
val segments = jellyfinRepository.getSegmentsTimestamps(item.id)
|
||||||
val credit = jellyfinRepository.getCreditTimestamps(item.id)
|
|
||||||
val trickPlayManifest = jellyfinRepository.getTrickPlayManifest(item.id)
|
val trickPlayManifest = jellyfinRepository.getTrickPlayManifest(item.id)
|
||||||
val trickPlayData = if (trickPlayManifest != null) {
|
val trickPlayData = if (trickPlayManifest != null) {
|
||||||
jellyfinRepository.getTrickPlayData(
|
jellyfinRepository.getTrickPlayData(
|
||||||
|
@ -80,11 +77,8 @@ class DownloaderImpl(
|
||||||
database.insertSource(source.toFindroidSourceDto(item.id, path.path.orEmpty()))
|
database.insertSource(source.toFindroidSourceDto(item.id, path.path.orEmpty()))
|
||||||
database.insertUserData(item.toFindroidUserDataDto(jellyfinRepository.getUserId()))
|
database.insertUserData(item.toFindroidUserDataDto(jellyfinRepository.getUserId()))
|
||||||
downloadExternalMediaStreams(item, source, storageIndex)
|
downloadExternalMediaStreams(item, source, storageIndex)
|
||||||
if (intro != null) {
|
if (segments != null) {
|
||||||
database.insertIntro(intro.toIntroDto(item.id))
|
database.insertSegments(segments.toFindroidSegmentsDto(item.id))
|
||||||
}
|
|
||||||
if (credit != null) {
|
|
||||||
database.insertCredit(credit.toCreditDto(item.id))
|
|
||||||
}
|
}
|
||||||
if (trickPlayManifest != null && trickPlayData != null) {
|
if (trickPlayManifest != null && trickPlayData != null) {
|
||||||
downloadTrickPlay(item, trickPlayManifest, trickPlayData)
|
downloadTrickPlay(item, trickPlayManifest, trickPlayData)
|
||||||
|
@ -112,11 +106,8 @@ class DownloaderImpl(
|
||||||
database.insertSource(source.toFindroidSourceDto(item.id, path.path.orEmpty()))
|
database.insertSource(source.toFindroidSourceDto(item.id, path.path.orEmpty()))
|
||||||
database.insertUserData(item.toFindroidUserDataDto(jellyfinRepository.getUserId()))
|
database.insertUserData(item.toFindroidUserDataDto(jellyfinRepository.getUserId()))
|
||||||
downloadExternalMediaStreams(item, source, storageIndex)
|
downloadExternalMediaStreams(item, source, storageIndex)
|
||||||
if (intro != null) {
|
if (segments != null) {
|
||||||
database.insertIntro(intro.toIntroDto(item.id))
|
database.insertSegments(segments.toFindroidSegmentsDto(item.id))
|
||||||
}
|
|
||||||
if (credit != null) {
|
|
||||||
database.insertCredit(credit.toCreditDto(item.id))
|
|
||||||
}
|
}
|
||||||
if (trickPlayManifest != null && trickPlayData != null) {
|
if (trickPlayManifest != null && trickPlayData != null) {
|
||||||
downloadTrickPlay(item, trickPlayManifest, trickPlayData)
|
downloadTrickPlay(item, trickPlayManifest, trickPlayData)
|
||||||
|
@ -181,7 +172,7 @@ class DownloaderImpl(
|
||||||
|
|
||||||
database.deleteUserData(item.id)
|
database.deleteUserData(item.id)
|
||||||
|
|
||||||
database.deleteIntro(item.id)
|
database.deleteSegments(item.id)
|
||||||
|
|
||||||
database.deleteTrickPlayManifest(item.id)
|
database.deleteTrickPlayManifest(item.id)
|
||||||
File(context.filesDir, "trickplay/${item.id}.bif").delete()
|
File(context.filesDir, "trickplay/${item.id}.bif").delete()
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
<string name="add">Aggiungi</string>
|
<string name="add">Aggiungi</string>
|
||||||
<string name="quick_connect">Connessione Rapida</string>
|
<string name="quick_connect">Connessione Rapida</string>
|
||||||
<string name="pref_player_intro_skipper">Salta intro</string>
|
<string name="pref_player_intro_skipper">Salta intro</string>
|
||||||
<string name="pref_player_intro_skipper_summary">Richiede il plugin <b>Intro Skipper</b> di <i>ConfusedPolarBear</i> installato sul server.\nInstalla <b>Intro Skipper v0.1.8.0 o maggiore</b> di <i>jumoog</i> per saltare anche i titoli di coda</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_summary">Scorri orizzontalmente per posizionarti avanti o indietro</string>
|
||||||
<string name="player_gestures_seek">Gesto posizionamento</string>
|
<string name="player_gestures_seek">Gesto posizionamento</string>
|
||||||
<string name="audio">Audio</string>
|
<string name="audio">Audio</string>
|
||||||
|
|
|
@ -145,7 +145,7 @@
|
||||||
<string name="pref_player_mpv_vo">Video output</string>
|
<string name="pref_player_mpv_vo">Video output</string>
|
||||||
<string name="pref_player_mpv_ao">Audio output</string>
|
<string name="pref_player_mpv_ao">Audio output</string>
|
||||||
<string name="pref_player_intro_skipper">Intro Skipper</string>
|
<string name="pref_player_intro_skipper">Intro Skipper</string>
|
||||||
<string name="pref_player_intro_skipper_summary">Requires <i>ConfusedPolarBear\'s</i> <b>Intro Skipper</b> plugin to be installed on the server.\nInstall <i>jumoog\'s</i> <b>Intro Skipper v0.1.8.0 or higher</b> to skip end credits.</string>
|
<string name="pref_player_intro_skipper_summary">Requires <i>jumoog\'s</i> <b>Intro Skipper</b> plugin to be installed on the server.</string>
|
||||||
<string name="pref_player_trick_play">Trick Play</string>
|
<string name="pref_player_trick_play">Trick Play</string>
|
||||||
<string name="pref_player_trick_play_summary">Requires <i>nicknsy\'s</i> <b>Jellyscrub</b> plugin to be installed on the server</string>
|
<string name="pref_player_trick_play_summary">Requires <i>nicknsy\'s</i> <b>Jellyscrub</b> plugin to be installed on the server</string>
|
||||||
<string name="pref_player_chapter_markers">Chapter markers</string>
|
<string name="pref_player_chapter_markers">Chapter markers</string>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 3,
|
"version": 3,
|
||||||
"identityHash": "2611f255654b3d481be40f080a8b5401",
|
"identityHash": "3cb9aaa3295b9e461cb94dfc708258ed",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "servers",
|
"tableName": "servers",
|
||||||
|
@ -758,50 +758,6 @@
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": []
|
"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",
|
"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`))",
|
"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": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
813
data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/5.json
Normal file
813
data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/5.json
Normal file
|
@ -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')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package dev.jdtech.jellyfin.database
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import dev.jdtech.jellyfin.models.FindroidChapter
|
import dev.jdtech.jellyfin.models.FindroidChapter
|
||||||
|
import dev.jdtech.jellyfin.models.FindroidSegment
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.jellyfin.sdk.model.DateTime
|
import org.jellyfin.sdk.model.DateTime
|
||||||
|
@ -38,4 +39,14 @@ class Converters {
|
||||||
fun fromStringToFindroidChapters(value: String?): List<FindroidChapter>? {
|
fun fromStringToFindroidChapters(value: String?): List<FindroidChapter>? {
|
||||||
return value?.let { Json.decodeFromString(value) }
|
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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,31 +2,40 @@ package dev.jdtech.jellyfin.database
|
||||||
|
|
||||||
import androidx.room.AutoMigration
|
import androidx.room.AutoMigration
|
||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
|
import androidx.room.DeleteTable
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
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.FindroidEpisodeDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidMediaStreamDto
|
import dev.jdtech.jellyfin.models.FindroidMediaStreamDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidMovieDto
|
import dev.jdtech.jellyfin.models.FindroidMovieDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidSeasonDto
|
import dev.jdtech.jellyfin.models.FindroidSeasonDto
|
||||||
|
import dev.jdtech.jellyfin.models.FindroidSegmentsDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidShowDto
|
import dev.jdtech.jellyfin.models.FindroidShowDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidSourceDto
|
import dev.jdtech.jellyfin.models.FindroidSourceDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidUserDataDto
|
import dev.jdtech.jellyfin.models.FindroidUserDataDto
|
||||||
import dev.jdtech.jellyfin.models.IntroDto
|
|
||||||
import dev.jdtech.jellyfin.models.Server
|
import dev.jdtech.jellyfin.models.Server
|
||||||
import dev.jdtech.jellyfin.models.ServerAddress
|
import dev.jdtech.jellyfin.models.ServerAddress
|
||||||
import dev.jdtech.jellyfin.models.TrickPlayManifestDto
|
import dev.jdtech.jellyfin.models.TrickPlayManifestDto
|
||||||
import dev.jdtech.jellyfin.models.User
|
import dev.jdtech.jellyfin.models.User
|
||||||
|
|
||||||
@Database(
|
@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],
|
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 = 4,
|
version = 5,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration(from = 2, to = 3),
|
AutoMigration(from = 2, to = 3),
|
||||||
AutoMigration(from = 3, to = 4),
|
AutoMigration(from = 3, to = 4),
|
||||||
|
AutoMigration(
|
||||||
|
from = 4,
|
||||||
|
to = 5,
|
||||||
|
spec = ServerDatabase.IntrosAutoMigration::class,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class ServerDatabase : RoomDatabase() {
|
abstract class ServerDatabase : RoomDatabase() {
|
||||||
abstract fun getServerDatabaseDao(): ServerDatabaseDao
|
abstract fun getServerDatabaseDao(): ServerDatabaseDao
|
||||||
|
|
||||||
|
@DeleteTable(tableName = "intros")
|
||||||
|
class IntrosAutoMigration : AutoMigrationSpec
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,14 @@ import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
import dev.jdtech.jellyfin.models.CreditDto
|
|
||||||
import dev.jdtech.jellyfin.models.FindroidEpisodeDto
|
import dev.jdtech.jellyfin.models.FindroidEpisodeDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidMediaStreamDto
|
import dev.jdtech.jellyfin.models.FindroidMediaStreamDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidMovieDto
|
import dev.jdtech.jellyfin.models.FindroidMovieDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidSeasonDto
|
import dev.jdtech.jellyfin.models.FindroidSeasonDto
|
||||||
|
import dev.jdtech.jellyfin.models.FindroidSegmentsDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidShowDto
|
import dev.jdtech.jellyfin.models.FindroidShowDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidSourceDto
|
import dev.jdtech.jellyfin.models.FindroidSourceDto
|
||||||
import dev.jdtech.jellyfin.models.FindroidUserDataDto
|
import dev.jdtech.jellyfin.models.FindroidUserDataDto
|
||||||
import dev.jdtech.jellyfin.models.IntroDto
|
|
||||||
import dev.jdtech.jellyfin.models.Server
|
import dev.jdtech.jellyfin.models.Server
|
||||||
import dev.jdtech.jellyfin.models.ServerAddress
|
import dev.jdtech.jellyfin.models.ServerAddress
|
||||||
import dev.jdtech.jellyfin.models.ServerWithAddressAndUser
|
import dev.jdtech.jellyfin.models.ServerWithAddressAndUser
|
||||||
|
@ -215,22 +214,13 @@ interface ServerDatabaseDao {
|
||||||
fun deleteEpisodesBySeasonId(seasonId: UUID)
|
fun deleteEpisodesBySeasonId(seasonId: UUID)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insertIntro(intro: IntroDto)
|
fun insertSegments(segment: FindroidSegmentsDto)
|
||||||
|
|
||||||
@Query("SELECT * FROM intros WHERE itemId = :itemId")
|
@Query("SELECT * FROM segments WHERE itemId = :itemId")
|
||||||
fun getIntro(itemId: UUID): IntroDto?
|
fun getSegments(itemId: UUID): FindroidSegmentsDto?
|
||||||
|
|
||||||
@Query("DELETE FROM intros WHERE itemId = :itemId")
|
@Query("DELETE FROM segments WHERE itemId = :itemId")
|
||||||
fun deleteIntro(itemId: UUID)
|
fun deleteSegments(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")
|
@Query("SELECT * FROM seasons")
|
||||||
fun getSeasons(): List<FindroidSeasonDto>
|
fun getSeasons(): List<FindroidSeasonDto>
|
||||||
|
|
|
@ -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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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<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 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<FindroidSegment>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun List<FindroidSegment>.toFindroidSegmentsDto(itemId: UUID): FindroidSegmentsDto {
|
||||||
|
return FindroidSegmentsDto(
|
||||||
|
itemId = itemId,
|
||||||
|
segments = this,
|
||||||
|
)
|
||||||
|
}
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,15 +1,14 @@
|
||||||
package dev.jdtech.jellyfin.repository
|
package dev.jdtech.jellyfin.repository
|
||||||
|
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import dev.jdtech.jellyfin.models.Credit
|
|
||||||
import dev.jdtech.jellyfin.models.FindroidCollection
|
import dev.jdtech.jellyfin.models.FindroidCollection
|
||||||
import dev.jdtech.jellyfin.models.FindroidEpisode
|
import dev.jdtech.jellyfin.models.FindroidEpisode
|
||||||
import dev.jdtech.jellyfin.models.FindroidItem
|
import dev.jdtech.jellyfin.models.FindroidItem
|
||||||
import dev.jdtech.jellyfin.models.FindroidMovie
|
import dev.jdtech.jellyfin.models.FindroidMovie
|
||||||
import dev.jdtech.jellyfin.models.FindroidSeason
|
import dev.jdtech.jellyfin.models.FindroidSeason
|
||||||
|
import dev.jdtech.jellyfin.models.FindroidSegment
|
||||||
import dev.jdtech.jellyfin.models.FindroidShow
|
import dev.jdtech.jellyfin.models.FindroidShow
|
||||||
import dev.jdtech.jellyfin.models.FindroidSource
|
import dev.jdtech.jellyfin.models.FindroidSource
|
||||||
import dev.jdtech.jellyfin.models.Intro
|
|
||||||
import dev.jdtech.jellyfin.models.SortBy
|
import dev.jdtech.jellyfin.models.SortBy
|
||||||
import dev.jdtech.jellyfin.models.TrickPlayManifest
|
import dev.jdtech.jellyfin.models.TrickPlayManifest
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -85,9 +84,7 @@ interface JellyfinRepository {
|
||||||
|
|
||||||
suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String
|
suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String): String
|
||||||
|
|
||||||
suspend fun getIntroTimestamps(itemId: UUID): Intro?
|
suspend fun getSegmentsTimestamps(itemId: UUID): List<FindroidSegment>?
|
||||||
|
|
||||||
suspend fun getCreditTimestamps(itemId: UUID): Credit?
|
|
||||||
|
|
||||||
suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest?
|
suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest?
|
||||||
|
|
||||||
|
|
|
@ -7,26 +7,25 @@ import androidx.paging.PagingData
|
||||||
import dev.jdtech.jellyfin.AppPreferences
|
import dev.jdtech.jellyfin.AppPreferences
|
||||||
import dev.jdtech.jellyfin.api.JellyfinApi
|
import dev.jdtech.jellyfin.api.JellyfinApi
|
||||||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||||
import dev.jdtech.jellyfin.models.Credit
|
|
||||||
import dev.jdtech.jellyfin.models.FindroidCollection
|
import dev.jdtech.jellyfin.models.FindroidCollection
|
||||||
import dev.jdtech.jellyfin.models.FindroidEpisode
|
import dev.jdtech.jellyfin.models.FindroidEpisode
|
||||||
import dev.jdtech.jellyfin.models.FindroidItem
|
import dev.jdtech.jellyfin.models.FindroidItem
|
||||||
import dev.jdtech.jellyfin.models.FindroidMovie
|
import dev.jdtech.jellyfin.models.FindroidMovie
|
||||||
import dev.jdtech.jellyfin.models.FindroidSeason
|
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.FindroidShow
|
||||||
import dev.jdtech.jellyfin.models.FindroidSource
|
import dev.jdtech.jellyfin.models.FindroidSource
|
||||||
import dev.jdtech.jellyfin.models.Intro
|
|
||||||
import dev.jdtech.jellyfin.models.SortBy
|
import dev.jdtech.jellyfin.models.SortBy
|
||||||
import dev.jdtech.jellyfin.models.TrickPlayManifest
|
import dev.jdtech.jellyfin.models.TrickPlayManifest
|
||||||
import dev.jdtech.jellyfin.models.toCredit
|
|
||||||
import dev.jdtech.jellyfin.models.toFindroidCollection
|
import dev.jdtech.jellyfin.models.toFindroidCollection
|
||||||
import dev.jdtech.jellyfin.models.toFindroidEpisode
|
import dev.jdtech.jellyfin.models.toFindroidEpisode
|
||||||
import dev.jdtech.jellyfin.models.toFindroidItem
|
import dev.jdtech.jellyfin.models.toFindroidItem
|
||||||
import dev.jdtech.jellyfin.models.toFindroidMovie
|
import dev.jdtech.jellyfin.models.toFindroidMovie
|
||||||
import dev.jdtech.jellyfin.models.toFindroidSeason
|
import dev.jdtech.jellyfin.models.toFindroidSeason
|
||||||
|
import dev.jdtech.jellyfin.models.toFindroidSegments
|
||||||
import dev.jdtech.jellyfin.models.toFindroidShow
|
import dev.jdtech.jellyfin.models.toFindroidShow
|
||||||
import dev.jdtech.jellyfin.models.toFindroidSource
|
import dev.jdtech.jellyfin.models.toFindroidSource
|
||||||
import dev.jdtech.jellyfin.models.toIntro
|
|
||||||
import dev.jdtech.jellyfin.models.toTrickPlayManifest
|
import dev.jdtech.jellyfin.models.toTrickPlayManifest
|
||||||
import io.ktor.util.cio.toByteArray
|
import io.ktor.util.cio.toByteArray
|
||||||
import io.ktor.utils.io.ByteReadChannel
|
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<FindroidSegment>? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val intro = database.getIntro(itemId)?.toIntro()
|
val segments = database.getSegments(itemId)?.toFindroidSegments()
|
||||||
|
|
||||||
if (intro != null) {
|
if (segments != null) {
|
||||||
return@withContext intro
|
return@withContext segments
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/ConfusedPolarBear/intro-skipper/blob/master/docs/api.md
|
// https://github.com/ConfusedPolarBear/intro-skipper/blob/master/docs/api.md
|
||||||
|
@ -354,32 +353,37 @@ class JellyfinRepositoryImpl(
|
||||||
pathParameters["itemId"] = itemId
|
pathParameters["itemId"] = itemId
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return@withContext jellyfinApi.api.get<Intro>(
|
val segmentToConvert = jellyfinApi.api.get<FindroidSegments>(
|
||||||
"/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<String, UUID>()
|
|
||||||
pathParameters["itemId"] = itemId
|
|
||||||
|
|
||||||
try {
|
|
||||||
return@withContext jellyfinApi.api.get<Credit>(
|
|
||||||
"/Episode/{itemId}/IntroSkipperSegments",
|
"/Episode/{itemId}/IntroSkipperSegments",
|
||||||
pathParameters,
|
pathParameters,
|
||||||
).content
|
).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) {
|
} catch (e: Exception) {
|
||||||
return@withContext null
|
return@withContext null
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,24 +5,22 @@ import androidx.paging.PagingData
|
||||||
import dev.jdtech.jellyfin.AppPreferences
|
import dev.jdtech.jellyfin.AppPreferences
|
||||||
import dev.jdtech.jellyfin.api.JellyfinApi
|
import dev.jdtech.jellyfin.api.JellyfinApi
|
||||||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||||
import dev.jdtech.jellyfin.models.Credit
|
|
||||||
import dev.jdtech.jellyfin.models.FindroidCollection
|
import dev.jdtech.jellyfin.models.FindroidCollection
|
||||||
import dev.jdtech.jellyfin.models.FindroidEpisode
|
import dev.jdtech.jellyfin.models.FindroidEpisode
|
||||||
import dev.jdtech.jellyfin.models.FindroidItem
|
import dev.jdtech.jellyfin.models.FindroidItem
|
||||||
import dev.jdtech.jellyfin.models.FindroidMovie
|
import dev.jdtech.jellyfin.models.FindroidMovie
|
||||||
import dev.jdtech.jellyfin.models.FindroidSeason
|
import dev.jdtech.jellyfin.models.FindroidSeason
|
||||||
|
import dev.jdtech.jellyfin.models.FindroidSegment
|
||||||
import dev.jdtech.jellyfin.models.FindroidShow
|
import dev.jdtech.jellyfin.models.FindroidShow
|
||||||
import dev.jdtech.jellyfin.models.FindroidSource
|
import dev.jdtech.jellyfin.models.FindroidSource
|
||||||
import dev.jdtech.jellyfin.models.Intro
|
|
||||||
import dev.jdtech.jellyfin.models.SortBy
|
import dev.jdtech.jellyfin.models.SortBy
|
||||||
import dev.jdtech.jellyfin.models.TrickPlayManifest
|
import dev.jdtech.jellyfin.models.TrickPlayManifest
|
||||||
import dev.jdtech.jellyfin.models.toCredit
|
|
||||||
import dev.jdtech.jellyfin.models.toFindroidEpisode
|
import dev.jdtech.jellyfin.models.toFindroidEpisode
|
||||||
import dev.jdtech.jellyfin.models.toFindroidMovie
|
import dev.jdtech.jellyfin.models.toFindroidMovie
|
||||||
import dev.jdtech.jellyfin.models.toFindroidSeason
|
import dev.jdtech.jellyfin.models.toFindroidSeason
|
||||||
|
import dev.jdtech.jellyfin.models.toFindroidSegments
|
||||||
import dev.jdtech.jellyfin.models.toFindroidShow
|
import dev.jdtech.jellyfin.models.toFindroidShow
|
||||||
import dev.jdtech.jellyfin.models.toFindroidSource
|
import dev.jdtech.jellyfin.models.toFindroidSource
|
||||||
import dev.jdtech.jellyfin.models.toIntro
|
|
||||||
import dev.jdtech.jellyfin.models.toTrickPlayManifest
|
import dev.jdtech.jellyfin.models.toTrickPlayManifest
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -181,14 +179,9 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getIntroTimestamps(itemId: UUID): Intro? =
|
override suspend fun getSegmentsTimestamps(itemId: UUID): List<FindroidSegment>? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
database.getIntro(itemId)?.toIntro()
|
database.getSegments(itemId)?.toFindroidSegments()
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getCreditTimestamps(itemId: UUID): Credit? =
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
database.getCredit(itemId)?.toCredit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest? =
|
override suspend fun getTrickPlayManifest(itemId: UUID): TrickPlayManifest? =
|
||||||
|
|
|
@ -18,8 +18,7 @@ import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dev.jdtech.jellyfin.AppPreferences
|
import dev.jdtech.jellyfin.AppPreferences
|
||||||
import dev.jdtech.jellyfin.models.Credits
|
import dev.jdtech.jellyfin.models.FindroidSegment
|
||||||
import dev.jdtech.jellyfin.models.Intro
|
|
||||||
import dev.jdtech.jellyfin.models.PlayerChapter
|
import dev.jdtech.jellyfin.models.PlayerChapter
|
||||||
import dev.jdtech.jellyfin.models.PlayerItem
|
import dev.jdtech.jellyfin.models.PlayerItem
|
||||||
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
||||||
|
@ -54,8 +53,8 @@ constructor(
|
||||||
private val _uiState = MutableStateFlow(
|
private val _uiState = MutableStateFlow(
|
||||||
UiState(
|
UiState(
|
||||||
currentItemTitle = "",
|
currentItemTitle = "",
|
||||||
currentIntro = null,
|
currentSegment = null,
|
||||||
currentCredit = null,
|
showSkip = false,
|
||||||
currentTrickPlay = null,
|
currentTrickPlay = null,
|
||||||
currentChapters = null,
|
currentChapters = null,
|
||||||
fileLoaded = false,
|
fileLoaded = false,
|
||||||
|
@ -66,15 +65,14 @@ constructor(
|
||||||
private val eventsChannel = Channel<PlayerEvents>()
|
private val eventsChannel = Channel<PlayerEvents>()
|
||||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||||
|
|
||||||
private val intros: MutableMap<UUID, Intro> = mutableMapOf()
|
private val segments: MutableMap<UUID, List<FindroidSegment>> = mutableMapOf()
|
||||||
private val credits: MutableMap<UUID, Credits> = mutableMapOf()
|
|
||||||
|
|
||||||
private val trickPlays: MutableMap<UUID, BifData> = mutableMapOf()
|
private val trickPlays: MutableMap<UUID, BifData> = mutableMapOf()
|
||||||
|
|
||||||
data class UiState(
|
data class UiState(
|
||||||
val currentItemTitle: String,
|
val currentItemTitle: String,
|
||||||
val currentIntro: Intro?,
|
val currentSegment: FindroidSegment?,
|
||||||
val currentCredit: Credits?,
|
val showSkip: Boolean?,
|
||||||
val currentTrickPlay: BifData?,
|
val currentTrickPlay: BifData?,
|
||||||
val currentChapters: List<PlayerChapter>?,
|
val currentChapters: List<PlayerChapter>?,
|
||||||
val fileLoaded: Boolean,
|
val fileLoaded: Boolean,
|
||||||
|
@ -154,12 +152,10 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appPreferences.playerIntroSkipper) {
|
if (appPreferences.playerIntroSkipper) {
|
||||||
jellyfinRepository.getIntroTimestamps(item.itemId)?.let { intro ->
|
jellyfinRepository.getSegmentsTimestamps(item.itemId)?.let { segment ->
|
||||||
intros[item.itemId] = intro
|
segments[item.itemId] = segment
|
||||||
}
|
|
||||||
jellyfinRepository.getCreditTimestamps(item.itemId)?.let { credit ->
|
|
||||||
credits[item.itemId] = credit.credit
|
|
||||||
}
|
}
|
||||||
|
Timber.tag("SegmentInfo").d("Segments: %s", segments)
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.d("Stream url: $streamUrl")
|
Timber.d("Stream url: $streamUrl")
|
||||||
|
@ -244,35 +240,25 @@ constructor(
|
||||||
handler.postDelayed(this, 5000L)
|
handler.postDelayed(this, 5000L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val skipCheckRunnable = object : Runnable {
|
val segmentCheckRunnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) {
|
val currentMediaItem = player.currentMediaItem
|
||||||
val itemId = UUID.fromString(player.currentMediaItem!!.mediaId)
|
if (currentMediaItem != null && currentMediaItem.mediaId.isNotEmpty()) {
|
||||||
|
val itemId = UUID.fromString(currentMediaItem.mediaId)
|
||||||
val seconds = player.currentPosition / 1000.0
|
val seconds = player.currentPosition / 1000.0
|
||||||
if (intros.isNotEmpty()) {
|
|
||||||
intros[itemId]?.let { intro ->
|
val currentSegment = segments[itemId]?.find { segment -> seconds in segment.startTime..segment.endTime }
|
||||||
if (seconds > intro.showSkipPromptAt && seconds < intro.hideSkipPromptAt) {
|
_uiState.update { it.copy(currentSegment = currentSegment) }
|
||||||
_uiState.update { it.copy(currentIntro = intro) }
|
Timber.tag("SegmentInfo").d("currentSegment: %s", currentSegment)
|
||||||
return@let
|
|
||||||
}
|
val showSkip = currentSegment?.let { it.skip && seconds in it.showAt..it.hideAt } ?: false
|
||||||
_uiState.update { it.copy(currentIntro = null) }
|
_uiState.update { it.copy(showSkip = showSkip) }
|
||||||
}
|
|
||||||
}
|
|
||||||
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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
handler.postDelayed(this, 1000L)
|
handler.postDelayed(this, 1000L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handler.post(playbackProgressRunnable)
|
handler.post(playbackProgressRunnable)
|
||||||
if (intros.isNotEmpty() || credits.isNotEmpty()) handler.post(skipCheckRunnable)
|
if (segments.isNotEmpty()) handler.post(segmentCheckRunnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
|
@ -291,9 +277,14 @@ constructor(
|
||||||
} else {
|
} else {
|
||||||
item.name
|
item.name
|
||||||
}
|
}
|
||||||
_uiState.update { it.copy(currentItemTitle = itemTitle, currentChapters = item.chapters, fileLoaded = false) }
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
_uiState.update { it.copy(currentCredit = null) }
|
currentItemTitle = itemTitle,
|
||||||
|
currentSegment = null,
|
||||||
|
currentChapters = item.chapters,
|
||||||
|
fileLoaded = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
jellyfinRepository.postPlaybackStart(item.itemId)
|
jellyfinRepository.postPlaybackStart(item.itemId)
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,4 @@
|
||||||
<string name="none">Ingen</string>
|
<string name="none">Ingen</string>
|
||||||
<string name="player_controls_progress">Process indikator</string>
|
<string name="player_controls_progress">Process indikator</string>
|
||||||
<string name="player_controls_rewind">Spol tilbage</string>
|
<string name="player_controls_rewind">Spol tilbage</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue