forward player playlist into viewmodel
This commit is contained in:
parent
99b79816f0
commit
888d518304
|
@ -21,16 +21,22 @@
|
|||
package net.newpipe.newplayer
|
||||
|
||||
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?)
|
||||
|
||||
data class MetaInfo(
|
||||
val title: String,
|
||||
val channelName: String,
|
||||
val thumbnail: Thumbnail?,
|
||||
val lengthInS: Int
|
||||
)
|
||||
|
||||
|
||||
interface MediaRepository {
|
||||
suspend fun getTitle(item: String) : String
|
||||
suspend fun getChannelName(item: String): String
|
||||
suspend fun getThumbnail(item: String): Thumbnail
|
||||
|
||||
suspend fun getMetaInfo(item: String): MetaInfo
|
||||
|
||||
suspend fun getAvailableStreamVariants(item: String): List<String>
|
||||
suspend fun getStream(item: String, streamSelector: String): Uri
|
||||
|
|
|
@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.asSharedFlow
|
|||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import net.newpipe.newplayer.utils.PlayList
|
||||
import net.newpipe.newplayer.playerInternals.PlayList
|
||||
import kotlin.Exception
|
||||
|
||||
enum class PlayMode {
|
||||
|
@ -60,6 +60,7 @@ interface NewPlayer {
|
|||
val duration: Long
|
||||
val bufferedPercentage: Int
|
||||
val repository: MediaRepository
|
||||
val sharingLinkWithOffsetPossible: Boolean
|
||||
var currentPosition: Long
|
||||
var fastSeekAmountSec: Int
|
||||
var playBackMode: PlayMode
|
||||
|
@ -84,13 +85,21 @@ interface NewPlayer {
|
|||
data class Builder(val app: Application, val repository: MediaRepository) {
|
||||
private var mediaSourceFactory: MediaSource.Factory? = null
|
||||
private var preferredStreamVariants: List<String> = emptyList()
|
||||
private var sharingLinkWithOffsetPossible = false
|
||||
|
||||
fun setMediaSourceFactory(mediaSourceFactory: MediaSource.Factory) {
|
||||
fun setMediaSourceFactory(mediaSourceFactory: MediaSource.Factory) : Builder {
|
||||
this.mediaSourceFactory = mediaSourceFactory
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPreferredStreamVariants(preferredStreamVariants: List<String>) {
|
||||
fun setPreferredStreamVariants(preferredStreamVariants: List<String>) : Builder {
|
||||
this.preferredStreamVariants = preferredStreamVariants
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSharingLinkWithOffsetPossible(possible: Boolean) : Builder {
|
||||
this.sharingLinkWithOffsetPossible = false
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): NewPlayer {
|
||||
|
@ -102,7 +111,8 @@ interface NewPlayer {
|
|||
app = app,
|
||||
internalPlayer = exoPlayerBuilder.build(),
|
||||
repository = repository,
|
||||
preferredStreamVariants = preferredStreamVariants
|
||||
preferredStreamVariants = preferredStreamVariants,
|
||||
sharingLinkWithOffsetPossible = sharingLinkWithOffsetPossible
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +124,7 @@ class NewPlayerImpl(
|
|||
override val internalPlayer: Player,
|
||||
override val preferredStreamVariants: List<String>,
|
||||
override val repository: MediaRepository,
|
||||
override val sharingLinkWithOffsetPossible: Boolean
|
||||
) : NewPlayer {
|
||||
|
||||
var mutableErrorFlow = MutableSharedFlow<Exception>()
|
||||
|
@ -121,6 +132,7 @@ class NewPlayerImpl(
|
|||
|
||||
override val bufferedPercentage: Int
|
||||
get() = internalPlayer.bufferedPercentage
|
||||
|
||||
override var currentPosition: Long
|
||||
get() = internalPlayer.currentPosition
|
||||
set(value) {
|
||||
|
|
|
@ -22,9 +22,10 @@ package net.newpipe.newplayer.model
|
|||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import net.newpipe.newplayer.Chapter
|
||||
import net.newpipe.newplayer.playerInternals.PlaylistItem
|
||||
import net.newpipe.newplayer.ui.ContentScale
|
||||
|
||||
@Parcelize
|
||||
data class VideoPlayerUIState(
|
||||
val uiMode: UIModeState,
|
||||
val playing: Boolean,
|
||||
|
@ -39,8 +40,10 @@ data class VideoPlayerUIState(
|
|||
val fastSeekSeconds: Int,
|
||||
val soundVolume: Float,
|
||||
val brightness: Float?, // when null use system value
|
||||
val embeddedUiConfig: EmbeddedUiConfig?
|
||||
) : Parcelable {
|
||||
val embeddedUiConfig: EmbeddedUiConfig?,
|
||||
val playList: List<PlaylistItem>,
|
||||
val chapters: List<Chapter>
|
||||
) {
|
||||
companion object {
|
||||
val DEFAULT = VideoPlayerUIState(
|
||||
// TODO: replace this with the placeholder state.
|
||||
|
@ -59,7 +62,9 @@ data class VideoPlayerUIState(
|
|||
fastSeekSeconds = 0,
|
||||
soundVolume = 0f,
|
||||
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 net.newpipe.newplayer.NewPlayer
|
||||
import net.newpipe.newplayer.ui.ContentScale
|
||||
import net.newpipe.newplayer.utils.Thumbnail
|
||||
|
||||
|
||||
interface VideoPlayerViewModel {
|
||||
var newPlayer: NewPlayer?
|
||||
|
|
|
@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat.getSystemService
|
|||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Player
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -43,6 +44,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import net.newpipe.newplayer.utils.VideoSize
|
||||
import net.newpipe.newplayer.NewPlayer
|
||||
import net.newpipe.newplayer.playerInternals.getPlaylistItemsFromItemList
|
||||
import net.newpipe.newplayer.ui.ContentScale
|
||||
|
||||
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()
|
||||
|
||||
private fun installNewPlayer() {
|
||||
|
@ -148,6 +150,11 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
it.copy(isLoading = isLoading)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlaylistMetadataChanged(mediaMetadata: MediaMetadata) {
|
||||
super.onPlaylistMetadataChanged(mediaMetadata)
|
||||
updatePlaylist()
|
||||
}
|
||||
})
|
||||
}
|
||||
newPlayer?.let { newPlayer ->
|
||||
|
@ -162,7 +169,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePlaylist()
|
||||
}
|
||||
|
||||
fun updateContentRatio(videoSize: VideoSize) {
|
||||
|
@ -393,4 +400,18 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
|
||||
|
||||
} ?: 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.Listener
|
||||
|
||||
|
||||
// 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
|
||||
// due to this reason some functions force the user to handle elements out of bounds exceptions.
|
||||
|
||||
|
||||
|
||||
|
||||
class PlayListIterator(
|
||||
val exoPlayer: Player,
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
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
|
||||
import net.newpipe.newplayer.MediaRepository
|
||||
import net.newpipe.newplayer.MetaInfo
|
||||
import net.newpipe.newplayer.utils.OnlineThumbnail
|
||||
import net.newpipe.newplayer.utils.Thumbnail
|
||||
import okhttp3.OkHttpClient
|
||||
|
@ -27,37 +22,30 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
|||
return client.newCall(request).execute()
|
||||
}
|
||||
|
||||
override suspend fun getTitle(item: String) =
|
||||
override suspend fun getMetaInfo(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" ->
|
||||
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")
|
||||
"6502" -> MetaInfo(
|
||||
title = context.getString(R.string.ccc_6502_title),
|
||||
channelName = context.getString(R.string.ccc_6502_channel),
|
||||
thumbnail = OnlineThumbnail(context.getString(R.string.ccc_6502_thumbnail)),
|
||||
lengthInS = context.resources.getInteger(R.integer.ccc_6502_length)
|
||||
)
|
||||
"imu" -> MetaInfo(
|
||||
title = context.getString(R.string.ccc_imu_title),
|
||||
channelName = context.getString(R.string.ccc_imu_channel),
|
||||
thumbnail = OnlineThumbnail(context.getString(R.string.ccc_imu_thumbnail)),
|
||||
lengthInS = context.resources.getInteger(R.integer.ccc_imu_length)
|
||||
)
|
||||
"portrait" -> MetaInfo(
|
||||
title = context.getString(R.string.portrait_title),
|
||||
channelName = context.getString(R.string.portrait_channel),
|
||||
thumbnail = null,
|
||||
lengthInS = context.resources.getInteger(R.integer.portrait_length)
|
||||
)
|
||||
|
||||
else -> throw Exception("Unknown stream: $item")
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getAvailableStreamVariants(item: String): List<String> =
|
||||
when (item) {
|
||||
"6502" -> listOf("576p")
|
||||
|
|
|
@ -35,10 +35,13 @@
|
|||
<item>2400000</item>
|
||||
<item>3600000</item>
|
||||
</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. -->
|
||||
<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>
|
||||
|
||||
<integer name="portrait_length">23</integer>
|
||||
|
||||
<!-- "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>
|
||||
|
@ -60,4 +63,5 @@
|
|||
<item>3600000</item>
|
||||
</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>
|
||||
<integer name="ccc_imu_length">3607</integer>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue