fix project layout and don't use fullscreen acitvity anymore
This commit is contained in:
parent
b111b77e04
commit
356744814c
|
@ -20,34 +20,21 @@
|
||||||
|
|
||||||
package net.newpipe.newplayer
|
package net.newpipe.newplayer
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.fragment.app.FragmentContainer
|
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||||
import androidx.fragment.app.FragmentContainerView
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import net.newpipe.newplayer.internal.VideoPlayerFragment
|
import net.newpipe.newplayer.model.VideoPlayerViewModel
|
||||||
|
import net.newpipe.newplayer.ui.VideoPlayerUI
|
||||||
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class VideoPlayerView : FrameLayout {
|
class VideoPlayerView : FrameLayout {
|
||||||
|
|
||||||
val videoPlayerFragment:VideoPlayerFragment
|
var viewModel: VideoPlayerViewModel? = null
|
||||||
|
|
||||||
var maxLayoutRatio: Float
|
|
||||||
get() = videoPlayerFragment.maxLayoutRatio
|
|
||||||
set(value) {videoPlayerFragment.maxLayoutRatio=value}
|
|
||||||
|
|
||||||
|
|
||||||
var minLayoutRatio: Float
|
|
||||||
get() = videoPlayerFragment.minLayoutRatio
|
|
||||||
set(value) {videoPlayerFragment.minLayoutRatio = value}
|
|
||||||
|
|
||||||
var fullScreenToggleListener: FullScreenToggleListener?
|
|
||||||
set(value) {videoPlayerFragment.fullScreenToggleListener = value}
|
|
||||||
get() = videoPlayerFragment.fullScreenToggleListener
|
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -56,21 +43,16 @@ class VideoPlayerView : FrameLayout {
|
||||||
defStyleAttr: Int = 0
|
defStyleAttr: Int = 0
|
||||||
) : super(context, attrs, defStyleAttr) {
|
) : super(context, attrs, defStyleAttr) {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.video_player_view, this)
|
val view = LayoutInflater.from(context).inflate(R.layout.video_player_view, this)
|
||||||
|
val composeView = view.findViewById<ComposeView>(R.id.video_player_compose_view)
|
||||||
|
|
||||||
videoPlayerFragment = VideoPlayerFragment()
|
composeView.apply {
|
||||||
when (context) {
|
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||||
is AppCompatActivity -> {
|
setContent {
|
||||||
context.supportFragmentManager.beginTransaction()
|
VideoPlayerTheme {
|
||||||
.add(R.id.video_player_fragment_container, videoPlayerFragment).commit()
|
VideoPlayerUI(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
|
||||||
throw Exception("The context that should host the NewPlayer Embedded VideoPlayerView is not an AppCompatActivity: $context")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
interface FullScreenToggleListener {
|
|
||||||
fun fullscreenToggle(turnOn: Boolean)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,125 +0,0 @@
|
||||||
/* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.newpipe.newplayer.internal
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.compose.ui.platform.ComposeView
|
|
||||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import net.newpipe.newplayer.NewPlayer
|
|
||||||
import net.newpipe.newplayer.R
|
|
||||||
import net.newpipe.newplayer.VideoPlayerView
|
|
||||||
import net.newpipe.newplayer.internal.model.VideoPlayerViewModel
|
|
||||||
import net.newpipe.newplayer.internal.model.VideoPlayerViewModelImpl
|
|
||||||
import net.newpipe.newplayer.internal.ui.VideoPlayerUI
|
|
||||||
import net.newpipe.newplayer.internal.ui.theme.VideoPlayerTheme
|
|
||||||
|
|
||||||
private const val TAG = "VideoPlayerFragment"
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class VideoPlayerFragment() : Fragment() {
|
|
||||||
|
|
||||||
private val viewModel: VideoPlayerViewModel by viewModels<VideoPlayerViewModelImpl>()
|
|
||||||
private var currentVideoRatio = 0F
|
|
||||||
private lateinit var composeView: ComposeView
|
|
||||||
|
|
||||||
var fullScreenToggleListener: VideoPlayerView.FullScreenToggleListener? = null
|
|
||||||
|
|
||||||
var minLayoutRatio = 4F / 3F
|
|
||||||
set(value) {
|
|
||||||
if (value <= 0 && maxLayoutRatio < minLayoutRatio)
|
|
||||||
Log.e(
|
|
||||||
TAG,
|
|
||||||
"minLayoutRatio can not be 0 or smaller or bigger then maxLayoutRatio. Ignore: $value"
|
|
||||||
)
|
|
||||||
else {
|
|
||||||
field = value
|
|
||||||
updateViewRatio()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxLayoutRatio = 16F / 9F
|
|
||||||
set(value) {
|
|
||||||
if (value <= 0 && value < minLayoutRatio)
|
|
||||||
Log.e(
|
|
||||||
TAG,
|
|
||||||
"maxLayoutRatio can not be 0 smaller ans smaller then minLayoutRatio. Ignore: $value"
|
|
||||||
)
|
|
||||||
else {
|
|
||||||
field = value
|
|
||||||
updateViewRatio()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
val window = activity?.window!!
|
|
||||||
val insetsController = WindowCompat.getInsetsController(window, window.decorView)
|
|
||||||
insetsController.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
|
|
||||||
val view = inflater.inflate(R.layout.video_player_framgent, container, false)
|
|
||||||
composeView = view.findViewById(R.id.player_copose_view)
|
|
||||||
|
|
||||||
viewModel.listener = object : VideoPlayerViewModel.Listener {
|
|
||||||
override fun requestUpdateLayoutRatio(videoRatio: Float) {
|
|
||||||
currentVideoRatio = videoRatio
|
|
||||||
updateViewRatio()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
composeView.apply {
|
|
||||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
|
||||||
setContent {
|
|
||||||
VideoPlayerTheme {
|
|
||||||
VideoPlayerUI(viewModel = viewModel,
|
|
||||||
{fullscreenOn ->
|
|
||||||
fullScreenToggleListener?.fullscreenToggle(fullscreenOn)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateViewRatio() {
|
|
||||||
if(this::composeView.isInitialized) {
|
|
||||||
composeView.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
|
||||||
val ratio = currentVideoRatio.coerceIn(minLayoutRatio, maxLayoutRatio)
|
|
||||||
dimensionRatio = "$ratio:1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
/* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.newpipe.newplayer.internal.ui
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.ActivityInfo
|
|
||||||
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.contract.ActivityResultContracts
|
|
||||||
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
|
|
||||||
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.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
|
||||||
import net.newpipe.newplayer.internal.model.VideoPlayerViewModel
|
|
||||||
import net.newpipe.newplayer.internal.model.VideoPlayerViewModelImpl
|
|
||||||
import net.newpipe.newplayer.internal.ui.theme.VideoPlayerTheme
|
|
||||||
import net.newpipe.newplayer.internal.utils.LockScreenOrientation
|
|
||||||
import net.newpipe.newplayer.internal.utils.findActivity
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun VideoPlayerUI(
|
|
||||||
viewModel: VideoPlayerViewModel,
|
|
||||||
fullscreenToggled: (Boolean) -> Unit
|
|
||||||
) {
|
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
|
||||||
|
|
||||||
var lifecycle by remember {
|
|
||||||
mutableStateOf(Lifecycle.Event.ON_CREATE)
|
|
||||||
}
|
|
||||||
|
|
||||||
val activity = LocalContext.current.findActivity()
|
|
||||||
|
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
|
||||||
|
|
||||||
// Prepare stuff for the SurfaceView to which the video will be rendered
|
|
||||||
DisposableEffect(lifecycleOwner) {
|
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
|
||||||
lifecycle = event
|
|
||||||
}
|
|
||||||
lifecycleOwner.lifecycle.addObserver(observer)
|
|
||||||
|
|
||||||
onDispose {
|
|
||||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Handle Fullscreen/Embedded view transition
|
|
||||||
if(uiState.fullscreen) {
|
|
||||||
BackHandler {
|
|
||||||
//closeFullscreen(viewModel, activity!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val fullscreenLauncher =
|
|
||||||
rememberLauncherForActivityResult(
|
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
|
||||||
) { result ->
|
|
||||||
println("gurken returned for result")
|
|
||||||
viewModel.initUIState(result.data?.extras!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = uiState.fullscreen) {
|
|
||||||
println("gurken launch fullscreen: ${uiState.fullscreen}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set Screen Rotation
|
|
||||||
if(uiState.fullscreen) {
|
|
||||||
if(uiState.contentRatio < 1) {
|
|
||||||
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
|
||||||
} else {
|
|
||||||
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set UI
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
color = Color.Black
|
|
||||||
) {
|
|
||||||
AndroidView(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
factory = { context ->
|
|
||||||
SurfaceView(context).also { view ->
|
|
||||||
viewModel.player?.setVideoSurfaceView(view)
|
|
||||||
}
|
|
||||||
}, update = { view ->
|
|
||||||
when (lifecycle) {
|
|
||||||
Lifecycle.Event.ON_RESUME -> {
|
|
||||||
viewModel.player?.setVideoSurfaceView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
VideoPlayerControllerUI(
|
|
||||||
isPlaying = uiState.playing,
|
|
||||||
fullscreen = uiState.fullscreen,
|
|
||||||
play = viewModel::play,
|
|
||||||
pause = viewModel::pause,
|
|
||||||
prevStream = viewModel::prevStream,
|
|
||||||
nextStream = viewModel::nextStream,
|
|
||||||
switchToFullscreen = viewModel::switchToFullscreen,
|
|
||||||
switchToEmbeddedView = viewModel::switchToEmbeddedView
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(device = "spec:width=1080px,height=700px,dpi=440,orientation=landscape")
|
|
||||||
@Composable
|
|
||||||
fun PlayerUIPreviewEmbeded() {
|
|
||||||
VideoPlayerTheme {
|
|
||||||
VideoPlayerUI(viewModel = VideoPlayerViewModelImpl.dummy, {})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.internal.model
|
package net.newpipe.newplayer.model
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -27,18 +27,14 @@ import android.os.Parcelable
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
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 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 kotlinx.coroutines.launch
|
import net.newpipe.newplayer.utils.VideoSize
|
||||||
import net.newpipe.newplayer.internal.utils.VideoSize
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import net.newpipe.newplayer.NewPlayer
|
import net.newpipe.newplayer.NewPlayer
|
||||||
|
|
||||||
|
@ -87,7 +83,6 @@ interface VideoPlayerViewModel {
|
||||||
class VideoPlayerViewModelImpl @Inject constructor(
|
class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
private val savedStateHandle: SavedStateHandle,
|
private val savedStateHandle: SavedStateHandle,
|
||||||
application: Application,
|
application: Application,
|
||||||
override var newPlayer: NewPlayer?
|
|
||||||
) : AndroidViewModel(application), VideoPlayerViewModel {
|
) : AndroidViewModel(application), VideoPlayerViewModel {
|
||||||
|
|
||||||
// private
|
// private
|
||||||
|
@ -95,17 +90,18 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
private var current_video_size = VideoSize.DEFAULT
|
private var current_video_size = VideoSize.DEFAULT
|
||||||
|
|
||||||
//interface
|
//interface
|
||||||
|
override var newPlayer: NewPlayer? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
installExoPlayer()
|
||||||
|
}
|
||||||
|
|
||||||
override val uiState = mutableUiState.asStateFlow()
|
override val uiState = mutableUiState.asStateFlow()
|
||||||
override var listener: VideoPlayerViewModel.Listener? = null
|
override var listener: VideoPlayerViewModel.Listener? = null
|
||||||
|
|
||||||
override val player:Player?
|
override val player:Player?
|
||||||
get() = newPlayer?.player
|
get() = newPlayer?.player
|
||||||
|
|
||||||
init {
|
|
||||||
installExoPlayer()
|
|
||||||
println("gurken reinit ViewModel")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun installExoPlayer() {
|
private fun installExoPlayer() {
|
||||||
player?.let { player ->
|
player?.let { player ->
|
||||||
player.addListener(object : Player.Listener {
|
player.addListener(object : Player.Listener {
|
||||||
|
@ -145,6 +141,11 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
println("gurken viewmodel cleared")
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||||
override fun initUIState(instanceState: Bundle) {
|
override fun initUIState(instanceState: Bundle) {
|
||||||
|
|
|
@ -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.internal.ui
|
package net.newpipe.newplayer.ui
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
@ -78,8 +78,8 @@ import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import net.newpipe.newplayer.R
|
import net.newpipe.newplayer.R
|
||||||
import net.newpipe.newplayer.internal.ui.theme.VideoPlayerTheme
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
import net.newpipe.newplayer.internal.ui.theme.video_player_onSurface
|
import net.newpipe.newplayer.ui.theme.video_player_onSurface
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoPlayerControllerUI(
|
fun VideoPlayerControllerUI(
|
||||||
|
@ -392,7 +392,7 @@ fun PreviewBackgroundSurface(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(device = "spec:width=1080px,height=700px,dpi=440,orientation=landscape")
|
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoPlayerControllerUIPreviewEmbeded() {
|
fun VideoPlayerControllerUIPreviewEmbeded() {
|
||||||
VideoPlayerTheme {
|
VideoPlayerTheme {
|
|
@ -0,0 +1,36 @@
|
||||||
|
package net.newpipe.newplayer.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoPlayerLoadingPlaceholder() {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(200.dp),
|
||||||
|
color = Color.Black
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.width(64.dp).align((Alignment.Center)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun VideoPlayerLoaidingPlaceholderPreview() {
|
||||||
|
VideoPlayerTheme {
|
||||||
|
VideoPlayerLoadingPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
/* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.newpipe.newplayer.ui
|
||||||
|
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.view.SurfaceView
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
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
|
||||||
|
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.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
|
import net.newpipe.newplayer.model.VideoPlayerViewModel
|
||||||
|
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
|
||||||
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
|
import net.newpipe.newplayer.utils.LockScreenOrientation
|
||||||
|
import net.newpipe.newplayer.utils.findActivity
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoPlayerUI(
|
||||||
|
viewModel: VideoPlayerViewModel?,
|
||||||
|
) {
|
||||||
|
if (viewModel?.player == null) {
|
||||||
|
VideoPlayerLoadingPlaceholder()
|
||||||
|
} else {
|
||||||
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
|
var lifecycle by remember {
|
||||||
|
mutableStateOf(Lifecycle.Event.ON_CREATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
|
// Prepare stuff for the SurfaceView to which the video will be rendered
|
||||||
|
DisposableEffect(lifecycleOwner) {
|
||||||
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
|
lifecycle = event
|
||||||
|
}
|
||||||
|
lifecycleOwner.lifecycle.addObserver(observer)
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Handle Fullscreen/Embedded view transition
|
||||||
|
if (uiState.fullscreen) {
|
||||||
|
BackHandler {
|
||||||
|
//closeFullscreen(viewModel, activity!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fullscreenLauncher =
|
||||||
|
rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
println("gurken returned for result")
|
||||||
|
viewModel.initUIState(result.data?.extras!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = uiState.fullscreen) {
|
||||||
|
println("gurken launch fullscreen: ${uiState.fullscreen}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Screen Rotation
|
||||||
|
if (uiState.fullscreen) {
|
||||||
|
if (uiState.contentRatio < 1) {
|
||||||
|
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
||||||
|
} else {
|
||||||
|
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set UI
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = Color.Black
|
||||||
|
) {
|
||||||
|
AndroidView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
factory = { context ->
|
||||||
|
SurfaceView(context).also { view ->
|
||||||
|
viewModel.player?.setVideoSurfaceView(view)
|
||||||
|
}
|
||||||
|
}, update = { view ->
|
||||||
|
when (lifecycle) {
|
||||||
|
Lifecycle.Event.ON_RESUME -> {
|
||||||
|
viewModel.player?.setVideoSurfaceView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
VideoPlayerControllerUI(
|
||||||
|
isPlaying = uiState.playing,
|
||||||
|
fullscreen = uiState.fullscreen,
|
||||||
|
play = viewModel::play,
|
||||||
|
pause = viewModel::pause,
|
||||||
|
prevStream = viewModel::prevStream,
|
||||||
|
nextStream = viewModel::nextStream,
|
||||||
|
switchToFullscreen = viewModel::switchToFullscreen,
|
||||||
|
switchToEmbeddedView = viewModel::switchToEmbeddedView
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=1080px,height=700px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun PlayerUIPreviewEmbeded() {
|
||||||
|
VideoPlayerTheme {
|
||||||
|
VideoPlayerUI(viewModel = VideoPlayerViewModelImpl.dummy)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.internal.ui.theme
|
package net.newpipe.newplayer.ui.theme
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
|
@ -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.internal.ui.theme
|
package net.newpipe.newplayer.ui.theme
|
||||||
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
|
@ -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.internal.ui.theme
|
package net.newpipe.newplayer.ui.theme
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
|
@ -1,4 +1,4 @@
|
||||||
package net.newpipe.newplayer.internal.utils
|
package net.newpipe.newplayer.utils
|
||||||
|
|
||||||
data class VideoSize(
|
data class VideoSize(
|
||||||
val width: Int,
|
val width: Int,
|
|
@ -18,19 +18,14 @@
|
||||||
* 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.internal.utils
|
package net.newpipe.newplayer.utils
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
|
||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
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 net.newpipe.newplayer.internal.model.VIDEOPLAYER_UI_STATE
|
|
||||||
import net.newpipe.newplayer.internal.model.VideoPlayerViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LockScreenOrientation(orientation: Int) {
|
fun LockScreenOrientation(orientation: Int) {
|
|
@ -17,10 +17,8 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
<androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<androidx.fragment.app.FragmentContainerView
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:id="@+id/video_player_compose_view"
|
||||||
android:id="@+id/video_player_fragment_container"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
android:minHeight="50dp" />
|
|
|
@ -20,20 +20,25 @@
|
||||||
|
|
||||||
package net.newpipe.newplayer.testapp
|
package net.newpipe.newplayer.testapp
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import net.newpipe.newplayer.NewPlayer
|
import net.newpipe.newplayer.NewPlayer
|
||||||
import net.newpipe.newplayer.VideoPlayerView
|
import net.newpipe.newplayer.VideoPlayerView
|
||||||
|
import net.newpipe.newplayer.model.VideoPlayerViewModel
|
||||||
|
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
val videoPlayerViewModel: VideoPlayerViewModel by viewModels<VideoPlayerViewModelImpl>()
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var newPlayer: NewPlayer
|
lateinit var newPlayer: NewPlayer
|
||||||
|
|
||||||
|
@ -43,7 +48,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
val video_view = findViewById<VideoPlayerView>(R.id.new_player_video_view)
|
val video_view = findViewById<VideoPlayerView>(R.id.new_player_video_view)
|
||||||
|
video_view.viewModel = videoPlayerViewModel
|
||||||
|
videoPlayerViewModel.newPlayer = newPlayer
|
||||||
|
|
||||||
|
/*
|
||||||
video_view.fullScreenToggleListener = object : VideoPlayerView.FullScreenToggleListener {
|
video_view.fullScreenToggleListener = object : VideoPlayerView.FullScreenToggleListener {
|
||||||
override fun fullscreenToggle(turnOn: Boolean) {
|
override fun fullscreenToggle(turnOn: Boolean) {
|
||||||
if (turnOn) {
|
if (turnOn) {
|
||||||
|
@ -70,7 +78,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
newPlayer.playWhenReady = true
|
newPlayer.playWhenReady = true
|
||||||
newPlayer.setStream(getString(R.string.ccc_chromebooks_video))
|
newPlayer.setStream(getString(R.string.ccc_chromebooks_video))
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
android:id="@+id/new_player_video_view"
|
android:id="@+id/new_player_video_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="50dp"
|
android:minHeight="200dp"
|
||||||
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"
|
||||||
|
|
Loading…
Reference in New Issue