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> <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">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/schabi/.android/avd/Medium_Phone_API_33.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

View File

@ -46,6 +46,9 @@ android {
versionName = "1.0" versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
} }
buildTypes { buildTypes {
@ -64,6 +67,11 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" 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.lifecycle.viewmodel.compose)
implementation(libs.androidx.foundation) implementation(libs.androidx.foundation)
implementation(libs.androidx.fragment.ktx) 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.hilt.android.compiler)
ksp(libs.androidx.hilt.compiler) ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.hilt.navigation.compose)

View File

@ -1,27 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?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" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".NewPlayerApp" android:name=".NewPlayerApp"
android:allowBackup="true" android:allowBackup="true"
@ -33,6 +15,11 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.NewPlayer" android:theme="@style/Theme.NewPlayer"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".VideoPlayerActivity"
android:exported="false"
android:label="@string/title_activity_video_player"
android:theme="@style/noAnimTheme" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"> 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.InstallIn
import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped import dagger.hilt.android.scopes.ViewModelScoped
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module @Module
@InstallIn(ViewModelComponent::class) @InstallIn(SingletonComponent::class)
object VideoPlayerComponent { object VideoPlayerComponent {
@Provides @Provides
@ViewModelScoped @Singleton
fun provideVideoPlayer(app: Application) : Player { fun provideVideoPlayer(app: Application) : Player {
return ExoPlayer.Builder(app).build() return ExoPlayer.Builder(app).build()
} }

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@
package net.newpipe.newplayer.ui package net.newpipe.newplayer.ui
import android.content.Intent
import android.view.SurfaceView import android.view.SurfaceView
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@ -32,21 +33,23 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
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
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
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()
var lifecycle by remember { 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( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = Color.Black color = Color.Black
@ -73,7 +92,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) {
@ -90,7 +109,7 @@ fun VideoPlayerUI(
println("is Player playing: $isPlaying") println("is Player playing: $isPlaying")
VideoPlayerControllerUI( VideoPlayerControllerUI(
isPlaying = uiState.playing, isPlaying = uiState.playing,
isFullscreen = uiState.fullscreen, fullscreen = uiState.fullscreen,
play = viewModel::play, play = viewModel::play,
pause = viewModel::pause, pause = viewModel::pause,
prevStream = viewModel::prevStream, prevStream = viewModel::prevStream,
@ -105,6 +124,6 @@ fun VideoPlayerUI(
@Composable @Composable
fun PlayerUIPreviewEmbeded() { fun PlayerUIPreviewEmbeded() {
VideoPlayerTheme { 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/>. * 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.app.Activity
import android.content.Context import android.content.Context
@ -26,7 +26,6 @@ import android.content.ContextWrapper
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.core.view.WindowCompat
@Composable @Composable
fun LockScreenOrientation(orientation: Int) { fun LockScreenOrientation(orientation: Int) {

View File

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

View File

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

View File

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

View File

@ -39,6 +39,8 @@ lifecycleViewmodelCompose = "2.8.3"
kspVersion = "1.9.0-1.0.13" kspVersion = "1.9.0-1.0.13"
runtimeLivedata = "1.7.0-beta04" runtimeLivedata = "1.7.0-beta04"
fragmentKtx = "1.8.1" fragmentKtx = "1.8.1"
lifecycleRuntimeKtx = "2.8.3"
composeBom = "2024.04.01"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 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-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-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "uiTooling" }
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }