introduce datasource

This commit is contained in:
Christian Schabesberger 2024-09-11 20:28:08 +02:00
parent f80e4c8e5f
commit b50e63077b
7 changed files with 114 additions and 36 deletions

View File

@ -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" }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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) =

View File

@ -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")