Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: 10.10 Media segments #881

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
856eff6
1 basic
cd16b Oct 27, 2024
37af528
milliseconds threshold between credits end and episode end to skip di…
cd16b Oct 27, 2024
0670824
fix: hide skip button
cd16b Oct 27, 2024
3740de5
pref_player_intro_skipper_next_episode_threshold number input
cd16b Oct 27, 2024
4e37115
Merge branch 'jarnedemeulemeester:main' into Next-episode
cd16b Oct 27, 2024
f444ecf
Jellyfin 10.10 MediaSegments
cd16b Oct 31, 2024
21c40a6
Add Next episode button
cd16b Oct 31, 2024
7e98ee9
Fix skip button visibility
cd16b Oct 31, 2024
0059ca6
Update preferences 1
cd16b Nov 1, 2024
b29a6e6
FindroidSegmentType, preferences summary
cd16b Nov 2, 2024
b232718
Update preferences 2
cd16b Nov 3, 2024
e3f8ea3
Update preferences 3
cd16b Nov 3, 2024
83e5a49
fix auto-skip segment (to review) + rename variable
cd16b Nov 7, 2024
249a7fb
fix auto-skip segment (to review)
cd16b Nov 7, 2024
755b67b
fix auto skip + add string "Skip"
cd16b Nov 8, 2024
bdda1dc
Merge branch 'jarnedemeulemeester:main' into Next-episode
cd16b Nov 8, 2024
6ad7cbb
Fix lint
cd16b Nov 8, 2024
54fafc0
refactor: rename string
cd16b Nov 9, 2024
a44c631
Update README.md
cd16b Nov 11, 2024
4b5a1d1
test: media segments in androidTv
cd16b Nov 12, 2024
cf899a6
Merge branch 'jarnedemeulemeester:main' into MediaSegments
cd16b Nov 12, 2024
de3a835
fix: lint
cd16b Nov 12, 2024
7d796ac
Update README.md
cd16b Nov 13, 2024
326fd28
Merge branch 'main' into MediaSegments
cd16b Nov 16, 2024
46b35d4
fix Button visibile for every segment type
cd16b Nov 18, 2024
456742d
Fix button always visible
cd16b Nov 18, 2024
85628c2
refactor: rename variable
cd16b Nov 19, 2024
a1239eb
fix: lint
cd16b Nov 19, 2024
d550875
move AndroidTv to a new PR
cd16b Nov 21, 2024
656879a
Refactor segment handling with PlayerSegment and auto-skip constants …
cd16b Nov 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ I am developing this application in my spare time.
- Media chapters
- Timeline markers
- Chapter navigation gestures
- Trickplay (requires Jellyfin 10.9 or higher)
- Media segments (requires Jellyfin 10.10 or higher)
- Skip button
- Auto skip

## Planned features
- Android TV
Expand Down
58 changes: 34 additions & 24 deletions app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,18 @@ import androidx.media3.ui.PlayerControlView
import androidx.media3.ui.PlayerView
import androidx.navigation.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.Constants.PlayerMediaSegmentsAutoSkip
import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding
import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment
import dev.jdtech.jellyfin.models.FindroidSegment
import dev.jdtech.jellyfin.models.FindroidSegmentType
import dev.jdtech.jellyfin.models.PlayerSegment
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
import dev.jdtech.jellyfin.utils.PreviewScrubListener
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerEvents
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import dev.jdtech.jellyfin.player.video.R as VideoR

var isControlsLocked: Boolean = false

Expand All @@ -63,7 +62,8 @@ class PlayerActivity : BasePlayerActivity() {
override val viewModel: PlayerActivityViewModel by viewModels()
private var previewScrubListener: PreviewScrubListener? = null
private var wasZoom: Boolean = false
private var segment: FindroidSegment? = null
private var currentMediaSegment: PlayerSegment? = null
private var skipButtonTimeoutExpired: Boolean = true

private lateinit var skipSegmentButton: Button

Expand All @@ -87,6 +87,7 @@ class PlayerActivity : BasePlayerActivity() {
private val skipButtonTimeout = Runnable {
if (!binding.playerView.isControllerFullyVisible) {
skipSegmentButton.isVisible = false
skipButtonTimeoutExpired = true
}
}

Expand Down Expand Up @@ -148,26 +149,34 @@ class PlayerActivity : BasePlayerActivity() {
// Title
videoNameTextView.text = currentItemTitle

// Skip segment button
segment = currentSegment
// Skip segment
currentMediaSegment = currentSegment
Timber.d("Preferences: %s", appPreferences.playerMediaSegmentsSkipButtonType)
currentSegment?.let { segment ->
// Button text
skipSegmentButton.text = when (segment.type) {
FindroidSegmentType.INTRO -> getString(VideoR.string.player_controls_skip_intro)
FindroidSegmentType.CREDITS -> getString(VideoR.string.player_controls_skip_credits)
else -> ""
}
// Buttons visibility
skipSegmentButton.isVisible = segment.type != FindroidSegmentType.UNKNOWN && !isInPictureInPictureMode
if (skipSegmentButton.isVisible) {
handler.removeCallbacks(skipButtonTimeout)
handler.postDelayed(skipButtonTimeout, 5000)
}

// onClick
skipSegmentButton.setOnClickListener {
binding.playerView.player?.seekTo((segment.endTime * 1000).toLong())
skipSegmentButton.isVisible = false
if ((
appPreferences.playerMediaSegmentsAutoSkip == PlayerMediaSegmentsAutoSkip.ALWAYS ||
(appPreferences.playerMediaSegmentsAutoSkip == PlayerMediaSegmentsAutoSkip.PIP && isInPictureInPictureMode)
) &&
appPreferences.playerMediaSegmentsAutoSkipType?.contains(segment.type.toString()) == true
) {
// Auto skip
viewModel.skipSegment(segment)
} else if (appPreferences.playerMediaSegmentsSkipButtonType?.contains(segment.type.toString()) == true) {
// Skip Button - text
skipSegmentButton.text = getString(viewModel.getSkipButtonTextStringId(segment))
// Skip Button - visibility
skipSegmentButton.isVisible = !isInPictureInPictureMode
if (skipSegmentButton.isVisible) {
skipButtonTimeoutExpired = false
handler.removeCallbacks(skipButtonTimeout)
handler.postDelayed(skipButtonTimeout, appPreferences.playerMediaSegmentsSkipButtonDuration * 1000)
}
// Skip Button - onClick
skipSegmentButton.setOnClickListener {
viewModel.skipSegment(segment)
currentMediaSegment = null
skipSegmentButton.isVisible = false
}
}
} ?: run {
skipSegmentButton.isVisible = false
Expand Down Expand Up @@ -308,7 +317,7 @@ class PlayerActivity : BasePlayerActivity() {

binding.playerView.setControllerVisibilityListener(
PlayerView.ControllerVisibilityListener { visibility ->
if (segment != null) {
if (appPreferences.playerMediaSegmentsSkipButtonType?.contains(currentMediaSegment?.type.toString()) == true && skipButtonTimeoutExpired) {
skipSegmentButton.visibility = visibility
}
},
Expand Down Expand Up @@ -343,6 +352,7 @@ class PlayerActivity : BasePlayerActivity() {
} catch (e: Exception) {
Timber.e(e)
}
handler.removeCallbacks(skipButtonTimeout)
finish()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import android.os.Bundle
import android.provider.Settings
import android.text.InputType
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import dev.jdtech.jellyfin.Constants.PlayerMediaSegmentsAutoSkip
import dev.jdtech.jellyfin.core.R as CoreR

class SettingsPlayerFragment : PreferenceFragmentCompat() {
Expand All @@ -22,5 +25,58 @@ class SettingsPlayerFragment : PreferenceFragmentCompat() {
startActivity(Intent(Settings.ACTION_CAPTIONING_SETTINGS))
true
}

// Media Segments - Skip Button
val skipButtonTypePreference = findPreference<MultiSelectListPreference>("pref_player_media_segments_skip_button_type")

skipButtonTypePreference?.let {
setupMultiSelectPreference(it, valueToDisplaySegmentsType)
}

findPreference<EditTextPreference>("pref_player_media_segments_skip_button_duration")?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER
}

// Media Segments - Auto Skip
val autoSkipPreference = findPreference<ListPreference>("pref_player_media_segments_auto_skip")
val autoSkipTypePreference = findPreference<MultiSelectListPreference>("pref_player_media_segments_auto_skip_type")

autoSkipTypePreference?.let {
setupMultiSelectPreference(it, valueToDisplaySegmentsType)
it.isEnabled = autoSkipPreference?.value != PlayerMediaSegmentsAutoSkip.NEVER
}
autoSkipPreference?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val isEnabled = newValue != PlayerMediaSegmentsAutoSkip.NEVER // Enable if value is not "never"
autoSkipTypePreference?.isEnabled = isEnabled
true
}

findPreference<EditTextPreference>("pref_player_media_segments_next_episode_threshold")?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER
}
}

private val valueToDisplaySegmentsType: Map<String, String> by lazy {
val values = resources.getStringArray(CoreR.array.media_segments_type_values)
val displays = resources.getStringArray(CoreR.array.media_segments_type)
values.zip(displays).toMap()
}

private fun setupMultiSelectPreference(preference: MultiSelectListPreference, valueToDisplayMap: Map<String, String>) {
preference.summary = createSummary(preference.values, valueToDisplayMap)
preference.setOnPreferenceChangeListener { _, newValue ->
preference.summary = createSummary(newValue as Set<*>, valueToDisplayMap)
true
}
}

private fun createSummary(selectedValues: Set<*>, valueToDisplayMap: Map<String, String>): String {
return if (selectedValues.isEmpty()) {
getString(CoreR.string.multi_select_preference_summary_none)
} else {
selectedValues.map { value ->
valueToDisplayMap[value] ?: value
}.joinToString(", ")
}
}
}
12 changes: 6 additions & 6 deletions core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ class DownloaderImpl(
database.insertSource(source.toFindroidSourceDto(item.id, path.path.orEmpty()))
database.insertUserData(item.toFindroidUserDataDto(jellyfinRepository.getUserId()))
downloadExternalMediaStreams(item, source, storageIndex)
if (trickplayInfo != null) {
downloadTrickplayData(item.id, sourceId, trickplayInfo)
}
segments.forEach {
database.insertSegment(it.toFindroidSegmentsDto(item.id))
}
if (trickplayInfo != null) {
downloadTrickplayData(item.id, sourceId, trickplayInfo)
}
val request = DownloadManager.Request(source.path.toUri())
.setTitle(item.name)
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
Expand All @@ -105,12 +105,12 @@ class DownloaderImpl(
database.insertSource(source.toFindroidSourceDto(item.id, path.path.orEmpty()))
database.insertUserData(item.toFindroidUserDataDto(jellyfinRepository.getUserId()))
downloadExternalMediaStreams(item, source, storageIndex)
if (trickplayInfo != null) {
downloadTrickplayData(item.id, sourceId, trickplayInfo)
}
segments.forEach {
database.insertSegment(it.toFindroidSegmentsDto(item.id))
}
if (trickplayInfo != null) {
downloadTrickplayData(item.id, sourceId, trickplayInfo)
}
val request = DownloadManager.Request(source.path.toUri())
.setTitle(item.name)
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-b+es+419/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@
<string name="users">Usuarios</string>
<string name="add_user">Agregar usuario</string>
<string name="quick_connect">Conexión rápida</string>
<string name="pref_player_intro_skipper">Saltador de intros (Intro Skipper)</string>
<string name="pref_player_intro_skipper_summary">Requiere que el complemento Intro Skipper de jumoog esté instalado en el servidor</string>
<string name="episode_name_with_end">%1$d-%2$d. %3$s</string>
<string name="episode_name_extended_with_end">T%1$d:E%2$d-%3$d - %4$s</string>
<string name="extra_info_summary">Mostrar información detallada de audio, video y subtítulos</string>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-bg/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@
<string name="sort_by_options_3">Дата Добавен</string>
<string name="seek_back_increment">Инкремент за връщане назад(ms)</string>
<string name="pref_player_mpv_vo">Изхода на видеото</string>
<string name="pref_player_intro_skipper_summary">Изисква Confused Polar Bears Intro Skipper да бъде инсталиран на сървъра</string>
<string name="remove_user">Премахване на потребител</string>
<string name="remove_user_dialog_text">Сигурни ли сте, че искате да премахнете потребител %1$s</string>
<string name="quick_connect">Бързо Свързване</string>
Expand Down Expand Up @@ -159,7 +158,6 @@
<string name="sort_by_options_5">Дата на издаване</string>
<string name="select_video_version_title">Изберете версия</string>
<string name="theme_dark">Тъмна</string>
<string name="pref_player_intro_skipper">Пропускане на интрота</string>
<string name="addresses">Адреси</string>
<string name="add_server_address">Добави адрес на сървър</string>
<string name="player_gestures_zoom">Жест за приближаване (zoom)</string>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-cs-rCZ/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,7 @@
<string name="no_server_connection">Žádné připojení k serveru Jellyfin, pro sledování offline povolte režim offline</string>
<string name="cancel_download">Zrušit stahování</string>
<string name="external">Externí</string>
<string name="pref_player_intro_skipper">Přeskočení úvodu</string>
<string name="shows_label">Seriály</string>
<string name="downloaded_indicator">Indikátor stahování</string>
<string name="sort_order">Pořadí řazení</string>
<string name="pref_player_intro_skipper_summary">Vyžaduje, aby byl na serveru nainstalován plugin Intro Skipper od jumoog</string>
</resources>
2 changes: 0 additions & 2 deletions core/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,6 @@
<string name="addresses">Adressen</string>
<string name="seek_back_increment">Zurückspulstufe (ms)</string>
<string name="users">Benutzer</string>
<string name="pref_player_intro_skipper">Intro überspringen</string>
<string name="pref_player_intro_skipper_summary">Benötigt <i>jumoog\'s</i> <b>Intro Skipper</b> Plugin installiert auf dem Server</string>
<string name="theme_system">Übernehme Systemeinstellung</string>
<string name="pref_player_mpv_hwdec">Hardware-Dekodierung</string>
<string name="pref_player_mpv_vo">Videoausgang</string>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-es-rMX/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@
<string name="theme_dark">Oscuro</string>
<string name="episodes_label">Episodios</string>
<string name="quick_connect">Conexión rápida</string>
<string name="pref_player_intro_skipper">Saltador de intros (Intro Skipper)</string>
<string name="pref_player_intro_skipper_summary">Requiere que el complemento Intro Skipper de jumoog esté instalado en el servidor</string>
<string name="settings_category_network">Red</string>
<string name="settings_connect_timeout">Espera de conexión (ms)</string>
<string name="settings_socket_timeout">Espera del conector (ms)</string>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,6 @@
<string name="remove_user">Quitar usuario</string>
<string name="remove_user_dialog_text">¿Está seguro de querer eliminar el usuario %1$s</string>
<string name="quick_connect">Conexión rápida</string>
<string name="pref_player_intro_skipper">Saltador de intros</string>
<string name="pref_player_intro_skipper_summary">Requiere que el complemento Intro Skipper de jumoog esté instalado en el servidor</string>
<string name="addresses">Direcciones</string>
<string name="add_address">Añadir dirección</string>
<string name="add_server_address">Agregar dirección de servidor</string>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@
<string name="remove_user_dialog_text">Voulez-vous vraiment supprimer l\'utilisateur %1$s</string>
<string name="users">Utilisateurs</string>
<string name="quick_connect">Connexion rapide</string>
<string name="pref_player_intro_skipper">Passer l\'introduction</string>
<string name="pref_player_intro_skipper_summary">Le plugin Intro Skipper de jumoog doit être installé sur le serveur</string>
<string name="theme_system">Suivre le thème du système</string>
<string name="video">Vidéo</string>
<string name="audio">Audio</string>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-hu/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,10 @@
<string name="seeking">Seekelés</string>
<string name="pref_player_mpv_vo">Videó kimenet</string>
<string name="pref_player_mpv_ao">Hang kimenet</string>
<string name="pref_player_intro_skipper">Intro átugró</string>
<string name="addresses">Címek</string>
<string name="add_address">Cím hozzáadása</string>
<string name="add_server_address">Szerver címének hozzáadása</string>
<string name="add">Hozzáadás</string>
<string name="pref_player_intro_skipper_summary">Jumoog Intro Skipper pluginja szükséges hogy telepítve legyen a szerveren</string>
<string name="episodes_label">Epizódok</string>
<string name="player_gestures_vb">Hang és fény gesztusok</string>
<string name="player_gestures_zoom">Zoom gesztus</string>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@
<string name="add_server_address">Aggiungi indirizzo server</string>
<string name="add">Aggiungi</string>
<string name="quick_connect">Connessione Rapida</string>
<string name="pref_player_intro_skipper">Salta intro (Intro Skipper)</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>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-iw/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@
<string name="settings_socket_timeout">זמן קצוב ל-Socket (מילי שניות)</string>
<string name="pref_player_mpv_vo">יציאת וידאו</string>
<string name="pref_player_mpv_ao">יציאת שמע</string>
<string name="pref_player_intro_skipper">מדלג פתיחים</string>
<string name="pref_player_intro_skipper_summary">דורש שתוסף מדלג פתיחים של jumoog יהיה מותקן על השרת</string>
<string name="subtitle_chip_text">כתוביות</string>
<string name="temp">זמני</string>
<string name="extra_info_summary">מציג מידע מפורט על שמע, וידאו וכתוביות</string>
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@
<string name="add_address">주소 추가</string>
<string name="add">추가</string>
<string name="quick_connect">Quick Connect</string>
<string name="pref_player_intro_skipper">Intro Skipper 기능</string>
<string name="pref_player_intro_skipper_summary">서버에 jumoog의 Intro Skipper 플러그인이 설치되어 있어야 합니다.</string>
<string name="internal">내부</string>
<string name="storage_name">%1$s (%2$d MB 사용 가능)</string>
<string name="external">외부</string>
Expand Down
Loading