Skip to content

Commit

Permalink
show available video streams
Browse files Browse the repository at this point in the history
  • Loading branch information
theScrabi committed Oct 16, 2024
1 parent bef09f7 commit ac16514
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import net.newpipe.newplayer.data.StreamSelection
import net.newpipe.newplayer.logic.StreamSelectionResponse
import net.newpipe.newplayer.data.StreamTrack
import net.newpipe.newplayer.logic.StreamSelector
import net.newpipe.newplayer.logic.TrackUtils
import kotlin.random.Random

private const val TAG = "NewPlayerImpl"
Expand Down Expand Up @@ -207,7 +208,7 @@ class NewPlayerImpl(
uniqueIdToStreamSelectionLookup[mediaItem.mediaId.toLong()]!!
launchJobAndCollectError {
mutableCurrentlyAvailableTracks.update {
StreamSelector.getNonDynamicTracksNonDuplicated(repository.getStreams(streamSelection.item))
TrackUtils.getNonDynamicTracksNonDuplicated(repository.getStreams(streamSelection.item))
}
}
} else {
Expand Down Expand Up @@ -344,7 +345,7 @@ class NewPlayerImpl(
override fun playStream(item: String, playMode: PlayMode) {
launchJobAndCollectError {
mutableCurrentlyAvailableTracks.update {
StreamSelector.getNonDynamicTracksNonDuplicated(repository.getStreams(item))
TrackUtils.getNonDynamicTracksNonDuplicated(repository.getStreams(item))
}

val mediaSource = toMediaSource(item)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,56 @@

package net.newpipe.newplayer.data

import net.newpipe.newplayer.logic.TrackUtils

interface StreamSelection {
val item: String

val tracks : List<StreamTrack>
val hasVideoTracks:Boolean
val hasAudioTracks:Boolean
val isDynamic:Boolean
}

data class SingleSelection(
val stream: Stream
) : StreamSelection {
override val item: String
get() = stream.item

override val tracks: List<StreamTrack>
get() = stream.streamTracks

override val hasVideoTracks: Boolean
get() = stream.hasVideoTracks

override val hasAudioTracks: Boolean
get() = stream.hasVideoTracks

override val isDynamic: Boolean
get() = stream.isDashOrHls
}

data class MultiSelection(
val streams: List<Stream>
) : StreamSelection {

override val item: String
get() = streams[0].item

override val tracks: List<StreamTrack>
get() {
val allTracks = mutableListOf<StreamTrack>()
streams.forEach { allTracks.addAll(it.streamTracks) }
return allTracks
}

override val hasVideoTracks: Boolean
get() = TrackUtils.hasVideoTracks(streams)

override val hasAudioTracks: Boolean
get() = TrackUtils.hasAudioTracks(streams)

override val isDynamic: Boolean
get() = TrackUtils.hasDynamicStreams(streams)
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ import net.newpipe.newplayer.data.SingleSelection
import net.newpipe.newplayer.data.Stream
import net.newpipe.newplayer.data.StreamSelection
import net.newpipe.newplayer.logic.TrackUtils.getDynamicStreams
import net.newpipe.newplayer.logic.TrackUtils.hasVideoStreams
import net.newpipe.newplayer.logic.TrackUtils.tryAndGetMedianAudioOnlyStream
import net.newpipe.newplayer.logic.TrackUtils.tryAndGetMedianCombinedVideoAndAudioStream
import net.newpipe.newplayer.logic.TrackUtils.tryAndGetMedianVideoOnlyStream
import net.newpipe.newplayer.logic.TrackUtils.hasVideoTracks
import net.newpipe.newplayer.logic.TrackUtils.tryAndGetMedianAudioOnlyTracks
import net.newpipe.newplayer.logic.TrackUtils.tryAndGetMedianCombinedVideoAndAudioTracks
import net.newpipe.newplayer.logic.TrackUtils.tryAndGetMedianVideoOnlyTracks

internal class StreamSelector(
val preferredLanguages: List<LanguageIdentifier>,
Expand All @@ -55,7 +55,7 @@ internal class StreamSelector(


// is it a video stream or a pure audio stream?
if (hasVideoStreams(availableStreams)) {
if (hasVideoTracks(availableStreams)) {

// first: try and get a dynamic stream variant
val dynamicStreams = getDynamicStreams(availablesInPreferredLanguage)
Expand All @@ -65,12 +65,12 @@ internal class StreamSelector(

// second: try and get separate audio and video stream variants

val videoOnlyStream = tryAndGetMedianVideoOnlyStream(availableStreams)
val videoOnlyStream = tryAndGetMedianVideoOnlyTracks(availableStreams)


if (videoOnlyStream != null) {

val audioStream = tryAndGetMedianAudioOnlyStream(availableStreams)
val audioStream = tryAndGetMedianAudioOnlyTracks(availableStreams)

if (videoOnlyStream != null && audioStream != null) {
return MultiSelection(listOf(videoOnlyStream, audioStream))
Expand All @@ -79,15 +79,15 @@ internal class StreamSelector(

// fourth: try to get a video and audio stream variant with the best fitting identifier

tryAndGetMedianCombinedVideoAndAudioStream(availableStreams)?.let {
tryAndGetMedianCombinedVideoAndAudioTracks(availableStreams)?.let {
return SingleSelection(it)
}

} else { /* if(!hasVideoStreams(availableStreams)) */

// first: try to get an audio stream variant with the best fitting identifier

tryAndGetMedianAudioOnlyStream(availableStreams)?.let {
tryAndGetMedianAudioOnlyTracks(availableStreams)?.let {
return SingleSelection(it)
}
}
Expand Down
28 changes: 22 additions & 6 deletions new-player/src/main/java/net/newpipe/newplayer/logic/TrackUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,20 @@ object TrackUtils {
)
}

internal fun tryAndGetMedianVideoOnlyStream(availableStreams: List<Stream>) =
internal fun tryAndGetMedianVideoOnlyTracks(availableStreams: List<Stream>) =
availableStreams.filter { !it.isDashOrHls && it.hasVideoTracks && !it.hasAudioTracks }
.ifEmpty { null }?.let {
it[it.size / 2]
}

internal fun tryAndGetMedianCombinedVideoAndAudioStream(availableStreams: List<Stream>) =
internal fun tryAndGetMedianCombinedVideoAndAudioTracks(availableStreams: List<Stream>) =
availableStreams.filter { !it.isDashOrHls && it.hasVideoTracks && it.hasVideoTracks }
.ifEmpty { null }
?.let {
it[it.size / 2]
}

internal fun tryAndGetMedianAudioOnlyStream(availableStreams: List<Stream>) =
internal fun tryAndGetMedianAudioOnlyTracks(availableStreams: List<Stream>) =
availableStreams.filter { !it.isDashOrHls && it.hasAudioTracks && !it.hasVideoTracks }
.ifEmpty { null }?.let {
it[it.size / 2]
Expand All @@ -96,19 +96,35 @@ object TrackUtils {
internal fun getDynamicStreams(availableStreams: List<Stream>) =
availableStreams.filter { it.isDashOrHls }

internal fun getNonDynamicVideoStreams(availableStreams: List<Stream>) =
internal fun getNonDynamicVideoTracks(availableStreams: List<Stream>) =
availableStreams.filter {
!it.isDashOrHls && it.hasVideoTracks && !it.hasAudioTracks
}

internal fun getNonDynamicAudioStreams(availableStreams: List<Stream>) =
internal fun getNonDynamicAudioTracks(availableStreams: List<Stream>) =
availableStreams.filter { !it.isDashOrHls && !it.hasVideoTracks && it.hasAudioTracks }

internal fun hasVideoStreams(availableStreams: List<Stream>): Boolean {
internal fun hasVideoTracks(availableStreams: List<Stream>): Boolean {
availableStreams.forEach {
if (it.hasVideoTracks)
return true
}
return false
}

internal fun hasAudioTracks(availableStreams: List<Stream>): Boolean {
availableStreams.forEach {
if (it.hasAudioTracks)
return true
}
return false
}

internal fun hasDynamicStreams(availableStreams: List<Stream>): Boolean {
availableStreams.forEach {
if (it.isDashOrHls)
return true
}
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import net.newpipe.newplayer.ui.common.getEmbeddedUiConfig

@OptIn(UnstableApi::class)
@Composable
internal fun DropDownMenu(viewModel: InternalNewPlayerViewModel, uiState: NewPlayerUIState) {
internal fun VideoPlayerMenu(viewModel: InternalNewPlayerViewModel, uiState: NewPlayerUIState) {
var showMainMenu: Boolean by remember { mutableStateOf(false) }

val pixel_density = LocalDensity.current
Expand Down Expand Up @@ -184,7 +184,7 @@ internal fun DropDownMenu(viewModel: InternalNewPlayerViewModel, uiState: NewPla
private fun VideoPlayerControllerDropDownPreview() {
VideoPlayerTheme {
Box(Modifier.fillMaxSize()) {
DropDownMenu(NewPlayerViewModelDummy(), NewPlayerUIState.DUMMY)
VideoPlayerMenu(NewPlayerViewModelDummy(), NewPlayerUIState.DUMMY)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package net.newpipe.newplayer.ui.videoplayer.controller

import android.app.Activity
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -32,13 +33,20 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.automirrored.filled.MenuBook
import androidx.compose.material.icons.filled.Language
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand All @@ -52,6 +60,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.R
import net.newpipe.newplayer.data.VideoStreamTrack
import net.newpipe.newplayer.logic.TrackUtils
import net.newpipe.newplayer.uiModel.EmbeddedUiConfig
import net.newpipe.newplayer.uiModel.NewPlayerUIState
import net.newpipe.newplayer.uiModel.InternalNewPlayerViewModel
Expand Down Expand Up @@ -86,7 +96,7 @@ internal fun TopUI(
maxLines = 1,
overflow = TextOverflow.Ellipsis
)

val creatorOffset = with(LocalDensity.current) {
14.sp.toDp()
}
Expand All @@ -101,17 +111,7 @@ internal fun TopUI(
} else {
Box(modifier = Modifier.weight(1F))
}
Button(
onClick = { /*TODO*/ },
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent, contentColor = video_player_onSurface
),
) {
Text(
"1080p", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
)
}
TrackSelectionMenu(viewModel, uiState)
IconButton(
onClick = { /*TODO*/ },
) {
Expand Down Expand Up @@ -149,7 +149,57 @@ internal fun TopUI(
)
}
}
DropDownMenu(viewModel, uiState)
VideoPlayerMenu(viewModel, uiState)
}
}

@OptIn(UnstableApi::class)
@Composable
private fun TrackSelectionMenu(viewModel: InternalNewPlayerViewModel, uiState: NewPlayerUIState) {
var menuVisible by remember {
mutableStateOf(false)
}

val context = LocalContext.current
val noOtherTracksText = stringResource(
id = R.string.no_other_tracks_available_toast
)

val availableVideoTracks =
uiState.currentlyAvailableTracks.filterIsInstance<VideoStreamTrack>()

Box {
Button(
onClick = {
if (1 < availableVideoTracks.size) {
viewModel.dialogVisible(true)
menuVisible = true
} else
Toast.makeText(
context,
noOtherTracksText,
Toast.LENGTH_SHORT

).show()
},
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent, contentColor = video_player_onSurface
),
) {
Text(
"1080p", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
)
}
DropdownMenu(expanded = menuVisible, onDismissRequest = { menuVisible = false }) {
for (track in availableVideoTracks) {
DropdownMenuItem(text = { Text(track.toLongIdentifierString()) },
onClick = { /*TODO*/
viewModel.dialogVisible(false)
menuVisible = false
})
}
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion new-player/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@
<string name="fullscreen_button_description">Switch to fullscreen video mode</string>
<string name="pip_button_description">Picture in picture</string>
<string name="playing_in_background">Playing in the background…</string>
<string name="seek_thumb_preview">Video seek preview thumbnail</string>
<string name="seek_thumb_preview">Video seek preview thumbnail</string>a
<string name="no_other_tracks_available_toast">No other stream tracks available.</string>
</resources>

0 comments on commit ac16514

Please sign in to comment.