forward player playlist into viewmodel
This commit is contained in:
parent
99b79816f0
commit
888d518304
|
@ -21,28 +21,34 @@
|
||||||
package net.newpipe.newplayer
|
package net.newpipe.newplayer
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import net.newpipe.newplayer.utils.Thumbnail
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
|
|
||||||
data class Chapter(val chapterStartInMs: Long, val chapterTitle: String?)
|
data class Chapter(val chapterStartInMs: Long, val chapterTitle: String?)
|
||||||
|
|
||||||
|
data class MetaInfo(
|
||||||
|
val title: String,
|
||||||
|
val channelName: String,
|
||||||
|
val thumbnail: Thumbnail?,
|
||||||
|
val lengthInS: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
interface MediaRepository {
|
interface MediaRepository {
|
||||||
suspend fun getTitle(item: String) : String
|
|
||||||
suspend fun getChannelName(item: String): String
|
suspend fun getMetaInfo(item: String): MetaInfo
|
||||||
suspend fun getThumbnail(item: String): Thumbnail
|
|
||||||
|
|
||||||
suspend fun getAvailableStreamVariants(item: String): List<String>
|
suspend fun getAvailableStreamVariants(item: String): List<String>
|
||||||
suspend fun getStream(item: String, streamSelector: String) : Uri
|
suspend fun getStream(item: String, streamSelector: String): Uri
|
||||||
|
|
||||||
suspend fun getAvailableSubtitleVariants(item: String): List<String>
|
suspend fun getAvailableSubtitleVariants(item: String): List<String>
|
||||||
suspend fun getSubtitle(item: String, variant: String): Uri
|
suspend fun getSubtitle(item: String, variant: String): Uri
|
||||||
|
|
||||||
suspend fun getPreviewThumbnails(item: String) : HashMap<Long, Thumbnail>?
|
suspend fun getPreviewThumbnails(item: String): HashMap<Long, Thumbnail>?
|
||||||
suspend fun getChapters(item: String): List<Chapter>
|
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): String
|
suspend fun getTimestampLink(item: String, timestampInSeconds: Long): String
|
||||||
|
|
||||||
suspend fun tryAndRescueError(item: String?, exception: PlaybackException) : Uri?
|
suspend fun tryAndRescueError(item: String?, exception: PlaybackException): Uri?
|
||||||
}
|
}
|
|
@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.newpipe.newplayer.utils.PlayList
|
import net.newpipe.newplayer.playerInternals.PlayList
|
||||||
import kotlin.Exception
|
import kotlin.Exception
|
||||||
|
|
||||||
enum class PlayMode {
|
enum class PlayMode {
|
||||||
|
@ -60,6 +60,7 @@ interface NewPlayer {
|
||||||
val duration: Long
|
val duration: Long
|
||||||
val bufferedPercentage: Int
|
val bufferedPercentage: Int
|
||||||
val repository: MediaRepository
|
val repository: MediaRepository
|
||||||
|
val sharingLinkWithOffsetPossible: Boolean
|
||||||
var currentPosition: Long
|
var currentPosition: Long
|
||||||
var fastSeekAmountSec: Int
|
var fastSeekAmountSec: Int
|
||||||
var playBackMode: PlayMode
|
var playBackMode: PlayMode
|
||||||
|
@ -84,13 +85,21 @@ interface NewPlayer {
|
||||||
data class Builder(val app: Application, val repository: MediaRepository) {
|
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()
|
private var preferredStreamVariants: List<String> = emptyList()
|
||||||
|
private var sharingLinkWithOffsetPossible = false
|
||||||
|
|
||||||
fun setMediaSourceFactory(mediaSourceFactory: MediaSource.Factory) {
|
fun setMediaSourceFactory(mediaSourceFactory: MediaSource.Factory) : Builder {
|
||||||
this.mediaSourceFactory = mediaSourceFactory
|
this.mediaSourceFactory = mediaSourceFactory
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPreferredStreamVariants(preferredStreamVariants: List<String>) {
|
fun setPreferredStreamVariants(preferredStreamVariants: List<String>) : Builder {
|
||||||
this.preferredStreamVariants = preferredStreamVariants
|
this.preferredStreamVariants = preferredStreamVariants
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSharingLinkWithOffsetPossible(possible: Boolean) : Builder {
|
||||||
|
this.sharingLinkWithOffsetPossible = false
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(): NewPlayer {
|
fun build(): NewPlayer {
|
||||||
|
@ -102,7 +111,8 @@ interface NewPlayer {
|
||||||
app = app,
|
app = app,
|
||||||
internalPlayer = exoPlayerBuilder.build(),
|
internalPlayer = exoPlayerBuilder.build(),
|
||||||
repository = repository,
|
repository = repository,
|
||||||
preferredStreamVariants = preferredStreamVariants
|
preferredStreamVariants = preferredStreamVariants,
|
||||||
|
sharingLinkWithOffsetPossible = sharingLinkWithOffsetPossible
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +124,7 @@ class NewPlayerImpl(
|
||||||
override val internalPlayer: Player,
|
override val internalPlayer: Player,
|
||||||
override val preferredStreamVariants: List<String>,
|
override val preferredStreamVariants: List<String>,
|
||||||
override val repository: MediaRepository,
|
override val repository: MediaRepository,
|
||||||
|
override val sharingLinkWithOffsetPossible: Boolean
|
||||||
) : NewPlayer {
|
) : NewPlayer {
|
||||||
|
|
||||||
var mutableErrorFlow = MutableSharedFlow<Exception>()
|
var mutableErrorFlow = MutableSharedFlow<Exception>()
|
||||||
|
@ -121,6 +132,7 @@ class NewPlayerImpl(
|
||||||
|
|
||||||
override val bufferedPercentage: Int
|
override val bufferedPercentage: Int
|
||||||
get() = internalPlayer.bufferedPercentage
|
get() = internalPlayer.bufferedPercentage
|
||||||
|
|
||||||
override var currentPosition: Long
|
override var currentPosition: Long
|
||||||
get() = internalPlayer.currentPosition
|
get() = internalPlayer.currentPosition
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
|
@ -22,9 +22,10 @@ package net.newpipe.newplayer.model
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import net.newpipe.newplayer.Chapter
|
||||||
|
import net.newpipe.newplayer.playerInternals.PlaylistItem
|
||||||
import net.newpipe.newplayer.ui.ContentScale
|
import net.newpipe.newplayer.ui.ContentScale
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class VideoPlayerUIState(
|
data class VideoPlayerUIState(
|
||||||
val uiMode: UIModeState,
|
val uiMode: UIModeState,
|
||||||
val playing: Boolean,
|
val playing: Boolean,
|
||||||
|
@ -39,8 +40,10 @@ data class VideoPlayerUIState(
|
||||||
val fastSeekSeconds: Int,
|
val fastSeekSeconds: Int,
|
||||||
val soundVolume: Float,
|
val soundVolume: Float,
|
||||||
val brightness: Float?, // when null use system value
|
val brightness: Float?, // when null use system value
|
||||||
val embeddedUiConfig: EmbeddedUiConfig?
|
val embeddedUiConfig: EmbeddedUiConfig?,
|
||||||
) : Parcelable {
|
val playList: List<PlaylistItem>,
|
||||||
|
val chapters: List<Chapter>
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT = VideoPlayerUIState(
|
val DEFAULT = VideoPlayerUIState(
|
||||||
// TODO: replace this with the placeholder state.
|
// TODO: replace this with the placeholder state.
|
||||||
|
@ -59,7 +62,9 @@ data class VideoPlayerUIState(
|
||||||
fastSeekSeconds = 0,
|
fastSeekSeconds = 0,
|
||||||
soundVolume = 0f,
|
soundVolume = 0f,
|
||||||
brightness = null,
|
brightness = null,
|
||||||
embeddedUiConfig = null
|
embeddedUiConfig = null,
|
||||||
|
playList = emptyList(),
|
||||||
|
chapters = emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,6 +26,8 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import net.newpipe.newplayer.NewPlayer
|
import net.newpipe.newplayer.NewPlayer
|
||||||
import net.newpipe.newplayer.ui.ContentScale
|
import net.newpipe.newplayer.ui.ContentScale
|
||||||
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
|
|
||||||
|
|
||||||
interface VideoPlayerViewModel {
|
interface VideoPlayerViewModel {
|
||||||
var newPlayer: NewPlayer?
|
var newPlayer: NewPlayer?
|
||||||
|
|
|
@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat.getSystemService
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
@ -43,6 +44,7 @@ import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.newpipe.newplayer.utils.VideoSize
|
import net.newpipe.newplayer.utils.VideoSize
|
||||||
import net.newpipe.newplayer.NewPlayer
|
import net.newpipe.newplayer.NewPlayer
|
||||||
|
import net.newpipe.newplayer.playerInternals.getPlaylistItemsFromItemList
|
||||||
import net.newpipe.newplayer.ui.ContentScale
|
import net.newpipe.newplayer.ui.ContentScale
|
||||||
|
|
||||||
val VIDEOPLAYER_UI_STATE = "video_player_ui_state"
|
val VIDEOPLAYER_UI_STATE = "video_player_ui_state"
|
||||||
|
@ -119,7 +121,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mutableEmbeddedPlayerDraggedDownBy = MutableSharedFlow<Float>()
|
private var mutableEmbeddedPlayerDraggedDownBy = MutableSharedFlow<Float>()
|
||||||
override val embeddedPlayerDraggedDownBy = mutableEmbeddedPlayerDraggedDownBy.asSharedFlow()
|
override val embeddedPlayerDraggedDownBy = mutableEmbeddedPlayerDraggedDownBy.asSharedFlow()
|
||||||
|
|
||||||
private fun installNewPlayer() {
|
private fun installNewPlayer() {
|
||||||
|
@ -148,6 +150,11 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
it.copy(isLoading = isLoading)
|
it.copy(isLoading = isLoading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPlaylistMetadataChanged(mediaMetadata: MediaMetadata) {
|
||||||
|
super.onPlaylistMetadataChanged(mediaMetadata)
|
||||||
|
updatePlaylist()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
newPlayer?.let { newPlayer ->
|
newPlayer?.let { newPlayer ->
|
||||||
|
@ -162,7 +169,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updatePlaylist()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateContentRatio(videoSize: VideoSize) {
|
fun updateContentRatio(videoSize: VideoSize) {
|
||||||
|
@ -393,4 +400,18 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
|
|
||||||
|
|
||||||
} ?: minContentRatio
|
} ?: minContentRatio
|
||||||
|
|
||||||
|
private fun updatePlaylist() {
|
||||||
|
newPlayer?.let { newPlayer ->
|
||||||
|
viewModelScope.launch {
|
||||||
|
val playlist = getPlaylistItemsFromItemList(
|
||||||
|
newPlayer.playlist,
|
||||||
|
newPlayer.repository
|
||||||
|
)
|
||||||
|
mutableUiState.update {
|
||||||
|
it.copy(playList = playlist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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.playerInternals
|
||||||
|
|
||||||
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
|
|
||||||
|
data class ChapterItem(
|
||||||
|
val title: String,
|
||||||
|
val offsetInMs: Long,
|
||||||
|
val thumbnail: Thumbnail
|
||||||
|
)
|
|
@ -19,11 +19,9 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package net.newpipe.newplayer.utils
|
package net.newpipe.newplayer.playerInternals
|
||||||
|
|
||||||
import androidx.media3.common.MediaMetadata
|
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.Player.Listener
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: This is cool, but it might still contains all raceconditions since two actors are mutating the
|
// TODO: This is cool, but it might still contains all raceconditions since two actors are mutating the
|
||||||
|
@ -33,9 +31,6 @@ import androidx.media3.common.Player.Listener
|
||||||
// a get element query the count of elements might have been changed by exoplayer itself
|
// a get element query the count of elements might have been changed by exoplayer itself
|
||||||
// due to this reason some functions force the user to handle elements out of bounds exceptions.
|
// due to this reason some functions force the user to handle elements out of bounds exceptions.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PlayListIterator(
|
class PlayListIterator(
|
||||||
val exoPlayer: Player,
|
val exoPlayer: Player,
|
||||||
val fromIndex: Int,
|
val fromIndex: Int,
|
|
@ -0,0 +1,56 @@
|
||||||
|
/* 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.playerInternals
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import net.newpipe.newplayer.MediaRepository
|
||||||
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
|
data class PlaylistItem(
|
||||||
|
val title: String,
|
||||||
|
val creator: String,
|
||||||
|
val id: String,
|
||||||
|
val thumbnail: Thumbnail?,
|
||||||
|
val lengthInS: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun getPlaylistItemsFromItemList(items: List<String>, mediaRepo: MediaRepository) =
|
||||||
|
with(CoroutineScope(coroutineContext)) {
|
||||||
|
items.map { item ->
|
||||||
|
Pair(item, async {
|
||||||
|
mediaRepo.getMetaInfo(item)
|
||||||
|
})
|
||||||
|
}.map {
|
||||||
|
val metaInfo = it.second.await()
|
||||||
|
PlaylistItem(
|
||||||
|
title = metaInfo.title,
|
||||||
|
creator = metaInfo.channelName,
|
||||||
|
id = it.first,
|
||||||
|
thumbnail = metaInfo.thumbnail,
|
||||||
|
lengthInS = metaInfo.lengthInS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
package net.newpipe.newplayer.testapp
|
package net.newpipe.newplayer.testapp
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.media.Image
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import net.newpipe.newplayer.Chapter
|
import net.newpipe.newplayer.Chapter
|
||||||
import net.newpipe.newplayer.MediaRepository
|
import net.newpipe.newplayer.MediaRepository
|
||||||
|
import net.newpipe.newplayer.MetaInfo
|
||||||
import net.newpipe.newplayer.utils.OnlineThumbnail
|
import net.newpipe.newplayer.utils.OnlineThumbnail
|
||||||
import net.newpipe.newplayer.utils.Thumbnail
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -27,37 +22,30 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
||||||
return client.newCall(request).execute()
|
return client.newCall(request).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTitle(item: String) =
|
override suspend fun getMetaInfo(item: String) =
|
||||||
when (item) {
|
when (item) {
|
||||||
"6502" -> "Reverse engineering the MOS 6502"
|
"6502" -> MetaInfo(
|
||||||
"portrait" -> "Imitating generative AI videos"
|
title = context.getString(R.string.ccc_6502_title),
|
||||||
"imu" -> "Intel Management Engine deep dive "
|
channelName = context.getString(R.string.ccc_6502_channel),
|
||||||
else -> throw Exception("Unknown stream: $item")
|
thumbnail = OnlineThumbnail(context.getString(R.string.ccc_6502_thumbnail)),
|
||||||
}
|
lengthInS = context.resources.getInteger(R.integer.ccc_6502_length)
|
||||||
|
)
|
||||||
override suspend fun getChannelName(item: String) =
|
"imu" -> MetaInfo(
|
||||||
when (item) {
|
title = context.getString(R.string.ccc_imu_title),
|
||||||
"6502" -> "27c3"
|
channelName = context.getString(R.string.ccc_imu_channel),
|
||||||
"portrait" -> "无所吊谓~"
|
thumbnail = OnlineThumbnail(context.getString(R.string.ccc_imu_thumbnail)),
|
||||||
"imu" -> "36c3"
|
lengthInS = context.resources.getInteger(R.integer.ccc_imu_length)
|
||||||
else -> throw Exception("Unknown stream: $item")
|
)
|
||||||
}
|
"portrait" -> MetaInfo(
|
||||||
|
title = context.getString(R.string.portrait_title),
|
||||||
override suspend fun getThumbnail(item: String) =
|
channelName = context.getString(R.string.portrait_channel),
|
||||||
when (item) {
|
thumbnail = null,
|
||||||
"6502" ->
|
lengthInS = context.resources.getInteger(R.integer.portrait_length)
|
||||||
OnlineThumbnail("https://static.media.ccc.de/media/congress/2010/27c3-4159-en-reverse_engineering_mos_6502_preview.jpg")
|
)
|
||||||
|
|
||||||
"portrait" ->
|
|
||||||
OnlineThumbnail("https://64.media.tumblr.com/13f7e4065b4c583573a9a3e40750ccf8/9e8cf97a92704864-4b/s540x810/d966c97f755384b46dbe6d5350d35d0e9d4128ad.jpg")
|
|
||||||
|
|
||||||
"imu" ->
|
|
||||||
OnlineThumbnail("https://static.media.ccc.de/media/congress/2019/10694-hd_preview.jpg")
|
|
||||||
|
|
||||||
else -> throw Exception("Unknown stream: $item")
|
else -> throw Exception("Unknown stream: $item")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override suspend fun getAvailableStreamVariants(item: String): List<String> =
|
override suspend fun getAvailableStreamVariants(item: String): List<String> =
|
||||||
when (item) {
|
when (item) {
|
||||||
"6502" -> listOf("576p")
|
"6502" -> listOf("576p")
|
||||||
|
|
|
@ -35,10 +35,13 @@
|
||||||
<item>2400000</item>
|
<item>2400000</item>
|
||||||
<item>3600000</item>
|
<item>3600000</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
<integer name="ccc_6502_length">3116</integer>
|
||||||
|
|
||||||
<!-- A thumbler Video. The creators tried to imitate an ai generated video. I found this in a Tom Scott newsletter. -->
|
<!-- A thumbler Video. The creators tried to imitate an ai generated video. I found this in a Tom Scott newsletter. -->
|
||||||
|
<string name="portrait_title" translatable="false">Imitating generative AI videos</string>
|
||||||
|
<string name="portrait_channel" translatable="false">rongzhi</string>
|
||||||
<string name="portrait_video_example" translatable="false">https://va.media.tumblr.com/tumblr_sh62vjBX0j1z8ckep.mp4</string>
|
<string name="portrait_video_example" translatable="false">https://va.media.tumblr.com/tumblr_sh62vjBX0j1z8ckep.mp4</string>
|
||||||
|
<integer name="portrait_length">23</integer>
|
||||||
|
|
||||||
<!-- "Intel Management Engine deep dive" a talk from 36c3 -->
|
<!-- "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_link" translatable="false">https://media.ccc.de/v/36c3-10694-intel_management_engine_deep_dive</string>
|
||||||
|
@ -60,4 +63,5 @@
|
||||||
<item>3600000</item>
|
<item>3600000</item>
|
||||||
</integer-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>
|
<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>
|
||||||
|
<integer name="ccc_imu_length">3607</integer>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue