make initial logic for NewPlayer and MediaReop

This commit is contained in:
Christian Schabesberger 2024-08-24 19:09:35 +02:00
parent ea099253a1
commit 47ad16c03d
12 changed files with 159 additions and 61 deletions

View File

@ -31,17 +31,18 @@ interface MediaRepository {
suspend fun getTitle(item: String) : String
suspend fun getChannelName(item: String): String
suspend fun getThumbnail(item: String): Thumbnail
suspend fun getAvailableStreamVariants(item: String): List<String>
suspend fun getAvailableSubtitleVariants(item: String): List<String>
suspend fun getAvailableStreamVariants(item: String): List<String>
suspend fun getStream(item: String, streamSelector: String) : Uri
suspend fun getSubtitle(item: String, )
suspend fun getAvailableSubtitleVariants(item: String): List<String>
suspend fun getSubtitle(item: String, variant: String): Uri
suspend fun getPreviewThumbnails(item: String) : HashMap<Long, Thumbnail>?
suspend fun getChapters(item: String): List<Chapter>
suspend fun getChapterThumbnail(item: String, chapter: Long) : Thumbnail
suspend fun getChapterThumbnail(item: String, chapter: Long) : Thumbnail?
suspend fun getTimestampLink(item: String, timestampInSeconds: Long)
suspend fun getTimestampLink(item: String, timestampInSeconds: Long): String
suspend fun tryAndRescueError(item: String?, exception: PlaybackException) : Uri?
}

View File

@ -128,7 +128,7 @@ class NewPlayerImpl(
override var fastSeekAmountSec: Int = 10
override var playBackMode: PlayMode = PlayMode.EMBEDDED_VIDEO
private var playerScope = CoroutineScope(Dispatchers.Default + Job())
private var playerScope = CoroutineScope(Dispatchers.Main + Job())
override var playMode = MutableStateFlow<PlayMode?>(null)
@ -192,7 +192,7 @@ class NewPlayerImpl(
override fun playStream(item: String, streamVariant: String, playMode: PlayMode) {
launchJobAndCollectError {
val stream = toMediaItem(item)
val stream = toMediaItem(item, streamVariant)
internalPlayStream(stream, playMode)
}
}
@ -202,6 +202,8 @@ class NewPlayerImpl(
internalPlayer.prepare()
}
this.playMode.update { playMode }
this.internalPlayer.setMediaItem(mediaItem)
this.internalPlayer.play()
}
private suspend fun toMediaItem(item: String, streamVariant: String): MediaItem {

View File

@ -98,7 +98,8 @@ enum class UIModeState {
companion object {
fun fromPlayMode(playMode: PlayMode) =
fun fromPlayMode(playMode: PlayMode?) =
if (playMode != null)
when (playMode) {
PlayMode.EMBEDDED_VIDEO -> EMBEDDED_VIDEO
PlayMode.FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO
@ -106,5 +107,7 @@ enum class UIModeState {
PlayMode.BACKGROUND -> TODO()
PlayMode.AUDIO_FOREGROUND -> TODO()
}
else PLACEHOLDER
}
}

View File

@ -28,6 +28,7 @@ import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getSystemService
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.media3.common.Player
@ -77,7 +78,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
override var newPlayer: NewPlayer? = null
set(value) {
field = value
installExoPlayer()
installNewPlayer()
}
override val uiState = mutableUiState.asStateFlow()
@ -118,7 +119,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
}
}
private fun installExoPlayer() {
private fun installNewPlayer() {
internalPlayer?.let { player ->
Log.d(TAG, "Install player: ${player.videoSize.width}")
@ -137,17 +138,29 @@ class VideoPlayerViewModelImpl @Inject constructor(
updateContentRatio(VideoSize.fromMedia3VideoSize(videoSize))
}
// TODO: This is not correctly applicable for loading indicator
override fun onIsLoadingChanged(isLoading: Boolean) {
super.onIsLoadingChanged(isLoading)
mutableUiState.update {
it.copy(isLoading = isLoading)
}
Log.i(
TAG, if (isLoading) "Player started loading" else "Player finished loading"
)
}
})
}
newPlayer?.let{ newPlayer ->
viewModelScope.launch {
while(true) {
newPlayer.playMode.collect { mode ->
println("blub: $mode")
mutableUiState.update {
it.copy(uiMode = UIModeState.fromPlayMode(mode))
}
}
}
}
}
}
fun updateContentRatio(videoSize: VideoSize) {

View File

@ -31,6 +31,10 @@ android {
namespace = "net.newpipe.newplayer.testapp"
compileSdk = 34
viewBinding {
enable = true
}
buildFeatures {
compose = true
}

View File

@ -38,6 +38,7 @@ import net.newpipe.newplayer.PlayMode
import net.newpipe.newplayer.VideoPlayerView
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
import net.newpipe.newplayer.testapp.databinding.ActivityMainBinding
import net.newpipe.newplayer.ui.ContentScale
import javax.inject.Inject
@ -51,20 +52,17 @@ class MainActivity : AppCompatActivity() {
var activityBrainSlug: ActivityBrainSlug? = null
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val embeddedPlayer = findViewById<VideoPlayerView>(R.id.new_player_video_view)
val startStreamButton = findViewById<Button>(R.id.start_stream_button)
val buttonsLayout = findViewById<View>(R.id.buttons_layout)
val embeddedPlayerLayout = findViewById<View>(R.id.player_column)
val fullscreenPlayer = findViewById<VideoPlayerView>(R.id.fullscreen_player)
startStreamButton.setOnClickListener {
binding.startStreamButton.setOnClickListener {
newPlayer.playWhenReady = true
newPlayer.playStream(getString(R.string.ccc_6502_video), PlayMode.EMBEDDED_VIDEO)
newPlayer.playStream("6502", PlayMode.EMBEDDED_VIDEO)
}
videoPlayerViewModel.newPlayer = newPlayer
@ -72,10 +70,10 @@ class MainActivity : AppCompatActivity() {
activityBrainSlug = ActivityBrainSlug(videoPlayerViewModel)
activityBrainSlug?.let {
it.embeddedPlayerView = embeddedPlayer
it.addViewToHideOnFullscreen(buttonsLayout)
it.addViewToHideOnFullscreen(embeddedPlayerLayout)
it.fullscreenPlayerView = fullscreenPlayer
it.embeddedPlayerView = binding.embeddedPlayer
it.addViewToHideOnFullscreen(binding.buttonsLayout as View)
it.addViewToHideOnFullscreen(binding.embeddedPlayerLayout as View)
it.fullscreenPlayerView = binding.fullscreenPlayer
it.rootView = findViewById(R.id.main)
}
}

View File

@ -21,7 +21,22 @@
package net.newpipe.newplayer.testapp
import android.app.Application
import android.util.Log
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import net.newpipe.newplayer.NewPlayer
import javax.inject.Inject
private const val TAG = "NewPlayerApp"
@HiltAndroidApp
class NewPlayerApp : Application()
class NewPlayerApp : Application() {
val appScope = CoroutineScope(Dispatchers.Default + Job())
}

View File

@ -21,19 +21,33 @@
package net.newpipe.newplayer.testapp
import android.app.Application
import android.util.Log
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.launch
import net.newpipe.newplayer.NewPlayer
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object NewPlayerComponent {
@Provides
@Singleton
fun provideNewPlayer(app: Application) : NewPlayer {
return NewPlayer.Builder(app, TestMediaRepository(app)).build()
val player = NewPlayer.Builder(app, TestMediaRepository(app)).build()
if(app is NewPlayerApp) {
app.appScope.launch {
while(true) {
player.errorFlow.collect { e ->
Log.e("NewPlayerException", e.stackTraceToString())
}
}
}
}
return player
}
}

View File

@ -6,6 +6,7 @@ import android.graphics.BitmapFactory
import android.media.Image
import android.net.Uri
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.newpipe.newplayer.Chapter
@ -65,9 +66,13 @@ class TestMediaRepository(val context: Context) : MediaRepository {
else -> throw Exception("Unknown stream: $item")
}
override suspend fun getAvailableSubtitleVariants(item: String): List<String> {
TODO("Not yet implemented")
}
override suspend fun getStream(item: String, streamSelector: String) =
MediaItem.fromUri(
Uri.parse(
when (item) {
"6502" -> context.getString(R.string.ccc_6502_video)
"portrait" -> context.getString(R.string.portrait_video_example)
@ -81,9 +86,13 @@ class TestMediaRepository(val context: Context) : MediaRepository {
}
)
override suspend fun getLinkWithStreamOffset(item: String): String {
TODO("Not yet implemented")
override suspend fun getSubtitle(item: String, variant: String) =
Uri.parse(
when (item) {
"imu" -> context.getString(R.string.ccc_imu_subtitles)
else -> ""
}
)
override suspend fun getPreviewThumbnails(item: String): HashMap<Long, Thumbnail>? {
val templateUrl = when (item) {
@ -93,16 +102,17 @@ class TestMediaRepository(val context: Context) : MediaRepository {
else -> throw Exception("Unknown stream: $item")
}
if(templateUrl != null) {
val thumbCount = when(item) {
if (templateUrl != null) {
val thumbCount = when (item) {
"6502" -> 312
"imu" -> 361
else -> throw Exception("Unknown stream: $item") }
else -> throw Exception("Unknown stream: $item")
}
var thumbMap = HashMap<Long, Thumbnail>()
for (i in 1..thumbCount) {
val timeStamp= (i-1) * 10 * 1000
val timeStamp = (i - 1) * 10 * 1000
thumbMap.put(timeStamp.toLong(), OnlineThumbnail(String.format(templateUrl, i)))
}
@ -112,11 +122,43 @@ class TestMediaRepository(val context: Context) : MediaRepository {
}
}
override suspend fun getChapters(item: String): List<Chapter> {
TODO("Not yet implemented")
override suspend fun getChapters(item: String) =
when (item) {
"6502" -> context.resources.getIntArray(R.array.ccc_6502_chapters)
"imu" -> TODO()
else -> intArrayOf()
}.map {
Chapter(it.toLong(), "Dummy Chapter at timestamp $it")
}
override suspend fun getChapterThumbnail(item: String, chapter: Long): Thumbnail {
override suspend fun getChapterThumbnail(item: String, chapter: Long) =
when (item) {
"6502" -> OnlineThumbnail(
String.format(
context.getString(R.string.ccc_6502_preview_thumbnails),
chapter / (10 * 1000)
)
)
"imu" -> OnlineThumbnail(
String.format(
context.getString(R.string.ccc_imu_preview_thumbnails),
chapter / (10 * 1000)
)
)
else -> null
}
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
when (item) {
"6502" -> "${context.getString(R.string.ccc_6502_link)}#t=$timestampInSeconds"
"imu" -> "${context.getString(R.string.ccc_imu_link)}#t=$timestampInSeconds"
else -> ""
}
override suspend fun tryAndRescueError(item: String?, exception: PlaybackException): Uri? {
TODO("Not yet implemented")
}
}

View File

@ -40,7 +40,7 @@
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/player_column"
android:id="@+id/embedded_player_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
@ -51,7 +51,7 @@
app:layout_constraintTop_toTopOf="parent">
<net.newpipe.newplayer.VideoPlayerView
android:id="@+id/new_player_video_view"
android:id="@+id/embedded_player"
android:name="net.newpipe.newplayer.VideoPlayerFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -72,7 +72,7 @@
app:layout_constraintHorizontal_weight="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/player_column"
app:layout_constraintStart_toEndOf="@id/embedded_player_layout"
app:layout_constraintTop_toTopOf="parent" >
<Button

View File

@ -38,8 +38,8 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/player_column"
<LinearLayout
android:id="@+id/embedded_player_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/buttons_layout"
@ -51,7 +51,7 @@
app:layout_constraintVertical_weight="1">
<net.newpipe.newplayer.VideoPlayerView
android:id="@+id/new_player_video_view"
android:id="@+id/embedded_player"
android:name="net.newpipe.newplayer.VideoPlayerFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -61,7 +61,7 @@
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
</LinearLayout>
<FrameLayout
android:id="@id/buttons_layout"
@ -69,7 +69,7 @@
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/player_column"
app:layout_constraintTop_toBottomOf="@id/embedded_player_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_weight="1">

View File

@ -20,22 +20,28 @@
-->
<resources>
<!-- "Reverse Engineering the MOS 6502 CPU" a talk from 27c3 -->
<string name="ccc_6502_link" translatable="false">https://media.ccc.de/v/27c3-4159-en-reverse_engineering_mos_6502</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_6502_title" translatable="false">Reverse Engineering the MOS 6502 CPU </string>
<string name="ccc_6502_channel" translatable="false">Michael Steil</string>
<string name="ccc_6402_thumbnail" translatable="false">https://static.media.ccc.de/media/congress/2010/27c3-4159-en-reverse_engineering_mos_6502.jpg</string>
<string name="ccc_6502_thumbnail" translatable="false">https://static.media.ccc.de/media/congress/2010/27c3-4159-en-reverse_engineering_mos_6502.jpg</string>
<string name="ccc_6502_preview_thumbnails" translatable="false">https://cloud.newpipe-ev.de/remote.php/dav/public-files/YKGkfBlBNbkavqw/6502/onethirdsize/f000000%03d.jpg</string>
<string-array name="ccc_6502_chapters">
<integer-array name="ccc_6502_chapters">
<item>600000</item>
<item>1200000</item>
<item>1800000</item>
<item>2400000</item>
<item>3600000</item>
</string-array>
</integer-array>
<!-- A thumbler Video. The creators tried to imitate an ai generated video. I found this in a Tom Scott newsletter. -->
<string name="portrait_video_example" translatable="false">https://va.media.tumblr.com/tumblr_sh62vjBX0j1z8ckep.mp4</string>
<!-- "Intel Management Engine deep dive" a talk from 36c3 -->
<string name ="ccc_imu_link" translatable="false">https://media.ccc.de/v/36c3-10694-intel_management_engine_deep_dive</string>
<string name="ccc_imu_1080_mp4" translatable="false">https://ftp.fau.de/cdn.media.ccc.de/congress/2019/h264-hd/36c3-10694-eng-deu-Intel_Management_Engine_deep_dive_hd.mp4</string>
<string name="ccc_imu_576_mp4" translatable="false">https://ftp.fau.de/cdn.media.ccc.de/congress/2019/h264-sd/36c3-10694-eng-deu-Intel_Management_Engine_deep_dive_sd.mp4</string>
<string name="ccc_imu_1080_webm" translatable="false">https://ftp.fau.de/cdn.media.ccc.de/congress/2019/webm-hd/36c3-10694-eng-deu-Intel_Management_Engine_deep_dive_webm-hd.webm</string>
@ -46,12 +52,12 @@
<string name="ccc_imu_channel" translatable="false">Peter Bosch</string>
<string name="ccc_imu_thumbnail" translatable="false">https://static.media.ccc.de/media/congress/2019/10694-hd.jpg</string>
<string name="ccc_imu_preview_thumbnails" translatable="false">https://cloud.newpipe-ev.de/remote.php/dav/public-files/YKGkfBlBNbkavqw/intel_mu/oneeigthsize/f000000%03d.jpg</string>
<string-array name="ccc_imu_chapters">
<integer-array name="ccc_imu_chapters">
<item>600000</item>
<item>1200000</item>
<item>1800000</item>
<item>2400000</item>
<item>3600000</item>
</string-array>
</integer-array>
<string name="ccc_imu_subtitles" translatable="false">https://cdn.media.ccc.de/congress/2019/36c3-10694-eng-deu-Intel_Management_Engine_deep_dive.en.srt</string>
</resources>