Skip to content

Commit

Permalink
同时请求多种视频编码的播放地址(App接口)
Browse files Browse the repository at this point in the history
原本因为 App 接口请求播放地址时只会返回一种编码的视频,这就会导致一些特殊规格视频例如杜比视界可能会无法获取到播放地址
现在在获取视频播放地址时将同时请求多种编码格式,以便用户切换编码和补全单一视频编码下可能缺失的特殊规格视频
  • Loading branch information
aaa1115910 committed Aug 7, 2024
1 parent 5e2d37c commit a3bc393
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ class VideoPlayerV3ViewModel(
videoPlayRepository.getPlayData(
aid = avid,
cid = cid,
preferCodec = Prefs.defaultVideoCodec.toBiliApiCodeType(),
preferApiType = Prefs.apiType
)
}
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand Down
18 changes: 12 additions & 6 deletions bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 29 additions & 4 deletions bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
)
}
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -115,7 +121,7 @@ data class PlayData(
dashAudios = dashAudios,
dolby = dolby,
flac = null,
codec = emptyMap(),
codec = codecs,
needPay = needPay
)
}
Expand Down Expand Up @@ -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
)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand All @@ -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
}
}
}
}
Expand Down Expand Up @@ -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
}
}
}
}
Expand Down

0 comments on commit a3bc393

Please sign in to comment.