mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 07:50:31 +05:30
Merge pull request #1863 from Bnyro/master
Option to change the audio track
This commit is contained in:
commit
d1f2e3ed27
@ -17,5 +17,7 @@ data class PipedStream(
|
|||||||
var indexEnd: Int? = null,
|
var indexEnd: Int? = null,
|
||||||
var width: Int? = null,
|
var width: Int? = null,
|
||||||
var height: Int? = null,
|
var height: Int? = null,
|
||||||
var fps: Int? = null
|
var fps: Int? = null,
|
||||||
|
val audioTrackName: String? = null,
|
||||||
|
val audioTrackId: String? = null
|
||||||
)
|
)
|
||||||
|
@ -38,6 +38,8 @@ import com.github.libretube.R
|
|||||||
import com.github.libretube.api.CronetHelper
|
import com.github.libretube.api.CronetHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.api.obj.ChapterSegment
|
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.SegmentData
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
@ -107,6 +109,7 @@ import kotlinx.coroutines.launch
|
|||||||
import org.chromium.net.CronetEngine
|
import org.chromium.net.CronetEngine
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@ -166,6 +169,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
private lateinit var shareData: ShareData
|
private lateinit var shareData: ShareData
|
||||||
|
|
||||||
|
private var selectedAudioSourceUrl: String? = null
|
||||||
|
private var selectedVideoSourceUrl: String? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
arguments?.let {
|
arguments?.let {
|
||||||
@ -536,7 +542,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
if (!::segmentData.isInitialized || segmentData.segments.isEmpty()) return
|
if (!::segmentData.isInitialized || segmentData.segments.isEmpty()) return
|
||||||
|
|
||||||
val currentPosition = exoPlayer.currentPosition
|
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 segmentStart = (segment.segment[0] * 1000f).toLong()
|
||||||
val segmentEnd = (segment.segment[1] * 1000f).toLong()
|
val segmentEnd = (segment.segment[1] * 1000f).toLong()
|
||||||
|
|
||||||
@ -1067,7 +1073,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setMediaSource(
|
private fun setMediaSource(
|
||||||
videoUri: Uri,
|
videoUrl: String,
|
||||||
audioUrl: String
|
audioUrl: String
|
||||||
) {
|
) {
|
||||||
val checkIntervalSize = when (PlayerHelper.progressiveLoadingIntervalSize) {
|
val checkIntervalSize = when (PlayerHelper.progressiveLoadingIntervalSize) {
|
||||||
@ -1079,7 +1085,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
DefaultHttpDataSource.Factory()
|
DefaultHttpDataSource.Factory()
|
||||||
|
|
||||||
val videoItem: MediaItem = MediaItem.Builder()
|
val videoItem: MediaItem = MediaItem.Builder()
|
||||||
.setUri(videoUri)
|
.setUri(videoUrl.toUri())
|
||||||
.setSubtitleConfigurations(subtitles)
|
.setSubtitleConfigurations(subtitles)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@ -1106,16 +1112,16 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
exoPlayer.setMediaItem(mediaItem)
|
exoPlayer.setMediaItem(mediaItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAvailableResolutions(): Pair<Array<String>, Array<Uri>> {
|
private fun getAvailableResolutions(): Pair<Array<String>, Array<String>> {
|
||||||
if (!this::streams.isInitialized) return Pair(arrayOf(), arrayOf())
|
if (!this::streams.isInitialized) return Pair(arrayOf(), arrayOf())
|
||||||
|
|
||||||
var videosNameArray: Array<String> = arrayOf()
|
var videosNameArray: Array<String> = arrayOf()
|
||||||
var videosUrlArray: Array<Uri> = arrayOf()
|
var videosUrlArray: Array<String> = arrayOf()
|
||||||
|
|
||||||
// append hls to list if available
|
// append hls to list if available
|
||||||
if (streams.hls != null) {
|
if (streams.hls != null) {
|
||||||
videosNameArray += getString(R.string.hls)
|
videosNameArray += getString(R.string.hls)
|
||||||
videosUrlArray += streams.hls!!.toUri()
|
videosUrlArray += streams.hls!!
|
||||||
}
|
}
|
||||||
|
|
||||||
val videoStreams = try {
|
val videoStreams = try {
|
||||||
@ -1138,10 +1144,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
val preferredMimeType = "video/${PlayerHelper.videoFormatPreference}"
|
val preferredMimeType = "video/${PlayerHelper.videoFormatPreference}"
|
||||||
if (vid.url != null && vid.mimeType == preferredMimeType) { // preferred format
|
if (vid.url != null && vid.mimeType == preferredMimeType) { // preferred format
|
||||||
videosNameArray += vid.quality.toString()
|
videosNameArray += vid.quality.toString()
|
||||||
videosUrlArray += vid.url!!.toUri()
|
videosUrlArray += vid.url!!
|
||||||
} else if (vid.quality.equals("LBRY") && vid.format.equals("MP4")) { // LBRY MP4 format
|
} else if (vid.quality.equals("LBRY") && vid.format.equals("MP4")) { // LBRY MP4 format
|
||||||
videosNameArray += "LBRY MP4"
|
videosNameArray += "LBRY MP4"
|
||||||
videosUrlArray += vid.url!!.toUri()
|
videosUrlArray += vid.url!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Pair(videosNameArray, videosUrlArray)
|
return Pair(videosNameArray, videosUrlArray)
|
||||||
@ -1189,17 +1195,16 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
private fun setStreamSource(
|
private fun setStreamSource(
|
||||||
streams: Streams,
|
streams: Streams,
|
||||||
videosNameArray: Array<String>,
|
videosNameArray: Array<String>,
|
||||||
videosUrlArray: Array<Uri>
|
videosUrlArray: Array<String>
|
||||||
) {
|
) {
|
||||||
val defaultResolution = PlayerHelper.getDefaultResolution(requireContext())
|
val defaultResolution = PlayerHelper.getDefaultResolution(requireContext())
|
||||||
if (defaultResolution != "") {
|
if (defaultResolution != "") {
|
||||||
videosNameArray.forEachIndexed { index, pipedStream ->
|
videosNameArray.forEachIndexed { index, pipedStream ->
|
||||||
// search for quality preference in the available stream sources
|
// search for quality preference in the available stream sources
|
||||||
if (pipedStream.contains(defaultResolution)) {
|
if (pipedStream.contains(defaultResolution)) {
|
||||||
val videoUri = videosUrlArray[index]
|
selectedVideoSourceUrl = videosUrlArray[index]
|
||||||
val audioUrl =
|
selectedAudioSourceUrl = selectedAudioSourceUrl ?: getAudioSource(streams.audioStreams)
|
||||||
PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!)
|
setMediaSource(selectedAudioSourceUrl!!, selectedVideoSourceUrl!!)
|
||||||
setMediaSource(videoUri, audioUrl)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1219,6 +1224,15 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getAudioSource(audioStreams: List<PipedStream>?): 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() {
|
private fun createExoPlayer() {
|
||||||
val cronetEngine: CronetEngine = CronetHelper.getCronetEngine()
|
val cronetEngine: CronetEngine = CronetHelper.getCronetEngine()
|
||||||
val cronetDataSourceFactory: CronetDataSource.Factory =
|
val cronetDataSourceFactory: CronetDataSource.Factory =
|
||||||
@ -1395,18 +1409,36 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
videosNameArray[which] == "LBRY HLS"
|
videosNameArray[which] == "LBRY HLS"
|
||||||
) {
|
) {
|
||||||
// set the progressive media source
|
// set the progressive media source
|
||||||
setHLSMediaSource(videosUrlArray[which])
|
setHLSMediaSource(videosUrlArray[which].toUri())
|
||||||
} else {
|
} else {
|
||||||
val videoUri = videosUrlArray[which]
|
selectedVideoSourceUrl = videosUrlArray[which]
|
||||||
val audioUrl =
|
selectedAudioSourceUrl = selectedAudioSourceUrl ?: getAudioSource(streams.audioStreams)
|
||||||
PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!)
|
setMediaSource(selectedVideoSourceUrl!!, selectedAudioSourceUrl!!)
|
||||||
setMediaSource(videoUri, audioUrl)
|
|
||||||
}
|
}
|
||||||
exoPlayer.seekTo(lastPosition)
|
exoPlayer.seekTo(lastPosition)
|
||||||
}
|
}
|
||||||
.show(childFragmentManager)
|
.show(childFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getAudioStreamGroups(audioStreams: List<PipedStream>?): Map<String?, List<PipedStream>> {
|
||||||
|
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) {
|
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
|
||||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
||||||
if (isInPictureInPictureMode) {
|
if (isInPictureInPictureMode) {
|
||||||
|
@ -4,4 +4,6 @@ interface OnlinePlayerOptions {
|
|||||||
fun onCaptionsClicked()
|
fun onCaptionsClicked()
|
||||||
|
|
||||||
fun onQualityClicked()
|
fun onQualityClicked()
|
||||||
|
|
||||||
|
fun onAudioStreamClicked()
|
||||||
}
|
}
|
||||||
|
@ -195,6 +195,14 @@ internal class CustomExoPlayerView(
|
|||||||
playerOptionsInterface?.onQualityClicked()
|
playerOptionsInterface?.onQualityClicked()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
items.add(
|
||||||
|
BottomSheetItem(
|
||||||
|
context.getString(R.string.audio_track),
|
||||||
|
R.drawable.ic_audio
|
||||||
|
) {
|
||||||
|
playerOptionsInterface?.onAudioStreamClicked()
|
||||||
|
}
|
||||||
|
)
|
||||||
items.add(
|
items.add(
|
||||||
BottomSheetItem(
|
BottomSheetItem(
|
||||||
context.getString(R.string.captions),
|
context.getString(R.string.captions),
|
||||||
|
@ -3,6 +3,7 @@ package com.github.libretube.util
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.view.accessibility.CaptioningManager
|
import android.view.accessibility.CaptioningManager
|
||||||
|
import com.github.libretube.api.obj.PipedStream
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
||||||
import com.google.android.exoplayer2.video.VideoSize
|
import com.google.android.exoplayer2.video.VideoSize
|
||||||
@ -12,7 +13,7 @@ object PlayerHelper {
|
|||||||
// get the audio source following the users preferences
|
// get the audio source following the users preferences
|
||||||
fun getAudioSource(
|
fun getAudioSource(
|
||||||
context: Context,
|
context: Context,
|
||||||
audios: List<com.github.libretube.api.obj.PipedStream>
|
audios: List<PipedStream>
|
||||||
): String {
|
): String {
|
||||||
val audioFormat = PreferenceHelper.getString(PreferenceKeys.PLAYER_AUDIO_FORMAT, "all")
|
val audioFormat = PreferenceHelper.getString(PreferenceKeys.PLAYER_AUDIO_FORMAT, "all")
|
||||||
val audioQuality = if (
|
val audioQuality = if (
|
||||||
@ -39,7 +40,7 @@ object PlayerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the best bit rate from audio streams
|
// get the best bit rate from audio streams
|
||||||
private fun getMostBitRate(audios: List<com.github.libretube.api.obj.PipedStream>): String {
|
private fun getMostBitRate(audios: List<PipedStream>): String {
|
||||||
var bitrate = 0
|
var bitrate = 0
|
||||||
var audioUrl = ""
|
var audioUrl = ""
|
||||||
audios.forEach {
|
audios.forEach {
|
||||||
@ -52,7 +53,7 @@ object PlayerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the best bit rate from audio streams
|
// get the best bit rate from audio streams
|
||||||
private fun getLeastBitRate(audios: List<com.github.libretube.api.obj.PipedStream>): String {
|
private fun getLeastBitRate(audios: List<PipedStream>): String {
|
||||||
var bitrate = 1000000000
|
var bitrate = 1000000000
|
||||||
var audioUrl = ""
|
var audioUrl = ""
|
||||||
audios.forEach {
|
audios.forEach {
|
||||||
|
10
app/src/main/res/drawable/ic_audio.xml
Normal file
10
app/src/main/res/drawable/ic_audio.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z" />
|
||||||
|
</vector>
|
@ -370,6 +370,8 @@
|
|||||||
<string name="layout">Layout</string>
|
<string name="layout">Layout</string>
|
||||||
<string name="alternative_player_layout">Alternative player layout</string>
|
<string name="alternative_player_layout">Alternative player layout</string>
|
||||||
<string name="alternative_player_layout_summary">Show the related videos as a row above the comments instead of below.</string>
|
<string name="alternative_player_layout_summary">Show the related videos as a row above the comments instead of below.</string>
|
||||||
|
<string name="audio_track">Audio track</string>
|
||||||
|
<string name="default_audio_track">Default</string>
|
||||||
|
|
||||||
<!-- Notification channel strings -->
|
<!-- Notification channel strings -->
|
||||||
<string name="download_channel_name">Download Service</string>
|
<string name="download_channel_name">Download Service</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user