experimental fullscreen videoplayeractivity

This commit is contained in:
Christian Schabesberger 2024-07-17 13:21:02 +02:00
parent 66b49e225a
commit 0d85401cdd
14 changed files with 131 additions and 57 deletions

View File

@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-07-17T11:02:47.259317096Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/schabi/.android/avd/Medium_Phone_API_33.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

View File

@ -46,6 +46,9 @@ android {
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
@ -64,6 +67,11 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
@ -83,6 +91,15 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.foundation)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
ksp(libs.hilt.android.compiler)
ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose)

View File

@ -1,27 +1,9 @@
<?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/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".NewPlayerApp"
android:allowBackup="true"
@ -33,6 +15,11 @@
android:supportsRtl="true"
android:theme="@style/Theme.NewPlayer"
tools:targetApi="31">
<activity
android:name=".VideoPlayerActivity"
android:exported="false"
android:label="@string/title_activity_video_player"
android:theme="@style/noAnimTheme" />
<activity
android:name=".MainActivity"
android:exported="true">

View File

@ -0,0 +1,28 @@
package net.newpipe.newplayer
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import dagger.hilt.android.AndroidEntryPoint
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
import net.newpipe.newplayer.ui.VideoPlayerUI
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
@AndroidEntryPoint
class VideoPlayerActivity : ComponentActivity() {
private val viewModel: VideoPlayerViewModel by viewModels<VideoPlayerViewModelImpl>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
VideoPlayerTheme {
VideoPlayerUI(viewModel = viewModel, isFullscreen = true)
}
}
}
}

View File

@ -28,12 +28,14 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(ViewModelComponent::class)
@InstallIn(SingletonComponent::class)
object VideoPlayerComponent {
@Provides
@ViewModelScoped
@Singleton
fun provideVideoPlayer(app: Application) : Player {
return ExoPlayer.Builder(app).build()
}

View File

@ -80,7 +80,7 @@ class VideoPlayerFragment() : Fragment() {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
VideoPlayerTheme {
VideoPlayerUI(viewModel)
VideoPlayerUI(viewModel = viewModel, isFullscreen = false)
}
}
}

View File

@ -21,6 +21,7 @@
package net.newpipe.newplayer.model
import android.app.Application
import android.content.Intent
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.media3.common.MediaItem
@ -32,20 +33,15 @@ 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 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(
playing = false,
fullscreen = false,
uiVissible = false,
0F
playing = false, fullscreen = false, uiVissible = false, 0F
)
}
}
@ -88,7 +84,12 @@ class VideoPlayerViewModelImpl @Inject constructor(
var current_video_size = VideoSize.DEFAULT
init {
player.prepare()
println("gurken $this")
if (player.playbackState == Player.STATE_IDLE) {
player.prepare()
}
player.addListener(object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
@ -109,13 +110,13 @@ class VideoPlayerViewModelImpl @Inject constructor(
val videoSize = VideoSize.fromMedia3VideoSize(media3VideoSize)
if(current_video_size != videoSize) {
if (current_video_size != videoSize) {
val newRatio = videoSize.getRatio()
if(current_video_size.getRatio() != newRatio) {
if (current_video_size.getRatio() != newRatio) {
mutableUiState.update {
it.copy(contentRatio = newRatio)
}
if(!mutableUiState.value.fullscreen) {
if (!mutableUiState.value.fullscreen) {
listener?.requestUpdateLayoutRatio(newRatio)
}
}
@ -125,17 +126,15 @@ class VideoPlayerViewModelImpl @Inject constructor(
})
player.setMediaItem(MediaItem.fromUri(app.getString(R.string.ccc_6502_video)))
player.playWhenReady = true
//player.playWhenReady = true
}
override fun play() {
println("gurken Play")
player.play()
//player.play()
}
override fun pause() {
println("gurken pause")
player.pause()
//player.pause()
}
override fun prevStream() {
@ -156,7 +155,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
mutableUiState.update {
it.copy(fullscreen = true)
}
listener?.switchToFullscreen()
//listener?.switchToFullscreen()
}
override fun onCleared() {

View File

@ -82,11 +82,12 @@ import androidx.compose.ui.unit.sp
import net.newpipe.newplayer.R
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.theme.video_player_onSurface
import net.newpipe.newplayer.utils.LockScreenOrientation
@Composable
fun VideoPlayerControllerUI(
isPlaying: Boolean,
isFullscreen: Boolean,
fullscreen: Boolean,
play: () -> Unit,
pause: () -> Unit,
prevStream: () -> Unit,
@ -98,7 +99,7 @@ fun VideoPlayerControllerUI(
modifier = Modifier.fillMaxSize(), color = Color(0x75000000)
) {
Box(
modifier = if (isFullscreen) {
modifier = if (fullscreen) {
Modifier
.background(Color.Transparent)
.windowInsetsPadding(WindowInsets.systemBars)
@ -128,13 +129,13 @@ fun VideoPlayerControllerUI(
.padding(start = 16.dp, end = 16.dp)
.defaultMinSize(minHeight = 40.dp)
.fillMaxWidth(),
isFullscreen = isFullscreen,
isFullscreen = fullscreen,
switchToFullscreen,
switchToEmbeddedView
)
}
}
if (isFullscreen) {
if (fullscreen) {
BackHandler {
switchToEmbeddedView()
}
@ -404,7 +405,7 @@ fun VideoPlayerControllerUIPreviewEmbeded() {
VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI(isPlaying = false,
isFullscreen = false,
fullscreen = false,
play = {},
pause = {},
prevStream = {},
@ -421,7 +422,7 @@ fun VideoPlayerControllerUIPreviewLandscape() {
VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI(isPlaying = true,
isFullscreen = true,
fullscreen = true,
play = {},
pause = {},
prevStream = {},
@ -439,7 +440,7 @@ fun VideoPlayerControllerUIPreviewPortrait() {
PreviewBackgroundSurface {
VideoPlayerControllerUI(
isPlaying = false,
isFullscreen = true,
fullscreen = true,
play = {},
pause = {},
prevStream = {},

View File

@ -20,6 +20,7 @@
package net.newpipe.newplayer.ui
import android.content.Intent
import android.view.SurfaceView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
@ -32,21 +33,23 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import net.newpipe.newplayer.VideoPlayerActivity
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.utils.findActivity
@Composable
fun VideoPlayerUI(
viewModel: VideoPlayerViewModel
viewModel: VideoPlayerViewModel,
isFullscreen: Boolean
) {
val uiState by viewModel.uiState.collectAsState()
var lifecycle by remember {
@ -65,6 +68,22 @@ fun VideoPlayerUI(
}
}
var fullscreen_requested by remember {
mutableStateOf(false)
}
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()
}
}
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.Black
@ -73,7 +92,7 @@ fun VideoPlayerUI(
modifier = Modifier.fillMaxSize(),
factory = { context ->
SurfaceView(context).also {
viewModel.player?.setVideoSurfaceView(it)
//viewModel.player?.setVideoSurfaceView(it)
}
}, update = {
when (lifecycle) {
@ -90,7 +109,7 @@ fun VideoPlayerUI(
println("is Player playing: $isPlaying")
VideoPlayerControllerUI(
isPlaying = uiState.playing,
isFullscreen = uiState.fullscreen,
fullscreen = uiState.fullscreen,
play = viewModel::play,
pause = viewModel::pause,
prevStream = viewModel::prevStream,
@ -105,6 +124,6 @@ fun VideoPlayerUI(
@Composable
fun PlayerUIPreviewEmbeded() {
VideoPlayerTheme {
VideoPlayerUI(viewModel = VideoPlayerViewModelImpl.dummy)
VideoPlayerUI(viewModel = VideoPlayerViewModelImpl.dummy, isFullscreen = false)
}
}

View File

@ -18,7 +18,7 @@
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
*/
package net.newpipe.newplayer.ui
package net.newpipe.newplayer.utils
import android.app.Activity
import android.content.Context
@ -26,7 +26,6 @@ import android.content.ContextWrapper
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalContext
import androidx.core.view.WindowCompat
@Composable
fun LockScreenOrientation(orientation: Int) {

View File

@ -36,7 +36,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:name="net.newpipe.newplayer.PlayerFragment"
android:name="net.newpipe.newplayer.VideoPlayerFragment"
/>
<TextView

View File

@ -35,4 +35,5 @@
<string name="widget_description_toggle_fullscreen">Toggle fullscreen</string>
<string name="widget_description_chapter_selection">Chapter selection</string>
<string name="widget_descriptoin_playlist_item_selection">Playlist item selection</string>
<string name="title_activity_video_player">VideoPlayerActivity</string>
</resources>

View File

@ -26,4 +26,8 @@
</style>
<style name="Theme.NewPlayer" parent="Base.Theme.NewPlayer" />
<style name="noAnimTheme" parent="Base.Theme.NewPlayer">
<item name="android:windowAnimationStyle">@null</item>
</style>
</resources>

View File

@ -39,6 +39,8 @@ lifecycleViewmodelCompose = "2.8.3"
kspVersion = "1.9.0-1.0.13"
runtimeLivedata = "1.7.0-beta04"
fragmentKtx = "1.8.1"
lifecycleRuntimeKtx = "2.8.3"
composeBom = "2024.04.01"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -62,6 +64,13 @@ hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-comp
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
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-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" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }