feat(player): Support for external sub/audio (#1206)

Co-authored-by: Quickdesh <devesh.ratra@gmail.com>
Co-authored-by: jmir1 <jhmiramon@gmail.com>
This commit is contained in:
Abdallah 2023-11-14 20:28:22 +01:00 committed by GitHub
parent 236849b6eb
commit 1c3f5613c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 251 additions and 183 deletions

View file

@ -55,7 +55,7 @@ import eu.kanade.tachiyomi.ui.player.settings.dialogs.SkipIntroLengthDialog
import eu.kanade.tachiyomi.ui.player.settings.dialogs.SpeedPickerDialog
import eu.kanade.tachiyomi.ui.player.settings.sheets.PlayerSettingsSheet
import eu.kanade.tachiyomi.ui.player.settings.sheets.ScreenshotOptionsSheet
import eu.kanade.tachiyomi.ui.player.settings.sheets.TracksCatalogSheet
import eu.kanade.tachiyomi.ui.player.settings.sheets.StreamsCatalogSheet
import eu.kanade.tachiyomi.ui.player.settings.sheets.VideoChaptersSheet
import eu.kanade.tachiyomi.ui.player.settings.sheets.subtitle.SubtitleSettingsSheet
import eu.kanade.tachiyomi.ui.player.settings.sheets.subtitle.toHexString
@ -247,22 +247,19 @@ class PlayerActivity : BaseActivity() {
private val animationHandler = Handler(Looper.getMainLooper())
private val streams: PlayerViewModel.VideoStreams
get() = viewModel.state.value.videoStreams
private var currentVideoList: List<Video>? = null
set(list) {
field = list
streams.quality.tracks = field?.map { Track("", it.quality) }?.toTypedArray() ?: emptyArray()
}
private var playerIsDestroyed = true
private var selectedQualityIndex = 0
private var subtitleTracks: Array<Track> = emptyArray()
private var selectedSubtitleIndex = 0
private var hadPreviousSubs = false
private var audioTracks: Array<Track> = emptyArray()
private var selectedAudioIndex = 0
private var hadPreviousAudio = false
private var videoChapters: List<VideoChapter> = emptyList()
@ -403,23 +400,23 @@ class PlayerActivity : BaseActivity() {
)
}
is PlayerViewModel.Sheet.TracksCatalog -> {
val qualityTracks = currentVideoList?.map { Track("", it.quality) }?.toTypedArray()?.takeUnless { it.isEmpty() }
val subtitleTracks = subtitleTracks.takeUnless { it.isEmpty() }
val audioTracks = audioTracks.takeUnless { it.isEmpty() }
is PlayerViewModel.Sheet.StreamsCatalog -> {
val qualityTracks = streams.quality.tracks.takeUnless { it.isEmpty() }
val subtitleTracks = streams.subtitle.tracks.takeUnless { it.isEmpty() }
val audioTracks = streams.audio.tracks.takeUnless { it.isEmpty() }
if (qualityTracks != null && subtitleTracks != null && audioTracks != null) {
fun onQualitySelected(qualityIndex: Int) {
if (playerIsDestroyed) return
if (selectedQualityIndex == qualityIndex) return
if (streams.quality.index == qualityIndex) return
showLoadingIndicator(true)
logcat(LogPriority.INFO) { "Changing quality" }
setVideoList(qualityIndex, currentVideoList)
}
fun onSubtitleSelected(index: Int) {
if (selectedSubtitleIndex == index || selectedSubtitleIndex > subtitleTracks.lastIndex) return
selectedSubtitleIndex = index
if (streams.subtitle.index == index || streams.subtitle.index > subtitleTracks.lastIndex) return
streams.subtitle.index = index
if (index == 0) {
player.sid = -1
return
@ -434,8 +431,8 @@ class PlayerActivity : BaseActivity() {
}
fun onAudioSelected(index: Int) {
if (selectedAudioIndex == index || selectedAudioIndex > audioTracks.lastIndex) return
selectedAudioIndex = index
if (streams.audio.index == index || streams.audio.index > audioTracks.lastIndex) return
streams.audio.index = index
if (index == 0) {
player.aid = -1
return
@ -449,14 +446,10 @@ class PlayerActivity : BaseActivity() {
?: MPVLib.command(arrayOf("audio-add", audioTracks[index].url, "select", audioTracks[index].url))
}
TracksCatalogSheet(
StreamsCatalogSheet(
isEpisodeOnline = viewModel.isEpisodeOnline(),
qualityTracks = qualityTracks,
subtitleTracks = subtitleTracks,
audioTracks = audioTracks,
selectedQualityIndex = selectedQualityIndex,
selectedSubtitleIndex = selectedSubtitleIndex,
selectedAudioIndex = selectedAudioIndex,
videoStreams = viewModel.state.collectAsState().value.videoStreams,
openContentFd = ::openContentFd,
onQualitySelected = ::onQualitySelected,
onSubtitleSelected = ::onSubtitleSelected,
onAudioSelected = ::onAudioSelected,
@ -468,7 +461,7 @@ class PlayerActivity : BaseActivity() {
is PlayerViewModel.Sheet.SubtitleSettings -> {
SubtitleSettingsSheet(
screenModel = PlayerSettingsScreenModel(viewModel.playerPreferences, subtitleTracks.size > 1),
screenModel = PlayerSettingsScreenModel(viewModel.playerPreferences, streams.subtitle.tracks.size > 1),
onDismissRequest = pauseForDialogSheet(fadeControls = true),
)
}
@ -1385,7 +1378,7 @@ class PlayerActivity : BaseActivity() {
if (playerIsDestroyed) return
currentVideoList = videos
currentVideoList?.getOrNull(qualityIndex)?.let {
selectedQualityIndex = qualityIndex
streams.quality.index = qualityIndex
setHttpOptions(it)
if (viewModel.state.value.isLoadingEpisode) {
viewModel.currentEpisode?.let { episode ->
@ -1401,8 +1394,9 @@ class PlayerActivity : BaseActivity() {
MPVLib.command(arrayOf("set", "start", "${player.timePos}"))
}
}
subtitleTracks = arrayOf(Track("nothing", "None")) + it.subtitleTracks.toTypedArray()
audioTracks = arrayOf(Track("nothing", "None")) + it.audioTracks.toTypedArray()
streams.subtitle.tracks = arrayOf(Track("nothing", "None")) + it.subtitleTracks.toTypedArray()
streams.audio
.tracks = arrayOf(Track("nothing", "None")) + it.audioTracks.toTypedArray()
MPVLib.command(arrayOf("loadfile", parseVideoUrl(it.videoUrl)))
}
refreshUi()
@ -1487,20 +1481,20 @@ class PlayerActivity : BaseActivity() {
val localLangName = LocaleHelper.getSimpleLocaleDisplayName()
clearTracks()
player.loadTracks()
subtitleTracks += player.tracks.getOrElse("sub") { emptyList() }
streams.subtitle.tracks += player.tracks.getOrElse("sub") { emptyList() }
.drop(1).map { track ->
Track(track.mpvId.toString(), track.name)
}.toTypedArray()
audioTracks += player.tracks.getOrElse("audio") { emptyList() }
streams.audio.tracks += player.tracks.getOrElse("audio") { emptyList() }
.drop(1).map { track ->
Track(track.mpvId.toString(), track.name)
}.toTypedArray()
if (hadPreviousSubs) {
subtitleTracks.getOrNull(selectedSubtitleIndex)?.let { sub ->
streams.subtitle.tracks.getOrNull(streams.subtitle.index)?.let { sub ->
MPVLib.command(arrayOf("sub-add", sub.url, "select", sub.url))
}
} else {
currentVideoList?.getOrNull(selectedQualityIndex)
currentVideoList?.getOrNull(streams.quality.index)
?.subtitleTracks?.let { tracks ->
val langIndex = tracks.indexOfFirst {
it.lang.contains(localLangName)
@ -1508,23 +1502,23 @@ class PlayerActivity : BaseActivity() {
val requestedLanguage = if (langIndex == -1) 0 else langIndex
tracks.getOrNull(requestedLanguage)?.let { sub ->
hadPreviousSubs = true
selectedSubtitleIndex = requestedLanguage + 1
streams.subtitle.index = requestedLanguage + 1
MPVLib.command(arrayOf("sub-add", sub.url, "select", sub.url))
}
} ?: run {
val mpvSub = player.tracks.getOrElse("sub") { emptyList() }
.firstOrNull { player.sid == it.mpvId }
selectedSubtitleIndex = mpvSub?.let {
subtitleTracks.indexOfFirst { it.url == mpvSub.mpvId.toString() }
streams.subtitle.index = mpvSub?.let {
streams.subtitle.tracks.indexOfFirst { it.url == mpvSub.mpvId.toString() }
}?.coerceAtLeast(0) ?: 0
}
}
if (hadPreviousAudio) {
audioTracks.getOrNull(selectedAudioIndex)?.let { audio ->
streams.audio.tracks.getOrNull(streams.audio.index)?.let { audio ->
MPVLib.command(arrayOf("audio-add", audio.url, "select", audio.url))
}
} else {
currentVideoList?.getOrNull(selectedQualityIndex)
currentVideoList?.getOrNull(streams.quality.index)
?.audioTracks?.let { tracks ->
val langIndex = tracks.indexOfFirst {
it.lang.contains(localLangName)
@ -1532,14 +1526,14 @@ class PlayerActivity : BaseActivity() {
val requestedLanguage = if (langIndex == -1) 0 else langIndex
tracks.getOrNull(requestedLanguage)?.let { audio ->
hadPreviousAudio = true
selectedAudioIndex = requestedLanguage + 1
streams.audio.index = requestedLanguage + 1
MPVLib.command(arrayOf("audio-add", audio.url, "select", audio.url))
}
} ?: run {
val mpvAudio = player.tracks.getOrElse("audio") { emptyList() }
.firstOrNull { player.aid == it.mpvId }
selectedAudioIndex = mpvAudio?.let {
audioTracks.indexOfFirst { it.url == mpvAudio.mpvId.toString() }
streams.audio.index = mpvAudio?.let {
streams.audio.tracks.indexOfFirst { it.url == mpvAudio.mpvId.toString() }
}?.coerceAtLeast(0) ?: 0
}
}

View file

@ -15,6 +15,7 @@ import eu.kanade.domain.track.anime.store.DelayedAnimeTrackingStore
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.data.database.models.anime.Episode
@ -701,8 +702,8 @@ class PlayerViewModel(
mutableState.update { it.copy(sheet = Sheet.VideoChapters) }
}
fun showTracksCatalog() {
mutableState.update { it.copy(sheet = Sheet.TracksCatalog) }
fun showStreamsCatalog() {
mutableState.update { it.copy(sheet = Sheet.StreamsCatalog) }
}
fun closeDialogSheet() {
@ -714,11 +715,17 @@ class PlayerViewModel(
val episode: Episode? = null,
val anime: Anime? = null,
val source: AnimeSource? = null,
val videoStreams: VideoStreams = VideoStreams(),
val isLoadingEpisode: Boolean = false,
val dialog: Dialog? = null,
val sheet: Sheet? = null,
)
class VideoStreams(val quality: Stream, val subtitle: Stream, val audio: Stream) {
constructor() : this(Stream(), Stream(), Stream())
class Stream(var index: Int = 0, var tracks: Array<Track> = emptyArray())
}
sealed class Dialog {
object EpisodeList : Dialog()
object SpeedPicker : Dialog()
@ -730,13 +737,12 @@ class PlayerViewModel(
object ScreenshotOptions : Sheet()
object PlayerSettings : Sheet()
object VideoChapters : Sheet()
object TracksCatalog : Sheet()
object StreamsCatalog : Sheet()
}
sealed class Event {
data class SetAnimeSkipIntro(val duration: Int) : Event()
data class SetCoverResult(val result: SetAsCover) : Event()
data class SavedImage(val result: SaveImageResult) : Event()
data class ShareImage(val uri: Uri, val seconds: String) : Event()
}

View file

@ -0,0 +1,188 @@
package eu.kanade.tachiyomi.ui.player.settings.sheets
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.ui.player.PlayerViewModel
import eu.kanade.tachiyomi.ui.player.settings.sheetDialogPadding
import `is`.xyz.mpv.MPVLib
import tachiyomi.presentation.core.components.material.padding
import java.io.File
@Composable
fun StreamsCatalogSheet(
isEpisodeOnline: Boolean?,
videoStreams: PlayerViewModel.VideoStreams,
openContentFd: (Uri) -> String?,
onQualitySelected: (Int) -> Unit,
onSubtitleSelected: (Int) -> Unit,
onAudioSelected: (Int) -> Unit,
onSettingsClicked: () -> Unit,
onDismissRequest: () -> Unit,
) {
val tabTitles = mutableListOf(
stringResource(id = R.string.subtitle_dialog_header),
stringResource(id = R.string.audio_dialog_header),
)
if (isEpisodeOnline == true) {
tabTitles.add(0, stringResource(id = R.string.quality_dialog_header))
}
TabbedDialog(
onDismissRequest = onDismissRequest,
tabTitles = tabTitles,
onOverflowMenuClicked = onSettingsClicked,
overflowIcon = Icons.Outlined.Settings,
hideSystemBars = true,
) { contentPadding, page ->
Column(
modifier = Modifier
.padding(contentPadding)
.padding(vertical = TabbedDialogPaddings.Vertical)
.verticalScroll(rememberScrollState()),
) {
@Composable fun QualityTracksPage() = StreamsPageBuilder(
externalTrackCode = null,
stream = videoStreams.quality,
openContentFd = openContentFd,
onTrackSelected = onQualitySelected,
)
@Composable fun SubtitleTracksPage() = StreamsPageBuilder(
externalTrackCode = "sub",
stream = videoStreams.subtitle,
openContentFd = openContentFd,
onTrackSelected = onSubtitleSelected,
)
@Composable fun AudioTracksPage() = StreamsPageBuilder(
externalTrackCode = "audio",
stream = videoStreams.audio,
openContentFd = openContentFd,
onTrackSelected = onAudioSelected,
)
when (page) {
0 -> if (isEpisodeOnline == true) QualityTracksPage() else SubtitleTracksPage()
1 -> if (isEpisodeOnline == true) SubtitleTracksPage() else AudioTracksPage()
2 -> if (isEpisodeOnline == true) AudioTracksPage()
}
}
}
}
@Composable
private fun StreamsPageBuilder(
externalTrackCode: String?,
stream: PlayerViewModel.VideoStreams.Stream,
openContentFd: (Uri) -> String?,
onTrackSelected: (Int) -> Unit,
) {
var tracks by remember { mutableStateOf(stream.tracks) }
var index by remember { mutableStateOf(stream.index) }
val onSelected: (Int) -> Unit = {
onTrackSelected(it)
index = it
stream.index = it
}
if (externalTrackCode != null) {
val addExternalTrack = rememberLauncherForActivityResult(
object : ActivityResultContracts.GetContent() {
override fun createIntent(context: Context, input: String): Intent {
val intent = super.createIntent(context, input)
return if (externalTrackCode == "audio") {
Intent.createChooser(intent, context.getString(R.string.player_add_external_audio_intent))
} else {
Intent.createChooser(intent, context.getString(R.string.player_add_external_subtitles_intent))
}
}
},
) {
if (it != null) {
val url = it.toString()
val path = if (url.startsWith("content://")) {
openContentFd(Uri.parse(url))
} else {
url
} ?: return@rememberLauncherForActivityResult
MPVLib.command(arrayOf("$externalTrackCode-add", path, "cached"))
val title = File(path).name
tracks += Track(path, title)
stream.tracks += Track(path, title)
index = tracks.lastIndex
stream.index = tracks.lastIndex
}
}
val addTrackRes = if (externalTrackCode == "sub") R.string.player_add_external_subtitles else R.string.player_add_external_audio
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { addExternalTrack.launch("*/*") })
.padding(sheetDialogPadding),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.padding(end = MaterialTheme.padding.tiny),
imageVector = Icons.Default.Add,
contentDescription = stringResource(id = addTrackRes),
)
Text(
text = stringResource(id = addTrackRes),
style = MaterialTheme.typography.bodyMedium,
)
}
}
tracks.forEachIndexed { i, track ->
val selected = index == i
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { onSelected(i) })
.padding(sheetDialogPadding),
) {
Text(
text = track.lang,
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
fontStyle = if (selected) FontStyle.Italic else FontStyle.Normal,
style = MaterialTheme.typography.bodyMedium,
color = if (selected) MaterialTheme.colorScheme.primary else Color.Unspecified,
)
}
}
}

View file

@ -1,124 +0,0 @@
package eu.kanade.tachiyomi.ui.player.settings.sheets
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.ui.player.settings.sheetDialogPadding
@Composable
fun TracksCatalogSheet(
isEpisodeOnline: Boolean?,
qualityTracks: Array<Track>,
subtitleTracks: Array<Track>,
audioTracks: Array<Track>,
selectedQualityIndex: Int,
selectedSubtitleIndex: Int,
selectedAudioIndex: Int,
onQualitySelected: (Int) -> Unit,
onSubtitleSelected: (Int) -> Unit,
onAudioSelected: (Int) -> Unit,
onSettingsClicked: () -> Unit,
onDismissRequest: () -> Unit,
) {
val tabTitles = mutableListOf(
stringResource(id = R.string.subtitle_dialog_header),
stringResource(id = R.string.audio_dialog_header),
)
if (isEpisodeOnline == true) {
tabTitles.add(0, stringResource(id = R.string.quality_dialog_header))
}
TabbedDialog(
onDismissRequest = onDismissRequest,
tabTitles = tabTitles,
onOverflowMenuClicked = onSettingsClicked,
overflowIcon = Icons.Outlined.Settings,
hideSystemBars = true,
) { contentPadding, page ->
Column(
modifier = Modifier
.padding(contentPadding)
.padding(vertical = TabbedDialogPaddings.Vertical)
.verticalScroll(rememberScrollState()),
) {
@Composable fun QualityTracksPage() = TracksPageBuilder(
tracks = qualityTracks,
selectedTrackIndex = selectedQualityIndex,
onTrackSelected = onQualitySelected,
)
@Composable fun SubtitleTracksPage() = TracksPageBuilder(
tracks = subtitleTracks,
selectedTrackIndex = selectedSubtitleIndex,
onTrackSelected = onSubtitleSelected,
)
@Composable fun AudioTracksPage() = TracksPageBuilder(
tracks = audioTracks,
selectedTrackIndex = selectedAudioIndex,
onTrackSelected = onAudioSelected,
)
when (page) {
0 -> if (isEpisodeOnline == true) QualityTracksPage() else SubtitleTracksPage()
1 -> if (isEpisodeOnline == true) SubtitleTracksPage() else AudioTracksPage()
2 -> if (isEpisodeOnline == true) AudioTracksPage()
}
}
}
}
@Composable
private fun TracksPageBuilder(
tracks: Array<Track>,
selectedTrackIndex: Int,
onTrackSelected: (Int) -> Unit,
) {
var selectedIndex by remember { mutableStateOf(selectedTrackIndex) }
val onSelected: (Int) -> Unit = { index ->
onTrackSelected(index)
selectedIndex = index
}
tracks.forEachIndexed { index, track ->
val selected = selectedIndex == index
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { onSelected(index) })
.padding(sheetDialogPadding),
) {
Text(
text = track.lang,
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
fontStyle = if (selected) FontStyle.Italic else FontStyle.Normal,
style = MaterialTheme.typography.bodyMedium,
color = if (selected) MaterialTheme.colorScheme.primary else Color.Unspecified,
)
}
}
}

View file

@ -23,7 +23,7 @@ import `is`.xyz.mpv.MPVLib
import tachiyomi.presentation.core.components.material.padding
@Composable
fun SubtitleDelayPage(
fun StreamsDelayPage(
screenModel: PlayerSettingsScreenModel,
) {
Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny)) {
@ -31,13 +31,13 @@ fun SubtitleDelayPage(
val subDelay by remember { mutableStateOf(screenModel.preferences.rememberSubtitlesDelay()) }
var currentSubDelay by rememberSaveable {
mutableStateOf(
(MPVLib.getPropertyDouble(Tracks.SUBTITLES.mpvProperty) * 1000)
(MPVLib.getPropertyDouble(Streams.SUBTITLES.mpvProperty) * 1000)
.toInt(),
)
}
var currentAudioDelay by rememberSaveable {
mutableStateOf(
(MPVLib.getPropertyDouble(Tracks.AUDIO.mpvProperty) * 1000)
(MPVLib.getPropertyDouble(Streams.AUDIO.mpvProperty) * 1000)
.toInt(),
)
}
@ -59,7 +59,7 @@ fun SubtitleDelayPage(
value = currentAudioDelay,
step = 100,
onValueChanged = {
MPVLib.setPropertyDouble(Tracks.AUDIO.mpvProperty, it / 1000.0)
MPVLib.setPropertyDouble(Streams.AUDIO.mpvProperty, it / 1000.0)
screenModel.preferences.audioDelay().set(it)
currentAudioDelay = it
},
@ -86,7 +86,7 @@ fun SubtitleDelayPage(
value = currentSubDelay,
step = 100,
onValueChanged = {
MPVLib.setPropertyDouble(Tracks.SUBTITLES.mpvProperty, it / 1000.0)
MPVLib.setPropertyDouble(Streams.SUBTITLES.mpvProperty, it / 1000.0)
screenModel.preferences.subtitlesDelay().set(it)
currentSubDelay = it
},
@ -95,7 +95,7 @@ fun SubtitleDelayPage(
}
}
private enum class Tracks(val mpvProperty: String) {
private enum class Streams(val mpvProperty: String) {
SUBTITLES("sub-delay"),
AUDIO("audio-delay"),
;

View file

@ -63,7 +63,7 @@ fun SubtitleSettingsSheet(
.verticalScroll(rememberScrollState()),
) {
when (page) {
0 -> SubtitleDelayPage(screenModel)
0 -> StreamsDelayPage(screenModel)
1 -> SubtitleFontPage(screenModel)
2 -> SubtitleColorPage(screenModel)
}

View file

@ -123,7 +123,7 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr
binding.settingsBtn.setOnClickListener { activity.viewModel.showPlayerSettings() }
binding.tracksBtn.setOnClickListener { activity.viewModel.showTracksCatalog() }
binding.streamsBtn.setOnClickListener { activity.viewModel.showStreamsCatalog() }
binding.chaptersBtn.setOnClickListener { activity.viewModel.showVideoChapters() }

View file

@ -117,13 +117,13 @@
android:background="?attr/selectableItemBackground"
android:contentDescription="Settings"
android:src="@drawable/ic_overflow_20dp"
app:layout_constraintLeft_toRightOf="@id/tracksBtn"
app:layout_constraintLeft_toRightOf="@id/streamsBtn"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/episodeListBtn"
app:tint="?attr/colorOnPrimarySurface" />
<ImageButton
android:id="@+id/tracksBtn"
android:id="@+id/streamsBtn"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="?attr/selectableItemBackground"
@ -143,8 +143,8 @@
android:src="@drawable/ic_video_chapter_20dp"
android:visibility="gone"
app:layout_constraintLeft_toRightOf="@id/toggleAutoplay"
app:layout_constraintRight_toLeftOf="@id/tracksBtn"
app:layout_constraintTop_toTopOf="@id/tracksBtn"
app:layout_constraintRight_toLeftOf="@id/streamsBtn"
app:layout_constraintTop_toTopOf="@id/streamsBtn"
app:tint="?attr/colorOnPrimarySurface" />
<com.google.android.material.switchmaterial.SwitchMaterial

View file

@ -333,6 +333,10 @@
<string name="player_subtitle_settings_font_tab">Font</string>
<string name="player_subtitle_settings_color_tab">Color</string>
<string name="player_subtitle_settings">Subtitle settings</string>
<string name="player_add_external_audio">Add external audio</string>
<string name="player_add_external_audio_intent">Select an audio file.</string>
<string name="player_add_external_subtitles">Add external subtitles</string>
<string name="player_add_external_subtitles_intent">Select a subtitle file.</string>
<string name="player_subtitle_empty_warning">Has no effect because there aren\'t any subtitle tracks in this video</string>
<string name="player_override_ass_subtitles">Override ASS subtitles</string>
<string name="player_reset_subtitles">Reset subtitles to default</string>