call fullscreen activity from fragment without dropping frames

This commit is contained in:
Christian Schabesberger 2024-07-17 16:12:30 +02:00
parent 0d85401cdd
commit fa9c4f6647
8 changed files with 75 additions and 82 deletions

View File

@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-07-17T11:02:47.259317096Z"> <DropdownSelection timestamp="2024-07-17T13:42:13.936108406Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/schabi/.android/avd/Medium_Phone_API_33.avd" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=981f7af2" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@ -5,6 +5,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.material3.Text
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
@ -21,7 +22,7 @@ class VideoPlayerActivity : ComponentActivity() {
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
VideoPlayerTheme { VideoPlayerTheme {
VideoPlayerUI(viewModel = viewModel, isFullscreen = true) VideoPlayerUI(viewModel = viewModel)
} }
} }
} }

View File

@ -84,6 +84,9 @@ class VideoPlayerFragment() : Fragment() {
} }
} }
} }
viewModel.preparePlayer()
return view return view
} }
} }

View File

@ -21,23 +21,28 @@
package net.newpipe.newplayer.model package net.newpipe.newplayer.model
import android.app.Application import android.app.Application
import android.content.Intent
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player import androidx.media3.common.Player
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import net.newpipe.newplayer.R import net.newpipe.newplayer.R
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import net.newpipe.newplayer.VideoPlayerActivity import kotlinx.coroutines.launch
import net.newpipe.newplayer.utils.VideoSize import net.newpipe.newplayer.utils.VideoSize
data class VideoPlayerUIState( data class VideoPlayerUIState(
val playing: Boolean, var fullscreen: Boolean, var uiVissible: Boolean, var contentRatio: Float val playing: Boolean,
var fullscreen: Boolean,
var uiVissible: Boolean,
var contentRatio: Float
) { ) {
companion object { companion object {
val DEFAULT = VideoPlayerUIState( val DEFAULT = VideoPlayerUIState(
@ -50,6 +55,9 @@ interface VideoPlayerViewModel {
val player: Player? val player: Player?
val uiState: StateFlow<VideoPlayerUIState> val uiState: StateFlow<VideoPlayerUIState>
var listener: Listener? var listener: Listener?
val events: SharedFlow<Events>?
fun preparePlayer()
fun play() fun play()
fun pause() fun pause()
fun prevStream() fun prevStream()
@ -61,6 +69,11 @@ interface VideoPlayerViewModel {
fun requestUpdateLayoutRatio(ratio: Float) fun requestUpdateLayoutRatio(ratio: Float)
fun switchToFullscreen() fun switchToFullscreen()
} }
sealed class Events {
object SwitchToFullscreen : Events()
object SwitchToEmbeddedView : Events()
}
} }
@ -77,6 +90,10 @@ class VideoPlayerViewModelImpl @Inject constructor(
VideoPlayerUIState.DEFAULT VideoPlayerUIState.DEFAULT
) )
private val mutableEvent = MutableSharedFlow<VideoPlayerViewModel.Events>()
override val events: SharedFlow<VideoPlayerViewModel.Events> = mutableEvent
override val uiState = mutableUiState.asStateFlow() override val uiState = mutableUiState.asStateFlow()
override var listener: VideoPlayerViewModel.Listener? = null override var listener: VideoPlayerViewModel.Listener? = null
@ -85,11 +102,6 @@ class VideoPlayerViewModelImpl @Inject constructor(
init { init {
println("gurken $this")
if (player.playbackState == Player.STATE_IDLE) {
player.prepare()
}
player.addListener(object : Player.Listener { player.addListener(object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying) super.onIsPlayingChanged(isPlaying)
@ -124,17 +136,23 @@ class VideoPlayerViewModelImpl @Inject constructor(
} }
} }
}) })
}
player.setMediaItem(MediaItem.fromUri(app.getString(R.string.ccc_6502_video))) override fun preparePlayer() {
//player.playWhenReady = true if (player.playbackState == Player.STATE_IDLE) {
player.prepare()
}
player.setMediaItem(MediaItem.fromUri(app.getString(R.string.ccc_chromebooks_video)))
player.playWhenReady = true
} }
override fun play() { override fun play() {
//player.play() player.play()
} }
override fun pause() { override fun pause() {
//player.pause() player.pause()
} }
override fun prevStream() { override fun prevStream() {
@ -146,20 +164,26 @@ class VideoPlayerViewModelImpl @Inject constructor(
} }
override fun switchToEmbeddedView() { override fun switchToEmbeddedView() {
mutableUiState.update { viewModelScope.launch {
it.copy(fullscreen = false) mutableEvent.emit(VideoPlayerViewModel.Events.SwitchToEmbeddedView)
} }
//mutableUiState.update {
// it.copy(fullscreen = false)
//}
} }
override fun switchToFullscreen() { override fun switchToFullscreen() {
mutableUiState.update { viewModelScope.launch {
it.copy(fullscreen = true) mutableEvent.emit(VideoPlayerViewModel.Events.SwitchToFullscreen)
} }
//mutableUiState.update {
// it.copy(fullscreen = true)
//}
//listener?.switchToFullscreen() //listener?.switchToFullscreen()
} }
override fun onCleared() { override fun onCleared() {
player.release() super.onCleared()
} }
companion object { companion object {
@ -167,6 +191,12 @@ class VideoPlayerViewModelImpl @Inject constructor(
override val player = null override val player = null
override val uiState = MutableStateFlow(VideoPlayerUIState.DEFAULT) override val uiState = MutableStateFlow(VideoPlayerUIState.DEFAULT)
override var listener: VideoPlayerViewModel.Listener? = null override var listener: VideoPlayerViewModel.Listener? = null
override val events: SharedFlow<VideoPlayerViewModel.Events>? = null
override fun preparePlayer() {
println("dummy impl")
}
override fun play() { override fun play() {
println("dummy impl") println("dummy impl")
} }

View File

@ -357,10 +357,6 @@ private fun BottomUI(
switchToEmbeddedView: () -> Unit switchToEmbeddedView: () -> Unit
) { ) {
if (isFullscreen) {
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
}
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
@ -385,7 +381,7 @@ private fun BottomUI(
@Composable @Composable
private fun ViewInFullScreen() { private fun ViewInFullScreen() {
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) //LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
} }
@Composable @Composable

View File

@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -39,6 +40,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import kotlinx.coroutines.flow.collectLatest
import net.newpipe.newplayer.VideoPlayerActivity import net.newpipe.newplayer.VideoPlayerActivity
import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
@ -48,7 +50,6 @@ import net.newpipe.newplayer.utils.findActivity
@Composable @Composable
fun VideoPlayerUI( fun VideoPlayerUI(
viewModel: VideoPlayerViewModel, viewModel: VideoPlayerViewModel,
isFullscreen: Boolean
) { ) {
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
@ -56,6 +57,8 @@ fun VideoPlayerUI(
mutableStateOf(Lifecycle.Event.ON_CREATE) mutableStateOf(Lifecycle.Event.ON_CREATE)
} }
val activity = LocalContext.current.findActivity()
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) { DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
@ -68,21 +71,22 @@ fun VideoPlayerUI(
} }
} }
var fullscreen_requested by remember { LaunchedEffect(key1 = Unit) {
mutableStateOf(false) viewModel.events?.collectLatest { event ->
when (event) {
VideoPlayerViewModel.Events.SwitchToEmbeddedView -> {
activity?.finish()
} }
if(isFullscreen != uiState.fullscreen && !fullscreen_requested) { VideoPlayerViewModel.Events.SwitchToFullscreen -> {
fullscreen_requested = true val fullscreen_activity_intent =
val current_acitivity = LocalContext.current.findActivity() Intent(activity!!.findActivity(), VideoPlayerActivity::class.java)
if(uiState.fullscreen) { activity.startActivity(fullscreen_activity_intent)
val fullscreen_acitivity_intent = Intent(current_acitivity, VideoPlayerActivity::class.java)
current_acitivity!!.startActivity(fullscreen_acitivity_intent)
} else {
current_acitivity!!.finish()
}
}
}
}
}
}
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@ -92,7 +96,7 @@ fun VideoPlayerUI(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
factory = { context -> factory = { context ->
SurfaceView(context).also { SurfaceView(context).also {
//viewModel.player?.setVideoSurfaceView(it) viewModel.player?.setVideoSurfaceView(it)
} }
}, update = { }, update = {
when (lifecycle) { when (lifecycle) {
@ -124,6 +128,6 @@ fun VideoPlayerUI(
@Composable @Composable
fun PlayerUIPreviewEmbeded() { fun PlayerUIPreviewEmbeded() {
VideoPlayerTheme { VideoPlayerTheme {
VideoPlayerUI(viewModel = VideoPlayerViewModelImpl.dummy, isFullscreen = false) VideoPlayerUI(viewModel = VideoPlayerViewModelImpl.dummy)
} }
} }

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- NewPlayer
@author Christian Schabesberger
Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
NewPlayer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NewPlayer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/player_frament_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="parent"
android:name="net.newpipe.newplayer.PlayerFragment"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,4 +2,5 @@
<resources> <resources>
<string name="ccc_6502_video">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4</string> <string name="ccc_6502_video">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4</string>
<string name="ccc_6502_audio">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg</string> <string name="ccc_6502_audio">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg</string>
<string name="ccc_chromebooks_video">https://ftp.fau.de/cdn.media.ccc.de/congress/2023/h264-hd/37c3-11929-eng-deu-swe-Turning_Chromebooks_into_regular_laptops_hd.mp4</string>
</resources> </resources>