diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index fff4b8ff..1e28fde6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -92,6 +92,10 @@ abstract class AbstractPlayerFragment( throw NotImplementedError() } + open fun embeddedSubtitlesFetched(subtitles: List) { + throw NotImplementedError() + } + open fun exitedPipMode() { throw NotImplementedError() } @@ -338,7 +342,9 @@ abstract class AbstractPlayerFragment( PRELOAD_NEXT_EPISODE_PERCENTAGE, NEXT_WATCH_EPISODE_PERCENTAGE, UPDATE_SYNC_PROGRESS_PERCENTAGE, - ), subtitlesUpdates = ::subtitlesChanged + ), + subtitlesUpdates = ::subtitlesChanged, + embeddedSubtitlesFetched = ::embeddedSubtitlesFetched, ) if (player is CS3IPlayer) { @@ -359,7 +365,7 @@ abstract class AbstractPlayerFragment( settingsManager.getInt(getString(R.string.video_buffer_disk_key), 0) val currentPrefBufferSec = settingsManager.getInt(getString(R.string.video_buffer_length_key), 0) - + player.cacheSize = currentPrefCacheSize * 1024L * 1024L player.simpleCacheSize = currentPrefDiskSize * 1024L * 1024L player.videoBufferMs = currentPrefBufferSec * 1000L diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 4d659034..0fdba535 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -27,9 +27,11 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorUri +import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import java.io.File import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext @@ -95,6 +97,7 @@ class CS3IPlayer : IPlayer { private var prevEpisode: (() -> Unit)? = null private var playerUpdated: ((Any?) -> Unit)? = null + private var embeddedSubtitlesFetched: ((List) -> Unit)? = null override fun initCallbacks( playerUpdated: (Any?) -> Unit, @@ -107,6 +110,7 @@ class CS3IPlayer : IPlayer { nextEpisode: (() -> Unit)?, prevEpisode: (() -> Unit)?, subtitlesUpdates: (() -> Unit)?, + embeddedSubtitlesFetched: ((List) -> Unit)?, ) { this.playerUpdated = playerUpdated this.updateIsPlaying = updateIsPlaying @@ -118,6 +122,7 @@ class CS3IPlayer : IPlayer { this.nextEpisode = nextEpisode this.prevEpisode = prevEpisode this.subtitlesUpdates = subtitlesUpdates + this.embeddedSubtitlesFetched = embeddedSubtitlesFetched } // I know, this is not a perfect solution, however it works for fixing subs @@ -202,7 +207,14 @@ class CS3IPlayer : IPlayer { trackSelector.setParameters( trackSelector.buildUponParameters() - .setPreferredTextLanguage("_$name") + .apply { + if (subtitle.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) + // The real Language (two letter) is in the url + // No underscore as the .url is the actual exoplayer designated language + setPreferredTextLanguage(subtitle.url) + else + setPreferredTextLanguage("_$name") + } ) // ugliest code I have written, it seeks 1ms to *update* the subtitles @@ -231,16 +243,20 @@ class CS3IPlayer : IPlayer { } override fun getSubtitleOffset(): Long { - return currentSubtitleOffset//currentTextRenderer?.getRenderOffsetMs() ?: currentSubtitleOffset + return currentSubtitleOffset //currentTextRenderer?.getRenderOffsetMs() ?: currentSubtitleOffset } override fun getCurrentPreferredSubtitle(): SubtitleData? { return subtitleHelper.getAllSubtitles().firstOrNull { sub -> exoPlayerSelectedTracks.any { + // When embedded the real language is in .url as the real name is a two letter code + val realName = + if (sub.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) sub.url else sub.name + // The replace is needed as exoplayer translates _ to - // Also we prefix the languages with _ it.second && it.first.replace("-", "").equals( - sub.name.replace("-", ""), + realName.replace("-", ""), ignoreCase = true ) } @@ -616,9 +632,46 @@ class CS3IPlayer : IPlayer { * 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 } } - subtitlesUpdates?.invoke() + fun Format.isSubtitle(): Boolean { + return this.sampleMimeType?.contains("video/") == false && + this.sampleMimeType?.contains("audio/") == false + } + + normalSafeApiCall { + exoPlayerSelectedTracks = + tracksInfo.trackGroupInfos.mapNotNull { + val format = it.trackGroup.getFormat(0) + if (format.isSubtitle()) + format.language?.let { lang -> lang to it.isSelected } + else null + } + + val exoPlayerReportedTracks = tracksInfo.trackGroupInfos.mapNotNull { + // Filter out unsupported tracks + if (it.isSupported) + it.trackGroup.getFormat(0) + else + null + }.mapNotNull { + // Filter out non subs, already used subs and subs without languages + if (!it.isSubtitle() || + // Anything starting with - is not embedded + it.language?.startsWith("-") == true || + it.language == null + ) return@mapNotNull null + return@mapNotNull SubtitleData( + // Nicer looking displayed names + fromTwoLettersToLanguage(it.language!!) ?: it.language!!, + // See setPreferredTextLanguage + it.language!!, + SubtitleOrigin.EMBEDDED_IN_VIDEO, + it.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP + ) + } + + embeddedSubtitlesFetched?.invoke(exoPlayerReportedTracks) + subtitlesUpdates?.invoke() + } super.onTracksInfoChanged(tracksInfo) } @@ -766,6 +819,15 @@ class CS3IPlayer : IPlayer { // TODO throw NotImplementedError() } + SubtitleOrigin.EMBEDDED_IN_VIDEO -> { + if (offlineSourceFactory != null) { + activeSubtitles.add(sub) + SingleSampleMediaSource.Factory(offlineSourceFactory) + .createMediaSource(subConfig, C.TIME_UNSET) + } else { + null + } + } } } return Pair(subSources, activeSubtitles) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt index f75707d8..fdacf067 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt @@ -40,7 +40,7 @@ class CustomDecoder : SubtitleDecoder { ) val captionRegex = listOf(Regex("""(-\s?|)[\[({][\w\d\s]*?[])}]\s*""")) - fun trimStr(string: String) : String { + fun trimStr(string: String): String { return string.trimStart().trim('\uFEFF', '\u200B').replace( Regex("[\u00A0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u205F]"), " " @@ -109,10 +109,10 @@ class CustomDecoder : SubtitleDecoder { captionRegex.forEach { rgx -> fullStr = fullStr.replace(rgx, "\n") } - fullStr.replace(Regex("(\r\n|\r|\n){2,}"),"\n") + fullStr.replace(Regex("(\r\n|\r|\n){2,}"), "\n") buff.data = ByteBuffer.wrap(fullStr.toByteArray()) - } catch (e : Exception) { + } catch (e: Exception) { data.position(pos) buff.data = data } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 0687547f..215f3a6a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -82,6 +82,10 @@ class GeneratorPlayer : FullScreenPlayer() { return player.setPreferredSubtitles(sub) } + override fun embeddedSubtitlesFetched(subtitles: List) { + viewModel.addSubtitles(subtitles.toSet()) + } + private fun noSubtitles(): Boolean { return setSubtitles(null) } @@ -258,7 +262,7 @@ class GeneratorPlayer : FullScreenPlayer() { var startSource = 0 val sortedUrls = sortLinks(useQualitySettings = false) - if (sortedUrls.isNullOrEmpty()) { + if (sortedUrls.isEmpty()) { sourceDialog.findViewById(R.id.sort_sources_holder)?.isGone = true } else { startSource = sortedUrls.indexOf(currentSelectedLink) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt index 4f608330..f703a0c3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt @@ -85,6 +85,7 @@ interface IPlayer { nextEpisode: (() -> Unit)? = null, // this is used by the player to load the next episode prevEpisode: (() -> Unit)? = null, // this is used by the player to load the previous episode subtitlesUpdates: (() -> Unit)? = null, // callback from player to inform that subtitles have updated in some way + embeddedSubtitlesFetched: ((List) -> Unit)? = null, // callback from player to give all embedded subtitles ) fun updateSubtitleStyle(style: SaveCaptionStyle) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index e29b10f1..b3537333 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -93,10 +93,18 @@ class PlayerGeneratorViewModel : ViewModel() { } } + /** + * If duplicate nothing will happen + * */ fun addSubtitles(file: Set) { - val subs = (_currentSubs.value?.toMutableSet() ?: mutableSetOf()) - subs.addAll(file) - _currentSubs.postValue(subs) + val currentSubs = _currentSubs.value ?: emptySet() + // Prevent duplicates + val allSubs = (currentSubs + file).distinct().toSet() + // Do not post if there's nothing new + // Posting will refresh subtitles which will in turn + // make the subs to english if previously unselected + if (allSubs != currentSubs) + _currentSubs.postValue(allSubs) } private var currentJob: Job? = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt index 385e5ab5..27d35228 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt @@ -23,8 +23,13 @@ enum class SubtitleOrigin { URL, DOWNLOADED_FILE, OPEN_SUBTITLES, + EMBEDDED_IN_VIDEO } +/** + * @param name To be displayed in the player + * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend language + * */ data class SubtitleData( val name: String, val url: String, @@ -77,6 +82,9 @@ class PlayerSubtitleHelper { // TODO throw NotImplementedError() } + SubtitleOrigin.EMBEDDED_IN_VIDEO -> { + throw NotImplementedError() + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index bd2baf7c..1a17ba43 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -201,7 +201,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio putString(URL_BUNDLE, card.url) putString(API_NAME_BUNDLE, card.apiName) if (card is DataStoreHelper.ResumeWatchingResult) { - println("CARD::::: $card") +// println("CARD::::: $card") if (card.season != null) putInt(SEASON_BUNDLE, card.season) if (card.episode != null)