call fullscreen activity from fragment without dropping frames
This commit is contained in:
parent
0d85401cdd
commit
fa9c4f6647
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,9 @@ class VideoPlayerFragment() : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.preparePlayer()
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
|
|
@ -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>
|
Loading…
Reference in New Issue