diff --git a/app/src/main/java/com/github/libretube/api/obj/PipedStream.kt b/app/src/main/java/com/github/libretube/api/obj/PipedStream.kt index ed9e9d973..58de468e8 100644 --- a/app/src/main/java/com/github/libretube/api/obj/PipedStream.kt +++ b/app/src/main/java/com/github/libretube/api/obj/PipedStream.kt @@ -17,5 +17,7 @@ data class PipedStream( var indexEnd: Int? = null, var width: Int? = null, var height: Int? = null, - var fps: Int? = null + var fps: Int? = null, + val audioTrackName: String? = null, + val audioTrackId: String? = null ) diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index 81b4e6c4b..f9d6700d5 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -38,6 +38,8 @@ import com.github.libretube.R import com.github.libretube.api.CronetHelper import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.obj.ChapterSegment +import com.github.libretube.api.obj.PipedStream +import com.github.libretube.api.obj.Segment import com.github.libretube.api.obj.SegmentData import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.Streams @@ -107,6 +109,7 @@ import kotlinx.coroutines.launch import org.chromium.net.CronetEngine import retrofit2.HttpException import java.io.IOException +import java.util.* import java.util.concurrent.Executors import kotlin.math.abs @@ -166,6 +169,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { private lateinit var shareData: ShareData + private var selectedAudioSourceUrl: String? = null + private var selectedVideoSourceUrl: String? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { @@ -536,7 +542,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { if (!::segmentData.isInitialized || segmentData.segments.isEmpty()) return val currentPosition = exoPlayer.currentPosition - segmentData.segments.forEach { segment: com.github.libretube.api.obj.Segment -> + segmentData.segments.forEach { segment: Segment -> val segmentStart = (segment.segment[0] * 1000f).toLong() val segmentEnd = (segment.segment[1] * 1000f).toLong() @@ -1067,7 +1073,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } private fun setMediaSource( - videoUri: Uri, + videoUrl: String, audioUrl: String ) { val checkIntervalSize = when (PlayerHelper.progressiveLoadingIntervalSize) { @@ -1079,7 +1085,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { DefaultHttpDataSource.Factory() val videoItem: MediaItem = MediaItem.Builder() - .setUri(videoUri) + .setUri(videoUrl.toUri()) .setSubtitleConfigurations(subtitles) .build() @@ -1106,16 +1112,16 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { exoPlayer.setMediaItem(mediaItem) } - private fun getAvailableResolutions(): Pair, Array> { + private fun getAvailableResolutions(): Pair, Array> { if (!this::streams.isInitialized) return Pair(arrayOf(), arrayOf()) var videosNameArray: Array = arrayOf() - var videosUrlArray: Array = arrayOf() + var videosUrlArray: Array = arrayOf() // append hls to list if available if (streams.hls != null) { videosNameArray += getString(R.string.hls) - videosUrlArray += streams.hls!!.toUri() + videosUrlArray += streams.hls!! } val videoStreams = try { @@ -1138,10 +1144,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { val preferredMimeType = "video/${PlayerHelper.videoFormatPreference}" if (vid.url != null && vid.mimeType == preferredMimeType) { // preferred format videosNameArray += vid.quality.toString() - videosUrlArray += vid.url!!.toUri() + videosUrlArray += vid.url!! } else if (vid.quality.equals("LBRY") && vid.format.equals("MP4")) { // LBRY MP4 format videosNameArray += "LBRY MP4" - videosUrlArray += vid.url!!.toUri() + videosUrlArray += vid.url!! } } return Pair(videosNameArray, videosUrlArray) @@ -1189,17 +1195,16 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { private fun setStreamSource( streams: Streams, videosNameArray: Array, - videosUrlArray: Array + videosUrlArray: Array ) { val defaultResolution = PlayerHelper.getDefaultResolution(requireContext()) if (defaultResolution != "") { videosNameArray.forEachIndexed { index, pipedStream -> // search for quality preference in the available stream sources if (pipedStream.contains(defaultResolution)) { - val videoUri = videosUrlArray[index] - val audioUrl = - PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!) - setMediaSource(videoUri, audioUrl) + selectedVideoSourceUrl = videosUrlArray[index] + selectedAudioSourceUrl = selectedAudioSourceUrl ?: getAudioSource(streams.audioStreams) + setMediaSource(selectedAudioSourceUrl!!, selectedVideoSourceUrl!!) return } } @@ -1219,6 +1224,15 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } } + private fun getAudioSource(audioStreams: List?): String { + val appLanguage = Locale.getDefault().language.lowercase().substring(0, 2) + val filteredStreams = audioStreams.orEmpty().filter { it.audioTrackId?.contains(appLanguage) ?: false } + return PlayerHelper.getAudioSource( + requireContext(), + filteredStreams.ifEmpty { audioStreams!! } + ) + } + private fun createExoPlayer() { val cronetEngine: CronetEngine = CronetHelper.getCronetEngine() val cronetDataSourceFactory: CronetDataSource.Factory = @@ -1395,18 +1409,36 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { videosNameArray[which] == "LBRY HLS" ) { // set the progressive media source - setHLSMediaSource(videosUrlArray[which]) + setHLSMediaSource(videosUrlArray[which].toUri()) } else { - val videoUri = videosUrlArray[which] - val audioUrl = - PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!) - setMediaSource(videoUri, audioUrl) + selectedVideoSourceUrl = videosUrlArray[which] + selectedAudioSourceUrl = selectedAudioSourceUrl ?: getAudioSource(streams.audioStreams) + setMediaSource(selectedVideoSourceUrl!!, selectedAudioSourceUrl!!) } exoPlayer.seekTo(lastPosition) } .show(childFragmentManager) } + private fun getAudioStreamGroups(audioStreams: List?): Map> { + return audioStreams.orEmpty() + .groupBy { it.audioTrackName } + } + + override fun onAudioStreamClicked() { + val audioGroups = getAudioStreamGroups(streams.audioStreams) + val audioLanguages = audioGroups.map { it.key ?: getString(R.string.default_audio_track) } + + BaseBottomSheet() + .setSimpleItems(audioLanguages) { index -> + val audioStreams = audioGroups.values.elementAt(index) + selectedAudioSourceUrl = PlayerHelper.getAudioSource(requireContext(), audioStreams) + selectedVideoSourceUrl = selectedVideoSourceUrl ?: streams.videoStreams!!.first().url!! + setMediaSource(selectedAudioSourceUrl!!, selectedVideoSourceUrl!!) + } + .show(childFragmentManager) + } + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { super.onPictureInPictureModeChanged(isInPictureInPictureMode) if (isInPictureInPictureMode) { diff --git a/app/src/main/java/com/github/libretube/ui/interfaces/OnlinePlayerOptions.kt b/app/src/main/java/com/github/libretube/ui/interfaces/OnlinePlayerOptions.kt index b8ec19a7e..d9e37f6e6 100644 --- a/app/src/main/java/com/github/libretube/ui/interfaces/OnlinePlayerOptions.kt +++ b/app/src/main/java/com/github/libretube/ui/interfaces/OnlinePlayerOptions.kt @@ -4,4 +4,6 @@ interface OnlinePlayerOptions { fun onCaptionsClicked() fun onQualityClicked() + + fun onAudioStreamClicked() } diff --git a/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt b/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt index d5fa8978c..22716453d 100644 --- a/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt +++ b/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt @@ -195,6 +195,14 @@ internal class CustomExoPlayerView( playerOptionsInterface?.onQualityClicked() } ) + items.add( + BottomSheetItem( + context.getString(R.string.audio_track), + R.drawable.ic_audio + ) { + playerOptionsInterface?.onAudioStreamClicked() + } + ) items.add( BottomSheetItem( context.getString(R.string.captions), diff --git a/app/src/main/java/com/github/libretube/util/PlayerHelper.kt b/app/src/main/java/com/github/libretube/util/PlayerHelper.kt index b8d7281fc..77ed8f8a4 100644 --- a/app/src/main/java/com/github/libretube/util/PlayerHelper.kt +++ b/app/src/main/java/com/github/libretube/util/PlayerHelper.kt @@ -3,6 +3,7 @@ package com.github.libretube.util import android.content.Context import android.content.pm.ActivityInfo import android.view.accessibility.CaptioningManager +import com.github.libretube.api.obj.PipedStream import com.github.libretube.constants.PreferenceKeys import com.google.android.exoplayer2.ui.CaptionStyleCompat import com.google.android.exoplayer2.video.VideoSize @@ -12,7 +13,7 @@ object PlayerHelper { // get the audio source following the users preferences fun getAudioSource( context: Context, - audios: List + audios: List ): String { val audioFormat = PreferenceHelper.getString(PreferenceKeys.PLAYER_AUDIO_FORMAT, "all") val audioQuality = if ( @@ -39,7 +40,7 @@ object PlayerHelper { } // get the best bit rate from audio streams - private fun getMostBitRate(audios: List): String { + private fun getMostBitRate(audios: List): String { var bitrate = 0 var audioUrl = "" audios.forEach { @@ -52,7 +53,7 @@ object PlayerHelper { } // get the best bit rate from audio streams - private fun getLeastBitRate(audios: List): String { + private fun getLeastBitRate(audios: List): String { var bitrate = 1000000000 var audioUrl = "" audios.forEach { diff --git a/app/src/main/res/drawable/ic_audio.xml b/app/src/main/res/drawable/ic_audio.xml new file mode 100644 index 000000000..ba1442961 --- /dev/null +++ b/app/src/main/res/drawable/ic_audio.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1faf35873..04d59f718 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -370,6 +370,8 @@ Layout Alternative player layout Show the related videos as a row above the comments instead of below. + Audio track + Default Download Service