introduce datasource
This commit is contained in:
parent
f80e4c8e5f
commit
b50e63077b
|
@ -45,6 +45,7 @@ okhttpAndroid = "5.0.0-alpha.14"
|
|||
coil = "2.7.0"
|
||||
reorderable = "2.4.0-alpha02"
|
||||
media3Session = "1.4.1"
|
||||
media3ExoplayerDash = "1.4.1"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
|
@ -81,6 +82,7 @@ okhttp-android = { group = "com.squareup.okhttp3", name = "okhttp-android", vers
|
|||
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
|
||||
reorderable = { group = "sh.calvin.reorderable", name = "reorderable", version.ref = "reorderable" }
|
||||
androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3Session" }
|
||||
androidx-media3-exoplayer-dash = { group = "androidx.media3", name = "media3-exoplayer-dash", version.ref = "media3ExoplayerDash" }
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ dependencies {
|
|||
implementation(libs.coil.compose)
|
||||
implementation(libs.reorderable)
|
||||
implementation(libs.androidx.media3.session)
|
||||
implementation(libs.androidx.media3.exoplayer.dash)
|
||||
|
||||
ksp(libs.hilt.android.compiler)
|
||||
ksp(libs.androidx.hilt.compiler)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
|
|
|
@ -27,12 +27,32 @@ import net.newpipe.newplayer.utils.Thumbnail
|
|||
|
||||
data class Chapter(val chapterStartInMs: Long, val chapterTitle: String?, val thumbnail: Uri?)
|
||||
|
||||
enum class StreamType {
|
||||
VIDEO,
|
||||
AUDIO,
|
||||
AUDIO_AND_VIDEO,
|
||||
DYNAMIC
|
||||
}
|
||||
|
||||
data class StreamVariant(
|
||||
val streamType: StreamType,
|
||||
val language: String?,
|
||||
val streamVariantIdentifier: String
|
||||
)
|
||||
|
||||
data class RepoMetaInfo(
|
||||
val canHandleTimestampedLinks: Boolean,
|
||||
val pullsDataFromNetwrok: Boolean
|
||||
)
|
||||
|
||||
interface MediaRepository {
|
||||
|
||||
fun getRepoInfo() : RepoMetaInfo
|
||||
|
||||
suspend fun getMetaInfo(item: String): MediaMetadata
|
||||
|
||||
suspend fun getAvailableStreamVariants(item: String): List<String>
|
||||
suspend fun getStream(item: String, streamSelector: String): Uri
|
||||
suspend fun getAvailableStreamVariants(item: String): List<StreamVariant>
|
||||
suspend fun getStream(item: String, streamVariantSelector: StreamVariant): Uri
|
||||
|
||||
suspend fun getAvailableSubtitleVariants(item: String): List<String>
|
||||
suspend fun getSubtitle(item: String, variant: String): Uri
|
||||
|
|
|
@ -25,6 +25,7 @@ import androidx.media3.common.Player
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.util.stream.Stream
|
||||
import kotlin.Exception
|
||||
|
||||
enum class PlayMode {
|
||||
|
@ -45,6 +46,7 @@ enum class RepeatMode {
|
|||
interface NewPlayer {
|
||||
// preferences
|
||||
val preferredStreamVariants: List<String>
|
||||
val preferredStreamLanguage: List<String>
|
||||
|
||||
val exoPlayer: StateFlow<Player?>
|
||||
var playWhenReady: Boolean
|
||||
|
@ -76,7 +78,7 @@ interface NewPlayer {
|
|||
fun removePlaylistItem(uniqueId: Long)
|
||||
fun playStream(item: String, playMode: PlayMode)
|
||||
fun selectChapter(index: Int)
|
||||
fun playStream(item: String, streamVariant: String, playMode: PlayMode)
|
||||
fun playStream(item: String, streamVariant: StreamVariant, playMode: PlayMode)
|
||||
fun release()
|
||||
fun getItemLinkOfMediaItem(mediaItem: MediaItem) : String
|
||||
}
|
||||
|
|
|
@ -23,11 +23,18 @@ package net.newpipe.newplayer
|
|||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.util.Log
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Timeline
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||
import androidx.media3.session.MediaController
|
||||
import androidx.media3.session.SessionToken
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
|
@ -50,7 +57,8 @@ private const val TAG = "NewPlayerImpl"
|
|||
class NewPlayerImpl(
|
||||
val app: Application,
|
||||
private val repository: MediaRepository,
|
||||
override val preferredStreamVariants: List<String> = emptyList()
|
||||
override val preferredStreamVariants: List<String> = emptyList(),
|
||||
override val preferredStreamLanguage: List<String> = emptyList()
|
||||
) : NewPlayer {
|
||||
|
||||
private val mutableExoPlayer = MutableStateFlow<ExoPlayer?>(null)
|
||||
|
@ -134,7 +142,11 @@ class NewPlayerImpl(
|
|||
}
|
||||
|
||||
private fun setupNewExoplayer() {
|
||||
val newExoPlayer = ExoPlayer.Builder(app).build()
|
||||
val newExoPlayer = ExoPlayer.Builder(app)
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(if (repository.getRepoInfo().pullsDataFromNetwrok) C.WAKE_MODE_NETWORK else C.WAKE_MODE_LOCAL)
|
||||
.build()
|
||||
newExoPlayer.addListener(object : Player.Listener {
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
launchJobAndCollectError {
|
||||
|
@ -190,7 +202,7 @@ class NewPlayerImpl(
|
|||
}
|
||||
|
||||
override fun prepare() {
|
||||
if(exoPlayer.value == null) {
|
||||
if (exoPlayer.value == null) {
|
||||
setupNewExoplayer()
|
||||
}
|
||||
exoPlayer.value?.prepare()
|
||||
|
@ -219,13 +231,14 @@ class NewPlayerImpl(
|
|||
exoPlayer.value?.pause()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun addToPlaylist(item: String) {
|
||||
if (exoPlayer.value == null) {
|
||||
prepare()
|
||||
}
|
||||
launchJobAndCollectError {
|
||||
val mediaItem = toMediaItem(item)
|
||||
exoPlayer.value?.addMediaItem(mediaItem)
|
||||
val mediaSource = toMediaSource(item, playBackMode.value)
|
||||
exoPlayer.value?.addMediaSource(mediaSource)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,22 +258,23 @@ class NewPlayerImpl(
|
|||
|
||||
override fun playStream(item: String, playMode: PlayMode) {
|
||||
launchJobAndCollectError {
|
||||
val mediaItem = toMediaItem(item)
|
||||
val mediaItem = toMediaSource(item, playMode)
|
||||
internalPlayStream(mediaItem, playMode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun playStream(
|
||||
item: String,
|
||||
streamVariant: String,
|
||||
streamVariant: StreamVariant,
|
||||
playMode: PlayMode
|
||||
) {
|
||||
launchJobAndCollectError {
|
||||
val stream = toMediaItem(item, streamVariant)
|
||||
val stream = toMediaSource(item, streamVariant)
|
||||
internalPlayStream(stream, playMode)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun selectChapter(index: Int) {
|
||||
val chapters = currentChapters.value
|
||||
assert(index in 0..<chapters.size) {
|
||||
|
@ -287,18 +301,20 @@ class NewPlayerImpl(
|
|||
uniqueIdToIdLookup[mediaItem.mediaId.toLong()]
|
||||
?: throw NewPlayerException("Could not find Media item with mediaId: ${mediaItem.mediaId}")
|
||||
|
||||
private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) {
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun internalPlayStream(mediaSource: MediaSource, playMode: PlayMode) {
|
||||
if (exoPlayer.value?.playbackState == Player.STATE_IDLE || exoPlayer.value == null) {
|
||||
prepare()
|
||||
}
|
||||
this.playBackMode.update { playMode }
|
||||
println("gurken: playervalue: ${this.exoPlayer.value}")
|
||||
this.exoPlayer.value?.setMediaItem(mediaItem)
|
||||
|
||||
this.exoPlayer.value?.setMediaSource(mediaSource)
|
||||
this.exoPlayer.value?.play()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private suspend
|
||||
fun toMediaItem(item: String, streamVariant: String): MediaItem {
|
||||
fun toMediaSource(item: String, streamVariant: StreamVariant): MediaSource {
|
||||
val dataStream = repository.getStream(item, streamVariant)
|
||||
|
||||
val uniqueId = Random.nextLong()
|
||||
|
@ -314,21 +330,25 @@ class NewPlayerImpl(
|
|||
mutableErrorFlow.emit(e)
|
||||
}
|
||||
|
||||
return mediaItemBuilder.build()
|
||||
val mediaItem = mediaItemBuilder.build()
|
||||
|
||||
return ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory())
|
||||
.createMediaSource(mediaItem)
|
||||
}
|
||||
|
||||
private suspend
|
||||
fun toMediaItem(item: String): MediaItem {
|
||||
val availableStream = repository.getAvailableStreamVariants(item)
|
||||
var selectedStream = availableStream[availableStream.size / 2]
|
||||
fun toMediaSource(item: String, playMode: PlayMode): MediaSource {
|
||||
val availableStreams = repository.getAvailableStreamVariants(item)
|
||||
var selectedStream = availableStreams[availableStreams.size / 2]
|
||||
for (preferredStream in preferredStreamVariants) {
|
||||
if (preferredStream in availableStream) {
|
||||
selectedStream = preferredStream
|
||||
break;
|
||||
for (availableStream in availableStreams) {
|
||||
if (preferredStream == availableStream.streamVariantIdentifier) {
|
||||
selectedStream = availableStream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toMediaItem(item, selectedStream)
|
||||
return toMediaSource(item, selectedStream)
|
||||
}
|
||||
|
||||
private fun launchJobAndCollectError(task: suspend () -> Unit) =
|
||||
|
|
|
@ -8,6 +8,9 @@ import androidx.media3.common.PlaybackException
|
|||
import androidx.media3.common.util.UnstableApi
|
||||
import net.newpipe.newplayer.Chapter
|
||||
import net.newpipe.newplayer.MediaRepository
|
||||
import net.newpipe.newplayer.RepoMetaInfo
|
||||
import net.newpipe.newplayer.StreamType
|
||||
import net.newpipe.newplayer.StreamVariant
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
@ -22,6 +25,9 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
|||
return client.newCall(request).execute()
|
||||
}
|
||||
|
||||
override fun getRepoInfo() =
|
||||
RepoMetaInfo(canHandleTimestampedLinks = true, pullsDataFromNetwrok = true)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override suspend fun getMetaInfo(item: String): MediaMetadata =
|
||||
when (item) {
|
||||
|
@ -55,11 +61,37 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
|||
else -> throw Exception("Unknown stream: $item")
|
||||
}
|
||||
|
||||
override suspend fun getAvailableStreamVariants(item: String): List<String> =
|
||||
override suspend fun getAvailableStreamVariants(item: String): List<StreamVariant> =
|
||||
when (item) {
|
||||
"6502" -> listOf("576p")
|
||||
"portrait" -> listOf("720p")
|
||||
"imu" -> listOf("1080p", "576p")
|
||||
"6502" -> listOf(
|
||||
StreamVariant(
|
||||
streamType = StreamType.AUDIO_AND_VIDEO,
|
||||
language = "Deutsch",
|
||||
streamVariantIdentifier = "576p",
|
||||
),
|
||||
)
|
||||
|
||||
"portrait" -> listOf(
|
||||
StreamVariant(
|
||||
streamType = StreamType.AUDIO_AND_VIDEO,
|
||||
language = null,
|
||||
streamVariantIdentifier = "720p",
|
||||
),
|
||||
)
|
||||
|
||||
"imu" -> listOf(
|
||||
StreamVariant(
|
||||
streamType = StreamType.AUDIO_AND_VIDEO,
|
||||
language = "Deutsch",
|
||||
streamVariantIdentifier = "1080p",
|
||||
),
|
||||
StreamVariant(
|
||||
streamType = StreamType.AUDIO_AND_VIDEO,
|
||||
language = "Deutsch",
|
||||
streamVariantIdentifier = "576p",
|
||||
)
|
||||
)
|
||||
|
||||
else -> throw Exception("Unknown stream: $item")
|
||||
}
|
||||
|
||||
|
@ -68,15 +100,15 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
|||
}
|
||||
|
||||
|
||||
override suspend fun getStream(item: String, streamSelector: String) =
|
||||
override suspend fun getStream(item: String, streamVariantSelector: StreamVariant) =
|
||||
Uri.parse(
|
||||
when (item) {
|
||||
"6502" -> context.getString(R.string.ccc_6502_video)
|
||||
"portrait" -> context.getString(R.string.portrait_video_example)
|
||||
"imu" -> when (streamSelector) {
|
||||
"imu" -> when (streamVariantSelector.streamVariantIdentifier) {
|
||||
"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 selector for $item: $streamVariantSelector")
|
||||
}
|
||||
|
||||
else -> throw Exception("Unknown stream: $item")
|
||||
|
|
Loading…
Reference in New Issue