From ba1b8c79d8b0c4b002406dff742474eec9cb44e2 Mon Sep 17 00:00:00 2001 From: Kavin <20838718+FireMasterK@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:17:27 +0000 Subject: [PATCH 01/10] Implement proper support for dash. --- app/build.gradle | 1 + .../github/libretube/api/obj/PipedStream.kt | 2 +- .../libretube/ui/fragments/PlayerFragment.kt | 84 ++------- .../com/github/libretube/util/DashHelper.kt | 176 ++++++++++++++++++ gradle/libs.versions.toml | 1 + 5 files changed, 199 insertions(+), 65 deletions(-) create mode 100644 app/src/main/java/com/github/libretube/util/DashHelper.kt diff --git a/app/build.gradle b/app/build.gradle index abad6cd8e..f65a4f934 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -103,6 +103,7 @@ dependencies { implementation libs.exoplayer implementation(libs.exoplayer.extension.cronet) { exclude group: 'com.google.android.gms' } implementation libs.exoplayer.extension.mediasession + implementation libs.exoplayer.dash /* Retrofit and Jackson */ implementation libs.square.retrofit 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 58de468e8..daa1b6f78 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 @@ -19,5 +19,5 @@ data class PipedStream( var height: Int? = null, var fps: Int? = null, val audioTrackName: String? = null, - val audioTrackId: 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 f9d6700d5..b28e7d646 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 @@ -18,6 +18,7 @@ import android.os.Looper import android.os.PowerManager import android.text.Html import android.text.format.DateUtils +import android.util.Base64 import android.util.Log import android.view.LayoutInflater import android.view.MotionEvent @@ -75,6 +76,7 @@ import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.sheets.BaseBottomSheet import com.github.libretube.ui.sheets.PlayingQueueSheet import com.github.libretube.util.BackgroundHelper +import com.github.libretube.util.DashHelper import com.github.libretube.util.ImageHelper import com.github.libretube.util.NowPlayingNotification import com.github.libretube.util.PlayerHelper @@ -102,6 +104,7 @@ import com.google.android.exoplayer2.ui.StyledPlayerView import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DefaultDataSource import com.google.android.exoplayer2.upstream.DefaultHttpDataSource +import com.google.android.exoplayer2.util.MimeTypes import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -771,7 +774,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { binding.apply { playerViewsInfo.text = context?.getString(R.string.views, response.views.formatShort()) + - if (!isLive) TextUtils.SEPARATOR + response.uploadDate else "" + if (!isLive) TextUtils.SEPARATOR + response.uploadDate else "" textLike.text = response.likes.formatShort() textDislike.text = response.dislikes.formatShort() @@ -941,7 +944,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } // update the subscribed state - binding.playerSubscribe.setupSubscriptionButton(streams.uploaderUrl?.toID(), streams.uploader) + binding.playerSubscribe.setupSubscriptionButton( + streams.uploaderUrl?.toID(), + streams.uploader + ) if (token != "") { binding.relPlayerSave.setOnClickListener { @@ -1072,41 +1078,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { return chapterIndex } - private fun setMediaSource( - videoUrl: String, - audioUrl: String - ) { - val checkIntervalSize = when (PlayerHelper.progressiveLoadingIntervalSize) { - "default" -> ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES - else -> PlayerHelper.progressiveLoadingIntervalSize.toInt() * 1024 - } - - val dataSourceFactory: DataSource.Factory = - DefaultHttpDataSource.Factory() - - val videoItem: MediaItem = MediaItem.Builder() - .setUri(videoUrl.toUri()) - .setSubtitleConfigurations(subtitles) - .build() - - val videoSource: MediaSource = - ProgressiveMediaSource.Factory(dataSourceFactory) - .setContinueLoadingCheckIntervalBytes(checkIntervalSize) - .createMediaSource(videoItem) - - val audioSource: MediaSource = - ProgressiveMediaSource.Factory(dataSourceFactory) - .setContinueLoadingCheckIntervalBytes(checkIntervalSize) - .createMediaSource(fromUri(audioUrl)) - - val mergeSource: MediaSource = - MergingMediaSource(videoSource, audioSource) - exoPlayer.setMediaSource(mergeSource) - } - - private fun setHLSMediaSource(uri: Uri) { + private fun setMediaSource(uri: Uri, mimeType: String) { val mediaItem: MediaItem = MediaItem.Builder() .setUri(uri) + .setMimeType(mimeType) .setSubtitleConfigurations(subtitles) .build() exoPlayer.setMediaItem(mediaItem) @@ -1187,41 +1162,24 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { // set media source and resolution in the beginning setStreamSource( streams, - videosNameArray, - videosUrlArray ) } private fun setStreamSource( - streams: Streams, - videosNameArray: Array, - videosUrlArray: Array + streams: Streams ) { val defaultResolution = PlayerHelper.getDefaultResolution(requireContext()) if (defaultResolution != "") { - videosNameArray.forEachIndexed { index, pipedStream -> - // search for quality preference in the available stream sources - if (pipedStream.contains(defaultResolution)) { - selectedVideoSourceUrl = videosUrlArray[index] - selectedAudioSourceUrl = selectedAudioSourceUrl ?: getAudioSource(streams.audioStreams) - setMediaSource(selectedAudioSourceUrl!!, selectedVideoSourceUrl!!) - return - } - } + // TODO: Fix this, we need to set it from the player! } - // if default resolution isn't set or available, use hls if available - if (streams.hls != null) { - setHLSMediaSource(Uri.parse(streams.hls)) - return - } + val manifest = DashHelper.createManifest(streams) - // if nothing found, use the first list entry - if (videosUrlArray.isNotEmpty()) { - val videoUri = videosUrlArray[0] - val audioUrl = PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!) - setMediaSource(videoUri, audioUrl) - } + // encode to base64 + val encoded = Base64.encodeToString(manifest.toByteArray(), Base64.DEFAULT) + val mediaItem = "data:application/dash+xml;charset=utf-8;base64,${encoded}" + + this.setMediaSource(mediaItem.toUri(), MimeTypes.APPLICATION_MPD) } private fun getAudioSource(audioStreams: List?): String { @@ -1409,11 +1367,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { videosNameArray[which] == "LBRY HLS" ) { // set the progressive media source - setHLSMediaSource(videosUrlArray[which].toUri()) + setMediaSource(videosUrlArray[which], MimeTypes.APPLICATION_M3U8) } else { - selectedVideoSourceUrl = videosUrlArray[which] - selectedAudioSourceUrl = selectedAudioSourceUrl ?: getAudioSource(streams.audioStreams) - setMediaSource(selectedVideoSourceUrl!!, selectedAudioSourceUrl!!) + // TODO: Fix this } exoPlayer.seekTo(lastPosition) } diff --git a/app/src/main/java/com/github/libretube/util/DashHelper.kt b/app/src/main/java/com/github/libretube/util/DashHelper.kt new file mode 100644 index 000000000..6f2a439b1 --- /dev/null +++ b/app/src/main/java/com/github/libretube/util/DashHelper.kt @@ -0,0 +1,176 @@ +package com.github.libretube.util + +import com.github.libretube.api.obj.PipedStream +import com.github.libretube.api.obj.Streams +import org.w3c.dom.Document +import org.w3c.dom.Element +import java.io.StringWriter +import javax.xml.parsers.DocumentBuilder +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +// Based off of https://github.com/TeamPiped/Piped/blob/master/src/utils/DashUtils.js + +class DashHelper { + companion object { + + private val builderFactory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() + private val transformerFactory: TransformerFactory = TransformerFactory.newInstance() + + private data class AdapSetInfo( + val mimeType: String, + val audioTrackId: String? = null, + val formats: MutableList = mutableListOf() + ) + + fun createManifest(streams: Streams): String { + val builder: DocumentBuilder = builderFactory.newDocumentBuilder() + + val doc = builder.newDocument() + val mpd = doc.createElement("MPD") + mpd.setAttribute("xmlns", "urn:mpeg:dash:schema:mpd:2011") + mpd.setAttribute("profiles", "urn:mpeg:dash:profile:full:2011") + mpd.setAttribute("minBufferTime", "PT1.5S") + mpd.setAttribute("type", "static") + mpd.setAttribute("mediaPresentationDuration", "PT${streams.duration}S") + + val period = doc.createElement("Period") + + val adapSetInfos = ArrayList() + + for (stream in streams.videoStreams!!) { + + // ignore dual format streams + if (!stream.videoOnly!!) + continue + + val adapSetInfo = adapSetInfos.find { it.mimeType == stream.mimeType } + if (adapSetInfo != null) { + adapSetInfo.formats.add(stream) + continue + } + adapSetInfos.add( + AdapSetInfo( + stream.mimeType!!, + null, + mutableListOf(stream) + ) + ) + } + + for (stream in streams.audioStreams!!) { + val adapSetInfo = + adapSetInfos.find { it.mimeType == stream.mimeType && it.audioTrackId == stream.audioTrackId } + if (adapSetInfo != null) { + adapSetInfo.formats.add(stream) + continue + } + adapSetInfos.add( + AdapSetInfo( + stream.mimeType!!, + null, + mutableListOf(stream) + ) + ) + } + + for (adapSet in adapSetInfos) { + val adapSetElement = doc.createElement("AdaptationSet") + adapSetElement.setAttribute("mimeType", adapSet.mimeType) + adapSetElement.setAttribute("startWithSAP", "1") + adapSetElement.setAttribute("subsegmentAlignment", "true") + if (adapSet.audioTrackId != null) { + adapSetElement.setAttribute("lang", adapSet.audioTrackId.substring(0, 2)) + } + + val isVideo = adapSet.mimeType.contains("video") + + if (isVideo) { + adapSetElement.setAttribute("scanType", "progressive") + } + + for (stream in adapSet.formats) { + val rep = let { + if (isVideo) { + createVideoRepresentation(doc, stream) + } else { + createAudioRepresentation(doc, stream) + } + } + adapSetElement.appendChild(rep) + } + + period.appendChild(adapSetElement) + } + + mpd.appendChild(period) + + doc.appendChild(mpd) + + val domSource = DOMSource(doc) + val writer = StringWriter() + + val transformer = transformerFactory.newTransformer() + transformer.transform(domSource, StreamResult(writer)) + + return writer.toString() + } + + private fun createAudioRepresentation(doc: Document, stream: PipedStream): Element { + val representation = doc.createElement("Representation") + representation.setAttribute("bandwidth", stream.bitrate.toString()) + representation.setAttribute("codecs", stream.codec!!) + representation.setAttribute("mimeType", stream.mimeType!!) + + val audioChannelConfiguration = doc.createElement("AudioChannelConfiguration") + audioChannelConfiguration.setAttribute( + "schemeIdUri", + "urn:mpeg:dash:23003:3:audio_channel_configuration:2011" + ) + audioChannelConfiguration.setAttribute("value", "2") + + val baseUrl = doc.createElement("BaseURL") + baseUrl.appendChild(doc.createTextNode(stream.url!!)) + + val segmentBase = doc.createElement("SegmentBase") + segmentBase.setAttribute("indexRange", "${stream.indexStart}-${stream.indexEnd}") + + val initialization = doc.createElement("Initialization") + initialization.setAttribute("range", "${stream.initStart}-${stream.initEnd}") + segmentBase.appendChild(initialization) + + representation.appendChild(audioChannelConfiguration) + representation.appendChild(baseUrl) + representation.appendChild(segmentBase) + + return representation + } + + private fun createVideoRepresentation(doc: Document, stream: PipedStream): Element { + val representation = doc.createElement("Representation") + representation.setAttribute("codecs", stream.codec!!) + representation.setAttribute("bandwidth", stream.bitrate.toString()) + representation.setAttribute("width", stream.width.toString()) + representation.setAttribute("height", stream.height.toString()) + representation.setAttribute("maxPlayoutRate", "1") + representation.setAttribute("frameRate", stream.fps.toString()) + + val baseUrl = doc.createElement("BaseURL") + baseUrl.appendChild(doc.createTextNode(stream.url!!)) + + val segmentBase = doc.createElement("SegmentBase") + segmentBase.setAttribute("indexRange", "${stream.indexStart}-${stream.indexEnd}") + + val initialization = doc.createElement("Initialization") + initialization.setAttribute("range", "${stream.initStart}-${stream.initEnd}") + segmentBase.appendChild(initialization) + + representation.appendChild(baseUrl) + representation.appendChild(segmentBase) + + return representation + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6fd6db684..c527e6aea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,6 +41,7 @@ square-retrofit-converterJackson = { group = "com.squareup.retrofit2", name = "c jacksonAnnotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jacksonAnnotations" } desugaring = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugaring" } exoplayer-extension-cronet = { group = "com.google.android.exoplayer", name = "extension-cronet", version.ref = "exoplayer" } +exoplayer-dash = { group = "com.google.android.exoplayer", name = "exoplayer-dash", version.ref = "exoplayer" } cronet-embedded = { group = "org.chromium.net", name = "cronet-embedded", version.ref = "cronetEmbedded" } cronet-okhttp = { group = "com.google.net.cronet", name = "cronet-okhttp", version.ref = "cronetOkHttp" } coil = { group = "io.coil-kt", name = "coil", version.ref="coil" } From db83051e5ea90650f003cb1584cf185c8bc404a9 Mon Sep 17 00:00:00 2001 From: Kavin <20838718+FireMasterK@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:22:22 +0000 Subject: [PATCH 02/10] Remove broken code. --- .../com/github/libretube/ui/fragments/PlayerFragment.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 b28e7d646..70ca93480 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 @@ -1367,7 +1367,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { videosNameArray[which] == "LBRY HLS" ) { // set the progressive media source - setMediaSource(videosUrlArray[which], MimeTypes.APPLICATION_M3U8) + setMediaSource(videosUrlArray[which].toUri(), MimeTypes.APPLICATION_M3U8) } else { // TODO: Fix this } @@ -1388,9 +1388,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { BaseBottomSheet() .setSimpleItems(audioLanguages) { index -> val audioStreams = audioGroups.values.elementAt(index) - selectedAudioSourceUrl = PlayerHelper.getAudioSource(requireContext(), audioStreams) - selectedVideoSourceUrl = selectedVideoSourceUrl ?: streams.videoStreams!!.first().url!! - setMediaSource(selectedAudioSourceUrl!!, selectedVideoSourceUrl!!) + // TODO: Fix this } .show(childFragmentManager) } From acc6afced657b1caf66d0ff49d4ac2d21e8d2761 Mon Sep 17 00:00:00 2001 From: Kavin <20838718+FireMasterK@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:26:25 +0000 Subject: [PATCH 03/10] Run ktlint. --- .../java/com/github/libretube/api/obj/PipedStream.kt | 2 +- .../github/libretube/ui/fragments/PlayerFragment.kt | 12 +++--------- .../java/com/github/libretube/util/DashHelper.kt | 4 ++-- 3 files changed, 6 insertions(+), 12 deletions(-) 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 daa1b6f78..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 @@ -19,5 +19,5 @@ data class PipedStream( var height: Int? = null, var fps: Int? = null, val audioTrackName: String? = null, - val audioTrackId: 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 70ca93480..92b018eea 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 @@ -88,22 +88,16 @@ import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MediaItem.SubtitleConfiguration -import com.google.android.exoplayer2.MediaItem.fromUri import com.google.android.exoplayer2.PlaybackException import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.cronet.CronetDataSource import com.google.android.exoplayer2.source.DefaultMediaSourceFactory -import com.google.android.exoplayer2.source.MediaSource -import com.google.android.exoplayer2.source.MergingMediaSource -import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.text.Cue.TEXT_SIZE_TYPE_ABSOLUTE import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.ui.CaptionStyleCompat import com.google.android.exoplayer2.ui.StyledPlayerView -import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DefaultDataSource -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource import com.google.android.exoplayer2.util.MimeTypes import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.CoroutineScope @@ -774,7 +768,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { binding.apply { playerViewsInfo.text = context?.getString(R.string.views, response.views.formatShort()) + - if (!isLive) TextUtils.SEPARATOR + response.uploadDate else "" + if (!isLive) TextUtils.SEPARATOR + response.uploadDate else "" textLike.text = response.likes.formatShort() textDislike.text = response.dislikes.formatShort() @@ -1161,7 +1155,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { // set media source and resolution in the beginning setStreamSource( - streams, + streams ) } @@ -1177,7 +1171,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { // encode to base64 val encoded = Base64.encodeToString(manifest.toByteArray(), Base64.DEFAULT) - val mediaItem = "data:application/dash+xml;charset=utf-8;base64,${encoded}" + val mediaItem = "data:application/dash+xml;charset=utf-8;base64,$encoded" this.setMediaSource(mediaItem.toUri(), MimeTypes.APPLICATION_MPD) } diff --git a/app/src/main/java/com/github/libretube/util/DashHelper.kt b/app/src/main/java/com/github/libretube/util/DashHelper.kt index 6f2a439b1..33684d742 100644 --- a/app/src/main/java/com/github/libretube/util/DashHelper.kt +++ b/app/src/main/java/com/github/libretube/util/DashHelper.kt @@ -41,10 +41,10 @@ class DashHelper { val adapSetInfos = ArrayList() for (stream in streams.videoStreams!!) { - // ignore dual format streams - if (!stream.videoOnly!!) + if (!stream.videoOnly!!) { continue + } val adapSetInfo = adapSetInfos.find { it.mimeType == stream.mimeType } if (adapSetInfo != null) { From 3e5681bae3a073210f3acce05c14c2172ae1987c Mon Sep 17 00:00:00 2001 From: Kavin <20838718+FireMasterK@users.noreply.github.com> Date: Wed, 16 Nov 2022 15:56:23 +0000 Subject: [PATCH 04/10] Remove companion object --- .../com/github/libretube/util/DashHelper.kt | 290 +++++++++--------- 1 file changed, 144 insertions(+), 146 deletions(-) diff --git a/app/src/main/java/com/github/libretube/util/DashHelper.kt b/app/src/main/java/com/github/libretube/util/DashHelper.kt index 33684d742..e543ff6ef 100644 --- a/app/src/main/java/com/github/libretube/util/DashHelper.kt +++ b/app/src/main/java/com/github/libretube/util/DashHelper.kt @@ -13,164 +13,162 @@ import javax.xml.transform.stream.StreamResult // Based off of https://github.com/TeamPiped/Piped/blob/master/src/utils/DashUtils.js -class DashHelper { - companion object { +object DashHelper { - private val builderFactory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() - private val transformerFactory: TransformerFactory = TransformerFactory.newInstance() + private val builderFactory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() + private val transformerFactory: TransformerFactory = TransformerFactory.newInstance() - private data class AdapSetInfo( - val mimeType: String, - val audioTrackId: String? = null, - val formats: MutableList = mutableListOf() - ) + private data class AdapSetInfo( + val mimeType: String, + val audioTrackId: String? = null, + val formats: MutableList = mutableListOf() + ) - fun createManifest(streams: Streams): String { - val builder: DocumentBuilder = builderFactory.newDocumentBuilder() + fun createManifest(streams: Streams): String { + val builder: DocumentBuilder = builderFactory.newDocumentBuilder() - val doc = builder.newDocument() - val mpd = doc.createElement("MPD") - mpd.setAttribute("xmlns", "urn:mpeg:dash:schema:mpd:2011") - mpd.setAttribute("profiles", "urn:mpeg:dash:profile:full:2011") - mpd.setAttribute("minBufferTime", "PT1.5S") - mpd.setAttribute("type", "static") - mpd.setAttribute("mediaPresentationDuration", "PT${streams.duration}S") + val doc = builder.newDocument() + val mpd = doc.createElement("MPD") + mpd.setAttribute("xmlns", "urn:mpeg:dash:schema:mpd:2011") + mpd.setAttribute("profiles", "urn:mpeg:dash:profile:full:2011") + mpd.setAttribute("minBufferTime", "PT1.5S") + mpd.setAttribute("type", "static") + mpd.setAttribute("mediaPresentationDuration", "PT${streams.duration}S") - val period = doc.createElement("Period") + val period = doc.createElement("Period") - val adapSetInfos = ArrayList() + val adapSetInfos = ArrayList() - for (stream in streams.videoStreams!!) { - // ignore dual format streams - if (!stream.videoOnly!!) { - continue - } + for (stream in streams.videoStreams!!) { + // ignore dual format streams + if (!stream.videoOnly!!) { + continue + } - val adapSetInfo = adapSetInfos.find { it.mimeType == stream.mimeType } - if (adapSetInfo != null) { - adapSetInfo.formats.add(stream) - continue - } - adapSetInfos.add( - AdapSetInfo( - stream.mimeType!!, - null, - mutableListOf(stream) - ) + val adapSetInfo = adapSetInfos.find { it.mimeType == stream.mimeType } + if (adapSetInfo != null) { + adapSetInfo.formats.add(stream) + continue + } + adapSetInfos.add( + AdapSetInfo( + stream.mimeType!!, + null, + mutableListOf(stream) ) - } - - for (stream in streams.audioStreams!!) { - val adapSetInfo = - adapSetInfos.find { it.mimeType == stream.mimeType && it.audioTrackId == stream.audioTrackId } - if (adapSetInfo != null) { - adapSetInfo.formats.add(stream) - continue - } - adapSetInfos.add( - AdapSetInfo( - stream.mimeType!!, - null, - mutableListOf(stream) - ) - ) - } - - for (adapSet in adapSetInfos) { - val adapSetElement = doc.createElement("AdaptationSet") - adapSetElement.setAttribute("mimeType", adapSet.mimeType) - adapSetElement.setAttribute("startWithSAP", "1") - adapSetElement.setAttribute("subsegmentAlignment", "true") - if (adapSet.audioTrackId != null) { - adapSetElement.setAttribute("lang", adapSet.audioTrackId.substring(0, 2)) - } - - val isVideo = adapSet.mimeType.contains("video") - - if (isVideo) { - adapSetElement.setAttribute("scanType", "progressive") - } - - for (stream in adapSet.formats) { - val rep = let { - if (isVideo) { - createVideoRepresentation(doc, stream) - } else { - createAudioRepresentation(doc, stream) - } - } - adapSetElement.appendChild(rep) - } - - period.appendChild(adapSetElement) - } - - mpd.appendChild(period) - - doc.appendChild(mpd) - - val domSource = DOMSource(doc) - val writer = StringWriter() - - val transformer = transformerFactory.newTransformer() - transformer.transform(domSource, StreamResult(writer)) - - return writer.toString() - } - - private fun createAudioRepresentation(doc: Document, stream: PipedStream): Element { - val representation = doc.createElement("Representation") - representation.setAttribute("bandwidth", stream.bitrate.toString()) - representation.setAttribute("codecs", stream.codec!!) - representation.setAttribute("mimeType", stream.mimeType!!) - - val audioChannelConfiguration = doc.createElement("AudioChannelConfiguration") - audioChannelConfiguration.setAttribute( - "schemeIdUri", - "urn:mpeg:dash:23003:3:audio_channel_configuration:2011" ) - audioChannelConfiguration.setAttribute("value", "2") - - val baseUrl = doc.createElement("BaseURL") - baseUrl.appendChild(doc.createTextNode(stream.url!!)) - - val segmentBase = doc.createElement("SegmentBase") - segmentBase.setAttribute("indexRange", "${stream.indexStart}-${stream.indexEnd}") - - val initialization = doc.createElement("Initialization") - initialization.setAttribute("range", "${stream.initStart}-${stream.initEnd}") - segmentBase.appendChild(initialization) - - representation.appendChild(audioChannelConfiguration) - representation.appendChild(baseUrl) - representation.appendChild(segmentBase) - - return representation } - private fun createVideoRepresentation(doc: Document, stream: PipedStream): Element { - val representation = doc.createElement("Representation") - representation.setAttribute("codecs", stream.codec!!) - representation.setAttribute("bandwidth", stream.bitrate.toString()) - representation.setAttribute("width", stream.width.toString()) - representation.setAttribute("height", stream.height.toString()) - representation.setAttribute("maxPlayoutRate", "1") - representation.setAttribute("frameRate", stream.fps.toString()) - - val baseUrl = doc.createElement("BaseURL") - baseUrl.appendChild(doc.createTextNode(stream.url!!)) - - val segmentBase = doc.createElement("SegmentBase") - segmentBase.setAttribute("indexRange", "${stream.indexStart}-${stream.indexEnd}") - - val initialization = doc.createElement("Initialization") - initialization.setAttribute("range", "${stream.initStart}-${stream.initEnd}") - segmentBase.appendChild(initialization) - - representation.appendChild(baseUrl) - representation.appendChild(segmentBase) - - return representation + for (stream in streams.audioStreams!!) { + val adapSetInfo = + adapSetInfos.find { it.mimeType == stream.mimeType && it.audioTrackId == stream.audioTrackId } + if (adapSetInfo != null) { + adapSetInfo.formats.add(stream) + continue + } + adapSetInfos.add( + AdapSetInfo( + stream.mimeType!!, + null, + mutableListOf(stream) + ) + ) } + + for (adapSet in adapSetInfos) { + val adapSetElement = doc.createElement("AdaptationSet") + adapSetElement.setAttribute("mimeType", adapSet.mimeType) + adapSetElement.setAttribute("startWithSAP", "1") + adapSetElement.setAttribute("subsegmentAlignment", "true") + if (adapSet.audioTrackId != null) { + adapSetElement.setAttribute("lang", adapSet.audioTrackId.substring(0, 2)) + } + + val isVideo = adapSet.mimeType.contains("video") + + if (isVideo) { + adapSetElement.setAttribute("scanType", "progressive") + } + + for (stream in adapSet.formats) { + val rep = let { + if (isVideo) { + createVideoRepresentation(doc, stream) + } else { + createAudioRepresentation(doc, stream) + } + } + adapSetElement.appendChild(rep) + } + + period.appendChild(adapSetElement) + } + + mpd.appendChild(period) + + doc.appendChild(mpd) + + val domSource = DOMSource(doc) + val writer = StringWriter() + + val transformer = transformerFactory.newTransformer() + transformer.transform(domSource, StreamResult(writer)) + + return writer.toString() + } + + private fun createAudioRepresentation(doc: Document, stream: PipedStream): Element { + val representation = doc.createElement("Representation") + representation.setAttribute("bandwidth", stream.bitrate.toString()) + representation.setAttribute("codecs", stream.codec!!) + representation.setAttribute("mimeType", stream.mimeType!!) + + val audioChannelConfiguration = doc.createElement("AudioChannelConfiguration") + audioChannelConfiguration.setAttribute( + "schemeIdUri", + "urn:mpeg:dash:23003:3:audio_channel_configuration:2011" + ) + audioChannelConfiguration.setAttribute("value", "2") + + val baseUrl = doc.createElement("BaseURL") + baseUrl.appendChild(doc.createTextNode(stream.url!!)) + + val segmentBase = doc.createElement("SegmentBase") + segmentBase.setAttribute("indexRange", "${stream.indexStart}-${stream.indexEnd}") + + val initialization = doc.createElement("Initialization") + initialization.setAttribute("range", "${stream.initStart}-${stream.initEnd}") + segmentBase.appendChild(initialization) + + representation.appendChild(audioChannelConfiguration) + representation.appendChild(baseUrl) + representation.appendChild(segmentBase) + + return representation + } + + private fun createVideoRepresentation(doc: Document, stream: PipedStream): Element { + val representation = doc.createElement("Representation") + representation.setAttribute("codecs", stream.codec!!) + representation.setAttribute("bandwidth", stream.bitrate.toString()) + representation.setAttribute("width", stream.width.toString()) + representation.setAttribute("height", stream.height.toString()) + representation.setAttribute("maxPlayoutRate", "1") + representation.setAttribute("frameRate", stream.fps.toString()) + + val baseUrl = doc.createElement("BaseURL") + baseUrl.appendChild(doc.createTextNode(stream.url!!)) + + val segmentBase = doc.createElement("SegmentBase") + segmentBase.setAttribute("indexRange", "${stream.indexStart}-${stream.indexEnd}") + + val initialization = doc.createElement("Initialization") + initialization.setAttribute("range", "${stream.initStart}-${stream.initEnd}") + segmentBase.appendChild(initialization) + + representation.appendChild(baseUrl) + representation.appendChild(segmentBase) + + return representation } } From e403bd379a3d306c38bfe46206cba3caa6949f7e Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 16 Nov 2022 18:14:47 +0100 Subject: [PATCH 05/10] reimplement quality selection and pref --- .../github/libretube/obj/VideoResolution.kt | 7 ++ .../libretube/ui/fragments/PlayerFragment.kt | 84 +++++++++++-------- 2 files changed, 54 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/com/github/libretube/obj/VideoResolution.kt diff --git a/app/src/main/java/com/github/libretube/obj/VideoResolution.kt b/app/src/main/java/com/github/libretube/obj/VideoResolution.kt new file mode 100644 index 000000000..b234cfda2 --- /dev/null +++ b/app/src/main/java/com/github/libretube/obj/VideoResolution.kt @@ -0,0 +1,7 @@ +package com.github.libretube.obj + +data class VideoResolution( + val name: String, + val resolution: Int? = null, + val adaptiveSourceUrl: 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 92b018eea..93e13620f 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 @@ -60,6 +60,7 @@ import com.github.libretube.extensions.query import com.github.libretube.extensions.toID import com.github.libretube.extensions.toStreamItem import com.github.libretube.obj.ShareData +import com.github.libretube.obj.VideoResolution import com.github.libretube.services.BackgroundMode import com.github.libretube.services.DownloadService import com.github.libretube.ui.activities.MainActivity @@ -166,9 +167,6 @@ 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 { @@ -1081,16 +1079,19 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { exoPlayer.setMediaItem(mediaItem) } - private fun getAvailableResolutions(): Pair, Array> { - if (!this::streams.isInitialized) return Pair(arrayOf(), arrayOf()) + private fun getAvailableResolutions(): List { + if (!this::streams.isInitialized) return listOf() - var videosNameArray: Array = arrayOf() - var videosUrlArray: Array = arrayOf() + val resolutions = mutableListOf() // append hls to list if available if (streams.hls != null) { - videosNameArray += getString(R.string.hls) - videosUrlArray += streams.hls!! + resolutions.add( + VideoResolution( + name = getString(R.string.hls), + adaptiveSourceUrl = streams.hls!! + ) + ) } val videoStreams = try { @@ -1112,20 +1113,26 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { // append quality to list if it has the preferred format (e.g. MPEG) val preferredMimeType = "video/${PlayerHelper.videoFormatPreference}" if (vid.url != null && vid.mimeType == preferredMimeType) { // preferred format - videosNameArray += vid.quality.toString() - videosUrlArray += vid.url!! + resolutions.add( + VideoResolution( + name = vid.quality!!, + resolution = vid.quality.toString().split("p").first().toInt() + ) + ) } else if (vid.quality.equals("LBRY") && vid.format.equals("MP4")) { // LBRY MP4 format - videosNameArray += "LBRY MP4" - videosUrlArray += vid.url!! + resolutions.add( + VideoResolution( + name = "LBRY MP4", + adaptiveSourceUrl = vid.url, + resolution = Int.MAX_VALUE + ) + ) } } - return Pair(videosNameArray, videosUrlArray) + return resolutions } private fun setResolutionAndSubtitles() { - // get the available resolutions - val (videosNameArray, videosUrlArray) = getAvailableResolutions() - // create a list of subtitles subtitles = mutableListOf() val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!) @@ -1159,12 +1166,13 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { ) } - private fun setStreamSource( - streams: Streams - ) { + private fun setStreamSource(streams: Streams) { val defaultResolution = PlayerHelper.getDefaultResolution(requireContext()) if (defaultResolution != "") { - // TODO: Fix this, we need to set it from the player! + val params = trackSelector.buildUponParameters() + .setMaxVideoSize(Int.MAX_VALUE, defaultResolution.toInt()) + .setMinVideoSize(Int.MAX_VALUE, defaultResolution.toInt()) + trackSelector.setParameters(params) } val manifest = DashHelper.createManifest(streams) @@ -1176,15 +1184,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { this.setMediaSource(mediaItem.toUri(), MimeTypes.APPLICATION_MPD) } - 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 = @@ -1216,6 +1215,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { // control for the track sources like subtitles and audio source trackSelector = DefaultTrackSelector(requireContext()) + val params = trackSelector.buildUponParameters().setPreferredAudioLanguage( + Locale.getDefault().language.lowercase().substring(0, 2) + ) + trackSelector.setParameters(params) + // limit hls to full hd if ( PreferenceHelper.getBoolean( @@ -1348,22 +1352,25 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { override fun onQualityClicked() { // get the available resolutions - val (videosNameArray, videosUrlArray) = getAvailableResolutions() + val resolutions = getAvailableResolutions() // Dialog for quality selection val lastPosition = exoPlayer.currentPosition BaseBottomSheet() .setSimpleItems( - videosNameArray.toList() + resolutions.map { it.name } ) { which -> if ( - videosNameArray[which] == getString(R.string.hls) || - videosNameArray[which] == "LBRY HLS" + resolutions[which].adaptiveSourceUrl != null ) { // set the progressive media source - setMediaSource(videosUrlArray[which].toUri(), MimeTypes.APPLICATION_M3U8) + setMediaSource(resolutions[which].adaptiveSourceUrl!!.toUri(), MimeTypes.APPLICATION_M3U8) } else { - // TODO: Fix this + val resolution = resolutions[which].resolution!! + val params = trackSelector.buildUponParameters() + .setMaxVideoSize(Int.MAX_VALUE, resolution) + .setMinVideoSize(Int.MAX_VALUE, resolution) + trackSelector.setParameters(params) } exoPlayer.seekTo(lastPosition) } @@ -1382,7 +1389,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { BaseBottomSheet() .setSimpleItems(audioLanguages) { index -> val audioStreams = audioGroups.values.elementAt(index) - // TODO: Fix this + val lang = audioStreams.first().audioTrackId!!.substring(0, 2) + val newParams = trackSelector.buildUponParameters() + .setPreferredAudioLanguage(lang) + trackSelector.setParameters(newParams) } .show(childFragmentManager) } From 74651b761709a5295f65ed18a96feb01bdb75953 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 16 Nov 2022 18:42:57 +0100 Subject: [PATCH 06/10] option to force hls --- .../libretube/constants/PreferenceKeys.kt | 1 + .../libretube/ui/fragments/PlayerFragment.kt | 57 +++++++++---------- app/src/main/res/values/array.xml | 2 +- app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/audio_video_settings.xml | 10 ++++ 5 files changed, 44 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt index 9f0f69781..a46100d37 100644 --- a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt @@ -81,6 +81,7 @@ object PreferenceKeys { const val LIMIT_HLS = "limit_hls" const val PROGRESSIVE_LOADING_INTERVAL_SIZE = "progressive_loading_interval" const val ALTERNATIVE_PLAYER_LAYOUT = "alternative_player_layout" + const val USE_HLS_OVER_DASH = "use_hls" /** * Background mode 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 93e13620f..89cd89da5 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 @@ -1084,16 +1084,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { val resolutions = mutableListOf() - // append hls to list if available - if (streams.hls != null) { - resolutions.add( - VideoResolution( - name = getString(R.string.hls), - adaptiveSourceUrl = streams.hls!! - ) - ) - } - val videoStreams = try { // attempt to sort the qualities, catch if there was an error ih parsing streams.videoStreams?.sortedBy { @@ -1129,6 +1119,15 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { ) } } + + if (resolutions.isEmpty()) { + return listOf( + VideoResolution(getString(R.string.hls), adaptiveSourceUrl = streams.hls) + ) + } + + resolutions.add(0, VideoResolution(getString(R.string.auto_quality), Int.MAX_VALUE)) + return resolutions } @@ -1175,13 +1174,21 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { trackSelector.setParameters(params) } - val manifest = DashHelper.createManifest(streams) + if (!PreferenceHelper.getBoolean(PreferenceKeys.USE_HLS_OVER_DASH, false) && + streams.videoStreams.orEmpty().isNotEmpty() + ) { + val manifest = DashHelper.createManifest(streams) - // encode to base64 - val encoded = Base64.encodeToString(manifest.toByteArray(), Base64.DEFAULT) - val mediaItem = "data:application/dash+xml;charset=utf-8;base64,$encoded" + // encode to base64 + val encoded = Base64.encodeToString(manifest.toByteArray(), Base64.DEFAULT) + val mediaItem = "data:application/dash+xml;charset=utf-8;base64,$encoded" - this.setMediaSource(mediaItem.toUri(), MimeTypes.APPLICATION_MPD) + this.setMediaSource(mediaItem.toUri(), MimeTypes.APPLICATION_MPD) + } else if (streams.hls != null) { + setMediaSource(streams.hls.toUri(), MimeTypes.APPLICATION_M3U8) + } else { + Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show() + } } private fun createExoPlayer() { @@ -1355,24 +1362,16 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { val resolutions = getAvailableResolutions() // Dialog for quality selection - val lastPosition = exoPlayer.currentPosition BaseBottomSheet() .setSimpleItems( resolutions.map { it.name } ) { which -> - if ( - resolutions[which].adaptiveSourceUrl != null - ) { - // set the progressive media source - setMediaSource(resolutions[which].adaptiveSourceUrl!!.toUri(), MimeTypes.APPLICATION_M3U8) - } else { - val resolution = resolutions[which].resolution!! - val params = trackSelector.buildUponParameters() - .setMaxVideoSize(Int.MAX_VALUE, resolution) - .setMinVideoSize(Int.MAX_VALUE, resolution) - trackSelector.setParameters(params) - } - exoPlayer.seekTo(lastPosition) + val resolution = resolutions[which].resolution!! + + val params = trackSelector.buildUponParameters() + .setMaxVideoSize(Int.MAX_VALUE, resolution) + .setMinVideoSize(Int.MAX_VALUE, resolution) + trackSelector.setParameters(params) } .show(childFragmentManager) } diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index f48a87cb5..d48ec1b73 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -188,7 +188,7 @@ - @string/hls + @string/auto_quality 2160p 1440p 1080p diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 04d59f718..62b315ad8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -208,7 +208,7 @@ Authentication instance Use a different instance for authenticated calls. Choose an auth instance - Auto + HLS GitHub Audio and video Fullscreen orientation @@ -372,6 +372,9 @@ Show the related videos as a row above the comments instead of below. Audio track Default + Use HLS + Use HLS instead of DASH (will be slower, not recommended) + Auto Download Service diff --git a/app/src/main/res/xml/audio_video_settings.xml b/app/src/main/res/xml/audio_video_settings.xml index 9d140d99b..fdeb60a2c 100644 --- a/app/src/main/res/xml/audio_video_settings.xml +++ b/app/src/main/res/xml/audio_video_settings.xml @@ -46,6 +46,7 @@ + @@ -62,10 +64,18 @@ app:defaultValue="all" app:entries="@array/playerAudioFormat" app:entryValues="@array/playerAudioFormatValues" + app:isPreferenceVisible="false" app:key="player_audio_format" app:title="@string/playerAudioFormat" app:useSimpleSummaryProvider="true" /> + + Date: Wed, 16 Nov 2022 18:55:12 +0100 Subject: [PATCH 07/10] fix crash caused by default resolution --- .../java/com/github/libretube/ui/fragments/PlayerFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 89cd89da5..2d85bfb7e 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 @@ -1166,7 +1166,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } private fun setStreamSource(streams: Streams) { - val defaultResolution = PlayerHelper.getDefaultResolution(requireContext()) + val defaultResolution = PlayerHelper.getDefaultResolution(requireContext()).replace("p", "") if (defaultResolution != "") { val params = trackSelector.buildUponParameters() .setMaxVideoSize(Int.MAX_VALUE, defaultResolution.toInt()) From 2acea5fddc8b5b9cbae7a36b57734a71b4288cc6 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 16 Nov 2022 19:04:47 +0100 Subject: [PATCH 08/10] avoid duplicated resolutions --- .../libretube/ui/fragments/PlayerFragment.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) 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 2d85bfb7e..a9325f397 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 @@ -1087,12 +1087,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { val videoStreams = try { // attempt to sort the qualities, catch if there was an error ih parsing streams.videoStreams?.sortedBy { - it.quality - .toString() - .split("p") - .first() - .replace("p", "") - .toLong() + it.quality?.toLong() ?: 0L }?.reversed() .orEmpty() } catch (_: Exception) { @@ -1102,14 +1097,20 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { for (vid in videoStreams) { // append quality to list if it has the preferred format (e.g. MPEG) val preferredMimeType = "video/${PlayerHelper.videoFormatPreference}" - if (vid.url != null && vid.mimeType == preferredMimeType) { // preferred format + if (vid.url != null && vid.mimeType == preferredMimeType) { + // avoid duplicated resolutions + if (resolutions.any { + it.resolution == vid.quality.toString().split("p").first().toInt() + } + ) continue + resolutions.add( VideoResolution( name = vid.quality!!, resolution = vid.quality.toString().split("p").first().toInt() ) ) - } else if (vid.quality.equals("LBRY") && vid.format.equals("MP4")) { // LBRY MP4 format + } else if (vid.quality.equals("LBRY") && vid.format.equals("MP4")) { resolutions.add( VideoResolution( name = "LBRY MP4", From 4aee2c35a14fee798eeca324e10e6b479a35095d Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 16 Nov 2022 19:11:07 +0100 Subject: [PATCH 09/10] remove the limit HLS pref as HLS now follows the default resolution --- .../github/libretube/constants/PreferenceKeys.kt | 1 - .../libretube/ui/fragments/PlayerFragment.kt | 16 +++------------- app/src/main/res/xml/audio_video_settings.xml | 6 ------ 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt index a46100d37..ac20cc559 100644 --- a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt @@ -78,7 +78,6 @@ object PreferenceKeys { const val PLAYER_RESIZE_MODE = "player_resize_mode" const val SB_SKIP_MANUALLY = "sb_skip_manually_key" const val SB_SHOW_MARKERS = "sb_show_markers" - const val LIMIT_HLS = "limit_hls" const val PROGRESSIVE_LOADING_INTERVAL_SIZE = "progressive_loading_interval" const val ALTERNATIVE_PLAYER_LAYOUT = "alternative_player_layout" const val USE_HLS_OVER_DASH = "use_hls" 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 a9325f397..ef913c0dc 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 @@ -1102,7 +1102,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { if (resolutions.any { it.resolution == vid.quality.toString().split("p").first().toInt() } - ) continue + ) { + continue + } resolutions.add( VideoResolution( @@ -1228,18 +1230,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { ) trackSelector.setParameters(params) - // limit hls to full hd - if ( - PreferenceHelper.getBoolean( - PreferenceKeys.LIMIT_HLS, - false - ) - ) { - val newParams = trackSelector.buildUponParameters() - .setMaxVideoSize(1920, 1080) - trackSelector.setParameters(newParams) - } - exoPlayer = ExoPlayer.Builder(requireContext()) .setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory)) .setLoadControl(loadControl) diff --git a/app/src/main/res/xml/audio_video_settings.xml b/app/src/main/res/xml/audio_video_settings.xml index fdeb60a2c..ee98f808f 100644 --- a/app/src/main/res/xml/audio_video_settings.xml +++ b/app/src/main/res/xml/audio_video_settings.xml @@ -76,12 +76,6 @@ android:title="@string/hls_instead_of_dash" app:key="use_hls" /> - - Date: Thu, 17 Nov 2022 10:57:44 +0100 Subject: [PATCH 10/10] fix some issues caused by the DASH integration --- .../libretube/ui/fragments/PlayerFragment.kt | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) 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 ef913c0dc..15897b143 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 @@ -1079,6 +1079,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { exoPlayer.setMediaItem(mediaItem) } + private fun String?.qualityToInt(): Int { + this ?: return 0 + return this.toString().split("p").first().toInt() + } + private fun getAvailableResolutions(): List { if (!this::streams.isInitialized) return listOf() @@ -1095,12 +1100,27 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } for (vid in videoStreams) { + if (resolutions.any { + it.resolution == vid.quality.qualityToInt() + } || vid.url == null + ) { + continue + } + + resolutions.add( + VideoResolution( + name = "${vid.quality.qualityToInt()}p", + resolution = vid.quality.qualityToInt() + ) + ) + + /* // append quality to list if it has the preferred format (e.g. MPEG) val preferredMimeType = "video/${PlayerHelper.videoFormatPreference}" if (vid.url != null && vid.mimeType == preferredMimeType) { // avoid duplicated resolutions if (resolutions.any { - it.resolution == vid.quality.toString().split("p").first().toInt() + it.resolution == vid.quality.qualityToInt() } ) { continue @@ -1108,19 +1128,12 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { resolutions.add( VideoResolution( - name = vid.quality!!, - resolution = vid.quality.toString().split("p").first().toInt() + name = "${vid.quality.qualityToInt()}p", + resolution = vid.quality.qualityToInt() ) ) - } else if (vid.quality.equals("LBRY") && vid.format.equals("MP4")) { - resolutions.add( - VideoResolution( - name = "LBRY MP4", - adaptiveSourceUrl = vid.url, - resolution = Int.MAX_VALUE - ) - ) - } + } + */ } if (resolutions.isEmpty()) { @@ -1129,7 +1142,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { ) } - resolutions.add(0, VideoResolution(getString(R.string.auto_quality), Int.MAX_VALUE)) + resolutions.add(0, VideoResolution(getString(R.string.auto_quality), null)) return resolutions } @@ -1168,14 +1181,18 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { ) } + private fun setPlayerResolution(resolution: Int?) { + val params = trackSelector.buildUponParameters() + when (resolution) { + null -> params.setMaxVideoSize(Int.MAX_VALUE, Int.MAX_VALUE).setMinVideoSize(0, 0) + else -> params.setMaxVideoSize(Int.MAX_VALUE, resolution).setMinVideoSize(Int.MIN_VALUE, resolution) + } + trackSelector.setParameters(params) + } + private fun setStreamSource(streams: Streams) { val defaultResolution = PlayerHelper.getDefaultResolution(requireContext()).replace("p", "") - if (defaultResolution != "") { - val params = trackSelector.buildUponParameters() - .setMaxVideoSize(Int.MAX_VALUE, defaultResolution.toInt()) - .setMinVideoSize(Int.MAX_VALUE, defaultResolution.toInt()) - trackSelector.setParameters(params) - } + if (defaultResolution != "") setPlayerResolution(defaultResolution.toInt()) if (!PreferenceHelper.getBoolean(PreferenceKeys.USE_HLS_OVER_DASH, false) && streams.videoStreams.orEmpty().isNotEmpty() @@ -1357,12 +1374,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { .setSimpleItems( resolutions.map { it.name } ) { which -> - val resolution = resolutions[which].resolution!! - - val params = trackSelector.buildUponParameters() - .setMaxVideoSize(Int.MAX_VALUE, resolution) - .setMinVideoSize(Int.MAX_VALUE, resolution) - trackSelector.setParameters(params) + setPlayerResolution(resolutions[which].resolution) } .show(childFragmentManager) } @@ -1379,7 +1391,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { BaseBottomSheet() .setSimpleItems(audioLanguages) { index -> val audioStreams = audioGroups.values.elementAt(index) - val lang = audioStreams.first().audioTrackId!!.substring(0, 2) + val lang = audioStreams.firstOrNull()?.audioTrackId?.substring(0, 2) val newParams = trackSelector.buildUponParameters() .setPreferredAudioLanguage(lang) trackSelector.setParameters(newParams)