merge StreamVariant and stream part 2

This commit is contained in:
Christian Schabesberger 2024-09-16 12:33:06 +02:00
parent ac97d4ee1f
commit 1b4e1d4f13
2 changed files with 88 additions and 93 deletions

View File

@ -2,18 +2,14 @@ package net.newpipe.newplayer.utils
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.HttpDataSource import androidx.media3.datasource.HttpDataSource
import androidx.media3.exoplayer.dash.DashMediaSource import androidx.media3.exoplayer.dash.DashMediaSource
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.exoplayer.source.MergingMediaSource
import androidx.media3.exoplayer.source.ProgressiveMediaSource import androidx.media3.exoplayer.source.ProgressiveMediaSource
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import net.newpipe.newplayer.MediaRepository import net.newpipe.newplayer.MediaRepository
import net.newpipe.newplayer.StreamType import net.newpipe.newplayer.StreamType
import net.newpipe.newplayer.StreamVariant import net.newpipe.newplayer.Stream
import kotlin.random.Random import kotlin.random.Random
class MediaSourceBuilder( class MediaSourceBuilder(
@ -23,31 +19,30 @@ class MediaSourceBuilder(
private val httpDataSourceFactory: HttpDataSource.Factory private val httpDataSourceFactory: HttpDataSource.Factory
) { ) {
suspend fun buildMediaSource(item: String) { suspend fun buildMediaSource(item: String) {
val availableStreamVariants = repository.getAvailableStreamVariants(item) val availableStreams = repository.getStreams(item)
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private suspend private
fun toMediaItem(item: String, streamVariant: StreamVariant): MediaItem { fun toMediaItem(item: String, stream: Stream): MediaItem {
val dataStream = repository.getStream(item, streamVariant)
val uniqueId = Random.nextLong() val uniqueId = Random.nextLong()
uniqueIdToIdLookup[uniqueId] = item uniqueIdToIdLookup[uniqueId] = item
val mediaItemBuilder = MediaItem.Builder() val mediaItemBuilder = MediaItem.Builder()
.setMediaId(uniqueId.toString()) .setMediaId(uniqueId.toString())
.setUri(dataStream.streamUri) .setUri(stream.streamUri)
if (dataStream.mimeType != null) { if (stream.mimeType != null) {
mediaItemBuilder.setMimeType(dataStream.mimeType) mediaItemBuilder.setMimeType(stream.mimeType)
} }
return mediaItemBuilder.build() return mediaItemBuilder.build()
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private fun toMediaSource(mediaItem: MediaItem, streamVariant: StreamVariant) = private fun toMediaSource(mediaItem: MediaItem, stream: Stream) =
if (streamVariant.streamType == StreamType.DYNAMIC) if (stream.streamType == StreamType.DYNAMIC)
DashMediaSource.Factory(httpDataSourceFactory) DashMediaSource.Factory(httpDataSourceFactory)
.createMediaSource(mediaItem) .createMediaSource(mediaItem)
else else

View File

@ -4,29 +4,29 @@ package net.newpipe.newplayer.utils
import net.newpipe.newplayer.NewPlayerException import net.newpipe.newplayer.NewPlayerException
import net.newpipe.newplayer.PlayMode import net.newpipe.newplayer.PlayMode
import net.newpipe.newplayer.StreamType import net.newpipe.newplayer.StreamType
import net.newpipe.newplayer.StreamVariant import net.newpipe.newplayer.Stream
object StreamSelect { object StreamSelect {
interface StreamSelection interface StreamSelection
data class SingleSelection( data class SingleSelection(
val streamVariant: StreamVariant val stream: Stream
) : StreamSelection ) : StreamSelection
data class MultiSelection( data class MultiSelection(
val videoStream: StreamVariant, val videoStream: Stream,
val audioStream: StreamVariant val audioStream: Stream
) : StreamSelection ) : StreamSelection
private fun getBestLanguageFit( private fun getBestLanguageFit(
availableStreamVariants: List<StreamVariant>, availableStreams: List<Stream>,
preferredLanguages: List<String> preferredLanguages: List<String>
): String? { ): String? {
for (preferredLanguage in preferredLanguages) { for (preferredLanguage in preferredLanguages) {
for (availableVariant in availableStreamVariants) { for (available in availableStreams) {
if (availableVariant.language == preferredLanguage) { if (available.language == preferredLanguage) {
return preferredLanguage return preferredLanguage
} }
} }
@ -34,21 +34,21 @@ object StreamSelect {
return null return null
} }
private fun filterVariantsByLanguage( private fun filtersByLanguage(
availableStreamVariants: List<StreamVariant>, availableStreams: List<Stream>,
language: String language: String
) = ) =
availableStreamVariants.filter { it.language == language } availableStreams.filter { it.language == language }
private fun getBestFittingVideoIdentifier( private fun getBestFittingVideoIdentifier(
availableStreamVariants: List<StreamVariant>, availableStreams: List<Stream>,
preferredVideoIdentifier: List<String> preferredVideoIdentifier: List<String>
): String? { ): String? {
for (preferredStream in preferredVideoIdentifier) { for (preferredStream in preferredVideoIdentifier) {
for (availableVariant in availableStreamVariants) { for (available in availableStreams) {
if ((availableVariant.streamType == StreamType.AUDIO_AND_VIDEO || if ((available.streamType == StreamType.AUDIO_AND_VIDEO ||
availableVariant.streamType == StreamType.VIDEO) available.streamType == StreamType.VIDEO)
&& preferredStream == availableVariant.streamVariantIdentifier && preferredStream == available.identifier
) { ) {
return preferredStream return preferredStream
} }
@ -57,25 +57,25 @@ object StreamSelect {
return null return null
} }
private fun getFirstVariantMatchingIdentifier( private fun getFirstMatchingIdentifier(
availableStreamVariants: List<StreamVariant>, availableStreams: List<Stream>,
identifier: String identifier: String
): StreamVariant? { ): Stream? {
for (variant in availableStreamVariants) { for (variant in availableStreams) {
if (variant.streamVariantIdentifier == identifier) if (variant.identifier == identifier)
return variant return variant
} }
return null return null
} }
private fun getBestFittingAudioVariant( private fun getBestFittingAudio(
availableStreamVariants: List<StreamVariant>, availableStreams: List<Stream>,
preferredAudioIdentifier: List<String> preferredAudioIdentifier: List<String>
): StreamVariant? { ): Stream? {
for (preferredStream in preferredAudioIdentifier) { for (preferredStream in preferredAudioIdentifier) {
for (availableStream in availableStreamVariants) { for (availableStream in availableStreams) {
if (availableStream.streamType == StreamType.AUDIO if (availableStream.streamType == StreamType.AUDIO
&& preferredStream == availableStream.streamVariantIdentifier && preferredStream == availableStream.identifier
) { ) {
return availableStream return availableStream
} }
@ -84,21 +84,21 @@ object StreamSelect {
return null return null
} }
private fun getVideoOnlyVariantWithMatchingIdentifier( private fun getVideoOnlyWithMatchingIdentifier(
availableStreamVariants: List<StreamVariant>, availableStreams: List<Stream>,
identifier: String identifier: String
): StreamVariant? { ): Stream? {
for (variant in availableStreamVariants) { for (variant in availableStreams) {
if (variant.streamType == StreamType.VIDEO if (variant.streamType == StreamType.VIDEO
&& variant.streamVariantIdentifier == identifier && variant.identifier == identifier
) )
return variant return variant
} }
return null return null
} }
private fun getDynamicStream(availableStreamVariants: List<StreamVariant>): StreamVariant? { private fun getDynamicStream(availableStreams: List<Stream>): Stream? {
for (variant in availableStreamVariants) { for (variant in availableStreams) {
if (variant.streamType == StreamType.DYNAMIC) { if (variant.streamType == StreamType.DYNAMIC) {
return variant return variant
} }
@ -106,16 +106,16 @@ object StreamSelect {
return null return null
} }
private fun getNonDynamicVideoVariants(availableStreamVariants: List<StreamVariant>) = private fun getNonDynamicVideos(availableStreams: List<Stream>) =
availableStreamVariants.filter { availableStreams.filter {
it.streamType == StreamType.VIDEO || it.streamType == StreamType.AUDIO_AND_VIDEO it.streamType == StreamType.VIDEO || it.streamType == StreamType.AUDIO_AND_VIDEO
} }
private fun getNonDynamicAudioVariants(availableStreamVariants: List<StreamVariant>) = private fun getNonDynamicAudios(availableStreams: List<Stream>) =
availableStreamVariants.filter { it.streamType == StreamType.AUDIO } availableStreams.filter { it.streamType == StreamType.AUDIO }
private fun hasVideoStreamVariants(availableStreamVariants: List<StreamVariant>): Boolean { private fun hasVideoStreams(availableStreams: List<Stream>): Boolean {
for (variant in availableStreamVariants) { for (variant in availableStreams) {
if (variant.streamType == StreamType.AUDIO_AND_VIDEO || variant.streamType == StreamType.VIDEO || variant.streamType == StreamType.DYNAMIC) if (variant.streamType == StreamType.AUDIO_AND_VIDEO || variant.streamType == StreamType.VIDEO || variant.streamType == StreamType.DYNAMIC)
return true return true
} }
@ -125,7 +125,7 @@ object StreamSelect {
fun selectStream( fun selectStream(
item: String, item: String,
playMode: PlayMode, playMode: PlayMode,
availableStreamVariants: List<StreamVariant>, availableStreams: List<Stream>,
preferredVideoIdentifier: List<String>, preferredVideoIdentifier: List<String>,
preferredAudioIdentifier: List<String>, preferredAudioIdentifier: List<String>,
preferredLanguage: List<String> preferredLanguage: List<String>
@ -133,10 +133,10 @@ object StreamSelect {
// filter for best fitting language stream variants // filter for best fitting language stream variants
val bestFittingLanguage = getBestLanguageFit(availableStreamVariants, preferredLanguage) val bestFittingLanguage = getBestLanguageFit(availableStreams, preferredLanguage)
val availableVariantsInPreferredLanguage = val availablesInPreferredLanguage =
if (bestFittingLanguage != null) filterVariantsByLanguage( if (bestFittingLanguage != null) filtersByLanguage(
availableStreamVariants, availableStreams,
bestFittingLanguage bestFittingLanguage
) )
else { else {
@ -145,12 +145,12 @@ object StreamSelect {
// is it a video stream or a pure audio stream? // is it a video stream or a pure audio stream?
if (hasVideoStreamVariants(availableStreamVariants)) { if (hasVideoStreams(availableStreams)) {
// first: try and get a dynamic stream variant // first: try and get a dynamic stream variant
getDynamicStream(availableVariantsInPreferredLanguage) getDynamicStream(availablesInPreferredLanguage)
?: getDynamicStream( ?: getDynamicStream(
availableStreamVariants availableStreams
)?.let { )?.let {
return SingleSelection(it) return SingleSelection(it)
} }
@ -159,35 +159,35 @@ object StreamSelect {
val bestVideoIdentifier = val bestVideoIdentifier =
getBestFittingVideoIdentifier( getBestFittingVideoIdentifier(
availableVariantsInPreferredLanguage, availablesInPreferredLanguage,
preferredVideoIdentifier preferredVideoIdentifier
)?.let { )?.let {
val videoVariants = val videos =
getNonDynamicVideoVariants(availableVariantsInPreferredLanguage) getNonDynamicVideos(availablesInPreferredLanguage)
videoVariants[videoVariants.size / 2].streamVariantIdentifier videos[videos.size / 2].identifier
} ?: getBestFittingVideoIdentifier( } ?: getBestFittingVideoIdentifier(
availableStreamVariants, availableStreams,
preferredVideoIdentifier preferredVideoIdentifier
) )
?: run { ?: run {
val videoVariants = getNonDynamicVideoVariants(availableStreamVariants) val videos = getNonDynamicVideos(availableStreams)
videoVariants[videoVariants.size / 2].streamVariantIdentifier videos[videos.size / 2].identifier
} }
val videoOnlyStream = val videoOnlyStream =
getVideoOnlyVariantWithMatchingIdentifier( getVideoOnlyWithMatchingIdentifier(
availableVariantsInPreferredLanguage, availablesInPreferredLanguage,
bestVideoIdentifier bestVideoIdentifier
) ?: getVideoOnlyVariantWithMatchingIdentifier( ) ?: getVideoOnlyWithMatchingIdentifier(
availableStreamVariants, availableStreams,
bestVideoIdentifier bestVideoIdentifier
) )
if (videoOnlyStream != null) { if (videoOnlyStream != null) {
getBestFittingAudioVariant( getBestFittingAudio(
availableVariantsInPreferredLanguage, availablesInPreferredLanguage,
preferredAudioIdentifier preferredAudioIdentifier
) ?: getBestFittingAudioVariant(availableStreamVariants, preferredAudioIdentifier) ) ?: getBestFittingAudio(availableStreams, preferredAudioIdentifier)
?.let { ?.let {
return MultiSelection(videoOnlyStream, it) return MultiSelection(videoOnlyStream, it)
} }
@ -195,12 +195,12 @@ object StreamSelect {
// fourth: try to get a video and audio stream variant with the best fitting identifier // fourth: try to get a video and audio stream variant with the best fitting identifier
getFirstVariantMatchingIdentifier( getFirstMatchingIdentifier(
availableVariantsInPreferredLanguage, availablesInPreferredLanguage,
bestVideoIdentifier bestVideoIdentifier
) )
?: getFirstVariantMatchingIdentifier( ?: getFirstMatchingIdentifier(
availableStreamVariants, availableStreams,
bestVideoIdentifier bestVideoIdentifier
)?.let { )?.let {
return SingleSelection(it) return SingleSelection(it)
@ -209,28 +209,28 @@ object StreamSelect {
// fifth: try and get the median video and audio stream variant // fifth: try and get the median video and audio stream variant
return SingleSelection(run { return SingleSelection(run {
val videoVariants = val videos =
getNonDynamicVideoVariants(availableVariantsInPreferredLanguage).ifEmpty { getNonDynamicVideos(availablesInPreferredLanguage).ifEmpty {
getNonDynamicVideoVariants(availableStreamVariants) getNonDynamicVideos(availableStreams)
} }
if (videoVariants.isNotEmpty()) { if (videos.isNotEmpty()) {
return@run videoVariants[videoVariants.size / 2] return@run videos[videos.size / 2]
} else { } else {
throw NewPlayerException("No fitting video stream could be found for stream item item: ${item}") throw NewPlayerException("No fitting video stream could be found for stream item item: ${item}")
} }
}) })
} else { /* if(hasVideoStreamVariants(availableStreamVariants)) */ } else { /* if(hasVideoStreams(availableStreams)) */
// first: try to get an audio stream variant with the best fitting identifier // first: try to get an audio stream variant with the best fitting identifier
getBestFittingAudioVariant( getBestFittingAudio(
availableVariantsInPreferredLanguage, availablesInPreferredLanguage,
preferredAudioIdentifier preferredAudioIdentifier
) )
?: getBestFittingAudioVariant( ?: getBestFittingAudio(
availableStreamVariants, availableStreams,
preferredAudioIdentifier preferredAudioIdentifier
)?.let { )?.let {
return SingleSelection(it) return SingleSelection(it)
@ -239,16 +239,16 @@ object StreamSelect {
// second: try and get the median audio stream variant // second: try and get the median audio stream variant
return SingleSelection(run { return SingleSelection(run {
val audioVariants = val audios =
getNonDynamicAudioVariants(availableVariantsInPreferredLanguage).let { getNonDynamicAudios(availablesInPreferredLanguage).let {
if (it.isNotEmpty()) { if (it.isNotEmpty()) {
it it
} else { } else {
getNonDynamicAudioVariants(availableStreamVariants) getNonDynamicAudios(availableStreams)
} }
} }
if (audioVariants.isNotEmpty()) { if (audios.isNotEmpty()) {
return@run audioVariants[audioVariants.size / 2] return@run audios[audios.size / 2]
} else { } else {
throw NewPlayerException("No fitting audio stream could be found for stream item item: ${item}") throw NewPlayerException("No fitting audio stream could be found for stream item item: ${item}")
} }