diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt index 7cce6e43..5d05ddf6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt @@ -29,6 +29,7 @@ import android.view.animation.Animation import android.view.animation.AnimationUtils import android.widget.* import android.widget.Toast.LENGTH_SHORT +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.graphics.blue import androidx.core.graphics.green @@ -47,7 +48,7 @@ import com.fasterxml.jackson.module.kotlin.readValue import com.google.android.exoplayer2.* import com.google.android.exoplayer2.C.TIME_UNSET import com.google.android.exoplayer2.database.StandaloneDatabaseProvider -import com.google.android.exoplayer2.source.DefaultMediaSourceFactory +import com.google.android.exoplayer2.source.* import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.ui.AspectRatioFrameLayout import com.google.android.exoplayer2.ui.SubtitleView @@ -63,6 +64,7 @@ import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastState import com.google.android.material.button.MaterialButton +import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.MainActivity.Companion.canEnterPipMode import com.lagradost.cloudstream3.MainActivity.Companion.getCastSession @@ -100,6 +102,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager.getId import kotlinx.android.synthetic.main.fragment_player.* import kotlinx.android.synthetic.main.player_custom_layout.* import kotlinx.coroutines.* +import okhttp3.internal.format import java.io.File import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext @@ -766,6 +769,32 @@ class PlayerFragment : Fragment() { safeReleasePlayer() } + // Open file picker + private val subsPathPicker = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + // It lies, it can be null if file manager quits. + if (uri == null) return@registerForActivityResult + val context = context ?: AcraApplication.context ?: return@registerForActivityResult + // RW perms for the path + val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + + context.contentResolver.takePersistableUriPermission(uri, flags) + + val file = UniFile.fromUri(context, uri) + println("Selected URI path: $uri - Full path: ${file.filePath}") + // DO NOT REMOVE THE FILE EXTENSION FROM NAME, IT'S NEEDED FOR MIME TYPES + val name = file.name ?: uri.toString() + + viewModel.loadSubtitleFile(uri, name, getEpisode()?.id) + setPreferredSubLanguage(name) + showToast( + activity, + format(context.getString(R.string.player_loaded_subtitles), name), + 1000 + ) + } + private class SettingsContentObserver(handler: Handler?, val activity: Activity) : ContentObserver(handler) { private val audioManager = activity.getSystemService(AUDIO_SERVICE) as? AudioManager @@ -1231,12 +1260,30 @@ class PlayerFragment : Fragment() { sourceDialog.findViewById(R.id.cancel_btt)!! val subsSettings = sourceDialog.findViewById(R.id.subs_settings)!! + val subtitleLoadButton = + sourceDialog.findViewById(R.id.load_btt)!! + + subtitleLoadButton.setOnClickListener { +// "vtt" -> "text/vtt" +// "srt" -> "application/x-subrip"// "text/plain" + subsPathPicker.launch( + arrayOf( + "text/vtt", + "application/x-subrip", + "text/plain", + "text/str", + "application/octet-stream" + ) + ) + } + subsSettings.setOnClickListener { autoHide() saveArguments() SubtitlesFragment.push(activity) sourceDialog.dismissSafe(activity) } + var sourceIndex = 0 var startSource = 0 var sources: List = emptyList() @@ -2116,40 +2163,7 @@ class PlayerFragment : Fragment() { mediaItemBuilder.setUri(uriPrimary) } - val subs = context?.getSubs() ?: emptyList() - val subItems = ArrayList() - val subItemsId = ArrayList() - - for (sub in sortSubs(subs)) { - val langId = - sub.lang.trimEnd() //SubtitleHelper.fromLanguageToTwoLetters(it.lang) ?: it.lang - subItemsId.add(langId) - subItems.add( - MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url)) - .setMimeType(sub.url.toSubtitleMimeType()) - .setLanguage("_$langId") - .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) - .build() - ) - - } - - activeSubtitles = subItemsId - mediaItemBuilder.setSubtitleConfigurations(subItems) - - //might add https://github.com/ed828a/Aihua/blob/1896f46888b5a954b367e83f40b845ce174a2328/app/src/main/java/com/dew/aihua/player/playerUI/VideoPlayer.kt#L287 toggle caps - - val mediaItem = mediaItemBuilder.build() - val trackSelector = DefaultTrackSelector(requireContext()) - // Disable subtitles - trackSelector.parameters = DefaultTrackSelector.ParametersBuilder(requireContext()) - // .setRendererDisabled(C.TRACK_TYPE_VIDEO, true) - .setRendererDisabled(C.TRACK_TYPE_TEXT, true) - .setDisabledTextTrackSelectionFlags(C.TRACK_TYPE_TEXT) - .clearSelectionOverrides() - .build() - - fun getDataSourceFactory(): DataSource.Factory { + fun getDataSourceFactory(isOnline: Boolean): DataSource.Factory { return if (isOnline) { DefaultHttpDataSource.Factory().apply { setUserAgent(USER_AGENT) @@ -2174,6 +2188,41 @@ class PlayerFragment : Fragment() { } } + val subs = context?.getSubs() ?: emptyList() + val subItemsId = ArrayList() + + val subSources = sortSubs(subs).map { sub -> + // The url can look like .../document/4294 when the name is EnglishSDH.srt + val subtitleMimeType = + if (sub.url.startsWith("content")) sub.lang.toSubtitleMimeType() else sub.url.toSubtitleMimeType() + + val langId = + sub.lang.trimEnd() //SubtitleHelper.fromLanguageToTwoLetters(it.lang) ?: it.lang + subItemsId.add(langId) + val subConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url)) + .setMimeType(subtitleMimeType) + .setLanguage("_$langId") + .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) + .build() + SingleSampleMediaSource.Factory(getDataSourceFactory(!sub.url.startsWith("content"))) + .createMediaSource(subConfig, TIME_UNSET) + } + + activeSubtitles = subItemsId +// mediaItemBuilder.setSubtitleConfigurations(subItems) + + //might add https://github.com/ed828a/Aihua/blob/1896f46888b5a954b367e83f40b845ce174a2328/app/src/main/java/com/dew/aihua/player/playerUI/VideoPlayer.kt#L287 toggle caps + + val mediaItem = mediaItemBuilder.build() + val trackSelector = DefaultTrackSelector(requireContext()) + // Disable subtitles + trackSelector.parameters = DefaultTrackSelector.ParametersBuilder(requireContext()) + // .setRendererDisabled(C.TRACK_TYPE_VIDEO, true) + .setRendererDisabled(C.TRACK_TYPE_TEXT, true) + .setDisabledTextTrackSelectionFlags(C.TRACK_TYPE_TEXT) + .clearSelectionOverrides() + .build() + normalSafeApiCall { val databaseProvider = StandaloneDatabaseProvider(requireContext()) simpleCache = SimpleCache( @@ -2187,18 +2236,23 @@ class PlayerFragment : Fragment() { val cacheFactory = CacheDataSource.Factory().apply { simpleCache?.let { setCache(it) } - setUpstreamDataSourceFactory(getDataSourceFactory()) + setUpstreamDataSourceFactory(getDataSourceFactory(isOnline)) } val exoPlayerBuilder = ExoPlayer.Builder(requireContext()) .setTrackSelector(trackSelector) + val videoMediaSource = + DefaultMediaSourceFactory(cacheFactory).createMediaSource(mediaItem) + exoPlayer = exoPlayerBuilder.build().apply { playWhenReady = isPlayerPlaying seekTo(currentWindow, playbackPosition) setMediaSource( - DefaultMediaSourceFactory(cacheFactory).createMediaSource(mediaItem), + MergingMediaSource( + videoMediaSource, *subSources.toTypedArray() + ), playbackPosition ) prepare() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index d65a0ce2..6cf7cab2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.result import android.content.Context +import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -57,7 +58,8 @@ class ResultViewModel : ViewModel() { private val _resultResponse: MutableLiveData> = MutableLiveData() private val _episodes: MutableLiveData> = MutableLiveData() - private val episodeById: MutableLiveData> = MutableLiveData() // lookup by ID to get Index + private val episodeById: MutableLiveData> = + MutableLiveData() // lookup by ID to get Index private val _publicEpisodes: MutableLiveData>> = MutableLiveData() private val _publicEpisodesCount: MutableLiveData = MutableLiveData() // before the sorting @@ -83,7 +85,8 @@ class ResultViewModel : ViewModel() { private val _dubSubSelections: MutableLiveData> = MutableLiveData() val dubSubEpisodes: LiveData>?> get() = _dubSubEpisodes - private val _dubSubEpisodes: MutableLiveData>?> = MutableLiveData() + private val _dubSubEpisodes: MutableLiveData>?> = + MutableLiveData() private val _watchStatus: MutableLiveData = MutableLiveData() val watchStatus: LiveData get() = _watchStatus @@ -291,7 +294,14 @@ class ResultViewModel : ViewModel() { _apiName.postValue(apiName) val api = getApiFromNameNull(apiName) if (api == null) { - _resultResponse.postValue(Resource.Failure(false, null, null, "This provider does not exist")) + _resultResponse.postValue( + Resource.Failure( + false, + null, + null, + "This provider does not exist" + ) + ) return@launch } repo = APIRepository(api) @@ -335,7 +345,8 @@ class ResultViewModel : ViewModel() { _dubStatus.postValue(dubStatus) _dubSubSelections.postValue(d.episodes.keys) - val fillerEpisodes = if (showFillers) safeApiCall { getFillerEpisodes(d.name) } else null + val fillerEpisodes = + if (showFillers) safeApiCall { getFillerEpisodes(d.name) } else null var idIndex = 0 val res = d.episodes.map { ep -> @@ -464,6 +475,20 @@ class ResultViewModel : ViewModel() { return loadEpisode(episode.id, episode.data, isCasting) } + fun loadSubtitleFile(uri: Uri, name: String, id: Int?) { + if (id == null) return + val hashMap: HashMap = _allEpisodesSubs.value?.get(id) ?: hashMapOf() + hashMap[name] = SubtitleFile( + name, + uri.toString() + ) + _allEpisodesSubs.value.apply { + this?.set(id, hashMap) + }?.let { + _allEpisodesSubs.postValue(it) + } + } + private suspend fun loadEpisode( id: Int, data: String, diff --git a/app/src/main/res/layout/player_select_source_and_subs.xml b/app/src/main/res/layout/player_select_source_and_subs.xml index d30de1f9..3de78583 100644 --- a/app/src/main/res/layout/player_select_source_and_subs.xml +++ b/app/src/main/res/layout/player_select_source_and_subs.xml @@ -1,137 +1,150 @@ - + + + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginBottom="60dp" + android:baselineAligned="false" + android:orientation="horizontal"> + android:id="@+id/sort_sources_holder" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="50" + android:orientation="vertical"> + + + + + + + + + + - - - - - - - + android:textSize="20sp" + android:textStyle="bold" /> - - + android:contentDescription="@string/home_change_provider_img_des" + android:src="@drawable/ic_outline_settings_24" + android:visibility="gone" /> + - + android:layout_height="match_parent" + android:layout_rowWeight="1" + android:background="?attr/primaryBlackBackground" + android:nextFocusLeft="@id/sort_providers" + android:nextFocusRight="@id/cancel_btt" + tools:listitem="@layout/sort_bottom_single_choice" /> + android:layout_width="match_parent" + android:layout_height="60dp" + android:layout_gravity="bottom" + android:layout_marginTop="-60dp" + android:orientation="horizontal"> - + - android:id="@+id/apply_btt" + - + - android:id="@+id/cancel_btt" - style="@style/BlackButton" - android:layout_gravity="center_vertical|end" - android:text="@string/sort_cancel" - android:layout_width="wrap_content" - /> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c8321a1..55454f60 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -368,4 +368,6 @@ The quick brown fox jumps over the lazy dog Recommended + Loaded %s + Load from file