start implementing NewPlayer interface

This commit is contained in:
Christian Schabesberger 2024-07-19 13:41:38 +02:00
parent f11d35818f
commit d526527e94
9 changed files with 148 additions and 107 deletions

View File

@ -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))
}
}

View File

@ -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(

View File

@ -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<ConstraintLayout.LayoutParams> {
val ratio = currentVideoRatio.coerceIn(minLayoutRatio, maxLayoutRatio)
dimensionRatio = "$ratio:1"
if(this::composeView.isInitialized) {
composeView.updateLayoutParams<ConstraintLayout.LayoutParams> {
val ratio = currentVideoRatio.coerceIn(minLayoutRatio, maxLayoutRatio)
dimensionRatio = "$ratio:1"
}
}
}
}

View File

@ -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<VideoPlayerUIState>
var listener: Listener?
val events: SharedFlow<Events>?
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<Application>()
private val mutableUiState = MutableStateFlow(VideoPlayerUIState.DEFAULT)
private val mutableEvent = MutableSharedFlow<VideoPlayerViewModel.Events>()
private var current_video_size = VideoSize.DEFAULT
@ -104,45 +99,51 @@ class VideoPlayerViewModelImpl @Inject constructor(
override val uiState = mutableUiState.asStateFlow()
override val events: SharedFlow<VideoPlayerViewModel.Events> = 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<VideoPlayerViewModel.Events>? = null
@ -211,10 +204,6 @@ class VideoPlayerViewModelImpl @Inject constructor(
println("dummy impl")
}
override fun preparePlayer() {
println("dummy impl")
}
override fun play() {
println("dummy impl")
}

View File

@ -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,

View File

@ -1,27 +0,0 @@
<?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/>.
-->
<resources>
<string name="ccc_6502_video">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4</string>
<string name="ccc_6502_audio">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg</string>
<string name="ccc_chromebooks_video">https://ftp.fau.de/cdn.media.ccc.de/congress/2023/h264-hd/37c3-11929-eng-deu-swe-Turning_Chromebooks_into_regular_laptops_hd.mp4</string>
<string name="portrait_video_example">https://videos.pexels.com/video-files/5512609/5512609-hd_1080_1920_25fps.mp4</string>
</resources>

View File

@ -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<VideoPlayerView>(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) {

View File

@ -18,11 +18,9 @@
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
*/
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 {

View File

@ -1,6 +1,27 @@
<?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/>.
-->
<resources>
<string name="ccc_6502_video">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4</string>
<string name="ccc_6502_audio">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg</string>
<string name="ccc_chromebooks_video">https://ftp.fau.de/cdn.media.ccc.de/congress/2023/h264-hd/37c3-11929-eng-deu-swe-Turning_Chromebooks_into_regular_laptops_hd.mp4</string>
<string name="ccc_6502_video" translatable="false">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4</string>
<string name="ccc_6502_audio" translatable="false">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg</string>
<string name="ccc_chromebooks_video" translatable="false">https://ftp.fau.de/cdn.media.ccc.de/congress/2023/h264-hd/37c3-11929-eng-deu-swe-Turning_Chromebooks_into_regular_laptops_hd.mp4</string>
<string name="portrait_video_example" translatable="false">https://videos.pexels.com/video-files/5512609/5512609-hd_1080_1920_25fps.mp4</string>
</resources>