第三方哔哩哔哩安卓客户端
一个专为平板、TV及车机设备优化的第三方哔哩哔哩安卓客户端,支持触摸与遥控操作,兼容安卓5.0以上系统。
功能特性
- 多设备适配:同时支持触摸屏(平板、手机)与遥控器(TV、车机)操作
- 完整功能导航:侧边栏集成搜索、推荐、分类、动态、直播、我的个人中心
- 扫码登录:支持B站扫码登录,安全便捷
- 强大视频播放:基于Media3(ExoPlayer)内核,支持分辨率切换、编码选择、播放倍速、字幕显示与弹幕系统
- 丰富的播放设置:独立的设置页面,可调整播放与弹幕偏好
- 追番追踪:专门的追番页面,不错过任何更新
安装指南
环境要求
- JDK 17
- Android SDK (compileSdk 36)
- 设备系统:Android 5.0+
构建步骤
-
克隆仓库
git clone https://github.com/your-repo/blbl-android.git cd blbl-android -
调试包构建
./gradlew assembleDebug -
发布包构建(已开启R8混淆与资源压缩)
./gradlew assembleRelease -
自定义版本号构建(适用于本地或CI)
./gradlew assembleRelease -PversionName=0.1.1 -PversionCode=2
临时更新方案:当前版本内置了国内可直接访问的直链,便于测试阶段覆盖更新。稳定后将移除该功能。如需纯净版本,请从Release页面下载Action编译的安装包。
使用说明
基础操作
- 侧边栏导航:从屏幕左侧滑动或点击左上角图标打开导航菜单
- 扫码登录:在"我的"页面点击登录,使用B站App扫码完成授权
- 视频播放:点击任意视频卡片进入播放页面,支持:
- 分辨率/编码切换
- 倍速播放(0.5x - 2.0x)
- 字幕开关与选择
- 弹幕显示/隐藏与设置
典型使用场景
场景一:平板追番
- 登录后进入"追番"页面
- 浏览追更列表,点击剧集卡片
- 在播放页调整清晰度与倍速,开启弹幕互动
场景二:TV遥控观看
- 使用遥控器方向键选择推荐视频
- 确认键进入播放,菜单键调出播放设置
- 按返回键返回上一级
核心模块概览
| 模块 | 说明 |
|---|---|
| 推荐页 | 个性化视频流推送 |
| 分类页 | 按分区浏览内容 |
| 动态页 | 关注UP主的最新动态 |
| 直播页 | 正在进行的直播列表 |
| 我的页 | 个人信息、历史记录与设置 |
| 搜索页 | 关键词搜索视频/UP主 |
核心代码
1. 视频播放器初始化(基于Media3 ExoPlayer)
// PlayerManager.kt - 播放器核心初始化片段
class PlayerManager(private val context: Context) {
private var exoPlayer: ExoPlayer? = null
fun initPlayer(rendererView: RendererView): ExoPlayer {
val trackSelector = DefaultTrackSelector(context).apply {
setParameters(buildUponParameters {
setMaxVideoSize(1920, 1080)
setPreferredVideoMimeType(MimeTypes.VIDEO_H264)
})
}
exoPlayer = ExoPlayer.Builder(context)
.setTrackSelector(trackSelector)
.setLoadControl(DefaultLoadControl())
.setBandwidthMeter(DefaultBandwidthMeter.Builder(context).build())
.build()
exoPlayer?.apply {
setVideoSurfaceView(rendererView.surfaceView)
addListener(playerEventListener)
}
return exoPlayer!!
}
fun preparePlayback(url: String, headers: Map<String, String>) {
val mediaItem = MediaItem.Builder()
.setUri(url)
.setCustomCacheKey(url)
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build()
exoPlayer?.setMediaItem(mediaItem)
exoPlayer?.prepare()
exoPlayer?.playWhenReady = true
}
}
2. 弹幕引擎渲染逻辑
// DanmakuEngine.kt - 弹幕解析与渲染核心
class DanmakuEngine(private val surfaceView: SurfaceView) {
private var danmakuContext: DanmakuContext? = null
private var danmakuParser: BaseDanmakuParser? = null
fun init() {
danmakuContext = DanmakuContext.create()
danmakuParser = object : BaseDanmakuParser() {
override fun parse(): DanmakuList? {
// 解析B站protobuf格式弹幕数据
val danmakuList = DanmakuList()
// 弹幕数据转换逻辑...
return danmakuList
}
}
DanmakuView(surfaceView.context).apply {
setCallback(object : DanmakuView.Callback {
override fun prepared() {
start()
}
override fun updateTimer(interval: Long) {}
override fun rendererFailed(exception: Throwable) {}
})
prepare(danmakuParser, danmakuContext)
}
}
fun addDanmaku(content: String, color: Int, type: Int) {
val danmaku = danmakuContext?.mDanmakuFactory?.createDanmaku(type)
danmaku?.apply {
text = content
textColor = color
setTime(System.currentTimeMillis())
timing = currentPosition
}
danmakuContext?.getDanmakuView()?.addDanmaku(danmaku)
}
}
3. 网络请求与API封装(OkHttp + Protobuf)
// ApiService.kt - B站API请求封装示例
class BiliApiService {
private val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(AuthInterceptor())
.addInterceptor(GzipInterceptor())
.build()
suspend fun getRecommendVideos(page: Int): List<VideoItem> {
val request = Request.Builder()
.url("https://api.bilibili.com/x/web-interface/index/top/rcmd")
.addHeader("User-Agent", USER_AGENT)
.addHeader("Referer", "https://www.bilibili.com")
.addQueryParameter("fresh_type", "4")
.addQueryParameter("version", "1")
.addQueryParameter("pn", page.toString())
.build()
val response = client.newCall(request).await()
val protobufData = response.body?.bytes() ?: return emptyList()
// 解析protobuf-lite格式数据
return BiliProto.RecommendResponse.parseFrom(protobufData).videoListList
.map { it.toVideoItem() }
}
private class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val request = original.newBuilder()
.addHeader("Cookie", getStoredCookie())
.addHeader("Authorization", "Bearer ${getAccessToken()}")
.build()
return chain.proceed(request)
}
}
}
4. 侧边栏导航与Fragment容器
// MainActivity.kt - 导航抽屉与Fragment切换
class MainActivity : AppCompatActivity() {
private lateinit var drawerLayout: DrawerLayout
private lateinit var navView: NavigationView
private lateinit var fragmentContainer: FrameLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
drawerLayout = findViewById(R.id.drawer_layout)
navView = findViewById(R.id.nav_view)
fragmentContainer = findViewById(R.id.fragment_container)
setupNavigation()
// 默认加载推荐页
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, RecommendFragment())
.commit()
}
}
private fun setupNavigation() {
navView.setNavigationItemSelectedListener { menuItem ->
val fragment = when (menuItem.itemId) {
R.id.nav_recommend -> RecommendFragment()
R.id.nav_category -> CategoryFragment()
R.id.nav_dynamic -> DynamicFragment()
R.id.nav_live -> LiveFragment()
R.id.nav_profile -> ProfileFragment()
R.id.nav_search -> SearchFragment()
R.id.nav_follow -> FollowAnimeFragment()
else -> null
}
fragment?.let {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, it)
.commit()
drawerLayout.closeDrawer(GravityCompat.START)
}
true
}
}
}
技术栈
- 语言与框架:Kotlin + AndroidX + ViewBinding
- 视频播放:Media3 (ExoPlayer)
- 网络请求:OkHttp + Protobuf-lite
- UI组件:Material Design Components / RecyclerView / ViewPager2
- 构建工具:Gradle + R8混淆 + 资源压缩
GitHub Actions
项目包含两套手动触发工作流:
- Android Debug:手动输入
version_name触发调试包构建 - Android Release:手动输入
version_name,需要配置签名Secrets
需要配置的仓库Secrets:
RELEASE_KEYSTORE_BASE64RELEASE_STORE_PASSWORDRELEASE_KEY_ALIASRELEASE_KEY_PASSWORD
待办事项
- 完善遥控器操作逻辑
- 统一样式大小计算规则
- 移除测试用内置直链更新方案
致谢
免责声明
不得利用本项目进行任何非法活动,不得干扰B站的正常运营,不得传播恶意软件或病毒。
为降低法律风险,请遵守以下规范:
- 🚫 禁止在官方平台(b站)及官方账号区域(如b站微博评论区)宣传本项目
- 🚫 禁止在微信公众号平台宣传本项目
- 🚫 禁止利用本项目牟利,本项目无任何盈利行为,第三方盈利与本项目无关
代码由AI生成,如有问题请联系 OpenAI 😤 GqIcyjPXfClN0ovBixvyWHpB0K/qCSlwybNZDTCBJwA=