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>
<SelectionState runConfigName="app">
<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">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/schabi/.android/avd/Medium_Phone_API_33.avd" />
<DeviceId pluginId="PhysicalDevice" identifier="serial=981f7af2" />
</handle>
</Target>
</DropdownSelection>

View File

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

View File

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

View File

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

View File

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

View File

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