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(),
)
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) {

View File

@ -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>()!!

View File

@ -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)

View File

@ -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)

View File

@ -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>