push player further to playlist handling
This commit is contained in:
parent
3d0fdabcf4
commit
a2e8f6c4ad
|
@ -1,20 +1,47 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
import net.newpipe.newplayer.utils.Thumbnail
|
||||
|
||||
data class Chapter(val chapterStartInMs: Long, val chapterTitle: String?)
|
||||
|
||||
interface MediaRepository {
|
||||
suspend fun getTitle(item: String) : String
|
||||
suspend fun getChannelName(item: String): String
|
||||
suspend fun getThumbnail(item: String): Bitmap
|
||||
suspend fun getThumbnail(item: String): Thumbnail
|
||||
suspend fun getAvailableStreamVariants(item: String): List<String>
|
||||
suspend fun getAvailableSubtitleVariants(item: String): List<String>
|
||||
|
||||
suspend fun getAvailableStreams(item: String): List<String>
|
||||
suspend fun getStream(item: String, streamSelector: String) : Uri
|
||||
suspend fun getSubtitle(item: String, )
|
||||
|
||||
suspend fun getStream(item: String, streamSelector: String) : MediaItem
|
||||
suspend fun getLinkWithStreamOffset(item: String) : String
|
||||
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 getPreviewThumbnails(item: String) : List<Bitmap>
|
||||
suspend fun getChapters(item: String): List<Long>
|
||||
suspend fun getChapterThumbnail(item: String, chapter: Long) : Bitmap
|
||||
suspend fun getTimestampLink(item: String, timestampInSeconds: Long)
|
||||
|
||||
suspend fun tryAndRescueError(item: String?, exception: PlaybackException) : Uri?
|
||||
}
|
|
@ -23,114 +23,218 @@ package net.newpipe.newplayer
|
|||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import java.lang.Exception
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.Exception
|
||||
|
||||
enum class PlayMode {
|
||||
EMBEDDED_VIDEO,
|
||||
FULLSCREEN_VIDEO,
|
||||
PIP,
|
||||
BACKGROND,
|
||||
AUDIO_FORGROUND,
|
||||
BACKGROUND,
|
||||
AUDIO_FOREGROUND,
|
||||
}
|
||||
|
||||
private val TAG = "NewPlayer"
|
||||
|
||||
interface NewPlayer {
|
||||
val internal_player: Player
|
||||
// preferences
|
||||
val preferredStreamVariants: List<String>
|
||||
|
||||
val internalPlayer: Player
|
||||
var playWhenReady: Boolean
|
||||
val duartion: Long
|
||||
val duration: Long
|
||||
val bufferedPercentage: Int
|
||||
val repository: MediaRepository
|
||||
var currentPosition: Long
|
||||
var fastSeekAmountSec: Int
|
||||
var playBackMode: PlayMode
|
||||
var playList: MutableList<String>
|
||||
var playMode: PlayMode?
|
||||
|
||||
// calbacks
|
||||
|
||||
interface Listener {
|
||||
fun playModeChange(playMode: PlayMode) {}
|
||||
fun onError(exception: Exception) {}
|
||||
}
|
||||
|
||||
// methods
|
||||
fun prepare()
|
||||
fun play()
|
||||
fun pause()
|
||||
fun addToPlaylist(newItem: String)
|
||||
fun addListener(callbackListener: Listener)
|
||||
|
||||
//TODO: This is only temporary
|
||||
fun setStream(stream: MediaItem)
|
||||
fun addToPlaylist(item: String)
|
||||
fun playStream(item: String, playMode: PlayMode)
|
||||
fun playStream(item: String, streamVariant: String, playMode: PlayMode)
|
||||
fun addCallbackListener(listener: Listener?)
|
||||
|
||||
data class Builder(val app: Application, val repository: MediaRepository) {
|
||||
private var mediaSourceFactory : MediaSource.Factory? = null
|
||||
private var mediaSourceFactory: MediaSource.Factory? = null
|
||||
private var preferredStreamVariants: List<String> = emptyList()
|
||||
|
||||
fun setMediaSourceFactory(mediaSourceFactory: MediaSource.Factory) {
|
||||
this.mediaSourceFactory = mediaSourceFactory
|
||||
}
|
||||
|
||||
fun setPreferredStreamVariants(preferredStreamVariants: List<String>) {
|
||||
this.preferredStreamVariants = preferredStreamVariants
|
||||
}
|
||||
|
||||
fun build(): NewPlayer {
|
||||
val exoPlayerBuilder = ExoPlayer.Builder(app)
|
||||
mediaSourceFactory?.let {
|
||||
exoPlayerBuilder.setMediaSourceFactory(it)
|
||||
}
|
||||
return NewPlayerImpl(exoPlayerBuilder.build(), repository = repository)
|
||||
return NewPlayerImpl(
|
||||
app = app,
|
||||
internalPlayer = exoPlayerBuilder.build(),
|
||||
repository = repository,
|
||||
preferredStreamVariants = preferredStreamVariants
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onError(exception: Exception)
|
||||
}
|
||||
}
|
||||
|
||||
class NewPlayerImpl(override val internal_player: Player, override val repository: MediaRepository) : NewPlayer {
|
||||
|
||||
private var callbackListeners: MutableList<NewPlayer.Listener> = ArrayList()
|
||||
|
||||
override val duartion: Long
|
||||
get() = internal_player.duration
|
||||
class NewPlayerImpl(
|
||||
val app: Application,
|
||||
override val internalPlayer: Player,
|
||||
override val preferredStreamVariants: List<String>,
|
||||
override val repository: MediaRepository,
|
||||
) : NewPlayer {
|
||||
|
||||
override val bufferedPercentage: Int
|
||||
get() = internal_player.bufferedPercentage
|
||||
get() = internalPlayer.bufferedPercentage
|
||||
override var currentPosition: Long
|
||||
get() = internal_player.currentPosition
|
||||
set(value) {internal_player.seekTo(value)}
|
||||
get() = internalPlayer.currentPosition
|
||||
set(value) {
|
||||
internalPlayer.seekTo(value)
|
||||
}
|
||||
|
||||
override var fastSeekAmountSec: Int = 10
|
||||
override var playBackMode: PlayMode = PlayMode.EMBEDDED_VIDEO
|
||||
override var playList: MutableList<String> = ArrayList<String>()
|
||||
|
||||
private var callbackListener: ArrayList<NewPlayer.Listener?> = ArrayList()
|
||||
private var playerScope = CoroutineScope(Dispatchers.Default + Job())
|
||||
|
||||
override var playMode: PlayMode? = null
|
||||
set(value) {
|
||||
field = value
|
||||
if (field != null) {
|
||||
callbackListener.forEach { it?.playModeChange(field!!) }
|
||||
}
|
||||
}
|
||||
|
||||
override var playWhenReady: Boolean
|
||||
set(value) {
|
||||
internal_player.playWhenReady = value
|
||||
internalPlayer.playWhenReady = value
|
||||
}
|
||||
get() = internal_player.playWhenReady
|
||||
get() = internalPlayer.playWhenReady
|
||||
|
||||
|
||||
override val duration: Long
|
||||
get() = internalPlayer.duration
|
||||
|
||||
|
||||
init {
|
||||
internalPlayer.addListener(object: Player.Listener {
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
launchJobAndCollectError {
|
||||
val item = internalPlayer.currentMediaItem?.mediaId
|
||||
val newUri = repository.tryAndRescueError(item, exception = error)
|
||||
if (newUri != null) {
|
||||
TODO("Implement handing new uri on fixed error")
|
||||
} else {
|
||||
callbackListener.forEach {
|
||||
it?.onError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun prepare() {
|
||||
internal_player.prepare()
|
||||
internalPlayer.prepare()
|
||||
}
|
||||
|
||||
override fun play() {
|
||||
if(internal_player.currentMediaItem != null) {
|
||||
internal_player.play()
|
||||
if (internalPlayer.currentMediaItem != null) {
|
||||
internalPlayer.play()
|
||||
} else {
|
||||
Log.i(TAG, "Tried to start playing but no media Item was cued")
|
||||
}
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
internal_player.pause()
|
||||
internalPlayer.pause()
|
||||
}
|
||||
|
||||
override fun addToPlaylist(newItem: String) {
|
||||
Log.d(TAG, "Not implemented add to playlist")
|
||||
override fun addToPlaylist(item: String) {
|
||||
launchJobAndCollectError {
|
||||
val mediaItem = toMediaItem(item)
|
||||
internalPlayer.addMediaItem(mediaItem)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addListener(callbackListener: NewPlayer.Listener) {
|
||||
callbackListeners.add(callbackListener)
|
||||
override fun playStream(item: String, playMode: PlayMode) {
|
||||
launchJobAndCollectError {
|
||||
val mediaItem = toMediaItem(item)
|
||||
internalPlayStream(mediaItem, playMode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setStream(stream: MediaItem) {
|
||||
if (internal_player.playbackState == Player.STATE_IDLE) {
|
||||
internal_player.prepare()
|
||||
override fun playStream(item: String, streamVariant: String, playMode: PlayMode) {
|
||||
launchJobAndCollectError {
|
||||
val stream = toMediaItem(item)
|
||||
internalPlayStream(stream, playMode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) {
|
||||
if (internalPlayer.playbackState == Player.STATE_IDLE) {
|
||||
internalPlayer.prepare()
|
||||
}
|
||||
this.playMode = playMode
|
||||
}
|
||||
|
||||
private suspend fun toMediaItem(item: String, streamVariant: String): MediaItem {
|
||||
val dataStream = repository.getStream(item, streamVariant)
|
||||
val mediaItem = MediaItem.Builder().setMediaId(item).setUri(dataStream)
|
||||
return mediaItem.build()
|
||||
}
|
||||
|
||||
private suspend fun toMediaItem(item: String): MediaItem {
|
||||
|
||||
val availableStream = repository.getAvailableStreamVariants(item)
|
||||
var selectedStream = availableStream[availableStream.size / 2]
|
||||
for (preferredStream in preferredStreamVariants) {
|
||||
if (preferredStream in availableStream) {
|
||||
selectedStream = preferredStream
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal_player.setMediaItem(stream)
|
||||
return toMediaItem(item, selectedStream)
|
||||
}
|
||||
|
||||
private fun launchJobAndCollectError(task: suspend () -> Unit) =
|
||||
playerScope.launch {
|
||||
try {
|
||||
task()
|
||||
} catch (e: Exception) {
|
||||
callbackListener.forEach {
|
||||
it?.onError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addCallbackListener(listener: NewPlayer.Listener?) {
|
||||
callbackListener.add(listener)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package net.newpipe.newplayer.model
|
||||
|
||||
import net.newpipe.newplayer.PlayMode
|
||||
|
||||
enum class UIModeState {
|
||||
PLACEHOLDER,
|
||||
|
||||
|
@ -93,4 +95,16 @@ enum class UIModeState {
|
|||
|
||||
else -> this
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun fromPlayMode(playMode: PlayMode) =
|
||||
when (playMode) {
|
||||
PlayMode.EMBEDDED_VIDEO -> EMBEDDED_VIDEO
|
||||
PlayMode.FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO
|
||||
PlayMode.PIP -> TODO()
|
||||
PlayMode.BACKGROUND -> TODO()
|
||||
PlayMode.AUDIO_FOREGROUND -> TODO()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -83,7 +83,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
override val uiState = mutableUiState.asStateFlow()
|
||||
|
||||
override val internalPlayer: Player?
|
||||
get() = newPlayer?.internal_player
|
||||
get() = newPlayer?.internalPlayer
|
||||
|
||||
override var minContentRatio: Float = 4F / 3F
|
||||
set(value) {
|
||||
|
|
|
@ -64,6 +64,10 @@ import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
|||
import net.newpipe.newplayer.utils.getLocale
|
||||
import net.newpipe.newplayer.utils.getTimeStringFromMs
|
||||
import coil.compose.AsyncImage
|
||||
import net.newpipe.newplayer.utils.BitmapThumbnail
|
||||
import net.newpipe.newplayer.utils.OnlineThumbnail
|
||||
import net.newpipe.newplayer.utils.Thumbnail
|
||||
import net.newpipe.newplayer.utils.VectorThumbnail
|
||||
|
||||
@Composable
|
||||
fun StreamSelectUI(
|
||||
|
@ -125,7 +129,7 @@ private fun StreamSelectTopBar() {
|
|||
private fun ChapterItem(
|
||||
modifier: Modifier = Modifier,
|
||||
id: Int,
|
||||
thumbnailUrl: String?,
|
||||
thumbnail: Thumbnail?,
|
||||
chapterTitle: String,
|
||||
chapterStartInMs: Long,
|
||||
onClicked: (Int) -> Unit
|
||||
|
@ -141,10 +145,27 @@ private fun ChapterItem(
|
|||
)
|
||||
.clickable { onClicked(id) }
|
||||
) {
|
||||
if (thumbnailUrl != null) {
|
||||
val contentDescription = stringResource(R.string.chapter)
|
||||
if (thumbnail != null) {
|
||||
when (thumbnail) {
|
||||
is OnlineThumbnail -> AsyncImage(
|
||||
model = thumbnail.url,
|
||||
contentDescription =contentDescription
|
||||
)
|
||||
|
||||
is BitmapThumbnail -> Image(
|
||||
bitmap = thumbnail.img,
|
||||
contentDescription = contentDescription
|
||||
)
|
||||
|
||||
is VectorThumbnail -> Image(
|
||||
imageVector = thumbnail.vec,
|
||||
contentDescription = contentDescription
|
||||
)
|
||||
}
|
||||
AsyncImage(
|
||||
model = thumbnailUrl,
|
||||
contentDescription = stringResource(R.string.chapter)
|
||||
model = thumbnail,
|
||||
contentDescription = contentDescription
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
|
@ -171,7 +192,7 @@ private fun StreamItem(
|
|||
id: Int,
|
||||
title: String,
|
||||
creator: String?,
|
||||
thumbnailUrl: String?,
|
||||
thumbnail: Thumbnail?,
|
||||
lengthInMs: Long,
|
||||
onDragStart: (Int) -> Unit,
|
||||
onDragEnd: (Int) -> Unit,
|
||||
|
@ -180,10 +201,27 @@ private fun StreamItem(
|
|||
val locale = getLocale()!!
|
||||
Row(modifier = modifier.clickable { onClicked(id) }) {
|
||||
Box {
|
||||
if (thumbnailUrl != null) {
|
||||
val contentDescription = stringResource(R.string.chapter)
|
||||
if (thumbnail != null) {
|
||||
when (thumbnail) {
|
||||
is OnlineThumbnail -> AsyncImage(
|
||||
model = thumbnail.url,
|
||||
contentDescription =contentDescription
|
||||
)
|
||||
|
||||
is BitmapThumbnail -> Image(
|
||||
bitmap = thumbnail.img,
|
||||
contentDescription = contentDescription
|
||||
)
|
||||
|
||||
is VectorThumbnail -> Image(
|
||||
imageVector = thumbnail.vec,
|
||||
contentDescription = contentDescription
|
||||
)
|
||||
}
|
||||
AsyncImage(
|
||||
model = thumbnailUrl,
|
||||
contentDescription = stringResource(R.string.chapter)
|
||||
model = thumbnail,
|
||||
contentDescription = contentDescription
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
|
@ -209,10 +247,12 @@ private fun StreamItem(
|
|||
}
|
||||
}
|
||||
|
||||
Column(modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.weight(1f)
|
||||
.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.weight(1f)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Text(text = title, fontSize = 18.sp, fontWeight = FontWeight.Bold)
|
||||
if (creator != null) {
|
||||
Text(text = creator)
|
||||
|
@ -223,15 +263,17 @@ private fun StreamItem(
|
|||
.fillMaxHeight()
|
||||
.aspectRatio(1f)
|
||||
.pointerInteropFilter {
|
||||
when(it.action) {
|
||||
when (it.action) {
|
||||
MotionEvent.ACTION_UP -> {
|
||||
onDragEnd(id)
|
||||
false
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
onDragStart(id)
|
||||
false
|
||||
}
|
||||
|
||||
else -> true
|
||||
}
|
||||
}) {
|
||||
|
@ -254,7 +296,7 @@ fun ChapterItemPreview() {
|
|||
Surface(modifier = Modifier.fillMaxSize(), color = Color.DarkGray) {
|
||||
ChapterItem(
|
||||
id = 0,
|
||||
thumbnailUrl = null,
|
||||
thumbnail = null,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
chapterTitle = "Chapter Title",
|
||||
chapterStartInMs = (4 * 60 + 32) * 1000,
|
||||
|
@ -274,7 +316,7 @@ fun StreamItemPreview() {
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
title = "Video Title",
|
||||
creator = "Video Creator",
|
||||
thumbnailUrl = null,
|
||||
thumbnail = null,
|
||||
lengthInMs = 15 * 60 * 1000,
|
||||
onDragStart = {},
|
||||
onDragEnd = {},
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.VectorDrawable
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
interface Thumbnail
|
||||
|
||||
data class OnlineThumbnail(val url: String) : Thumbnail
|
||||
data class BitmapThumbnail(val img: ImageBitmap) : Thumbnail
|
||||
data class VectorThumbnail(val vec:ImageVector) : Thumbnail
|
|
@ -8,7 +8,10 @@ import android.net.Uri
|
|||
import androidx.media3.common.MediaItem
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.newpipe.newplayer.Chapter
|
||||
import net.newpipe.newplayer.MediaRepository
|
||||
import net.newpipe.newplayer.utils.OnlineThumbnail
|
||||
import net.newpipe.newplayer.utils.Thumbnail
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
@ -41,33 +44,20 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
|||
|
||||
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")
|
||||
"6502" ->
|
||||
OnlineThumbnail("https://static.media.ccc.de/media/congress/2010/27c3-4159-en-reverse_engineering_mos_6502_preview.jpg")
|
||||
|
||||
BitmapFactory.decodeStream(response.body.byteStream())
|
||||
}
|
||||
"portrait" ->
|
||||
OnlineThumbnail("https://64.media.tumblr.com/13f7e4065b4c583573a9a3e40750ccf8/9e8cf97a92704864-4b/s540x810/d966c97f755384b46dbe6d5350d35d0e9d4128ad.jpg")
|
||||
|
||||
|
||||
"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())
|
||||
}
|
||||
"imu" ->
|
||||
OnlineThumbnail("https://static.media.ccc.de/media/congress/2019/10694-hd_preview.jpg")
|
||||
|
||||
else -> throw Exception("Unknown stream: $item")
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getAvailableStreams(item: String): List<String> =
|
||||
override suspend fun getAvailableStreamVariants(item: String): List<String> =
|
||||
when (item) {
|
||||
"6502" -> listOf("576p")
|
||||
"portrait" -> listOf("720p")
|
||||
|
@ -95,15 +85,38 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
|||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getPreviewThumbnails(item: String): List<Bitmap> {
|
||||
override suspend fun getPreviewThumbnails(item: String): HashMap<Long, Thumbnail>? {
|
||||
val templateUrl = when (item) {
|
||||
"6502" -> context.getString(R.string.ccc_6502_preview_thumbnails)
|
||||
"imu" -> context.getString(R.string.ccc_imu_preview_thumbnails)
|
||||
"portrait" -> null
|
||||
else -> throw Exception("Unknown stream: $item")
|
||||
}
|
||||
|
||||
if(templateUrl != null) {
|
||||
val thumbCount = when(item) {
|
||||
"6502" -> 312
|
||||
"imu" -> 361
|
||||
else -> throw Exception("Unknown stream: $item") }
|
||||
|
||||
var thumbMap = HashMap<Long, Thumbnail>()
|
||||
|
||||
for (i in 1..thumbCount) {
|
||||
val timeStamp= (i-1) * 10 * 1000
|
||||
thumbMap.put(timeStamp.toLong(), OnlineThumbnail(String.format(templateUrl, i)))
|
||||
}
|
||||
|
||||
return thumbMap
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getChapters(item: String): List<Chapter> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getChapters(item: String): List<Long> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getChapterThumbnail(item: String, chapter: Long): Bitmap {
|
||||
override suspend fun getChapterThumbnail(item: String, chapter: Long): Thumbnail {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue