forked from recloudstream/cloudstream
fixed downloaded subtitles and custom subtitles on downloaded videos
This commit is contained in:
parent
4ec287d1e9
commit
a4bea62392
2 changed files with 93 additions and 31 deletions
|
@ -117,7 +117,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
|
||||||
bottomSheetDialog.findViewById<LinearLayout>(R.id.sort_subtitles_holder)?.visibility = GONE
|
bottomSheetDialog.findViewById<LinearLayout>(R.id.sort_subtitles_holder)?.visibility = GONE
|
||||||
} else {
|
} else {
|
||||||
val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)
|
val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)
|
||||||
arrayAdapter.add("No Subtitles")
|
arrayAdapter.add(view.context.getString(R.string.no_subtitles))
|
||||||
arrayAdapter.addAll(subTracks.mapNotNull { it.name })
|
arrayAdapter.addAll(subTracks.mapNotNull { it.name })
|
||||||
|
|
||||||
subtitleList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
subtitleList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||||
|
|
|
@ -182,6 +182,13 @@ class PlayerFragment : Fragment() {
|
||||||
//private var torrentStream: TorrentStream? = null
|
//private var torrentStream: TorrentStream? = null
|
||||||
private var lastTorrentUrl = ""
|
private var lastTorrentUrl = ""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks reported to be used by exoplayer, since sometimes it has a mind of it's own when selecting subs.
|
||||||
|
* String = lowercase language as set by .setLanguage("_$langId")
|
||||||
|
* Boolean = if it's active
|
||||||
|
* */
|
||||||
|
var exoPlayerSelectedTracks = listOf<Pair<String, Boolean>>()
|
||||||
|
|
||||||
//private val isTorrent: Boolean get() = torrentStream != null
|
//private val isTorrent: Boolean get() = torrentStream != null
|
||||||
private fun initTorrentStream(torrentUrl: String) {
|
private fun initTorrentStream(torrentUrl: String) {
|
||||||
if (lastTorrentUrl == torrentUrl) return
|
if (lastTorrentUrl == torrentUrl) return
|
||||||
|
@ -541,7 +548,11 @@ class PlayerFragment : Fragment() {
|
||||||
when (motionEvent.action) {
|
when (motionEvent.action) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
// SO YOU CAN PULL DOWN STATUSBAR OR NAVBAR
|
// SO YOU CAN PULL DOWN STATUSBAR OR NAVBAR
|
||||||
if (motionEvent.rawY > statusBarHeight && motionEvent.rawX < max(width, height) - navigationBarHeight) {
|
if (motionEvent.rawY > statusBarHeight && motionEvent.rawX < max(
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
) - navigationBarHeight
|
||||||
|
) {
|
||||||
currentX = motionEvent.rawX
|
currentX = motionEvent.rawX
|
||||||
currentY = motionEvent.rawY
|
currentY = motionEvent.rawY
|
||||||
isValidTouch = true
|
isValidTouch = true
|
||||||
|
@ -772,27 +783,42 @@ class PlayerFragment : Fragment() {
|
||||||
// Open file picker
|
// Open file picker
|
||||||
private val subsPathPicker =
|
private val subsPathPicker =
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||||
// It lies, it can be null if file manager quits.
|
normalSafeApiCall {
|
||||||
if (uri == null) return@registerForActivityResult
|
// It lies, it can be null if file manager quits.
|
||||||
val context = context ?: AcraApplication.context ?: return@registerForActivityResult
|
if (uri == null) return@normalSafeApiCall
|
||||||
// RW perms for the path
|
val context = context ?: AcraApplication.context ?: return@normalSafeApiCall
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
// RW perms for the path
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
|
||||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
|
|
||||||
val file = UniFile.fromUri(context, uri)
|
val file = UniFile.fromUri(context, uri)
|
||||||
println("Selected URI path: $uri - Full path: ${file.filePath}")
|
println("Loaded subtitle file. Selected URI path: $uri - Name: ${file.name}")
|
||||||
// DO NOT REMOVE THE FILE EXTENSION FROM NAME, IT'S NEEDED FOR MIME TYPES
|
// DO NOT REMOVE THE FILE EXTENSION FROM NAME, IT'S NEEDED FOR MIME TYPES
|
||||||
val name = file.name ?: uri.toString()
|
val name = file.name ?: uri.toString()
|
||||||
|
|
||||||
viewModel.loadSubtitleFile(uri, name, getEpisode()?.id)
|
// Sets subs manually if downloaded since the viewModel won't exist.
|
||||||
setPreferredSubLanguage(name)
|
if (isDownloadedFile && uriData.id != null)
|
||||||
showToast(
|
allEpisodesSubs[uriData.id!!] =
|
||||||
activity,
|
(allEpisodesSubs[uriData.id]
|
||||||
format(context.getString(R.string.player_loaded_subtitles), name),
|
?: hashMapOf()).apply {
|
||||||
1000
|
this[name] = SubtitleFile(name, uri.toString())
|
||||||
)
|
}
|
||||||
|
else
|
||||||
|
viewModel.loadSubtitleFile(
|
||||||
|
uri,
|
||||||
|
name,
|
||||||
|
getEpisode()?.id
|
||||||
|
)
|
||||||
|
|
||||||
|
setPreferredSubLanguage(name)
|
||||||
|
showToast(
|
||||||
|
activity,
|
||||||
|
String.format(context.getString(R.string.player_loaded_subtitles), name),
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SettingsContentObserver(handler: Handler?, val activity: Activity) :
|
private class SettingsContentObserver(handler: Handler?, val activity: Activity) :
|
||||||
|
@ -811,6 +837,13 @@ class PlayerFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
fun String.toSubtitleMimeType(): String {
|
fun String.toSubtitleMimeType(): String {
|
||||||
return when {
|
return when {
|
||||||
|
// Checks the file name if downloaded.
|
||||||
|
startsWith("content://") -> {
|
||||||
|
UniFile.fromUri(
|
||||||
|
AcraApplication.context,
|
||||||
|
Uri.parse(this)
|
||||||
|
).name?.toSubtitleMimeType() ?: MimeTypes.APPLICATION_SUBRIP
|
||||||
|
}
|
||||||
endsWith("vtt", true) -> MimeTypes.TEXT_VTT
|
endsWith("vtt", true) -> MimeTypes.TEXT_VTT
|
||||||
endsWith("srt", true) -> MimeTypes.APPLICATION_SUBRIP
|
endsWith("srt", true) -> MimeTypes.APPLICATION_SUBRIP
|
||||||
endsWith("xml", true) || endsWith("ttml", true) -> MimeTypes.APPLICATION_TTML
|
endsWith("xml", true) || endsWith("ttml", true) -> MimeTypes.APPLICATION_TTML
|
||||||
|
@ -1316,9 +1349,23 @@ class PlayerFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val startIndexFromMap =
|
/**
|
||||||
currentSubtitles.map { it.trimEnd() }
|
* This will get the actual real track played by exoplayer.
|
||||||
.indexOf(preferredSubtitles.trimEnd()) + 1
|
* */
|
||||||
|
val currentSubtitle = currentSubtitles.firstOrNull { sub ->
|
||||||
|
exoPlayerSelectedTracks.any {
|
||||||
|
// The replace is needed as exoplayer translates _ to -
|
||||||
|
// Also we prefix the languages with _
|
||||||
|
it.second && it.first.replace("-", "") .equals(
|
||||||
|
sub.replace("-", ""),
|
||||||
|
ignoreCase = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val startIndexFromMap = currentSubtitles.indexOf(currentSubtitle) + 1
|
||||||
|
// currentSubtitles.map { it.trimEnd() }
|
||||||
|
// .indexOf(preferredSubtitles.trimEnd()) + 1
|
||||||
var subtitleIndex = startIndexFromMap
|
var subtitleIndex = startIndexFromMap
|
||||||
|
|
||||||
if (currentSubtitles.isEmpty()) {
|
if (currentSubtitles.isEmpty()) {
|
||||||
|
@ -1403,7 +1450,7 @@ class PlayerFragment : Fragment() {
|
||||||
?: realLang else realLang
|
?: realLang else realLang
|
||||||
|
|
||||||
if (!this::exoPlayer.isInitialized) return
|
if (!this::exoPlayer.isInitialized) return
|
||||||
(exoPlayer.trackSelector as DefaultTrackSelector?)?.let { trackSelector ->
|
(exoPlayer.trackSelector as? DefaultTrackSelector?)?.let { trackSelector ->
|
||||||
if (lang.isNullOrBlank()) {
|
if (lang.isNullOrBlank()) {
|
||||||
trackSelector.setParameters(
|
trackSelector.setParameters(
|
||||||
trackSelector.buildUponParameters()
|
trackSelector.buildUponParameters()
|
||||||
|
@ -1582,11 +1629,11 @@ class PlayerFragment : Fragment() {
|
||||||
setPreferredSubLanguage(it)
|
setPreferredSubLanguage(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
sources_btt.visibility =
|
// sources_btt.visibility =
|
||||||
if (isDownloadedFile)
|
// if (isDownloadedFile)
|
||||||
if (context?.getSubs()?.isNullOrEmpty() != false)
|
// if (context?.getSubs()?.isNullOrEmpty() != false)
|
||||||
GONE else VISIBLE
|
// GONE else VISIBLE
|
||||||
else VISIBLE
|
// else VISIBLE
|
||||||
|
|
||||||
player_media_route_button?.isVisible = !isDownloadedFile
|
player_media_route_button?.isVisible = !isDownloadedFile
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
|
@ -1867,6 +1914,10 @@ class PlayerFragment : Fragment() {
|
||||||
if (isDownloadedFile) {
|
if (isDownloadedFile) {
|
||||||
if (!supportsDownloadedFiles) return null
|
if (!supportsDownloadedFiles) return null
|
||||||
val list = ArrayList<SubtitleFile>()
|
val list = ArrayList<SubtitleFile>()
|
||||||
|
|
||||||
|
// Adds custom subtitles
|
||||||
|
allEpisodesSubs[uriData.id]?.values?.forEach { list.add(it) }
|
||||||
|
|
||||||
VideoDownloadManager.getFolder(this, uriData.relativePath, uriData.basePath)
|
VideoDownloadManager.getFolder(this, uriData.relativePath, uriData.basePath)
|
||||||
?.forEach { file ->
|
?.forEach { file ->
|
||||||
val name = uriData.displayName.removeSuffix(".mp4")
|
val name = uriData.displayName.removeSuffix(".mp4")
|
||||||
|
@ -2194,17 +2245,18 @@ class PlayerFragment : Fragment() {
|
||||||
val subSources = sortSubs(subs).map { sub ->
|
val subSources = sortSubs(subs).map { sub ->
|
||||||
// The url can look like .../document/4294 when the name is EnglishSDH.srt
|
// The url can look like .../document/4294 when the name is EnglishSDH.srt
|
||||||
val subtitleMimeType =
|
val subtitleMimeType =
|
||||||
if (sub.url.startsWith("content")) sub.lang.toSubtitleMimeType() else sub.url.toSubtitleMimeType()
|
sub.url.toSubtitleMimeType()
|
||||||
|
|
||||||
val langId =
|
val langId =
|
||||||
sub.lang.trimEnd() //SubtitleHelper.fromLanguageToTwoLetters(it.lang) ?: it.lang
|
sub.lang.trimEnd() //SubtitleHelper.fromLanguageToTwoLetters(it.lang) ?: it.lang
|
||||||
|
|
||||||
subItemsId.add(langId)
|
subItemsId.add(langId)
|
||||||
val subConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url))
|
val subConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url))
|
||||||
.setMimeType(subtitleMimeType)
|
.setMimeType(subtitleMimeType)
|
||||||
.setLanguage("_$langId")
|
.setLanguage("_$langId")
|
||||||
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
||||||
.build()
|
.build()
|
||||||
SingleSampleMediaSource.Factory(getDataSourceFactory(!sub.url.startsWith("content")))
|
SingleSampleMediaSource.Factory(getDataSourceFactory(!sub.url.startsWith("content://")))
|
||||||
.createMediaSource(subConfig, TIME_UNSET)
|
.createMediaSource(subConfig, TIME_UNSET)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2352,6 +2404,16 @@ class PlayerFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
exoPlayer.addListener(object : Player.Listener {
|
exoPlayer.addListener(object : Player.Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the current used subtitle/track. Needed as exoplayer seems to have loose track language selection.
|
||||||
|
* */
|
||||||
|
override fun onTracksInfoChanged(tracksInfo: TracksInfo) {
|
||||||
|
exoPlayerSelectedTracks =
|
||||||
|
tracksInfo.trackGroupInfos.mapNotNull { it.trackGroup.getFormat(0).language?.let { lang -> lang to it.isSelected } }
|
||||||
|
super.onTracksInfoChanged(tracksInfo)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onRenderedFirstFrame() {
|
override fun onRenderedFirstFrame() {
|
||||||
super.onRenderedFirstFrame()
|
super.onRenderedFirstFrame()
|
||||||
isCurrentlySkippingEp = false
|
isCurrentlySkippingEp = false
|
||||||
|
|
Loading…
Reference in a new issue