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(),
|
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) {
|
||||||
|
@ -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>()!!
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user