make initial logic for NewPlayer and MediaReop
This commit is contained in:
parent
ea099253a1
commit
47ad16c03d
|
@ -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?
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -31,6 +31,10 @@ android {
|
|||
namespace = "net.newpipe.newplayer.testapp"
|
||||
compileSdk = 34
|
||||
|
||||
viewBinding {
|
||||
enable = true
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue