mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-25 22:29:45 +03:00
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:
parent
236849b6eb
commit
1c3f5613c9
9 changed files with 251 additions and 183 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
;
|
||||
|
|
|
@ -63,7 +63,7 @@ fun SubtitleSettingsSheet(
|
|||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
when (page) {
|
||||
0 -> SubtitleDelayPage(screenModel)
|
||||
0 -> StreamsDelayPage(screenModel)
|
||||
1 -> SubtitleFontPage(screenModel)
|
||||
2 -> SubtitleColorPage(screenModel)
|
||||
}
|
||||
|
|
|
@ -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() }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue