Merge pull request #3878 from Bnyro/master

Use DASH in the audio only/background player
This commit is contained in:
Bnyro 2023-06-01 18:30:24 +02:00 committed by GitHub
commit bb434dbc42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 84 deletions

View File

@ -24,8 +24,8 @@ object DashHelper {
val formats: MutableList<PipedStream> = mutableListOf(), val formats: MutableList<PipedStream> = mutableListOf(),
) )
fun createManifest(streams: Streams, supportsHdr: Boolean): String { fun createManifest(streams: Streams, supportsHdr: Boolean, audioOnly: Boolean = false): String {
val builder: DocumentBuilder = builderFactory.newDocumentBuilder() val builder = builderFactory.newDocumentBuilder()
val doc = builder.newDocument() val doc = builder.newDocument()
val mpd = doc.createElement("MPD") val mpd = doc.createElement("MPD")
@ -39,41 +39,43 @@ object DashHelper {
val adapSetInfos = ArrayList<AdapSetInfo>() val adapSetInfos = ArrayList<AdapSetInfo>()
val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs if (!audioOnly) {
for ( val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs
stream in streams.videoStreams for (
// used to avoid including LBRY HLS inside the streams in the manifest stream in streams.videoStreams
.filter { !it.format.orEmpty().contains("HLS") } // used to avoid including LBRY HLS inside the streams in the manifest
// filter the codecs according to the user's preferences .filter { !it.format.orEmpty().contains("HLS") }
.filter { // filter the codecs according to the user's preferences
enabledVideoCodecs == "all" || it.codec.orEmpty().lowercase().startsWith( .filter {
enabledVideoCodecs, 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) { for (stream in streams.audioStreams) {

View File

@ -5,6 +5,8 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.net.Uri
import android.util.Base64
import android.view.accessibility.CaptioningManager import android.view.accessibility.CaptioningManager
import android.widget.Toast import android.widget.Toast
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -12,6 +14,7 @@ import androidx.core.app.PendingIntentCompat
import androidx.core.app.RemoteActionCompat import androidx.core.app.RemoteActionCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.core.net.toUri
import androidx.media3.common.AudioAttributes import androidx.media3.common.AudioAttributes
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.PlaybackParameters import androidx.media3.common.PlaybackParameters
@ -22,6 +25,7 @@ import androidx.media3.ui.CaptionStyleCompat
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.PipedStream import com.github.libretube.api.obj.PipedStream
import com.github.libretube.api.obj.Segment import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.Streams
import com.github.libretube.constants.PreferenceKeys import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.enums.AudioQuality import com.github.libretube.enums.AudioQuality
import com.github.libretube.enums.PlayerEvent import com.github.libretube.enums.PlayerEvent
@ -33,48 +37,23 @@ object PlayerHelper {
const val CONTROL_TYPE = "control_type" 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 { fun createDashSource(streams: Streams, context: Context, audioOnly: Boolean = false): Uri {
val audioFormat = PreferenceHelper.getString(PreferenceKeys.PLAYER_AUDIO_FORMAT, "all") val manifest = DashHelper.createManifest(
val audioPrefKey = if (NetworkHelper.isNetworkMetered(context)) { streams,
PreferenceKeys.PLAYER_AUDIO_QUALITY_MOBILE DisplayHelper.supportsHdr(context),
} else { audioOnly
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,
) )
// 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 * Get the system's default captions style
* @param audios list of the audio streams
* @param quality Whether to use the best or worst quality available
* @return Url of the audio source
*/ */
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) @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
fun getCaptionStyle(context: Context): CaptionStyleCompat { fun getCaptionStyle(context: Context): CaptionStyleCompat {
val captioningManager = context.getSystemService<CaptioningManager>()!! val captioningManager = context.getSystemService<CaptioningManager>()!!

View File

@ -10,9 +10,11 @@ import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.core.net.toUri
import androidx.lifecycle.LifecycleService import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
@ -299,14 +301,15 @@ class OnlinePlayerService : LifecycleService() {
private fun setMediaItem() { private fun setMediaItem() {
val streams = streams ?: return val streams = streams ?: return
val uri = if (streams.audioStreams.isNotEmpty()) { val (uri, mimeType) = if (streams.audioStreams.isNotEmpty()) {
PlayerHelper.getAudioSource(this, streams.audioStreams) PlayerHelper.createDashSource(streams, this, true) to MimeTypes.APPLICATION_MPD
} else { } else {
streams.hls ?: return ProxyHelper.rewriteUrl(streams.hls)?.toUri() to MimeTypes.APPLICATION_M3U8
} }
val mediaItem = MediaItem.Builder() val mediaItem = MediaItem.Builder()
.setUri(ProxyHelper.rewriteUrl(uri)) .setUri(uri)
.setMimeType(mimeType)
.setMetadata(streams) .setMetadata(streams)
.build() .build()
player?.setMediaItem(mediaItem) player?.setMediaItem(mediaItem)

View File

@ -1329,15 +1329,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
val uri = streams.dash?.let { ProxyHelper.unwrapIfEnabled(it) }?.toUri().takeIf { val uri = streams.dash?.let { ProxyHelper.unwrapIfEnabled(it) }?.toUri().takeIf {
streams.livestream || streams.videoStreams.isEmpty() streams.livestream || streams.videoStreams.isEmpty()
} ?: let { } ?: let {
val manifest = DashHelper.createManifest( PlayerHelper.createDashSource(streams, requireContext())
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()
} }
this.setMediaSource(uri, MimeTypes.APPLICATION_MPD) this.setMediaSource(uri, MimeTypes.APPLICATION_MPD)

View File

@ -13,6 +13,7 @@
app:title="@string/defres" app:title="@string/defres"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<!--
<ListPreference <ListPreference
android:icon="@drawable/ic_headphones" android:icon="@drawable/ic_headphones"
app:defaultValue="best" app:defaultValue="best"
@ -21,6 +22,7 @@
app:key="player_audio_quality" app:key="player_audio_quality"
app:title="@string/playerAudioQuality" app:title="@string/playerAudioQuality"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
-->
</PreferenceCategory> </PreferenceCategory>
@ -35,6 +37,7 @@
app:title="@string/defres" app:title="@string/defres"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<!--
<ListPreference <ListPreference
android:icon="@drawable/ic_headphones" android:icon="@drawable/ic_headphones"
app:defaultValue="best" app:defaultValue="best"
@ -43,6 +46,7 @@
app:key="player_audio_quality_mobile" app:key="player_audio_quality_mobile"
app:title="@string/playerAudioQuality" app:title="@string/playerAudioQuality"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
-->
</PreferenceCategory> </PreferenceCategory>