From bdbd8caf432a7cf7939bb70a3b6ef9879bca6600 Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Wed, 31 Jul 2024 14:14:16 +0200 Subject: [PATCH] implement test MediaRepository --- gradle/libs.versions.toml | 2 + .../net/newpipe/newplayer/MediaRepository.kt | 19 ++++ .../java/net/newpipe/newplayer/NewPlayer.kt | 70 +++++++++--- .../model/VideoPlayerViewModelImpl.kt | 2 +- test-app/build.gradle.kts | 1 + .../newplayer/testapp/TestMediaRepository.kt | 105 ++++++++++++++++++ test-app/src/main/res/values/strings.xml | 4 + test-app/src/main/res/values/test_streams.xml | 11 +- 8 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 new-player/src/main/java/net/newpipe/newplayer/MediaRepository.kt create mode 100644 test-app/src/main/java/net/newpipe/newplayer/testapp/TestMediaRepository.kt create mode 100644 test-app/src/main/res/values/strings.xml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6293d9d..756960f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,6 +41,7 @@ fragmentKtx = "1.8.1" lifecycleRuntimeKtx = "2.8.3" kotlinParcelize = "2.0.20-Beta2" newplayer = "master-SNAPSHOT" +okhttpAndroid = "5.0.0-alpha.14" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -73,6 +74,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } newplayer = { group = "com.github.theScrabi.NewPlayer", name = "new-player", version.ref = "newplayer" } +okhttp-android = { group = "com.squareup.okhttp3", name = "okhttp-android", version.ref = "okhttpAndroid" } diff --git a/new-player/src/main/java/net/newpipe/newplayer/MediaRepository.kt b/new-player/src/main/java/net/newpipe/newplayer/MediaRepository.kt new file mode 100644 index 0000000..cc604e7 --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/MediaRepository.kt @@ -0,0 +1,19 @@ +package net.newpipe.newplayer + +import android.graphics.Bitmap +import android.net.Uri + +interface MediaRepository { + suspend fun getTitle(item: String) : String + suspend fun getChannelName(item: String): String + suspend fun getThumbnail(item: String): Bitmap + + suspend fun getAvailableStreams(item: String): List + + suspend fun getStream(item: String, streamSelector: String) : String + suspend fun getLinkWithStreamOffset(item: String) : String + + suspend fun getPreviewThumbnails(item: String) : List + suspend fun getChapters(item: String): List + suspend fun getChapterThumbnail(item: String, chapter: Long) : Bitmap +} \ No newline at end of file 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 8fd7abe..5bd59b2 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt @@ -24,53 +24,97 @@ import android.app.Application import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer +import java.lang.Exception +enum class PlayMode { + EMBEDDED_VIDEO, + FULLSCREEN_VIDEO, + PIP, + BACKGROND, + AUDIO_FORGROUND, +} interface NewPlayer { - val player: Player + val internal_player: Player var playWhenReady: Boolean + val duartion: Long + val bufferedPercentage: Int + val repository: MediaRepository + var currentPosition: Long + var fastSeekAmountSec: Long + var playBackMode: PlayMode + var playList: MutableList fun prepare() fun play() fun pause() + fun fastSeekForward() + fun fastSeekBackward() + fun addToPlaylist(newItem: String) + fun addListener(callbackListener: Listener) //TODO: This is only temporary fun setStream(uri: String) - data class Builder(val app: Application) { + data class Builder(val app: Application, val repository: MediaRepository) { fun build(): NewPlayer { - return NewPlayerImpl(ExoPlayer.Builder(app).build()) + return NewPlayerImpl(ExoPlayer.Builder(app).build(), repository = repository) } } + + interface Listener { + fun onError(exception: Exception) + } } -class NewPlayerImpl(internal_player: Player) : NewPlayer { - override val player = internal_player +class NewPlayerImpl(override val internal_player: Player, override val repository: MediaRepository) : NewPlayer { + override val duartion: Long = internal_player.duration + override val bufferedPercentage: Int = internal_player.bufferedPercentage + override var currentPosition: Long = internal_player.currentPosition + override var fastSeekAmountSec: Long = 100 + override var playBackMode: PlayMode = PlayMode.EMBEDDED_VIDEO + override var playList: MutableList = ArrayList() override var playWhenReady: Boolean set(value) { - player.playWhenReady = value + internal_player.playWhenReady = value } - get() = player.playWhenReady + get() = internal_player.playWhenReady override fun prepare() { - player.prepare() + internal_player.prepare() } override fun play() { - player.play() + internal_player.play() } override fun pause() { - player.pause() + internal_player.pause() + } + + override fun fastSeekForward() { + TODO("Not yet implemented") + } + + override fun fastSeekBackward() { + TODO("Not yet implemented") + } + + override fun addToPlaylist(newItem: String) { + TODO("Not yet implemented") + } + + override fun addListener(callbackListener: NewPlayer.Listener) { + TODO("Not yet implemented") } override fun setStream(uri: String) { - if (player.playbackState == Player.STATE_IDLE) { - player.prepare() + if (internal_player.playbackState == Player.STATE_IDLE) { + internal_player.prepare() } - player.setMediaItem(MediaItem.fromUri(uri)) + internal_player.setMediaItem(MediaItem.fromUri(uri)) } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt index 15cfea5..2ad1fb7 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt @@ -70,7 +70,7 @@ class VideoPlayerViewModelImpl @Inject constructor( override val uiState = mutableUiState.asStateFlow() override val player: Player? - get() = newPlayer?.player + get() = newPlayer?.internal_player override var minContentRatio: Float = 4F / 3F set(value) { diff --git a/test-app/build.gradle.kts b/test-app/build.gradle.kts index 40f5395..9b7899e 100644 --- a/test-app/build.gradle.kts +++ b/test-app/build.gradle.kts @@ -104,6 +104,7 @@ dependencies { // development impl implementation(project(":new-player")) + implementation(libs.okhttp.android) //jitpack test //implementation(libs.newplayer) diff --git a/test-app/src/main/java/net/newpipe/newplayer/testapp/TestMediaRepository.kt b/test-app/src/main/java/net/newpipe/newplayer/testapp/TestMediaRepository.kt new file mode 100644 index 0000000..370ed9f --- /dev/null +++ b/test-app/src/main/java/net/newpipe/newplayer/testapp/TestMediaRepository.kt @@ -0,0 +1,105 @@ +package net.newpipe.newplayer.testapp + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.media.Image +import android.net.Uri +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import net.newpipe.newplayer.MediaRepository +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response + +class TestMediaRepository(val context: Context) : MediaRepository { + val client = OkHttpClient() + + private fun get(url: String): Response { + val request = Request.Builder() + .url(url) + .build() + return client.newCall(request).execute() + } + + override suspend fun getTitle(item: String) = + when (item) { + "6502" -> "Reverse engineering the MOS 6502" + "portrait" -> "Imitating generative AI videos" + "imu" -> "Intel Management Engine deep dive " + else -> throw Exception("Unknown stream: $item") + } + + override suspend fun getChannelName(item: String) = + when (item) { + "6502" -> "27c3" + "portrait" -> "无所吊谓~" + "imu" -> "36c3" + else -> throw Exception("Unknown stream: $item") + } + + override suspend fun getThumbnail(item: String) = + when (item) { + "6502" -> withContext(Dispatchers.IO) { + val response = + get("https://static.media.ccc.de/media/congress/2010/27c3-4159-en-reverse_engineering_mos_6502_preview.jpg") + + BitmapFactory.decodeStream(response.body.byteStream()) + } + + + "portrait" -> withContext(Dispatchers.IO) { + val response = + get("https://64.media.tumblr.com/13f7e4065b4c583573a9a3e40750ccf8/9e8cf97a92704864-4b/s540x810/d966c97f755384b46dbe6d5350d35d0e9d4128ad.jpg") + + BitmapFactory.decodeStream(response.body.byteStream()) + } + + "imu" -> withContext(Dispatchers.IO) { + val response = + get("https://static.media.ccc.de/media/congress/2019/10694-hd_preview.jpg") + + BitmapFactory.decodeStream(response.body.byteStream()) + } + + else -> throw Exception("Unknown stream: $item") + } + + + override suspend fun getAvailableStreams(item: String): List = + when (item) { + "6502" -> listOf("576p") + "portrait" -> listOf("720p") + "imu" -> listOf("1080p", "576p") + else -> throw Exception("Unknown stream: $item") + } + + + override suspend fun getStream(item: String, streamSelector: String) = + when (item) { + "6502" -> context.getString(R.string.ccc_6502_video) + "portrait" -> context.getString(R.string.portrait_video_example) + "imu" -> when(streamSelector) { + "1080p" -> context.getString(R.string.ccc_imu_1080_mp4) + "576p" -> context.getString(R.string.ccc_imu_576_mp4) + else -> throw Exception("Unknown stream selector for $item: $streamSelector") + } + else -> throw Exception("Unknown stream: $item") + } + + override suspend fun getLinkWithStreamOffset(item: String): String { + TODO("Not yet implemented") + } + + override suspend fun getPreviewThumbnails(item: String): List { + TODO("Not yet implemented") + } + + override suspend fun getChapters(item: String): List { + TODO("Not yet implemented") + } + + override suspend fun getChapterThumbnail(item: String, chapter: Long): Bitmap { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/test-app/src/main/res/values/strings.xml b/test-app/src/main/res/values/strings.xml new file mode 100644 index 0000000..0d2c4cc --- /dev/null +++ b/test-app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test-app/src/main/res/values/test_streams.xml b/test-app/src/main/res/values/test_streams.xml index 5533eb9..614bbb3 100644 --- a/test-app/src/main/res/values/test_streams.xml +++ b/test-app/src/main/res/values/test_streams.xml @@ -22,5 +22,14 @@ 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://videos.pexels.com/video-files/5512609/5512609-hd_1080_1920_25fps.mp4 + + https://va.media.tumblr.com/tumblr_sh62vjBX0j1z8ckep.mp4 + + https://ftp.fau.de/cdn.media.ccc.de/congress/2019/h264-hd/36c3-10694-eng-deu-Intel_Management_Engine_deep_dive_hd.mp4 + https://ftp.fau.de/cdn.media.ccc.de/congress/2019/h264-sd/36c3-10694-eng-deu-Intel_Management_Engine_deep_dive_sd.mp4 + https://ftp.fau.de/cdn.media.ccc.de/congress/2019/webm-hd/36c3-10694-eng-deu-Intel_Management_Engine_deep_dive_webm-hd.webm + https://ftp.fau.de/cdn.media.ccc.de/congress/2019/webm-sd/36c3-10694-eng-deu-Intel_Management_Engine_deep_dive_webm-sd.webm + https://ftp.fau.de/cdn.media.ccc.de/congress/2019/mp3/36c3-10694-eng-Intel_Management_Engine_deep_dive_mp3.mp3 + https://ftp.fau.de/cdn.media.ccc.de/congress/2019/opus/36c3-10694-eng-Intel_Management_Engine_deep_dive_opus.opus + \ No newline at end of file