make play media utilize stream selection and MediaSourceBuilder
This commit is contained in:
parent
1b4e1d4f13
commit
20c9bd67e7
|
@ -78,7 +78,6 @@ interface NewPlayer {
|
||||||
fun removePlaylistItem(uniqueId: Long)
|
fun removePlaylistItem(uniqueId: Long)
|
||||||
fun playStream(item: String, playMode: PlayMode)
|
fun playStream(item: String, playMode: PlayMode)
|
||||||
fun selectChapter(index: Int)
|
fun selectChapter(index: Int)
|
||||||
fun playStream(item: String, streamVariant: StreamVariant, playMode: PlayMode)
|
|
||||||
fun release()
|
fun release()
|
||||||
fun getItemLinkOfMediaItem(mediaItem: MediaItem) : String
|
fun getItemLinkOfMediaItem(mediaItem: MediaItem) : String
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.newpipe.newplayer.service.NewPlayerService
|
import net.newpipe.newplayer.service.NewPlayerService
|
||||||
|
import net.newpipe.newplayer.utils.MediaSourceBuilder
|
||||||
import net.newpipe.newplayer.utils.StreamSelect
|
import net.newpipe.newplayer.utils.StreamSelect
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@ -268,17 +269,6 @@ class NewPlayerImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun playStream(
|
|
||||||
item: String,
|
|
||||||
streamVariant: StreamVariant,
|
|
||||||
playMode: PlayMode
|
|
||||||
) {
|
|
||||||
launchJobAndCollectError {
|
|
||||||
val stream = toMediaSource(item, streamVariant)
|
|
||||||
internalPlayStream(stream, playMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
override fun selectChapter(index: Int) {
|
override fun selectChapter(index: Int) {
|
||||||
val chapters = currentChapters.value
|
val chapters = currentChapters.value
|
||||||
|
@ -317,64 +307,21 @@ class NewPlayerImpl(
|
||||||
this.exoPlayer.value?.play()
|
this.exoPlayer.value?.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
|
||||||
private suspend
|
|
||||||
fun toMediaSource(item: String, streamVariant: StreamVariant): MediaSource {
|
|
||||||
val dataStream = repository.getStream(item, streamVariant)
|
|
||||||
|
|
||||||
val uniqueId = Random.nextLong()
|
|
||||||
uniqueIdToIdLookup[uniqueId] = item
|
|
||||||
val mediaItemBuilder = MediaItem.Builder()
|
|
||||||
.setMediaId(uniqueId.toString())
|
|
||||||
.setUri(dataStream.streamUri)
|
|
||||||
|
|
||||||
if (dataStream.mimeType != null) {
|
|
||||||
mediaItemBuilder.setMimeType(dataStream.mimeType)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val metadata = repository.getMetaInfo(item)
|
|
||||||
mediaItemBuilder.setMediaMetadata(metadata)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
mutableErrorFlow.emit(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
val mediaItem = mediaItemBuilder.build()
|
|
||||||
|
|
||||||
return ProgressiveMediaSource.Factory(httpDataSourceFactory)
|
|
||||||
.createMediaSource(mediaItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
private suspend
|
private suspend
|
||||||
fun toMediaSource(item: String, playMode: PlayMode): MediaSource {
|
fun toMediaSource(item: String, playMode: PlayMode): MediaSource {
|
||||||
val availableStreamVariants = repository.getAvailableStreamVariants(item)
|
val builder = MediaSourceBuilder(
|
||||||
val selectedStreamVariant = StreamSelect.selectStream(
|
repository = repository,
|
||||||
item,
|
uniqueIdToIdLookup = uniqueIdToIdLookup,
|
||||||
playMode,
|
preferredLanguage = preferredStreamLanguage,
|
||||||
availableStreamVariants,
|
preferredAudioId = preferredAudioVariants,
|
||||||
preferredVideoVariants,
|
preferredVideoId = preferredVideoVariants,
|
||||||
preferredAudioVariants,
|
playMode = playMode,
|
||||||
preferredStreamLanguage
|
mutableErrorFlow = mutableErrorFlow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return builder.buildMediaSource(item)
|
||||||
return when (selectedStreamVariant) {
|
|
||||||
is StreamSelect.SingleSelection -> toMediaSource(
|
|
||||||
item,
|
|
||||||
selectedStreamVariant.streamVariant
|
|
||||||
)
|
|
||||||
|
|
||||||
is StreamSelect.MultiSelection -> MergingMediaSource(
|
|
||||||
toMediaSource(
|
|
||||||
item,
|
|
||||||
selectedStreamVariant.videoStream
|
|
||||||
), toMediaSource(item, selectedStreamVariant.audioStream)
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> throw NewPlayerException("Unknown Stream selection type: Serious Programming error. :${selectedStreamVariant.javaClass}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchJobAndCollectError(task: suspend () -> Unit) =
|
private fun launchJobAndCollectError(task: suspend () -> Unit) =
|
||||||
|
|
|
@ -3,11 +3,16 @@ 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.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.datasource.DefaultHttpDataSource
|
||||||
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.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.NewPlayerException
|
||||||
|
import net.newpipe.newplayer.PlayMode
|
||||||
import net.newpipe.newplayer.StreamType
|
import net.newpipe.newplayer.StreamType
|
||||||
import net.newpipe.newplayer.Stream
|
import net.newpipe.newplayer.Stream
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
@ -15,12 +20,47 @@ import kotlin.random.Random
|
||||||
class MediaSourceBuilder(
|
class MediaSourceBuilder(
|
||||||
private val repository: MediaRepository,
|
private val repository: MediaRepository,
|
||||||
private val uniqueIdToIdLookup: HashMap<Long, String>,
|
private val uniqueIdToIdLookup: HashMap<Long, String>,
|
||||||
|
private val preferredLanguage: List<String>,
|
||||||
|
private val preferredAudioId: List<String>,
|
||||||
|
private val preferredVideoId: List<String>,
|
||||||
|
private val playMode: PlayMode,
|
||||||
private val mutableErrorFlow: MutableSharedFlow<Exception>,
|
private val mutableErrorFlow: MutableSharedFlow<Exception>,
|
||||||
private val httpDataSourceFactory: HttpDataSource.Factory
|
private val httpDataSourceFactory: HttpDataSource.Factory = DefaultHttpDataSource.Factory(),
|
||||||
) {
|
) {
|
||||||
suspend fun buildMediaSource(item: String) {
|
@OptIn(UnstableApi::class)
|
||||||
|
suspend fun buildMediaSource(item: String): MediaSource {
|
||||||
val availableStreams = repository.getStreams(item)
|
val availableStreams = repository.getStreams(item)
|
||||||
|
val selectedStream = StreamSelect.selectStream(
|
||||||
|
item,
|
||||||
|
playMode,
|
||||||
|
availableStreams,
|
||||||
|
preferredAudioId,
|
||||||
|
preferredAudioId,
|
||||||
|
preferredLanguage
|
||||||
|
)
|
||||||
|
|
||||||
|
val mediaSource = when (selectedStream) {
|
||||||
|
is StreamSelect.SingleSelection -> {
|
||||||
|
val mediaItem = toMediaItem(item, selectedStream.stream)
|
||||||
|
val mediaItemWithMetadata = addMetadata(mediaItem, item)
|
||||||
|
toMediaSource(mediaItemWithMetadata, selectedStream.stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
is StreamSelect.MultiSelection -> {
|
||||||
|
val mediaItems = ArrayList(selectedStream.streams.map { toMediaItem(item, it) })
|
||||||
|
mediaItems[0] = addMetadata(mediaItems[0], item)
|
||||||
|
val mediaSources = mediaItems.zip(selectedStream.streams)
|
||||||
|
.map { toMediaSource(it.first, it.second) }
|
||||||
|
MergingMediaSource(
|
||||||
|
true, true,
|
||||||
|
*mediaSources.toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw NewPlayerException("Unknown stream selection class: ${selectedStream.javaClass}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaSource
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
|
@ -41,7 +81,7 @@ class MediaSourceBuilder(
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
private fun toMediaSource(mediaItem: MediaItem, stream: Stream) =
|
private fun toMediaSource(mediaItem: MediaItem, stream: Stream): MediaSource =
|
||||||
if (stream.streamType == StreamType.DYNAMIC)
|
if (stream.streamType == StreamType.DYNAMIC)
|
||||||
DashMediaSource.Factory(httpDataSourceFactory)
|
DashMediaSource.Factory(httpDataSourceFactory)
|
||||||
.createMediaSource(mediaItem)
|
.createMediaSource(mediaItem)
|
||||||
|
|
|
@ -15,14 +15,12 @@ object StreamSelect {
|
||||||
) : StreamSelection
|
) : StreamSelection
|
||||||
|
|
||||||
data class MultiSelection(
|
data class MultiSelection(
|
||||||
val videoStream: Stream,
|
val streams: List<Stream>
|
||||||
val audioStream: Stream
|
|
||||||
) : StreamSelection
|
) : StreamSelection
|
||||||
|
|
||||||
|
|
||||||
private fun getBestLanguageFit(
|
private fun getBestLanguageFit(
|
||||||
availableStreams: List<Stream>,
|
availableStreams: List<Stream>, preferredLanguages: List<String>
|
||||||
preferredLanguages: List<String>
|
|
||||||
): String? {
|
): String? {
|
||||||
for (preferredLanguage in preferredLanguages) {
|
for (preferredLanguage in preferredLanguages) {
|
||||||
for (available in availableStreams) {
|
for (available in availableStreams) {
|
||||||
|
@ -35,21 +33,15 @@ object StreamSelect {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filtersByLanguage(
|
private fun filtersByLanguage(
|
||||||
availableStreams: List<Stream>,
|
availableStreams: List<Stream>, language: String
|
||||||
language: String
|
) = availableStreams.filter { it.language == language }
|
||||||
) =
|
|
||||||
availableStreams.filter { it.language == language }
|
|
||||||
|
|
||||||
private fun getBestFittingVideoIdentifier(
|
private fun getBestFittingVideoIdentifier(
|
||||||
availableStreams: List<Stream>,
|
availableStreams: List<Stream>, preferredVideoIdentifier: List<String>
|
||||||
preferredVideoIdentifier: List<String>
|
|
||||||
): String? {
|
): String? {
|
||||||
for (preferredStream in preferredVideoIdentifier) {
|
for (preferredStream in preferredVideoIdentifier) {
|
||||||
for (available in availableStreams) {
|
for (available in availableStreams) {
|
||||||
if ((available.streamType == StreamType.AUDIO_AND_VIDEO ||
|
if ((available.streamType == StreamType.AUDIO_AND_VIDEO || available.streamType == StreamType.VIDEO) && preferredStream == available.identifier) {
|
||||||
available.streamType == StreamType.VIDEO)
|
|
||||||
&& preferredStream == available.identifier
|
|
||||||
) {
|
|
||||||
return preferredStream
|
return preferredStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,26 +49,31 @@ object StreamSelect {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tryAndGetMedianVideoOnlyStream(availableStreams: List<Stream>) =
|
||||||
|
availableStreams.filter { it.streamType == StreamType.VIDEO }.ifEmpty { null }?.let {
|
||||||
|
it[it.size / 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryAndGetMedianAudioOnlyStream(availableStreams: List<Stream>) =
|
||||||
|
availableStreams.filter { it.streamType == StreamType.AUDIO }.ifEmpty { null }?.let {
|
||||||
|
it[it.size / 2]
|
||||||
|
}
|
||||||
|
|
||||||
private fun getFirstMatchingIdentifier(
|
private fun getFirstMatchingIdentifier(
|
||||||
availableStreams: List<Stream>,
|
availableStreams: List<Stream>, identifier: String
|
||||||
identifier: String
|
|
||||||
): Stream? {
|
): Stream? {
|
||||||
for (variant in availableStreams) {
|
for (variant in availableStreams) {
|
||||||
if (variant.identifier == identifier)
|
if (variant.identifier == identifier) return variant
|
||||||
return variant
|
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBestFittingAudio(
|
private fun getBestFittingAudio(
|
||||||
availableStreams: List<Stream>,
|
availableStreams: List<Stream>, preferredAudioIdentifier: List<String>
|
||||||
preferredAudioIdentifier: List<String>
|
|
||||||
): Stream? {
|
): Stream? {
|
||||||
for (preferredStream in preferredAudioIdentifier) {
|
for (preferredStream in preferredAudioIdentifier) {
|
||||||
for (availableStream in availableStreams) {
|
for (availableStream in availableStreams) {
|
||||||
if (availableStream.streamType == StreamType.AUDIO
|
if (availableStream.streamType == StreamType.AUDIO && preferredStream == availableStream.identifier) {
|
||||||
&& preferredStream == availableStream.identifier
|
|
||||||
) {
|
|
||||||
return availableStream
|
return availableStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,15 +81,48 @@ object StreamSelect {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVideoOnlyWithMatchingIdentifier(
|
private fun getBestFittingAudioStreams(
|
||||||
|
availableStreams: List<Stream>, preferredAudioIdentifier: List<String>
|
||||||
|
): List<Stream>? {
|
||||||
|
val bundles =
|
||||||
|
bundledStreamsWithSameIdentifier(availableStreams.filter { it.streamType == StreamType.AUDIO })
|
||||||
|
for (preferred in preferredAudioIdentifier) {
|
||||||
|
val streams = bundles[preferred]
|
||||||
|
if (streams != null) {
|
||||||
|
return streams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDemuxedStreams(
|
||||||
|
availableStreams: List<Stream>
|
||||||
|
) = availableStreams.filter {
|
||||||
|
it.streamType == StreamType.AUDIO || it.streamType == StreamType.VIDEO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is needed to bundle streams with the same quality but different languages
|
||||||
|
*/
|
||||||
|
private fun bundledStreamsWithSameIdentifier(
|
||||||
availableStreams: List<Stream>,
|
availableStreams: List<Stream>,
|
||||||
identifier: String
|
): HashMap<String, ArrayList<Stream>> {
|
||||||
|
val streamsBundledByIdentifier = HashMap<String, ArrayList<Stream>>()
|
||||||
|
for (stream in availableStreams) {
|
||||||
|
streamsBundledByIdentifier[stream.identifier] ?: run {
|
||||||
|
val array = ArrayList<Stream>()
|
||||||
|
streamsBundledByIdentifier[stream.identifier] = array
|
||||||
|
array
|
||||||
|
}.add(stream)
|
||||||
|
}
|
||||||
|
return streamsBundledByIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVideoOnlyWithMatchingIdentifier(
|
||||||
|
availableStreams: List<Stream>, identifier: String
|
||||||
): Stream? {
|
): Stream? {
|
||||||
for (variant in availableStreams) {
|
for (variant in availableStreams) {
|
||||||
if (variant.streamType == StreamType.VIDEO
|
if (variant.streamType == StreamType.VIDEO && variant.identifier == identifier) return variant
|
||||||
&& variant.identifier == identifier
|
|
||||||
)
|
|
||||||
return variant
|
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -106,113 +136,108 @@ object StreamSelect {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNonDynamicVideos(availableStreams: List<Stream>) =
|
private fun getNonDynamicVideos(availableStreams: List<Stream>) = availableStreams.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 getNonDynamicAudios(availableStreams: List<Stream>) =
|
private fun getNonDynamicAudios(availableStreams: List<Stream>) =
|
||||||
availableStreams.filter { it.streamType == StreamType.AUDIO }
|
availableStreams.filter { it.streamType == StreamType.AUDIO }
|
||||||
|
|
||||||
private fun hasVideoStreams(availableStreams: List<Stream>): Boolean {
|
private fun hasVideoStreams(availableStreams: List<Stream>): Boolean {
|
||||||
for (variant in availableStreams) {
|
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
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class DemuxedStreamBundeling {
|
||||||
|
DO_NOT_BUNDLE, BUNDLE_STREAMS_WITH_SAME_ID, BUNDLE_AUDIOSTREAMS_WITH_SAME_ID
|
||||||
|
}
|
||||||
|
|
||||||
fun selectStream(
|
fun selectStream(
|
||||||
item: String,
|
item: String,
|
||||||
playMode: PlayMode,
|
playMode: PlayMode,
|
||||||
availableStreams: List<Stream>,
|
availableStreams: List<Stream>,
|
||||||
preferredVideoIdentifier: List<String>,
|
preferredVideoIdentifier: List<String>,
|
||||||
preferredAudioIdentifier: List<String>,
|
preferredAudioIdentifier: List<String>,
|
||||||
preferredLanguage: List<String>
|
preferredLanguage: List<String>,
|
||||||
|
demuxedStreamBundeling: DemuxedStreamBundeling = DemuxedStreamBundeling.DO_NOT_BUNDLE
|
||||||
): StreamSelection {
|
): StreamSelection {
|
||||||
|
|
||||||
// filter for best fitting language stream variants
|
// filter for best fitting language stream variants
|
||||||
|
|
||||||
val bestFittingLanguage = getBestLanguageFit(availableStreams, preferredLanguage)
|
val bestFittingLanguage = getBestLanguageFit(availableStreams, preferredLanguage)
|
||||||
val availablesInPreferredLanguage =
|
val availablesInPreferredLanguage = if (bestFittingLanguage != null) filtersByLanguage(
|
||||||
if (bestFittingLanguage != null) filtersByLanguage(
|
availableStreams, bestFittingLanguage
|
||||||
availableStreams,
|
)
|
||||||
bestFittingLanguage
|
else {
|
||||||
)
|
emptyList()
|
||||||
else {
|
}
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// is it a video stream or a pure audio stream?
|
// is it a video stream or a pure audio stream?
|
||||||
if (hasVideoStreams(availableStreams)) {
|
if (hasVideoStreams(availableStreams)) {
|
||||||
|
|
||||||
// first: try and get a dynamic stream variant
|
// first: try and get a dynamic stream variant
|
||||||
getDynamicStream(availablesInPreferredLanguage)
|
getDynamicStream(availablesInPreferredLanguage) ?: getDynamicStream(
|
||||||
?: getDynamicStream(
|
availableStreams
|
||||||
availableStreams
|
)?.let {
|
||||||
)?.let {
|
return SingleSelection(it)
|
||||||
return SingleSelection(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// second: try and get seperate audio and video stream variants
|
|
||||||
|
|
||||||
val bestVideoIdentifier =
|
|
||||||
getBestFittingVideoIdentifier(
|
|
||||||
availablesInPreferredLanguage,
|
|
||||||
preferredVideoIdentifier
|
|
||||||
)?.let {
|
|
||||||
val videos =
|
|
||||||
getNonDynamicVideos(availablesInPreferredLanguage)
|
|
||||||
videos[videos.size / 2].identifier
|
|
||||||
} ?: getBestFittingVideoIdentifier(
|
|
||||||
availableStreams,
|
|
||||||
preferredVideoIdentifier
|
|
||||||
)
|
|
||||||
?: run {
|
|
||||||
val videos = getNonDynamicVideos(availableStreams)
|
|
||||||
videos[videos.size / 2].identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
val videoOnlyStream =
|
|
||||||
getVideoOnlyWithMatchingIdentifier(
|
|
||||||
availablesInPreferredLanguage,
|
|
||||||
bestVideoIdentifier
|
|
||||||
) ?: getVideoOnlyWithMatchingIdentifier(
|
|
||||||
availableStreams,
|
|
||||||
bestVideoIdentifier
|
|
||||||
)
|
|
||||||
|
|
||||||
if (videoOnlyStream != null) {
|
|
||||||
getBestFittingAudio(
|
|
||||||
availablesInPreferredLanguage,
|
|
||||||
preferredAudioIdentifier
|
|
||||||
) ?: getBestFittingAudio(availableStreams, preferredAudioIdentifier)
|
|
||||||
?.let {
|
|
||||||
return MultiSelection(videoOnlyStream, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// second: try and get separate audio and video stream variants
|
||||||
|
|
||||||
|
val bestVideoIdentifier = getBestFittingVideoIdentifier(
|
||||||
|
availablesInPreferredLanguage, preferredVideoIdentifier
|
||||||
|
) ?: tryAndGetMedianVideoOnlyStream(availablesInPreferredLanguage)?.identifier
|
||||||
|
?: getBestFittingVideoIdentifier(
|
||||||
|
availableStreams, preferredVideoIdentifier
|
||||||
|
) ?: tryAndGetMedianVideoOnlyStream(availableStreams)?.identifier ?: ""
|
||||||
|
|
||||||
|
if (demuxedStreamBundeling == DemuxedStreamBundeling.BUNDLE_STREAMS_WITH_SAME_ID) {
|
||||||
|
return MultiSelection(availableStreams.filter { it.streamType == StreamType.VIDEO || it.streamType == StreamType.AUDIO })
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val videoOnlyStream = getVideoOnlyWithMatchingIdentifier(
|
||||||
|
availablesInPreferredLanguage, bestVideoIdentifier
|
||||||
|
) ?: getVideoOnlyWithMatchingIdentifier(
|
||||||
|
availableStreams, bestVideoIdentifier
|
||||||
|
)
|
||||||
|
|
||||||
|
if (videoOnlyStream != null) {
|
||||||
|
if (demuxedStreamBundeling == DemuxedStreamBundeling.BUNDLE_AUDIOSTREAMS_WITH_SAME_ID) {
|
||||||
|
val bestFittingAudioStreams = getBestFittingAudioStreams(availableStreams, preferredAudioIdentifier)
|
||||||
|
?: listOf(availableStreams.filter { it.streamType == StreamType.AUDIO }[0])
|
||||||
|
|
||||||
|
val streams = mutableListOf(videoOnlyStream)
|
||||||
|
streams.addAll(bestFittingAudioStreams)
|
||||||
|
return MultiSelection(streams)
|
||||||
|
}
|
||||||
|
val audioStream = getBestFittingAudio(
|
||||||
|
availablesInPreferredLanguage, preferredAudioIdentifier
|
||||||
|
) ?: getBestFittingAudio(availableStreams, preferredAudioIdentifier)
|
||||||
|
?: availableStreams.filter { it.streamType == StreamType.AUDIO }[0]
|
||||||
|
|
||||||
|
return MultiSelection(listOf(videoOnlyStream, audioStream))
|
||||||
|
} /* if (vdeioOnlyStream != null) */
|
||||||
|
} /* else (demuxedStreamBundeling == DemuxedStreamBundeling.BUNDLE_STREAMS_WITH_SAME_ID) */
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
getFirstMatchingIdentifier(
|
getFirstMatchingIdentifier(
|
||||||
availablesInPreferredLanguage,
|
availablesInPreferredLanguage, bestVideoIdentifier
|
||||||
bestVideoIdentifier
|
) ?: getFirstMatchingIdentifier(
|
||||||
)
|
availableStreams, bestVideoIdentifier
|
||||||
?: getFirstMatchingIdentifier(
|
)?.let {
|
||||||
availableStreams,
|
return SingleSelection(it)
|
||||||
bestVideoIdentifier
|
}
|
||||||
)?.let {
|
|
||||||
return SingleSelection(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 videos =
|
val videos = getNonDynamicVideos(availablesInPreferredLanguage).ifEmpty {
|
||||||
getNonDynamicVideos(availablesInPreferredLanguage).ifEmpty {
|
getNonDynamicVideos(availableStreams)
|
||||||
getNonDynamicVideos(availableStreams)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (videos.isNotEmpty()) {
|
if (videos.isNotEmpty()) {
|
||||||
return@run videos[videos.size / 2]
|
return@run videos[videos.size / 2]
|
||||||
|
@ -226,27 +251,23 @@ object StreamSelect {
|
||||||
// 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
|
||||||
|
|
||||||
getBestFittingAudio(
|
getBestFittingAudio(
|
||||||
availablesInPreferredLanguage,
|
availablesInPreferredLanguage, preferredAudioIdentifier
|
||||||
preferredAudioIdentifier
|
) ?: getBestFittingAudio(
|
||||||
)
|
availableStreams, preferredAudioIdentifier
|
||||||
?: getBestFittingAudio(
|
)?.let {
|
||||||
availableStreams,
|
return SingleSelection(it)
|
||||||
preferredAudioIdentifier
|
}
|
||||||
)?.let {
|
|
||||||
return SingleSelection(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 audios =
|
val audios = getNonDynamicAudios(availablesInPreferredLanguage).let {
|
||||||
getNonDynamicAudios(availablesInPreferredLanguage).let {
|
if (it.isNotEmpty()) {
|
||||||
if (it.isNotEmpty()) {
|
it
|
||||||
it
|
} else {
|
||||||
} else {
|
getNonDynamicAudios(availableStreams)
|
||||||
getNonDynamicAudios(availableStreams)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (audios.isNotEmpty()) {
|
if (audios.isNotEmpty()) {
|
||||||
return@run audios[audios.size / 2]
|
return@run audios[audios.size / 2]
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue