From d526527e9499348b5fa24a33ffd81969cf13d332 Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Fri, 19 Jul 2024 13:41:38 +0200 Subject: [PATCH] start implementing NewPlayer interface --- .../java/net/newpipe/newplayer/NewPlayer.kt | 36 ++++++ .../net/newpipe/newplayer/VideoPlayerView.kt | 6 +- .../newplayer/internal/VideoPlayerFragment.kt | 26 ++++- .../internal/model/VideoPlayerViewModel.kt | 107 ++++++++---------- .../newplayer/internal/ui/VideoPlayerUI.kt | 9 +- .../src/main/res/values/test_streams.xml | 27 ----- .../newpipe/newplayer/testapp/MainActivity.kt | 11 +- .../newplayer/testapp/NewPlayerComponent.kt | 6 +- test-app/src/main/res/values/test_streams.xml | 27 ++++- 9 files changed, 148 insertions(+), 107 deletions(-) delete mode 100644 new-player/src/main/res/values/test_streams.xml rename new-player/src/main/java/net/newpipe/newplayer/internal/VideoPlayerComponent.kt => test-app/src/main/java/net/newpipe/newplayer/testapp/NewPlayerComponent.kt (88%) diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt index c198b94..8fd7abe 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt @@ -21,12 +21,21 @@ package net.newpipe.newplayer import android.app.Application +import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer interface NewPlayer { val player: Player + var playWhenReady: Boolean + + fun prepare() + fun play() + fun pause() + + //TODO: This is only temporary + fun setStream(uri: String) data class Builder(val app: Application) { fun build(): NewPlayer { @@ -37,4 +46,31 @@ interface NewPlayer { class NewPlayerImpl(internal_player: Player) : NewPlayer { override val player = internal_player + + override var playWhenReady: Boolean + set(value) { + player.playWhenReady = value + } + get() = player.playWhenReady + + override fun prepare() { + player.prepare() + } + + override fun play() { + player.play() + } + + override fun pause() { + player.pause() + } + + + override fun setStream(uri: String) { + if (player.playbackState == Player.STATE_IDLE) { + player.prepare() + } + + player.setMediaItem(MediaItem.fromUri(uri)) + } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/VideoPlayerView.kt b/new-player/src/main/java/net/newpipe/newplayer/VideoPlayerView.kt index 9fc3f68..18bd892 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/VideoPlayerView.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/VideoPlayerView.kt @@ -43,7 +43,11 @@ class VideoPlayerView : FrameLayout { var minLayoutRatio: Float get() = videoPlayerFragment.minLayoutRatio - set(value) {videoPlayerFragment.maxLayoutRatio = value} + set(value) {videoPlayerFragment.minLayoutRatio = value} + + var newPlayer:NewPlayer? + set(value) {videoPlayerFragment.newPlayer = value} + get() = videoPlayerFragment.newPlayer @JvmOverloads constructor( diff --git a/new-player/src/main/java/net/newpipe/newplayer/internal/VideoPlayerFragment.kt b/new-player/src/main/java/net/newpipe/newplayer/internal/VideoPlayerFragment.kt index ba1802e..1859966 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/internal/VideoPlayerFragment.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/internal/VideoPlayerFragment.kt @@ -34,6 +34,7 @@ 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.internal.model.VideoPlayerViewModel import net.newpipe.newplayer.internal.model.VideoPlayerViewModelImpl @@ -48,6 +49,16 @@ class VideoPlayerFragment() : Fragment() { private var currentVideoRatio = 0F private lateinit var composeView: ComposeView + var newPlayer: NewPlayer? = null + set(value) { + if(context != null) { + viewModel.newPlayer = value + } else { + field = value + } + } + get() = viewModel.newPlayer ?: field + var minLayoutRatio = 4F / 3F set(value) { if (value <= 0 && maxLayoutRatio < minLayoutRatio) @@ -87,6 +98,11 @@ class VideoPlayerFragment() : Fragment() { val view = inflater.inflate(R.layout.video_player_framgent, container, false) composeView = view.findViewById(R.id.player_copose_view) + // late init player in case player was set before fragment was attached to a context + if (viewModel.newPlayer == null) { + viewModel.newPlayer = newPlayer + } + viewModel.listener = object : VideoPlayerViewModel.Listener { override fun requestUpdateLayoutRatio(videoRatio: Float) { currentVideoRatio = videoRatio @@ -103,15 +119,15 @@ class VideoPlayerFragment() : Fragment() { } } - viewModel.preparePlayer() - return view } private fun updateViewRatio() { - composeView.updateLayoutParams { - val ratio = currentVideoRatio.coerceIn(minLayoutRatio, maxLayoutRatio) - dimensionRatio = "$ratio:1" + if(this::composeView.isInitialized) { + composeView.updateLayoutParams { + val ratio = currentVideoRatio.coerceIn(minLayoutRatio, maxLayoutRatio) + dimensionRatio = "$ratio:1" + } } } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/internal/model/VideoPlayerViewModel.kt b/new-player/src/main/java/net/newpipe/newplayer/internal/model/VideoPlayerViewModel.kt index a1f64ff..4dcb204 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/internal/model/VideoPlayerViewModel.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/internal/model/VideoPlayerViewModel.kt @@ -28,13 +28,11 @@ import androidx.annotation.RequiresApi import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import androidx.media3.common.MediaItem import androidx.media3.common.Player import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow -import net.newpipe.newplayer.R import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -61,14 +59,13 @@ data class VideoPlayerUIState( } interface VideoPlayerViewModel { - val new_player: NewPlayer? + var newPlayer: NewPlayer? val player: Player? val uiState: StateFlow var listener: Listener? val events: SharedFlow? fun initUIState(instanceState: Bundle) - fun preparePlayer() fun play() fun pause() fun prevStream() @@ -90,12 +87,10 @@ interface VideoPlayerViewModel { @HiltViewModel class VideoPlayerViewModelImpl @Inject constructor( private val savedStateHandle: SavedStateHandle, - override val new_player: NewPlayer, application: Application ) : AndroidViewModel(application), VideoPlayerViewModel { // private - private val app = getApplication() private val mutableUiState = MutableStateFlow(VideoPlayerUIState.DEFAULT) private val mutableEvent = MutableSharedFlow() private var current_video_size = VideoSize.DEFAULT @@ -104,45 +99,51 @@ class VideoPlayerViewModelImpl @Inject constructor( override val uiState = mutableUiState.asStateFlow() override val events: SharedFlow = mutableEvent override var listener: VideoPlayerViewModel.Listener? = null - override val player = new_player.player + override var newPlayer: NewPlayer? = null + set(value) { + field = value + installExoPlayer() + } + override val player:Player? + get() = newPlayer?.player - - init { - - player.addListener(object : Player.Listener { - override fun onIsPlayingChanged(isPlaying: Boolean) { - super.onIsPlayingChanged(isPlaying) - println("gurken playerstate: $isPlaying") - mutableUiState.update { - it.copy(playing = isPlaying) - } - } - - // We need to updated the layout of our player view if the video ratio changes - // However, this should be done differently depending on weather we are in - // embedded or fullscreen view. - // If we are in embedded view, we tell the mother layout (only ConstraintLayout supported!) - // to change the ratio of the whole player view. - // If we are in fullscreen we only want to change the ratio of the SurfaceView - override fun onVideoSizeChanged(media3VideoSize: androidx.media3.common.VideoSize) { - super.onVideoSizeChanged(media3VideoSize) - - val videoSize = VideoSize.fromMedia3VideoSize(media3VideoSize) - - if (current_video_size != videoSize) { - val newRatio = videoSize.getRatio() - if (current_video_size.getRatio() != newRatio) { - mutableUiState.update { - it.copy(contentRatio = newRatio) - } - if (!mutableUiState.value.fullscreen) { - listener?.requestUpdateLayoutRatio(newRatio) - } + private fun installExoPlayer() { + player?.let { player -> + player.addListener(object : Player.Listener { + override fun onIsPlayingChanged(isPlaying: Boolean) { + super.onIsPlayingChanged(isPlaying) + println("gurken playerstate: $isPlaying") + mutableUiState.update { + it.copy(playing = isPlaying) } - current_video_size = videoSize } - } - }) + + // We need to updated the layout of our player view if the video ratio changes + // However, this should be done differently depending on weather we are in + // embedded or fullscreen view. + // If we are in embedded view, we tell the mother layout (only ConstraintLayout supported!) + // to change the ratio of the whole player view. + // If we are in fullscreen we only want to change the ratio of the SurfaceView + override fun onVideoSizeChanged(media3VideoSize: androidx.media3.common.VideoSize) { + super.onVideoSizeChanged(media3VideoSize) + + val videoSize = VideoSize.fromMedia3VideoSize(media3VideoSize) + + if (current_video_size != videoSize) { + val newRatio = videoSize.getRatio() + if (current_video_size.getRatio() != newRatio) { + mutableUiState.update { + it.copy(contentRatio = newRatio) + } + if (!mutableUiState.value.fullscreen) { + listener?.requestUpdateLayoutRatio(newRatio) + } + } + current_video_size = videoSize + } + } + }) + } } @RequiresApi(Build.VERSION_CODES.TIRAMISU) @@ -161,21 +162,13 @@ class VideoPlayerViewModelImpl @Inject constructor( } } - override fun preparePlayer() { - if (player.playbackState == Player.STATE_IDLE) { - player.prepare() - } - - player.setMediaItem(MediaItem.fromUri(app.getString(R.string.portrait_video_example))) - player.playWhenReady = true - } - override fun play() { - player.play() + println("gurken player: $newPlayer") + newPlayer?.play() } override fun pause() { - player.pause() + newPlayer?.pause() } override fun prevStream() { @@ -201,8 +194,8 @@ class VideoPlayerViewModelImpl @Inject constructor( companion object { val dummy = object : VideoPlayerViewModel { - override val new_player = null - override val player = null + override var newPlayer: NewPlayer? = null + override val player: Player? = null override val uiState = MutableStateFlow(VideoPlayerUIState.DEFAULT) override var listener: VideoPlayerViewModel.Listener? = null override val events: SharedFlow? = null @@ -211,10 +204,6 @@ class VideoPlayerViewModelImpl @Inject constructor( println("dummy impl") } - override fun preparePlayer() { - println("dummy impl") - } - override fun play() { println("dummy impl") } diff --git a/new-player/src/main/java/net/newpipe/newplayer/internal/ui/VideoPlayerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/internal/ui/VideoPlayerUI.kt index c507510..893d5d5 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/internal/ui/VideoPlayerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/internal/ui/VideoPlayerUI.kt @@ -112,16 +112,13 @@ fun VideoPlayerUI( modifier = Modifier.fillMaxSize(), factory = { context -> SurfaceView(context).also { view -> + println("gurken attach player: ${viewModel.player}") viewModel.player?.setVideoSurfaceView(view) } }, update = { view -> when (lifecycle) { - Lifecycle.Event.ON_PAUSE -> { - println("gurken state on pause") - } - Lifecycle.Event.ON_RESUME -> { - println("gurken resume") + println("gurken reattach player: ${viewModel.player}") viewModel.player?.setVideoSurfaceView(view) } @@ -129,7 +126,7 @@ fun VideoPlayerUI( } }) - val isPlaying = viewModel.player!!.isPlaying + val isPlaying = viewModel.player?.isPlaying ?: false VideoPlayerControllerUI( isPlaying = uiState.playing, diff --git a/new-player/src/main/res/values/test_streams.xml b/new-player/src/main/res/values/test_streams.xml deleted file mode 100644 index 728cc6b..0000000 --- a/new-player/src/main/res/values/test_streams.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4 - https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg - https://ftp.fau.de/cdn.media.ccc.de/congress/2023/h264-hd/37c3-11929-eng-deu-swe-Turning_Chromebooks_into_regular_laptops_hd.mp4 - https://videos.pexels.com/video-files/5512609/5512609-hd_1080_1920_25fps.mp4 - \ No newline at end of file diff --git a/test-app/src/main/java/net/newpipe/newplayer/testapp/MainActivity.kt b/test-app/src/main/java/net/newpipe/newplayer/testapp/MainActivity.kt index 6d1793c..a8bdf74 100644 --- a/test-app/src/main/java/net/newpipe/newplayer/testapp/MainActivity.kt +++ b/test-app/src/main/java/net/newpipe/newplayer/testapp/MainActivity.kt @@ -27,18 +27,25 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import dagger.hilt.android.AndroidEntryPoint +import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.VideoPlayerView +import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity() { + + @Inject + lateinit var newPlayer: NewPlayer + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContentView(R.layout.activity_main) val video_view = findViewById(R.id.new_player_video_view) - - video_view.minLayoutRatio + video_view.newPlayer = newPlayer + newPlayer.playWhenReady = true + newPlayer.setStream(getString(R.string.ccc_chromebooks_video)) //TODO: This is a dirty hack. Fix this later on if (getResources().configuration.orientation != Configuration.ORIENTATION_LANDSCAPE) { diff --git a/new-player/src/main/java/net/newpipe/newplayer/internal/VideoPlayerComponent.kt b/test-app/src/main/java/net/newpipe/newplayer/testapp/NewPlayerComponent.kt similarity index 88% rename from new-player/src/main/java/net/newpipe/newplayer/internal/VideoPlayerComponent.kt rename to test-app/src/main/java/net/newpipe/newplayer/testapp/NewPlayerComponent.kt index 02fb3b7..9590439 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/internal/VideoPlayerComponent.kt +++ b/test-app/src/main/java/net/newpipe/newplayer/testapp/NewPlayerComponent.kt @@ -18,11 +18,9 @@ * along with NewPlayer. If not, see . */ -package net.newpipe.newplayer.internal +package net.newpipe.newplayer.testapp import android.app.Application -import androidx.media3.common.Player -import androidx.media3.exoplayer.ExoPlayer import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -32,7 +30,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object VideoPlayerComponent { +object NewPlayerComponent { @Provides @Singleton fun provideNewPlayer(app: Application) : NewPlayer { diff --git a/test-app/src/main/res/values/test_streams.xml b/test-app/src/main/res/values/test_streams.xml index 61e86f5..fde2163 100644 --- a/test-app/src/main/res/values/test_streams.xml +++ b/test-app/src/main/res/values/test_streams.xml @@ -1,6 +1,27 @@ + + - https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4 - https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg - https://ftp.fau.de/cdn.media.ccc.de/congress/2023/h264-hd/37c3-11929-eng-deu-swe-Turning_Chromebooks_into_regular_laptops_hd.mp4 + https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4 + https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg + https://ftp.fau.de/cdn.media.ccc.de/congress/2023/h264-hd/37c3-11929-eng-deu-swe-Turning_Chromebooks_into_regular_laptops_hd.mp4 + https://videos.pexels.com/video-files/5512609/5512609-hd_1080_1920_25fps.mp4 \ No newline at end of file