start implementing NewPlayer interface
This commit is contained in:
parent
f11d35818f
commit
d526527e94
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
|
@ -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>
|
Loading…
Reference in New Issue