Initial skip segments implementation

This commit is contained in:
Damontecres 2025-10-09 21:10:43 -04:00
parent 165fd80c0e
commit 405faa388e
No known key found for this signature in database
11 changed files with 262 additions and 9 deletions

View file

@ -6,6 +6,7 @@ import androidx.annotation.StringRes
import com.github.damontecres.dolphin.R import com.github.damontecres.dolphin.R
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.preferences.PreferenceGroup import com.github.damontecres.dolphin.ui.preferences.PreferenceGroup
import com.github.damontecres.dolphin.ui.preferences.PreferenceScreenOption
import com.github.damontecres.dolphin.ui.preferences.PreferenceValidation import com.github.damontecres.dolphin.ui.preferences.PreferenceValidation
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
@ -360,6 +361,77 @@ sealed interface AppPreference<T> {
title = R.string.license_info, title = R.string.license_info,
destination = Destination.License, destination = Destination.License,
) )
val AdvancedSettings =
AppDestinationPreference(
title = R.string.advanced_settings,
destination = Destination.Settings(PreferenceScreenOption.ADVANCED),
)
val SkipIntros =
AppChoicePreference<SkipSegmentBehavior>(
title = R.string.skip_intro_behavior,
defaultValue = SkipSegmentBehavior.ASK_TO_SKIP,
getter = { it.playbackPreferences.skipIntros },
setter = { prefs, value ->
prefs.updatePlaybackPreferences { skipIntros = value }
},
displayValues = R.array.skip_behaviors,
indexToValue = { SkipSegmentBehavior.forNumber(it) },
valueToIndex = { it.number },
)
val SkipOutros =
AppChoicePreference<SkipSegmentBehavior>(
title = R.string.skip_outro_behavior,
defaultValue = SkipSegmentBehavior.ASK_TO_SKIP,
getter = { it.playbackPreferences.skipOutros },
setter = { prefs, value ->
prefs.updatePlaybackPreferences { skipOutros = value }
},
displayValues = R.array.skip_behaviors,
indexToValue = { SkipSegmentBehavior.forNumber(it) },
valueToIndex = { it.number },
)
val SkipCommercials =
AppChoicePreference<SkipSegmentBehavior>(
title = R.string.skip_comercials_behavior,
defaultValue = SkipSegmentBehavior.ASK_TO_SKIP,
getter = { it.playbackPreferences.skipCommercials },
setter = { prefs, value ->
prefs.updatePlaybackPreferences { skipCommercials = value }
},
displayValues = R.array.skip_behaviors,
indexToValue = { SkipSegmentBehavior.forNumber(it) },
valueToIndex = { it.number },
)
val SkipPreviews =
AppChoicePreference<SkipSegmentBehavior>(
title = R.string.skip_previews_behavior,
defaultValue = SkipSegmentBehavior.IGNORE,
getter = { it.playbackPreferences.skipPreviews },
setter = { prefs, value ->
prefs.updatePlaybackPreferences { skipPreviews = value }
},
displayValues = R.array.skip_behaviors,
indexToValue = { SkipSegmentBehavior.forNumber(it) },
valueToIndex = { it.number },
)
val SkipRecaps =
AppChoicePreference<SkipSegmentBehavior>(
title = R.string.skip_recap_behavior,
defaultValue = SkipSegmentBehavior.IGNORE,
getter = { it.playbackPreferences.skipRecaps },
setter = { prefs, value ->
prefs.updatePlaybackPreferences { skipRecaps = value }
},
displayValues = R.array.skip_behaviors,
indexToValue = { SkipSegmentBehavior.forNumber(it) },
valueToIndex = { it.number },
)
} }
} }
@ -386,8 +458,6 @@ val basicPreferences =
AppPreference.AutoPlayNextUp, AppPreference.AutoPlayNextUp,
AppPreference.AutoPlayNextDelay, AppPreference.AutoPlayNextDelay,
AppPreference.SkipBackOnResume, AppPreference.SkipBackOnResume,
AppPreference.MaxBitrate,
AppPreference.PlaybackDebugInfo,
), ),
), ),
PreferenceGroup( PreferenceGroup(
@ -398,11 +468,33 @@ val basicPreferences =
AppPreference.OssLicenseInfo, AppPreference.OssLicenseInfo,
), ),
), ),
PreferenceGroup(
title = R.string.more,
preferences =
listOf(
AppPreference.AdvancedSettings,
),
),
) )
val uiPreferences = listOf<PreferenceGroup>() val uiPreferences = listOf<PreferenceGroup>()
val advancedPreferences = listOf<PreferenceGroup>() val advancedPreferences =
listOf(
PreferenceGroup(
title = R.string.playback,
preferences =
listOf(
AppPreference.SkipIntros,
AppPreference.SkipOutros,
AppPreference.SkipCommercials,
AppPreference.SkipPreviews,
AppPreference.SkipRecaps,
AppPreference.MaxBitrate,
AppPreference.PlaybackDebugInfo,
),
),
)
data class AppSwitchPreference( data class AppSwitchPreference(
@get:StringRes override val title: Int, @get:StringRes override val title: Int,

View file

@ -34,6 +34,11 @@ class AppPreferencesSerializer
skipBackOnResumeSeconds = skipBackOnResumeSeconds =
AppPreference.SkipBackOnResume.defaultValue.seconds.inWholeMilliseconds AppPreference.SkipBackOnResume.defaultValue.seconds.inWholeMilliseconds
maxBitrate = AppPreference.DEFAULT_BITRATE maxBitrate = AppPreference.DEFAULT_BITRATE
skipIntros = AppPreference.SkipIntros.defaultValue
skipOutros = AppPreference.SkipOutros.defaultValue
skipCommercials = AppPreference.SkipCommercials.defaultValue
skipPreviews = AppPreference.SkipPreviews.defaultValue
skipRecaps = AppPreference.SkipRecaps.defaultValue
}.build() }.build()
homePagePreferences = homePagePreferences =
HomePagePreferences HomePagePreferences

View file

@ -5,6 +5,7 @@ package com.github.damontecres.dolphin.ui.nav
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import com.github.damontecres.dolphin.data.model.BaseItem import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.ui.detail.series.SeasonEpisode import com.github.damontecres.dolphin.ui.detail.series.SeasonEpisode
import com.github.damontecres.dolphin.ui.preferences.PreferenceScreenOption
import com.github.damontecres.dolphin.util.UuidSerializer import com.github.damontecres.dolphin.util.UuidSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
@ -28,7 +29,9 @@ sealed class Destination(
) : Destination() ) : Destination()
@Serializable @Serializable
data object Settings : Destination(true) data class Settings(
val screen: PreferenceScreenOption,
) : Destination(true)
@Serializable @Serializable
data object Search : Destination() data object Search : Destination()

View file

@ -16,7 +16,6 @@ import com.github.damontecres.dolphin.ui.detail.series.SeriesOverview
import com.github.damontecres.dolphin.ui.main.HomePage import com.github.damontecres.dolphin.ui.main.HomePage
import com.github.damontecres.dolphin.ui.main.SearchPage import com.github.damontecres.dolphin.ui.main.SearchPage
import com.github.damontecres.dolphin.ui.playback.PlaybackContent import com.github.damontecres.dolphin.ui.playback.PlaybackContent
import com.github.damontecres.dolphin.ui.preferences.PreferenceScreenOption
import com.github.damontecres.dolphin.ui.preferences.PreferencesPage import com.github.damontecres.dolphin.ui.preferences.PreferencesPage
import com.github.damontecres.dolphin.ui.setup.SwitchServerContent import com.github.damontecres.dolphin.ui.setup.SwitchServerContent
import com.github.damontecres.dolphin.ui.setup.SwitchUserContent import com.github.damontecres.dolphin.ui.setup.SwitchUserContent
@ -49,10 +48,10 @@ fun DestinationContent(
Destination.ServerList -> SwitchServerContent(modifier) Destination.ServerList -> SwitchServerContent(modifier)
Destination.UserList -> SwitchUserContent(modifier) Destination.UserList -> SwitchUserContent(modifier)
Destination.Settings -> is Destination.Settings ->
PreferencesPage( PreferencesPage(
preferences.appPreferences, preferences.appPreferences,
PreferenceScreenOption.BASIC, destination.screen,
modifier, modifier,
) )

View file

@ -60,6 +60,7 @@ import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.FontAwesome import com.github.damontecres.dolphin.ui.FontAwesome
import com.github.damontecres.dolphin.ui.ifElse import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.preferences.PreferenceScreenOption
import com.github.damontecres.dolphin.ui.spacedByWithFooter import com.github.damontecres.dolphin.ui.spacedByWithFooter
import com.github.damontecres.dolphin.ui.tryRequestFocus import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.ExceptionHandler import com.github.damontecres.dolphin.util.ExceptionHandler
@ -218,7 +219,11 @@ fun NavDrawer(
icon = Icons.Default.Settings, icon = Icons.Default.Settings,
selected = false, selected = false,
onClick = { onClick = {
viewModel.navigationManager.navigateTo(Destination.Settings) viewModel.navigationManager.navigateTo(
Destination.Settings(
PreferenceScreenOption.BASIC,
),
)
}, },
modifier = Modifier, modifier = Modifier,
) )

View file

@ -54,7 +54,9 @@ import androidx.media3.ui.compose.state.rememberNextButtonState
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
import androidx.media3.ui.compose.state.rememberPresentationState import androidx.media3.ui.compose.state.rememberPresentationState
import androidx.media3.ui.compose.state.rememberPreviousButtonState import androidx.media3.ui.compose.state.rememberPreviousButtonState
import androidx.tv.material3.Button
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import androidx.tv.material3.surfaceColorAtElevation import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.preferences.skipBackOnResume import com.github.damontecres.dolphin.preferences.skipBackOnResume
@ -65,6 +67,7 @@ import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.seasonEpisode import com.github.damontecres.dolphin.util.seasonEpisode
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.jellyfin.sdk.model.api.DeviceProfile import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.extensions.ticks
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -94,6 +97,7 @@ fun PlaybackContent(
val trickplay by viewModel.trickplay.observeAsState(null) val trickplay by viewModel.trickplay.observeAsState(null)
val chapters by viewModel.chapters.observeAsState(listOf()) val chapters by viewModel.chapters.observeAsState(listOf())
val currentPlayback by viewModel.currentPlayback.observeAsState(null) val currentPlayback by viewModel.currentPlayback.observeAsState(null)
val currentSegment by viewModel.currentSegment.observeAsState(null)
var cues by remember { mutableStateOf<List<Cue>>(listOf()) } var cues by remember { mutableStateOf<List<Cue>>(listOf()) }
var showDebugInfo by remember { mutableStateOf(prefs.showDebugInfo) } var showDebugInfo by remember { mutableStateOf(prefs.showDebugInfo) }
@ -315,6 +319,29 @@ fun PlaybackContent(
.background(Color.Transparent), .background(Color.Transparent),
) )
} }
AnimatedVisibility(
currentSegment != null && !controllerViewState.controlsVisible && skipIndicatorDuration == 0L,
modifier =
Modifier
.padding(40.dp)
.align(Alignment.BottomEnd),
) {
currentSegment?.let { segment ->
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Button(
onClick = {
player.seekTo(segment.endTicks.ticks.inWholeMilliseconds)
},
modifier = Modifier.focusRequester(focusRequester),
) {
Text(
text = "Skip ${segment.type.serialName}",
)
}
}
}
} }
BackHandler(nextUp != null) { BackHandler(nextUp != null) {

View file

@ -17,11 +17,13 @@ import androidx.media3.exoplayer.ExoPlayer
import com.github.damontecres.dolphin.data.model.BaseItem import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.data.model.Chapter import com.github.damontecres.dolphin.data.model.Chapter
import com.github.damontecres.dolphin.preferences.AppPreference import com.github.damontecres.dolphin.preferences.AppPreference
import com.github.damontecres.dolphin.preferences.SkipSegmentBehavior
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.DefaultItemFields import com.github.damontecres.dolphin.ui.DefaultItemFields
import com.github.damontecres.dolphin.ui.indexOfFirstOrNull import com.github.damontecres.dolphin.ui.indexOfFirstOrNull
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.util.ApiRequestPager import com.github.damontecres.dolphin.util.ApiRequestPager
import com.github.damontecres.dolphin.util.EqualityMutableLiveData
import com.github.damontecres.dolphin.util.ExceptionHandler import com.github.damontecres.dolphin.util.ExceptionHandler
import com.github.damontecres.dolphin.util.GetEpisodesRequestHandler import com.github.damontecres.dolphin.util.GetEpisodesRequestHandler
import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener
@ -33,28 +35,36 @@ import com.github.damontecres.dolphin.util.subtitleMimeTypes
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
import org.jellyfin.sdk.api.client.extensions.mediaSegmentsApi
import org.jellyfin.sdk.api.client.extensions.trickplayApi import org.jellyfin.sdk.api.client.extensions.trickplayApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.api.client.extensions.videosApi import org.jellyfin.sdk.api.client.extensions.videosApi
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.DeviceProfile import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.MediaSegmentDto
import org.jellyfin.sdk.model.api.MediaSegmentType
import org.jellyfin.sdk.model.api.MediaSourceInfo import org.jellyfin.sdk.model.api.MediaSourceInfo
import org.jellyfin.sdk.model.api.MediaStreamType import org.jellyfin.sdk.model.api.MediaStreamType
import org.jellyfin.sdk.model.api.PlaybackInfoDto import org.jellyfin.sdk.model.api.PlaybackInfoDto
import org.jellyfin.sdk.model.api.SubtitlePlaybackMode import org.jellyfin.sdk.model.api.SubtitlePlaybackMode
import org.jellyfin.sdk.model.api.TrickplayInfo import org.jellyfin.sdk.model.api.TrickplayInfo
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
import org.jellyfin.sdk.model.extensions.inWholeTicks
import org.jellyfin.sdk.model.extensions.ticks import org.jellyfin.sdk.model.extensions.ticks
import org.jellyfin.sdk.model.serializer.toUUIDOrNull import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
enum class TranscodeType { enum class TranscodeType {
DIRECT_PLAY, DIRECT_PLAY,
@ -93,17 +103,20 @@ class PlaybackViewModel
val currentPlayback = MutableLiveData<CurrentPlayback?>(null) val currentPlayback = MutableLiveData<CurrentPlayback?>(null)
val trickplay = MutableLiveData<TrickplayInfo?>(null) val trickplay = MutableLiveData<TrickplayInfo?>(null)
val chapters = MutableLiveData<List<Chapter>>(listOf()) val chapters = MutableLiveData<List<Chapter>>(listOf())
val currentSegment = EqualityMutableLiveData<MediaSegmentDto?>(null)
private lateinit var preferences: UserPreferences private lateinit var preferences: UserPreferences
private lateinit var deviceProfile: DeviceProfile private lateinit var deviceProfile: DeviceProfile
private lateinit var itemId: UUID private lateinit var itemId: UUID
private lateinit var dto: BaseItemDto private lateinit var dto: BaseItemDto
private var activityListener: TrackActivityPlaybackListener? = null
private val episodes = MutableLiveData<ApiRequestPager<GetEpisodesRequest>>() private val episodes = MutableLiveData<ApiRequestPager<GetEpisodesRequest>>()
private var currentEpisodeIndex = Int.MAX_VALUE private var currentEpisodeIndex = Int.MAX_VALUE
val nextUpEpisode = MutableLiveData<BaseItem?>() val nextUpEpisode = MutableLiveData<BaseItem?>()
init { init {
addCloseable { this@PlaybackViewModel.activityListener?.release() }
addCloseable { player.release() } addCloseable { player.release() }
} }
@ -231,10 +244,15 @@ class PlaybackViewModel
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
this@PlaybackViewModel.audioStreams.value = audioStreams this@PlaybackViewModel.audioStreams.value = audioStreams
this@PlaybackViewModel.subtitleStreams.value = subtitleStreams this@PlaybackViewModel.subtitleStreams.value = subtitleStreams
this@PlaybackViewModel.activityListener?.let {
it.release()
player.removeListener(it)
}
val activityListener = val activityListener =
TrackActivityPlaybackListener(api, itemId, player) TrackActivityPlaybackListener(api, itemId, player)
addCloseable { activityListener.release() }
player.addListener(activityListener) player.addListener(activityListener)
this@PlaybackViewModel.activityListener = activityListener
changeStreams( changeStreams(
itemId, itemId,
audioIndex, audioIndex,
@ -249,6 +267,7 @@ class PlaybackViewModel
if (base.type == BaseItemKind.EPISODE) { if (base.type == BaseItemKind.EPISODE) {
base.seriesId?.let(::getEpisodes) base.seriesId?.let(::getEpisodes)
} }
listenForSegments()
} }
} }
@ -485,6 +504,59 @@ class PlaybackViewModel
} }
} }
private var segmentJob: Job? = null
private fun listenForSegments() {
segmentJob?.cancel()
segmentJob =
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
val prefs = preferences.appPreferences.playbackPreferences
val segments by api.mediaSegmentsApi.getItemSegments(itemId)
if (segments.items.isNotEmpty()) {
while (isActive) {
delay(500L)
val currentTicks = player.currentPosition.milliseconds.inWholeTicks
val currentSegment =
segments.items
.firstOrNull {
it.type != MediaSegmentType.UNKNOWN && currentTicks >= it.startTicks && currentTicks < it.endTicks
}
if (currentSegment != null) {
val behavior =
when (currentSegment.type) {
MediaSegmentType.COMMERCIAL -> prefs.skipCommercials
MediaSegmentType.PREVIEW -> prefs.skipPreviews
MediaSegmentType.RECAP -> prefs.skipRecaps
MediaSegmentType.OUTRO -> prefs.skipIntros
MediaSegmentType.INTRO -> prefs.skipOutros
MediaSegmentType.UNKNOWN -> SkipSegmentBehavior.IGNORE
}
withContext(Dispatchers.Main) {
when (behavior) {
SkipSegmentBehavior.AUTO_SKIP -> {
this@PlaybackViewModel.currentSegment.value = null
player.seekTo(currentSegment.endTicks.ticks.inWholeMilliseconds + 1)
}
SkipSegmentBehavior.ASK_TO_SKIP -> {
this@PlaybackViewModel.currentSegment.value = currentSegment
}
else -> {
this@PlaybackViewModel.currentSegment.value = null
}
}
}
} else {
withContext(Dispatchers.Main) {
this@PlaybackViewModel.currentSegment.value = null
}
}
}
}
}
}
fun playUpNextEpisode() { fun playUpNextEpisode() {
nextUpEpisode.value?.let { nextUpEpisode.value?.let {
init(Destination.Playback(it.id, 0, it), deviceProfile, preferences) init(Destination.Playback(it.id, 0, it), deviceProfile, preferences)

View file

@ -0,0 +1,27 @@
package com.github.damontecres.dolphin.util
import androidx.lifecycle.MutableLiveData
/**
* A [MutableLiveData] that only notifies observers if the value does not equal the previous value
*/
class EqualityMutableLiveData<T> : MutableLiveData<T> {
constructor() : super()
constructor(value: T) : super(value)
override fun setValue(value: T?) {
if (value != getValue()) {
super.setValue(value)
}
}
fun setValueNoCheck(value: T?) {
super.setValue(value)
}
override fun postValue(value: T?) {
if (value != getValue()) {
super.postValue(value)
}
}
}

View file

@ -3,6 +3,12 @@ syntax = "proto3";
option java_package = "com.github.damontecres.dolphin.preferences"; option java_package = "com.github.damontecres.dolphin.preferences";
option java_multiple_files = true; option java_multiple_files = true;
enum SkipSegmentBehavior{
IGNORE = 0;
AUTO_SKIP = 1;
ASK_TO_SKIP = 2;
}
message PlaybackPreferences { message PlaybackPreferences {
int64 skip_forward_ms = 1; int64 skip_forward_ms = 1;
int64 skip_back_ms = 2; int64 skip_back_ms = 2;
@ -13,6 +19,12 @@ message PlaybackPreferences {
int64 auto_play_next_delay_seconds = 7; int64 auto_play_next_delay_seconds = 7;
int64 skip_back_on_resume_seconds = 8; int64 skip_back_on_resume_seconds = 8;
int64 max_bitrate = 9; int64 max_bitrate = 9;
SkipSegmentBehavior skip_intros = 10;
SkipSegmentBehavior skip_outros = 11;
SkipSegmentBehavior skip_commercials = 12;
SkipSegmentBehavior skip_recaps = 13;
SkipSegmentBehavior skip_previews = 14;
} }
message HomePagePreferences{ message HomePagePreferences{

View file

@ -16,4 +16,10 @@
<item>Orange</item> <item>Orange</item>
</string-array> </string-array>
<string-array name="skip_behaviors">
<item>Ignore</item>
<item>Skip automatically</item>
<item>Ask to skip</item>
</string-array>
</resources> </resources>

View file

@ -63,5 +63,10 @@
<string name="director">Director</string> <string name="director">Director</string>
<string name="max_bitrate">Max bitrate</string> <string name="max_bitrate">Max bitrate</string>
<string name="app_theme">Application Theme</string> <string name="app_theme">Application Theme</string>
<string name="skip_recap_behavior">Skip recap behavior</string>
<string name="skip_previews_behavior">Skip previews behavior</string>
<string name="skip_comercials_behavior">Skip commercials behavior</string>
<string name="skip_outro_behavior">Skip outro behavior</string>
<string name="skip_intro_behavior">Skip intro behavior</string>
</resources> </resources>