switch to fullscreen and back without dropping frames

This commit is contained in:
Christian Schabesberger 2024-07-17 18:24:20 +02:00
parent 0290eaf9ea
commit 19a48c45a6
7 changed files with 91 additions and 30 deletions

View File

@ -18,8 +18,6 @@
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
*/
import java.util.regex.Pattern.compile
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)

View File

@ -6,6 +6,7 @@
<application
android:name=".NewPlayerApp"
android:enableOnBackInvokedCallback="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@ -13,8 +14,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NewPlayer"
tools:targetApi="31">
android:theme="@style/Theme.NewPlayer">
<activity
android:name=".VideoPlayerActivity"
android:exported="false"

View File

@ -19,6 +19,9 @@ class VideoPlayerActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.initUIState(intent.extras!!)
enableEdgeToEdge()
setContent {
VideoPlayerTheme {

View File

@ -56,7 +56,7 @@ class VideoPlayerFragment() : Fragment() {
insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
if (viewModel.uiState.value.fullscreen) {
println("gurken fragment created for fullscreen")
//println("gurken fragment created for fullscreen")
//insetsController.hide(WindowInsetsCompat.Type.systemBars())
}
@ -72,7 +72,7 @@ class VideoPlayerFragment() : Fragment() {
}
override fun switchToFullscreen() {
println("gurken fullscreen")
//println("gurken fullscreen")
}
}
@ -80,7 +80,7 @@ class VideoPlayerFragment() : Fragment() {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
VideoPlayerTheme {
VideoPlayerUI(viewModel = viewModel, isFullscreen = false)
VideoPlayerUI(viewModel = viewModel)
}
}
}

View File

@ -21,7 +21,10 @@
package net.newpipe.newplayer.model
import android.app.Application
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import androidx.annotation.RequiresApi
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
@ -62,6 +65,7 @@ interface VideoPlayerViewModel {
var listener: Listener?
val events: SharedFlow<Events>?
fun initUIState(instanceState: Bundle)
fun preparePlayer()
fun play()
fun pause()
@ -99,6 +103,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
override val events: SharedFlow<VideoPlayerViewModel.Events> = mutableEvent
override val uiState = mutableUiState.asStateFlow()
override var listener: VideoPlayerViewModel.Listener? = null
@ -143,6 +148,22 @@ class VideoPlayerViewModelImpl @Inject constructor(
})
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun initUIState(instanceState: Bundle) {
val uiState =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
instanceState.getParcelable(VIDEOPLAYER_UI_STATE, VideoPlayerUIState::class.java)
else
instanceState.getParcelable(VIDEOPLAYER_UI_STATE)
uiState?.let { uiState ->
mutableUiState.update {
uiState
}
}
}
override fun preparePlayer() {
if (player.playbackState == Player.STATE_IDLE) {
player.prepare()
@ -172,19 +193,12 @@ class VideoPlayerViewModelImpl @Inject constructor(
viewModelScope.launch {
mutableEvent.emit(VideoPlayerViewModel.Events.SwitchToEmbeddedView)
}
//mutableUiState.update {
// it.copy(fullscreen = false)
//}
}
override fun switchToFullscreen() {
viewModelScope.launch {
mutableEvent.emit(VideoPlayerViewModel.Events.SwitchToFullscreen)
}
//mutableUiState.update {
// it.copy(fullscreen = true)
//}
//listener?.switchToFullscreen()
}
companion object {
@ -194,6 +208,10 @@ class VideoPlayerViewModelImpl @Inject constructor(
override var listener: VideoPlayerViewModel.Listener? = null
override val events: SharedFlow<VideoPlayerViewModel.Events>? = null
override fun initUIState(instanceState: Bundle) {
println("dummy impl")
}
override fun preparePlayer() {
println("dummy impl")
}

View File

@ -20,8 +20,15 @@
package net.newpipe.newplayer.ui
import android.app.Activity
import android.content.Intent
import android.view.SurfaceView
import androidx.activity.compose.BackHandler
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
@ -40,6 +47,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
import kotlinx.coroutines.flow.collectLatest
import net.newpipe.newplayer.VideoPlayerActivity
import net.newpipe.newplayer.model.VIDEOPLAYER_UI_STATE
@ -72,19 +80,27 @@ fun VideoPlayerUI(
}
}
BackHandler {
closeFullscreen(viewModel, activity!!)
}
val fullscreenLauncher =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
println("gurken returned for result")
viewModel.initUIState(result.data?.extras!!)
}
LaunchedEffect(key1 = Unit) {
viewModel.events?.collectLatest { event ->
when (event) {
VideoPlayerViewModel.Events.SwitchToEmbeddedView -> {
activity?.finish()
closeFullscreen(viewModel, activity!!)
}
VideoPlayerViewModel.Events.SwitchToFullscreen -> {
val fullscreen_activity_intent =
Intent(activity!!.findActivity(), VideoPlayerActivity::class.java)
fullscreen_activity_intent.putExtra(VIDEOPLAYER_UI_STATE, viewModel.uiState.value)
activity.startActivity(fullscreen_activity_intent)
openFullscreen(viewModel, activity!!, fullscreenLauncher)
}
}
}
@ -97,14 +113,18 @@ fun VideoPlayerUI(
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
SurfaceView(context).also {
viewModel.player?.setVideoSurfaceView(it)
SurfaceView(context).also { view ->
viewModel.player?.setVideoSurfaceView(view)
}
}, update = {
}, update = { view ->
when (lifecycle) {
Lifecycle.Event.ON_PAUSE -> {
println("gurken state on pause")
viewModel.pause()
}
Lifecycle.Event.ON_RESUME -> {
println("gurken resume")
viewModel.player?.setVideoSurfaceView(view)
}
else -> Unit
@ -112,7 +132,7 @@ fun VideoPlayerUI(
})
val isPlaying = viewModel.player!!.isPlaying
println("is Player playing: $isPlaying")
VideoPlayerControllerUI(
isPlaying = uiState.playing,
fullscreen = uiState.fullscreen,
@ -126,6 +146,28 @@ fun VideoPlayerUI(
}
}
fun closeFullscreen(viewModel: VideoPlayerViewModel, activity: Activity) {
val return_fullscreen_intent = Intent()
var uiState = viewModel.uiState.value
uiState.fullscreen = false
return_fullscreen_intent.putExtra(VIDEOPLAYER_UI_STATE, uiState)
activity.setResult(0, return_fullscreen_intent)
activity.finish()
}
fun openFullscreen(
viewModel: VideoPlayerViewModel,
activity: Activity,
fullscreenLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>
) {
val fullscreen_activity_intent =
Intent(activity!!.findActivity(), VideoPlayerActivity::class.java)
var uiState = viewModel.uiState.value
uiState.fullscreen = true
fullscreen_activity_intent.putExtra(VIDEOPLAYER_UI_STATE, uiState)
fullscreenLauncher.launch(fullscreen_activity_intent)
}
@Preview(device = "spec:width=1080px,height=700px,dpi=440,orientation=landscape")
@Composable
fun PlayerUIPreviewEmbeded() {

View File

@ -19,7 +19,7 @@
[versions]
agp = "8.5.0"
kotlin = "2.0.0"
kotlin = "2.0.20-Beta2"
coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.2.1"
@ -30,16 +30,16 @@ androidx = "1.9.0"
constraintlayout = "2.1.4"
material3 = "1.2.1"
uiTooling = "1.6.8"
materialIconsExtendedAndroid = "1.7.0-beta04"
materialIconsExtendedAndroid = "1.7.0-beta05"
media3Ui = "1.4.0-beta01"
hiltAndroid = "2.51.1"
hiltCompiler = "1.2.0"
hiltNavigationCompose = "1.2.0"
lifecycleViewmodelCompose = "2.8.3"
kspVersion = "1.9.0-1.0.13"
kspVersion = "2.0.20-Beta2-1.0.23"
fragmentKtx = "1.8.1"
lifecycleRuntimeKtx = "2.8.3"
composeBom = "2024.04.01"
composeBom = "2024.06.01"
kotlinParcelize = "2.0.20-Beta2"
[libraries]
@ -65,7 +65,7 @@ androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "l
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "uiTooling" }
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version = "2024.06.00" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }