2022-01-07 19:27:25 +00:00
|
|
|
package com.lagradost.cloudstream3.ui.player
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.content.Intent
|
|
|
|
import android.os.Bundle
|
2022-04-08 19:38:19 +00:00
|
|
|
import android.util.Log
|
2022-01-07 19:27:25 +00:00
|
|
|
import android.view.LayoutInflater
|
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
|
|
|
import android.widget.*
|
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
import androidx.appcompat.app.AlertDialog
|
|
|
|
import androidx.core.view.isGone
|
|
|
|
import androidx.core.view.isVisible
|
|
|
|
import androidx.lifecycle.ViewModelProvider
|
2022-04-08 19:38:19 +00:00
|
|
|
import androidx.preference.PreferenceManager
|
2022-02-13 20:59:41 +00:00
|
|
|
import com.google.android.exoplayer2.util.MimeTypes
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.google.android.material.button.MaterialButton
|
|
|
|
import com.hippo.unifile.UniFile
|
|
|
|
import com.lagradost.cloudstream3.*
|
2022-02-11 09:17:04 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
|
|
|
import com.lagradost.cloudstream3.mvvm.Resource
|
2022-01-13 21:09:05 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.logError
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
|
|
|
import com.lagradost.cloudstream3.mvvm.observe
|
|
|
|
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
|
|
|
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
2022-01-18 14:10:01 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
2022-04-08 19:38:19 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.result.SyncViewModel
|
2022-01-18 00:24:23 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
2022-01-08 19:39:22 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
|
|
|
import com.lagradost.cloudstream3.utils.*
|
2022-02-11 09:17:04 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
|
|
|
import kotlinx.android.synthetic.main.fragment_player.*
|
|
|
|
import kotlinx.android.synthetic.main.player_custom_layout.*
|
2022-02-11 09:17:04 +00:00
|
|
|
import kotlinx.coroutines.Job
|
2022-01-07 19:27:25 +00:00
|
|
|
|
|
|
|
class GeneratorPlayer : FullScreenPlayer() {
|
|
|
|
companion object {
|
|
|
|
private var lastUsedGenerator: IGenerator? = null
|
2022-04-08 19:38:19 +00:00
|
|
|
fun newInstance(generator: IGenerator, syncData: HashMap<String, String>? = null): Bundle {
|
|
|
|
Log.i(TAG, "newInstance = $syncData")
|
2022-01-07 19:27:25 +00:00
|
|
|
lastUsedGenerator = generator
|
2022-04-08 19:38:19 +00:00
|
|
|
return Bundle().apply {
|
|
|
|
if (syncData != null)
|
|
|
|
putSerializable("syncData", syncData)
|
|
|
|
}
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private lateinit var viewModel: PlayerGeneratorViewModel //by activityViewModels()
|
2022-04-08 19:38:19 +00:00
|
|
|
private lateinit var sync: SyncViewModel
|
2022-01-07 19:27:25 +00:00
|
|
|
private var currentLinks: Set<Pair<ExtractorLink?, ExtractorUri?>> = setOf()
|
|
|
|
private var currentSubs: Set<SubtitleData> = setOf()
|
|
|
|
|
|
|
|
private var currentSelectedLink: Pair<ExtractorLink?, ExtractorUri?>? = null
|
|
|
|
private var currentSelectedSubtitles: SubtitleData? = null
|
|
|
|
private var currentMeta: Any? = null
|
|
|
|
private var nextMeta: Any? = null
|
|
|
|
private var isActive: Boolean = false
|
|
|
|
private var isNextEpisode: Boolean = false // this is used to reset the watch time
|
|
|
|
|
2022-01-08 19:39:22 +00:00
|
|
|
private var preferredAutoSelectSubtitles: String? = null // null means do nothing, "" means none
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
private fun startLoading() {
|
|
|
|
player.release()
|
|
|
|
currentSelectedSubtitles = null
|
|
|
|
isActive = false
|
|
|
|
overlay_loading_skip_button?.isVisible = false
|
|
|
|
player_loading_overlay?.isVisible = true
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun setSubtitles(sub: SubtitleData?): Boolean {
|
|
|
|
currentSelectedSubtitles = sub
|
|
|
|
return player.setPreferredSubtitles(sub)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun noSubtitles(): Boolean {
|
|
|
|
return setSubtitles(null)
|
|
|
|
}
|
|
|
|
|
2022-01-30 22:02:57 +00:00
|
|
|
private fun getPos(): Long {
|
|
|
|
val durPos = DataStoreHelper.getViewPos(viewModel.getId()) ?: return 0L
|
|
|
|
if (durPos.duration == 0L) return 0L
|
|
|
|
if (durPos.position * 100L / durPos.duration > 95L) {
|
|
|
|
return 0L
|
|
|
|
}
|
|
|
|
return durPos.position
|
|
|
|
}
|
|
|
|
|
2022-02-11 09:17:04 +00:00
|
|
|
var currentVerifyLink: Job? = null
|
|
|
|
|
|
|
|
private fun loadExtractorJob(extractorLink: ExtractorLink?) {
|
|
|
|
currentVerifyLink?.cancel()
|
|
|
|
extractorLink?.let {
|
|
|
|
currentVerifyLink = ioSafe {
|
|
|
|
if (it.extractorData != null) {
|
|
|
|
getApiFromNameNull(it.source)?.extractorVerifierJob(it.extractorData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
private fun loadLink(link: Pair<ExtractorLink?, ExtractorUri?>?, sameEpisode: Boolean) {
|
|
|
|
if (link == null) return
|
|
|
|
|
|
|
|
// manage UI
|
|
|
|
player_loading_overlay?.isVisible = false
|
|
|
|
uiReset()
|
|
|
|
currentSelectedLink = link
|
|
|
|
currentMeta = viewModel.getMeta()
|
|
|
|
nextMeta = viewModel.getNextMeta()
|
|
|
|
isActive = true
|
|
|
|
setPlayerDimen(null)
|
|
|
|
setTitle()
|
|
|
|
|
2022-02-11 09:17:04 +00:00
|
|
|
loadExtractorJob(link.first)
|
2022-01-07 19:27:25 +00:00
|
|
|
// load player
|
|
|
|
context?.let { ctx ->
|
|
|
|
val (url, uri) = link
|
|
|
|
player.loadPlayer(
|
|
|
|
ctx,
|
|
|
|
sameEpisode,
|
|
|
|
url,
|
|
|
|
uri,
|
|
|
|
startPosition = if (sameEpisode) null else {
|
2022-01-30 22:02:57 +00:00
|
|
|
if (isNextEpisode) 0L else getPos()
|
2022-01-07 19:27:25 +00:00
|
|
|
},
|
|
|
|
currentSubs,
|
2022-04-08 19:38:19 +00:00
|
|
|
(if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(
|
|
|
|
currentSubs,
|
|
|
|
settings = true,
|
|
|
|
downloads = true
|
|
|
|
),
|
2022-01-07 19:27:25 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun sortLinks(useQualitySettings: Boolean = true): List<Pair<ExtractorLink?, ExtractorUri?>> {
|
|
|
|
return currentLinks.sortedBy {
|
|
|
|
val (linkData, _) = it
|
|
|
|
var quality = linkData?.quality ?: Qualities.Unknown.value
|
|
|
|
|
2022-01-12 17:32:03 +00:00
|
|
|
// we set all qualities above current max as reverse
|
2022-01-07 19:27:25 +00:00
|
|
|
if (useQualitySettings && quality > currentPrefQuality) {
|
2022-01-12 17:32:03 +00:00
|
|
|
quality = currentPrefQuality - quality - 1
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
// negative because we want to sort highest quality first
|
|
|
|
-(quality)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun openSubPicker() {
|
2022-02-18 21:20:35 +00:00
|
|
|
try {
|
|
|
|
subsPathPicker.launch(
|
|
|
|
arrayOf(
|
|
|
|
"text/plain",
|
|
|
|
"text/str",
|
|
|
|
"application/octet-stream",
|
|
|
|
MimeTypes.TEXT_UNKNOWN,
|
|
|
|
MimeTypes.TEXT_VTT,
|
|
|
|
MimeTypes.TEXT_SSA,
|
|
|
|
MimeTypes.APPLICATION_TTML,
|
|
|
|
MimeTypes.APPLICATION_MP4VTT,
|
|
|
|
MimeTypes.APPLICATION_SUBRIP,
|
|
|
|
)
|
2022-01-07 19:27:25 +00:00
|
|
|
)
|
2022-03-01 21:15:18 +00:00
|
|
|
} catch (e: Exception) {
|
2022-02-18 21:20:35 +00:00
|
|
|
logError(e)
|
|
|
|
}
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Open file picker
|
|
|
|
private val subsPathPicker =
|
|
|
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
|
|
|
normalSafeApiCall {
|
|
|
|
// It lies, it can be null if file manager quits.
|
|
|
|
if (uri == null) return@normalSafeApiCall
|
|
|
|
val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall
|
|
|
|
// RW perms for the path
|
|
|
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
|
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
|
|
|
|
|
|
ctx.contentResolver.takePersistableUriPermission(uri, flags)
|
|
|
|
|
|
|
|
val file = UniFile.fromUri(ctx, uri)
|
|
|
|
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
|
|
|
|
val name = file.name ?: uri.toString()
|
|
|
|
|
|
|
|
val subtitleData = SubtitleData(
|
|
|
|
name,
|
|
|
|
uri.toString(),
|
|
|
|
SubtitleOrigin.DOWNLOADED_FILE,
|
|
|
|
name.toSubtitleMimeType()
|
|
|
|
)
|
|
|
|
|
|
|
|
setSubtitles(subtitleData)
|
|
|
|
|
|
|
|
// this is used instead of observe, because observe is too slow
|
|
|
|
val subs = currentSubs.toMutableSet()
|
|
|
|
subs.add(subtitleData)
|
|
|
|
player.setActiveSubtitles(subs)
|
|
|
|
player.reloadPlayer(ctx)
|
|
|
|
|
|
|
|
viewModel.addSubtitles(setOf(subtitleData))
|
|
|
|
|
|
|
|
selectSourceDialog?.dismissSafe()
|
|
|
|
|
|
|
|
showToast(
|
|
|
|
activity,
|
|
|
|
String.format(ctx.getString(R.string.player_loaded_subtitles), name),
|
|
|
|
Toast.LENGTH_LONG
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var selectSourceDialog: AlertDialog? = null
|
|
|
|
override fun showMirrorsDialogue() {
|
2022-01-13 21:09:05 +00:00
|
|
|
try {
|
|
|
|
currentSelectedSubtitles = player.getCurrentPreferredSubtitle()
|
|
|
|
context?.let { ctx ->
|
|
|
|
val isPlaying = player.getIsPlaying()
|
|
|
|
player.handleEvent(CSPlayerEvent.Pause)
|
|
|
|
val currentSubtitles = sortSubs(currentSubs)
|
|
|
|
|
|
|
|
val sourceBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack)
|
|
|
|
.setView(R.layout.player_select_source_and_subs)
|
|
|
|
|
|
|
|
val sourceDialog = sourceBuilder.create()
|
|
|
|
selectSourceDialog = sourceDialog
|
|
|
|
sourceDialog.show()
|
|
|
|
val providerList =
|
|
|
|
sourceDialog.findViewById<ListView>(R.id.sort_providers)!!
|
|
|
|
val subtitleList =
|
|
|
|
sourceDialog.findViewById<ListView>(R.id.sort_subtitles)!!
|
|
|
|
val applyButton =
|
|
|
|
sourceDialog.findViewById<MaterialButton>(R.id.apply_btt)!!
|
|
|
|
val cancelButton =
|
|
|
|
sourceDialog.findViewById<MaterialButton>(R.id.cancel_btt)!!
|
|
|
|
|
|
|
|
val footer: TextView =
|
|
|
|
layoutInflater.inflate(R.layout.sort_bottom_footer_add_choice, null) as TextView
|
|
|
|
footer.text = ctx.getString(R.string.player_load_subtitles)
|
|
|
|
footer.setOnClickListener {
|
|
|
|
openSubPicker()
|
|
|
|
}
|
|
|
|
subtitleList.addFooterView(footer)
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
var sourceIndex = 0
|
|
|
|
var startSource = 0
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
val sortedUrls = sortLinks(useQualitySettings = false)
|
|
|
|
if (sortedUrls.isNullOrEmpty()) {
|
|
|
|
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone = true
|
|
|
|
} else {
|
|
|
|
startSource = sortedUrls.indexOf(currentSelectedLink)
|
|
|
|
sourceIndex = startSource
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
val sourcesArrayAdapter =
|
|
|
|
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
sourcesArrayAdapter.addAll(sortedUrls.map {
|
|
|
|
it.first?.name ?: it.second?.name ?: "NULL"
|
|
|
|
})
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
|
|
|
providerList.adapter = sourcesArrayAdapter
|
|
|
|
providerList.setSelection(sourceIndex)
|
|
|
|
providerList.setItemChecked(sourceIndex, true)
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
providerList.setOnItemClickListener { _, _, which, _ ->
|
|
|
|
sourceIndex = which
|
|
|
|
providerList.setItemChecked(which, true)
|
|
|
|
}
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
sourceDialog.setOnDismissListener {
|
|
|
|
if (isPlaying) {
|
|
|
|
player.handleEvent(CSPlayerEvent.Play)
|
|
|
|
}
|
|
|
|
activity?.hideSystemUI()
|
|
|
|
selectSourceDialog = null
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
val subtitleIndexStart = currentSubtitles.indexOf(currentSelectedSubtitles) + 1
|
|
|
|
var subtitleIndex = subtitleIndexStart
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
val subsArrayAdapter =
|
|
|
|
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
|
|
|
subsArrayAdapter.add(getString(R.string.no_subtitles))
|
|
|
|
subsArrayAdapter.addAll(currentSubtitles.map { it.name })
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
subtitleList.adapter = subsArrayAdapter
|
|
|
|
subtitleList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
subtitleList.setSelection(subtitleIndex)
|
|
|
|
subtitleList.setItemChecked(subtitleIndex, true)
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
subtitleList.setOnItemClickListener { _, _, which, _ ->
|
|
|
|
subtitleIndex = which
|
|
|
|
subtitleList.setItemChecked(which, true)
|
|
|
|
}
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-01-13 21:09:05 +00:00
|
|
|
cancelButton.setOnClickListener {
|
|
|
|
sourceDialog.dismissSafe(activity)
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
2022-01-13 21:09:05 +00:00
|
|
|
|
|
|
|
applyButton.setOnClickListener {
|
|
|
|
var init = false
|
|
|
|
if (sourceIndex != startSource) {
|
|
|
|
init = true
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
2022-01-13 21:09:05 +00:00
|
|
|
if (subtitleIndex != subtitleIndexStart) {
|
|
|
|
init = init || if (subtitleIndex <= 0) {
|
|
|
|
noSubtitles()
|
|
|
|
} else {
|
2022-01-30 22:02:57 +00:00
|
|
|
currentSubtitles.getOrNull(subtitleIndex - 1)?.let {
|
|
|
|
setSubtitles(it)
|
|
|
|
} ?: false
|
2022-01-13 21:09:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (init) {
|
2022-01-30 22:02:57 +00:00
|
|
|
sortedUrls.getOrNull(sourceIndex)?.let {
|
|
|
|
loadLink(it, true)
|
|
|
|
}
|
2022-01-13 21:09:05 +00:00
|
|
|
}
|
|
|
|
sourceDialog.dismissSafe(activity)
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-18 00:24:23 +00:00
|
|
|
} catch (e: Exception) {
|
2022-01-13 21:09:05 +00:00
|
|
|
logError(e)
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun noLinksFound() {
|
|
|
|
showToast(activity, R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
|
|
|
activity?.popCurrentPage()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun startPlayer() {
|
|
|
|
if (isActive) return // we don't want double load when you skip loading
|
|
|
|
|
|
|
|
val links = sortLinks()
|
|
|
|
if (links.isEmpty()) {
|
|
|
|
noLinksFound()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
loadLink(links.first(), false)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun nextEpisode() {
|
|
|
|
isNextEpisode = true
|
2022-01-30 22:02:57 +00:00
|
|
|
player.release()
|
2022-01-07 19:27:25 +00:00
|
|
|
viewModel.loadLinksNext()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun prevEpisode() {
|
|
|
|
isNextEpisode = true
|
2022-01-30 22:02:57 +00:00
|
|
|
player.release()
|
2022-01-07 19:27:25 +00:00
|
|
|
viewModel.loadLinksPrev()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun nextMirror() {
|
|
|
|
val links = sortLinks()
|
|
|
|
if (links.isEmpty()) {
|
|
|
|
noLinksFound()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
val newIndex = links.indexOf(currentSelectedLink) + 1
|
|
|
|
if (newIndex >= links.size) {
|
|
|
|
noLinksFound()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
loadLink(links[newIndex], true)
|
|
|
|
}
|
|
|
|
|
2022-01-18 14:10:01 +00:00
|
|
|
override fun onDestroy() {
|
|
|
|
ResultFragment.updateUI()
|
2022-02-11 09:17:04 +00:00
|
|
|
currentVerifyLink?.cancel()
|
2022-01-18 14:10:01 +00:00
|
|
|
super.onDestroy()
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
override fun playerPositionChanged(posDur: Pair<Long, Long>) {
|
|
|
|
val (position, duration) = posDur
|
|
|
|
viewModel.getId()?.let {
|
2022-01-30 22:02:57 +00:00
|
|
|
println("SET VIEW ID: $it ($position/$duration)")
|
2022-01-07 19:27:25 +00:00
|
|
|
DataStoreHelper.setViewPos(it, position, duration)
|
|
|
|
}
|
|
|
|
val percentage = position * 100L / duration
|
|
|
|
|
|
|
|
val nextEp = percentage >= NEXT_WATCH_EPISODE_PERCENTAGE
|
|
|
|
val resumeMeta = if (nextEp) nextMeta else currentMeta
|
|
|
|
if (resumeMeta == null && nextEp) {
|
|
|
|
// remove last watched as it is the last episode and you have watched too much
|
|
|
|
when (val newMeta = currentMeta) {
|
|
|
|
is ResultEpisode -> {
|
|
|
|
DataStoreHelper.removeLastWatched(newMeta.parentId)
|
|
|
|
}
|
|
|
|
is ExtractorUri -> {
|
|
|
|
DataStoreHelper.removeLastWatched(newMeta.parentId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// save resume
|
|
|
|
when (resumeMeta) {
|
|
|
|
is ResultEpisode -> {
|
|
|
|
DataStoreHelper.setLastWatched(
|
|
|
|
resumeMeta.parentId,
|
|
|
|
resumeMeta.id,
|
|
|
|
resumeMeta.episode,
|
|
|
|
resumeMeta.season,
|
|
|
|
isFromDownload = false
|
|
|
|
)
|
|
|
|
}
|
|
|
|
is ExtractorUri -> {
|
|
|
|
DataStoreHelper.setLastWatched(
|
|
|
|
resumeMeta.parentId,
|
|
|
|
resumeMeta.id,
|
|
|
|
resumeMeta.episode,
|
|
|
|
resumeMeta.season,
|
|
|
|
isFromDownload = true
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var isOpVisible = false
|
|
|
|
when (val meta = currentMeta) {
|
|
|
|
is ResultEpisode -> {
|
2022-04-08 19:38:19 +00:00
|
|
|
if (percentage >= UPDATE_SYNC_PROGRESS_PERCENTAGE) {
|
|
|
|
context?.let { ctx ->
|
|
|
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
|
|
|
if (settingsManager.getBoolean(
|
|
|
|
ctx.getString(R.string.episode_sync_enabled_key),
|
|
|
|
true
|
|
|
|
)
|
|
|
|
)
|
|
|
|
sync.modifyMaxEpisode(meta.episode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
if (meta.tvType.isAnimeOp())
|
|
|
|
isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
player_skip_op?.isVisible = isOpVisible
|
|
|
|
player_skip_episode?.isVisible = !isOpVisible && viewModel.hasNextEpisode() == true
|
|
|
|
|
2022-04-08 19:38:19 +00:00
|
|
|
if (percentage >= PRELOAD_NEXT_EPISODE_PERCENTAGE) {
|
2022-01-07 19:27:25 +00:00
|
|
|
viewModel.preLoadNextLinks()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 17:18:05 +00:00
|
|
|
private fun getAutoSelectSubtitle(
|
|
|
|
subtitles: Set<SubtitleData>,
|
|
|
|
settings: Boolean,
|
|
|
|
downloads: Boolean
|
|
|
|
): SubtitleData? {
|
|
|
|
val langCode = preferredAutoSelectSubtitles ?: return null
|
|
|
|
val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null
|
2022-01-08 19:39:22 +00:00
|
|
|
|
2022-03-04 17:18:05 +00:00
|
|
|
if (settings)
|
|
|
|
subtitles.firstOrNull { sub ->
|
2022-01-08 19:39:22 +00:00
|
|
|
sub.name.startsWith(lang)
|
|
|
|
|| sub.name.trim() == langCode
|
|
|
|
}?.let { sub ->
|
2022-03-04 17:18:05 +00:00
|
|
|
return sub
|
|
|
|
}
|
|
|
|
if (downloads) {
|
|
|
|
return subtitles.firstOrNull { sub ->
|
|
|
|
(sub.origin == SubtitleOrigin.DOWNLOADED_FILE || sub.name == context?.getString(
|
|
|
|
R.string.default_subtitles
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun autoSelectFromSettings() {
|
|
|
|
// auto select subtitle based of settings
|
|
|
|
val langCode = preferredAutoSelectSubtitles
|
|
|
|
|
|
|
|
if (!langCode.isNullOrEmpty() && player.getCurrentPreferredSubtitle() == null) {
|
|
|
|
getAutoSelectSubtitle(currentSubs, settings = true, downloads = false)?.let { sub ->
|
2022-01-08 19:39:22 +00:00
|
|
|
context?.let { ctx ->
|
|
|
|
if (setSubtitles(sub)) {
|
|
|
|
player.reloadPlayer(ctx)
|
|
|
|
player.handleEvent(CSPlayerEvent.Play)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun autoSelectFromDownloads() {
|
|
|
|
if (player.getCurrentPreferredSubtitle() == null) {
|
2022-03-04 17:18:05 +00:00
|
|
|
getAutoSelectSubtitle(currentSubs, settings = false, downloads = true)?.let { sub ->
|
2022-01-08 19:39:22 +00:00
|
|
|
context?.let { ctx ->
|
|
|
|
if (setSubtitles(sub)) {
|
|
|
|
player.reloadPlayer(ctx)
|
|
|
|
player.handleEvent(CSPlayerEvent.Play)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun autoSelectSubtitles() {
|
|
|
|
normalSafeApiCall {
|
|
|
|
autoSelectFromSettings()
|
|
|
|
autoSelectFromDownloads()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
@SuppressLint("SetTextI18n")
|
|
|
|
fun setTitle() {
|
|
|
|
var headerName: String? = null
|
2022-03-01 21:15:18 +00:00
|
|
|
var subName: String? = null
|
2022-01-07 19:27:25 +00:00
|
|
|
var episode: Int? = null
|
|
|
|
var season: Int? = null
|
|
|
|
var tvType: TvType? = null
|
|
|
|
|
2022-03-01 21:15:18 +00:00
|
|
|
var isFiller: Boolean? = null
|
2022-01-07 19:27:25 +00:00
|
|
|
when (val meta = currentMeta) {
|
|
|
|
is ResultEpisode -> {
|
2022-02-11 14:04:03 +00:00
|
|
|
isFiller = meta.isFiller
|
2022-01-07 19:27:25 +00:00
|
|
|
headerName = meta.headerName
|
2022-02-27 00:13:55 +00:00
|
|
|
subName = meta.name
|
2022-01-07 19:27:25 +00:00
|
|
|
episode = meta.episode
|
|
|
|
season = meta.season
|
|
|
|
tvType = meta.tvType
|
|
|
|
}
|
|
|
|
is ExtractorUri -> {
|
|
|
|
headerName = meta.headerName
|
2022-02-27 00:13:55 +00:00
|
|
|
subName = meta.name
|
2022-01-07 19:27:25 +00:00
|
|
|
episode = meta.episode
|
|
|
|
season = meta.season
|
|
|
|
tvType = meta.tvType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-11 14:04:03 +00:00
|
|
|
player_episode_filler_holder?.isVisible = isFiller ?: false
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
player_video_title?.text = if (headerName != null) {
|
2022-02-27 00:13:55 +00:00
|
|
|
(headerName +
|
2022-01-07 19:27:25 +00:00
|
|
|
if (tvType.isEpisodeBased() && episode != null)
|
|
|
|
if (season == null)
|
|
|
|
" - ${getString(R.string.episode)} $episode"
|
|
|
|
else
|
|
|
|
" \"${getString(R.string.season_short)}${season}:${getString(R.string.episode_short)}${episode}\""
|
2022-03-01 21:15:18 +00:00
|
|
|
else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName"
|
2022-01-07 19:27:25 +00:00
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("SetTextI18n")
|
|
|
|
fun setPlayerDimen(widthHeight: Pair<Int, Int>?) {
|
|
|
|
val extra = if (widthHeight != null) {
|
|
|
|
val (width, height) = widthHeight
|
|
|
|
" - ${width}x${height}"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
player_video_title_rez?.text =
|
|
|
|
(currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name
|
|
|
|
?: "NULL") + extra
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) {
|
|
|
|
setPlayerDimen(widthHeight)
|
|
|
|
}
|
|
|
|
|
2022-04-08 19:38:19 +00:00
|
|
|
private fun unwrapBundle(savedInstanceState: Bundle?) {
|
|
|
|
Log.i(TAG, "unwrapBundle = $savedInstanceState")
|
|
|
|
savedInstanceState?.let { bundle ->
|
|
|
|
sync.addSyncs(bundle.getSerializable("syncData") as? HashMap<String, String>?)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
override fun onCreateView(
|
|
|
|
inflater: LayoutInflater,
|
|
|
|
container: ViewGroup?,
|
|
|
|
savedInstanceState: Bundle?
|
|
|
|
): View? {
|
2022-01-18 00:24:23 +00:00
|
|
|
// this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
|
|
|
|
layout =
|
|
|
|
if (context?.isTvSettings() == true) R.layout.fragment_player_tv else R.layout.fragment_player
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
|
2022-04-08 19:38:19 +00:00
|
|
|
sync = ViewModelProvider(this)[SyncViewModel::class.java]
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
viewModel.attachGenerator(lastUsedGenerator)
|
2022-04-08 19:38:19 +00:00
|
|
|
unwrapBundle(savedInstanceState)
|
|
|
|
unwrapBundle(arguments)
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
return super.onCreateView(inflater, container, savedInstanceState)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
|
2022-04-08 19:38:19 +00:00
|
|
|
unwrapBundle(savedInstanceState)
|
|
|
|
unwrapBundle(arguments)
|
|
|
|
|
|
|
|
sync.updateUserData()
|
|
|
|
|
2022-01-08 19:39:22 +00:00
|
|
|
preferredAutoSelectSubtitles = SubtitlesFragment.getAutoSelectLanguageISO639_1()
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
if (currentSelectedLink == null) {
|
|
|
|
viewModel.loadLinks()
|
|
|
|
}
|
|
|
|
|
|
|
|
overlay_loading_skip_button?.setOnClickListener {
|
|
|
|
startPlayer()
|
|
|
|
}
|
|
|
|
|
|
|
|
player_loading_go_back?.setOnClickListener {
|
|
|
|
player.release()
|
|
|
|
activity?.popCurrentPage()
|
|
|
|
}
|
|
|
|
|
|
|
|
observe(viewModel.loadingLinks) {
|
|
|
|
when (it) {
|
|
|
|
is Resource.Loading -> {
|
|
|
|
startLoading()
|
|
|
|
}
|
|
|
|
is Resource.Success -> {
|
|
|
|
// provider returned false
|
|
|
|
//if (it.value != true) {
|
|
|
|
// showToast(activity, R.string.unexpected_error, Toast.LENGTH_SHORT)
|
|
|
|
//}
|
|
|
|
startPlayer()
|
|
|
|
}
|
|
|
|
is Resource.Failure -> {
|
|
|
|
showToast(activity, it.errorString, Toast.LENGTH_LONG)
|
|
|
|
startPlayer()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
observe(viewModel.currentLinks) {
|
|
|
|
currentLinks = it
|
2022-02-02 22:12:52 +00:00
|
|
|
val turnVisible = it.isNotEmpty()
|
2022-03-29 21:50:07 +00:00
|
|
|
val wasGone = overlay_loading_skip_button?.isGone == true
|
|
|
|
overlay_loading_skip_button?.isVisible = turnVisible
|
|
|
|
if (turnVisible && wasGone) {
|
2022-02-02 22:12:52 +00:00
|
|
|
overlay_loading_skip_button?.requestFocus()
|
|
|
|
}
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
|
2022-01-08 19:39:22 +00:00
|
|
|
observe(viewModel.currentSubs) { set ->
|
|
|
|
currentSubs = set
|
|
|
|
player.setActiveSubtitles(set)
|
|
|
|
|
|
|
|
autoSelectSubtitles()
|
2022-01-07 19:27:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|