From a3bc393357773db8423d46010fc51e855ff24193 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Wed, 7 Aug 2024 20:45:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=97=B6=E8=AF=B7=E6=B1=82=E5=A4=9A?= =?UTF-8?q?=E7=A7=8D=E8=A7=86=E9=A2=91=E7=BC=96=E7=A0=81=E7=9A=84=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=9C=B0=E5=9D=80=EF=BC=88App=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原本因为 App 接口请求播放地址时只会返回一种编码的视频,这就会导致一些特殊规格视频例如杜比视界可能会无法获取到播放地址 现在在获取视频播放地址时将同时请求多种编码格式,以便用户切换编码和补全单一视频编码下可能缺失的特殊规格视频 --- .../controllers2/playermenu/PictureMenu.kt | 10 +- .../dev/aaa1115910/bv/entity/VideoCodec.kt | 2 +- .../settings/content/VideoCodecSetting.kt | 2 +- .../bv/viewmodel/VideoPlayerV3ViewModel.kt | 8 +- .../dev/aaa1115910/biliapi/entity/CodeType.kt | 18 ++- .../dev/aaa1115910/biliapi/entity/PlayData.kt | 33 ++++- .../repositories/VideoPlayRepository.kt | 113 ++++++++++++------ 7 files changed, 127 insertions(+), 59 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt index de84e537..bb668ceb 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.foundation.lazy.list.itemsIndexed -import dev.aaa1115910.biliapi.entity.ApiType import dev.aaa1115910.bv.component.controllers.LocalVideoPlayerControllerData import dev.aaa1115910.bv.component.controllers2.LocalMenuFocusStateData import dev.aaa1115910.bv.component.controllers2.MenuFocusState @@ -39,7 +38,6 @@ import dev.aaa1115910.bv.entity.Audio import dev.aaa1115910.bv.entity.Resolution import dev.aaa1115910.bv.entity.VideoAspectRatio import dev.aaa1115910.bv.entity.VideoCodec -import dev.aaa1115910.bv.util.Prefs import kotlin.math.roundToInt @Composable @@ -161,13 +159,7 @@ fun PictureMenuList( verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(8.dp) ) { - val menuList = when (Prefs.apiType) { - ApiType.Web -> VideoPlayerPictureMenuItem.entries.toMutableList() - ApiType.App -> VideoPlayerPictureMenuItem.entries.toMutableList().apply { - this.remove(VideoPlayerPictureMenuItem.Codec) - } - } - itemsIndexed(menuList) { index, item -> + itemsIndexed(VideoPlayerPictureMenuItem.entries.toMutableList()) { index, item -> MenuListItem( modifier = Modifier .ifElse(index == 0, focusRestorerModifiers.childModifier), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt b/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt index b97f70fc..26117abe 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt @@ -34,6 +34,6 @@ enum class VideoCodec(private val strRes: Int, val prefix: String, val codecId: AVC -> CodeType.Code264 HEVC -> CodeType.Code265 AV1 -> CodeType.CodeAv1 - DVH1, HVC1 -> CodeType.NoCode + DVH1, HVC1 -> CodeType.Code265 } } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt index 7739cd84..be471d75 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt @@ -50,7 +50,7 @@ fun VideoCodecSetting( TvLazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { - items(items = VideoCodec.entries) { videoCodec -> + items(items = VideoCodec.entries.filter { it != VideoCodec.DVH1 && it != VideoCodec.HVC1 }) { videoCodec -> SettingsMenuSelectItem( text = videoCodec.getDisplayName(context), selected = selectedVideoCodec == videoCodec, diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt index 7c209a8d..b0d64965 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt @@ -199,7 +199,6 @@ class VideoPlayerV3ViewModel( videoPlayRepository.getPlayData( aid = avid, cid = cid, - preferCodec = Prefs.defaultVideoCodec.toBiliApiCodeType(), preferApiType = Prefs.apiType ) } @@ -291,7 +290,7 @@ class VideoPlayerV3ViewModel( } fun updateAvailableCodec() { - if (Prefs.apiType == ApiType.App) { + if (Prefs.apiType == ApiType.App && playData!!.codec.isEmpty()) { // 纠正当前实际播放的编码 val videoItem = playData!!.dashVideos .find { it.quality == currentQuality } @@ -327,7 +326,10 @@ class VideoPlayerV3ViewModel( val videoItem = playData!!.dashVideos.find { when (Prefs.apiType) { ApiType.Web -> it.quality == qn && it.codecs!!.startsWith(codec.prefix) - ApiType.App -> it.quality == qn + ApiType.App -> { + if (playData!!.codec.isEmpty()) it.quality == qn + else it.quality == qn && it.codecs!!.startsWith(codec.prefix) + } } } var videoUrl = videoItem?.baseUrl ?: playData!!.dashVideos.first().baseUrl diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt index d8afb511..2d4d7662 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt @@ -3,12 +3,18 @@ package dev.aaa1115910.biliapi.entity import bilibili.pgc.gateway.player.v2.CodeType as PgcPlayUrlCodeType import bilibili.playershared.CodeType as PlayerSharedCodeType -enum class CodeType { - NoCode, - Code264, - Code265, - CodeAv1, - Unrecognized; +enum class CodeType(val str: String, val codecId: Int) { + NoCode("none", 0), + Code264("avc1", 7), + Code265("hev1", 12), + CodeAv1("av01", 13), + Unrecognized("unknown", 0); + + companion object{ + fun fromCodecId(code: Int?) = runCatching { + entries.find { it.codecId == code }!! + }.getOrDefault(NoCode) + } fun toPlayerSharedCodeType() = when (this) { NoCode -> PlayerSharedCodeType.NOCODE diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt index 64ff8603..d1266da4 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt @@ -34,7 +34,7 @@ data class PlayData( height = it.dashVideo.height, frameRate = it.dashVideo.frameRate, backUrl = it.dashVideo.backupUrlList, - codecs = null + codecs = CodeType.fromCodecId(it.dashVideo.codecid).str ) } val dashAudios = audioList.map { @@ -62,13 +62,16 @@ data class PlayData( ) } + val codecs = playViewUniteReply.vodInfo.streamListList.associate { + it.streamInfo.quality to listOf(CodeType.fromCodecId(it.dashVideo.codecid).str) + } return PlayData( dashVideos = dashVideos, dashAudios = dashAudios, dolby = dolby, flac = flac, - codec = emptyMap(), + codec = codecs, needPay = false ) } @@ -78,6 +81,9 @@ data class PlayData( pgcPlayViewReply.videoInfo.streamListList.filter { it.dashVideoOrNull != null } val audioList = pgcPlayViewReply.videoInfo.dashAudioList val dolbyItem = pgcPlayViewReply.videoInfo.dolbyOrNull?.audio + val codecs = pgcPlayViewReply.videoInfo.streamListList.associate { + it.info.quality to listOf(CodeType.fromCodecId(it.dashVideo.codecid).str) + } val needPay = pgcPlayViewReply.business.isPreview val dashVideos = streamList.map { @@ -90,7 +96,7 @@ data class PlayData( height = it.dashVideo.height, frameRate = it.dashVideo.frameRate, backUrl = it.dashVideo.backupUrlList, - codecs = null + codecs = CodeType.fromCodecId(it.dashVideo.codecid).str ) } val dashAudios = audioList.map { @@ -115,7 +121,7 @@ data class PlayData( dashAudios = dashAudios, dolby = dolby, flac = null, - codec = emptyMap(), + codec = codecs, needPay = needPay ) } @@ -294,6 +300,25 @@ data class PlayData( ) } } + + operator fun plus(other: PlayData): PlayData { + return PlayData( + dashVideos = (dashVideos + other.dashVideos) + .distinctBy { "${it.codecId}_${it.quality}" } + .sortedByDescending { it.quality }, + dashAudios = (dashAudios + other.dashAudios) + .distinctBy { it.codecId } + .sortedByDescending { it.codecId }, + dolby = dolby ?: other.dolby, + flac = flac ?: other.flac, + codec = codec.map { + it.key to (it.value + other.codec[it.key].orEmpty()) + .distinct() + .filter { it != "none" } + }.toMap(), + needPay = needPay || other.needPay + ) + } } /** diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt index 51f36fbe..52a7400a 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt @@ -18,6 +18,10 @@ import dev.aaa1115910.biliapi.entity.video.VideoShot import dev.aaa1115910.biliapi.grpc.utils.handleGrpcException import dev.aaa1115910.biliapi.http.BiliHttpApi import dev.aaa1115910.biliapi.http.BiliHttpProxyApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext import bilibili.pgc.gateway.player.v2.PlayURLGrpcKt as PgcPlayURLGrpcKt class VideoPlayRepository( @@ -46,7 +50,6 @@ class VideoPlayRepository( suspend fun getPlayData( aid: Long, cid: Long, - preferCodec: CodeType = CodeType.NoCode, preferApiType: ApiType = ApiType.Web ): PlayData { return when (preferApiType) { @@ -64,20 +67,40 @@ class VideoPlayRepository( } ApiType.App -> { - val playUniteReplay = runCatching { - playerStub?.playViewUnite(playViewUniteReq { - vod = videoVod { - this.aid = aid - this.cid = cid - fnval = 4048 - qn = 127 - fnver = 0 - fourk = true - preferCodecType = preferCodec.toPlayerSharedCodeType() + withContext(Dispatchers.IO) { + val codecTypes = listOf( + CodeType.Code264, + CodeType.Code265, + CodeType.CodeAv1 + ) + val replies = codecTypes.map { codecType -> + async { + val playUniteReply = runCatching { + playerStub?.playViewUnite(playViewUniteReq { + vod = videoVod { + this.aid = aid + this.cid = cid + fnval = 4048 + qn = 127 + fnver = 0 + fourk = true + preferCodecType = codecType.toPlayerSharedCodeType() + } + }) ?: throw IllegalStateException("Player stub is not initialized") + }.onFailure { + // dont throw + runCatching { handleGrpcException(it) } + }.getOrNull() + playUniteReply } - }) ?: throw IllegalStateException("Player stub is not initialized") - }.onFailure { handleGrpcException(it) }.getOrThrow() - PlayData.fromPlayViewUniteReply(playUniteReplay) + }.awaitAll() + val result = replies.map { + it?.let { PlayData.fromPlayViewUniteReply(it) } + }.reduce { acc, playData -> + acc?.let { playData?.let { acc + playData } ?: acc } ?: playData + } ?: throw IllegalStateException("All codec types are failed to get play data") + result + } } } } @@ -120,27 +143,47 @@ class VideoPlayRepository( } ApiType.App -> { - val pgcPlayViewReply = runCatching { - val req = playViewReq { - this.epid = epid.toLong() - this.cid = cid.toLong() - qn = 127 - fnver = 0 - fnval = 4048 - fourk = true - forceHost = 0 - download = 0 - preferCodecType = preferCodec.toPgcPlayUrlCodeType() - } - if (enableProxy) { - proxyPgcPlayUrlStub?.playView(req) - ?: throw IllegalStateException("Proxy pgc play url stub is not initialized") - } else { - pgcPlayUrlStub?.playView(req) - ?: throw IllegalStateException("Pgc play url stub is not initialized") - } - }.onFailure { handleGrpcException(it) }.getOrThrow() - PlayData.fromPgcPlayViewReply(pgcPlayViewReply) + withContext(Dispatchers.IO) { + val codecTypes = listOf( + CodeType.Code264, + CodeType.Code265, + CodeType.CodeAv1 + ) + val replies = codecTypes.map { codecType -> + val req = playViewReq { + this.epid = epid.toLong() + this.cid = cid + qn = 127 + fnver = 0 + fnval = 4048 + fourk = true + forceHost = 0 + download = 0 + preferCodecType = codecType.toPgcPlayUrlCodeType() + } + async { + val playReply = runCatching { + if (enableProxy) { + proxyPgcPlayUrlStub?.playView(req) + ?: throw IllegalStateException("Proxy pgc play url stub is not initialized") + } else { + pgcPlayUrlStub?.playView(req) + ?: throw IllegalStateException("Pgc play url stub is not initialized") + } + }.onFailure { + // dont throw + runCatching { handleGrpcException(it) } + }.getOrNull() + playReply + } + }.awaitAll() + val result = replies.map { + it?.let { PlayData.fromPgcPlayViewReply(it) } + }.reduce { acc, playData -> + acc?.let { playData?.let { acc + playData } ?: acc } ?: playData + } ?: throw IllegalStateException("All codec types are failed to get play data") + result + } } } }