mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 14:20:30 +05:30
Merge pull request #3878 from Bnyro/master
Use DASH in the audio only/background player
This commit is contained in:
commit
bb434dbc42
@ -24,8 +24,8 @@ object DashHelper {
|
||||
val formats: MutableList<PipedStream> = mutableListOf(),
|
||||
)
|
||||
|
||||
fun createManifest(streams: Streams, supportsHdr: Boolean): String {
|
||||
val builder: DocumentBuilder = builderFactory.newDocumentBuilder()
|
||||
fun createManifest(streams: Streams, supportsHdr: Boolean, audioOnly: Boolean = false): String {
|
||||
val builder = builderFactory.newDocumentBuilder()
|
||||
|
||||
val doc = builder.newDocument()
|
||||
val mpd = doc.createElement("MPD")
|
||||
@ -39,41 +39,43 @@ object DashHelper {
|
||||
|
||||
val adapSetInfos = ArrayList<AdapSetInfo>()
|
||||
|
||||
val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs
|
||||
for (
|
||||
stream in streams.videoStreams
|
||||
// used to avoid including LBRY HLS inside the streams in the manifest
|
||||
.filter { !it.format.orEmpty().contains("HLS") }
|
||||
// filter the codecs according to the user's preferences
|
||||
.filter {
|
||||
enabledVideoCodecs == "all" || it.codec.orEmpty().lowercase().startsWith(
|
||||
enabledVideoCodecs,
|
||||
if (!audioOnly) {
|
||||
val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs
|
||||
for (
|
||||
stream in streams.videoStreams
|
||||
// used to avoid including LBRY HLS inside the streams in the manifest
|
||||
.filter { !it.format.orEmpty().contains("HLS") }
|
||||
// filter the codecs according to the user's preferences
|
||||
.filter {
|
||||
enabledVideoCodecs == "all" || it.codec.orEmpty().lowercase().startsWith(
|
||||
enabledVideoCodecs,
|
||||
)
|
||||
}
|
||||
.filter { supportsHdr || !it.quality.orEmpty().uppercase().contains("HDR") }
|
||||
) {
|
||||
// ignore dual format streams
|
||||
if (!stream.videoOnly!!) {
|
||||
continue
|
||||
}
|
||||
|
||||
// ignore streams which might be OTF
|
||||
if (stream.indexEnd!! <= 0) {
|
||||
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),
|
||||
),
|
||||
)
|
||||
}
|
||||
.filter { supportsHdr || !it.quality.orEmpty().uppercase().contains("HDR") }
|
||||
) {
|
||||
// ignore dual format streams
|
||||
if (!stream.videoOnly!!) {
|
||||
continue
|
||||
}
|
||||
|
||||
// ignore streams which might be OTF
|
||||
if (stream.indexEnd!! <= 0) {
|
||||
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) {
|
||||
|
@ -5,6 +5,8 @@ import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import android.view.accessibility.CaptioningManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
@ -12,6 +14,7 @@ import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.RemoteActionCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.PlaybackParameters
|
||||
@ -22,6 +25,7 @@ import androidx.media3.ui.CaptionStyleCompat
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.api.obj.PipedStream
|
||||
import com.github.libretube.api.obj.Segment
|
||||
import com.github.libretube.api.obj.Streams
|
||||
import com.github.libretube.constants.PreferenceKeys
|
||||
import com.github.libretube.enums.AudioQuality
|
||||
import com.github.libretube.enums.PlayerEvent
|
||||
@ -33,48 +37,23 @@ object PlayerHelper {
|
||||
const val CONTROL_TYPE = "control_type"
|
||||
|
||||
/**
|
||||
* Get the audio source following the users preferences
|
||||
* Create a base64 encoded DASH stream manifest
|
||||
*/
|
||||
fun getAudioSource(context: Context, audios: List<PipedStream>): String {
|
||||
val audioFormat = PreferenceHelper.getString(PreferenceKeys.PLAYER_AUDIO_FORMAT, "all")
|
||||
val audioPrefKey = if (NetworkHelper.isNetworkMetered(context)) {
|
||||
PreferenceKeys.PLAYER_AUDIO_QUALITY_MOBILE
|
||||
} else {
|
||||
PreferenceKeys.PLAYER_AUDIO_QUALITY
|
||||
}
|
||||
|
||||
val audioQuality = PreferenceHelper.getString(audioPrefKey, "best")
|
||||
|
||||
val filteredAudios = audios.filter {
|
||||
val audioMimeType = "audio/$audioFormat"
|
||||
it.mimeType != audioMimeType || audioFormat == "all"
|
||||
}
|
||||
|
||||
return getBitRate(
|
||||
filteredAudios,
|
||||
if (audioQuality == "best") AudioQuality.BEST else AudioQuality.WORST,
|
||||
fun createDashSource(streams: Streams, context: Context, audioOnly: Boolean = false): Uri {
|
||||
val manifest = DashHelper.createManifest(
|
||||
streams,
|
||||
DisplayHelper.supportsHdr(context),
|
||||
audioOnly
|
||||
)
|
||||
|
||||
// encode to base64
|
||||
val encoded = Base64.encodeToString(manifest.toByteArray(), Base64.DEFAULT)
|
||||
return "data:application/dash+xml;charset=utf-8;base64,$encoded".toUri()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best or worst bitrate from a list of audio streams
|
||||
* @param audios list of the audio streams
|
||||
* @param quality Whether to use the best or worst quality available
|
||||
* @return Url of the audio source
|
||||
* Get the system's default captions style
|
||||
*/
|
||||
private fun getBitRate(audios: List<PipedStream>, quality: AudioQuality): String {
|
||||
val filteredAudios = audios.filter {
|
||||
it.bitrate != null
|
||||
}.sortedBy {
|
||||
it.bitrate
|
||||
}
|
||||
return when (quality) {
|
||||
AudioQuality.BEST -> filteredAudios.last()
|
||||
AudioQuality.WORST -> filteredAudios.first()
|
||||
}.url!!
|
||||
}
|
||||
|
||||
// get the system default caption style
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun getCaptionStyle(context: Context): CaptionStyleCompat {
|
||||
val captioningManager = context.getSystemService<CaptioningManager>()!!
|
||||
|
@ -10,9 +10,11 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
@ -299,14 +301,15 @@ class OnlinePlayerService : LifecycleService() {
|
||||
private fun setMediaItem() {
|
||||
val streams = streams ?: return
|
||||
|
||||
val uri = if (streams.audioStreams.isNotEmpty()) {
|
||||
PlayerHelper.getAudioSource(this, streams.audioStreams)
|
||||
val (uri, mimeType) = if (streams.audioStreams.isNotEmpty()) {
|
||||
PlayerHelper.createDashSource(streams, this, true) to MimeTypes.APPLICATION_MPD
|
||||
} else {
|
||||
streams.hls ?: return
|
||||
ProxyHelper.rewriteUrl(streams.hls)?.toUri() to MimeTypes.APPLICATION_M3U8
|
||||
}
|
||||
|
||||
val mediaItem = MediaItem.Builder()
|
||||
.setUri(ProxyHelper.rewriteUrl(uri))
|
||||
.setUri(uri)
|
||||
.setMimeType(mimeType)
|
||||
.setMetadata(streams)
|
||||
.build()
|
||||
player?.setMediaItem(mediaItem)
|
||||
|
@ -1329,15 +1329,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
val uri = streams.dash?.let { ProxyHelper.unwrapIfEnabled(it) }?.toUri().takeIf {
|
||||
streams.livestream || streams.videoStreams.isEmpty()
|
||||
} ?: let {
|
||||
val manifest = DashHelper.createManifest(
|
||||
streams,
|
||||
DisplayHelper.supportsHdr(requireContext()),
|
||||
)
|
||||
|
||||
// encode to base64
|
||||
val encoded = Base64.encodeToString(manifest.toByteArray(), Base64.DEFAULT)
|
||||
|
||||
"data:application/dash+xml;charset=utf-8;base64,$encoded".toUri()
|
||||
PlayerHelper.createDashSource(streams, requireContext())
|
||||
}
|
||||
|
||||
this.setMediaSource(uri, MimeTypes.APPLICATION_MPD)
|
||||
|
@ -13,6 +13,7 @@
|
||||
app:title="@string/defres"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<!--
|
||||
<ListPreference
|
||||
android:icon="@drawable/ic_headphones"
|
||||
app:defaultValue="best"
|
||||
@ -21,6 +22,7 @@
|
||||
app:key="player_audio_quality"
|
||||
app:title="@string/playerAudioQuality"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
-->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
@ -35,6 +37,7 @@
|
||||
app:title="@string/defres"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<!--
|
||||
<ListPreference
|
||||
android:icon="@drawable/ic_headphones"
|
||||
app:defaultValue="best"
|
||||
@ -43,6 +46,7 @@
|
||||
app:key="player_audio_quality_mobile"
|
||||
app:title="@string/playerAudioQuality"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
-->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user