Files
JKVideo/docs/superpowers/plans/2026-03-10-dash-1080p.md
Developer 5bb6a3cd68 feat: unlock 1080P+ on Android via DASH streaming
- getPlayUrl uses fnval=16 (DASH) on Android, keeping fnval=0/html5 for iOS/web
- New utils/dash.ts builds a valid DASH MPD from Bilibili's segmentBase ranges
  and returns it as a data: URI for ExoPlayer consumption
- NativeVideoPlayer selects DASH path (type='mpd') or durl fallback automatically
- Extend PlayUrlResponse types with DashVideoItem/DashAudioItem/DashSegmentBase
2026-03-10 20:04:48 +08:00

8.1 KiB
Raw Blame History

DASH 1080P Playback Implementation Plan

For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在 Android 上解锁 1080P+ 画质,通过切换至 DASH 格式并生成合法 MPD data URI 传给 ExoPlayer。

Architecture: getPlayUrl 按平台分叉——Android 使用 fnval=16 获取 DASH JSON含分离的 video/audio 流及 segmentBase 范围iOS/Web 保持 fnval=0,platform=html5720P MP4。DASH 数据在 utils/dash.ts 中组装成标准 MPD XMLbtoa 编码为 data: URI传给 react-native-video 的 ExoPlayer 播放。

Tech Stack: React Native 0.83, Expo SDK 55, react-native-video 6.x (ExoPlayer on Android), TypeScript


File Map

文件 操作 说明
services/types.ts Modify 扩展 dash video/audio 条目类型,加 segmentBase/width/height/mimeType/frameRate
services/bilibili.ts Modify getPlayUrl 按 Platform.OS 分叉 fnval
utils/dash.ts Create buildDashDataUri(playData, qn): string
components/NativeVideoPlayer.tsx Modify 有 dash 时用 data URI + type="mpd",否则走原 durl 路径

Task 1: 扩展 PlayUrlResponse 类型

Files:

  • Modify: services/types.ts

  • Step 1: 更新 dash video/audio 条目类型,加入 B 站实际返回字段

// services/types.ts — 替换现有 PlayUrlResponse

export interface DashSegmentBase {
  Initialization: string;  // e.g. "0-938"
  indexRange: string;       // e.g. "939-2694"
}

export interface DashVideoItem {
  id: number;
  baseUrl: string;
  bandwidth: number;
  mimeType: string;         // "video/mp4"
  codecs: string;           // "avc1.640028"
  width: number;
  height: number;
  frameRate: string;        // "25" or "25000/1000"
  segmentBase: DashSegmentBase;
}

export interface DashAudioItem {
  id: number;
  baseUrl: string;
  bandwidth: number;
  mimeType: string;         // "audio/mp4"
  codecs: string;           // "mp4a.40.2"
  segmentBase: DashSegmentBase;
}

export interface PlayUrlResponse {
  durl?: Array<{
    url: string;
    length: number;
    size: number;
  }>;
  dash?: {
    duration: number;
    video: DashVideoItem[];
    audio: DashAudioItem[];
  };
  quality: number;
  accept_quality: number[];
  accept_description: string[];
}
  • Step 2: 验证 TypeScript 无报错
cd C:/claude-code-studly/reactBilibiliApp && npx tsc --noEmit 2>&1 | head -30
  • Step 3: Commit
git add services/types.ts
git commit -m "feat: extend PlayUrlResponse types for DASH segmentBase fields"

Task 2: getPlayUrl 按平台分叉

Files:

  • Modify: services/bilibili.ts (line 97102)

  • Step 1: 修改 getPlayUrl 函数

将现有函数替换为:

export async function getPlayUrl(bvid: string, cid: number, qn = 64): Promise<PlayUrlResponse> {
  const isAndroid = Platform.OS === 'android';
  const params = isAndroid
    ? { bvid, cid, qn, fnval: 16, fourk: 1 }
    : { bvid, cid, qn, fnval: 0, platform: 'html5', fourk: 1 };
  const res = await api.get('/x/player/playurl', { params });
  return res.data.data as PlayUrlResponse;
}

注:Platform 已在文件顶部从 react-native 导入(第 3 行),无需新增 import。

  • Step 2: 验证 TypeScript 无报错
cd C:/claude-code-studly/reactBilibiliApp && npx tsc --noEmit 2>&1 | head -30
  • Step 3: Commit
git add services/bilibili.ts
git commit -m "feat: use DASH (fnval=16) on Android for getPlayUrl"

Task 3: 新建 utils/dash.ts

Files:

  • Create: utils/dash.ts

  • Step 1: 创建文件,实现 buildDashDataUri

// utils/dash.ts
import type { PlayUrlResponse } from '../services/types';

/**
 * 从 Bilibili DASH 响应生成 MPD data URI。
 * 选取 id === qn 的视频流(找不到则取第一条),带宽最高的音频流。
 * 返回 "data:application/dash+xml;base64,..." 供 react-native-video 使用。
 */
export function buildDashDataUri(playData: PlayUrlResponse, qn: number): string {
  const dash = playData.dash!;

  const video =
    dash.video.find(v => v.id === qn) ?? dash.video[0];
  const audio = dash.audio.reduce((best, a) =>
    a.bandwidth > best.bandwidth ? a : best
  );

  const dur = dash.duration;

  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
     profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"
     type="static"
     mediaPresentationDuration="PT${dur}S">
  <Period duration="PT${dur}S">
    <AdaptationSet id="1" mimeType="${video.mimeType}" codecs="${video.codecs}" startWithSAP="1" subsegmentAlignment="true">
      <Representation id="v1" bandwidth="${video.bandwidth}" width="${video.width}" height="${video.height}" frameRate="${video.frameRate}">
        <BaseURL>${escapeXml(video.baseUrl)}</BaseURL>
        <SegmentBase indexRange="${video.segmentBase.indexRange}">
          <Initialization range="${video.segmentBase.Initialization}"/>
        </SegmentBase>
      </Representation>
    </AdaptationSet>
    <AdaptationSet id="2" mimeType="${audio.mimeType}" codecs="${audio.codecs}" startWithSAP="1" subsegmentAlignment="true">
      <Representation id="a1" bandwidth="${audio.bandwidth}">
        <BaseURL>${escapeXml(audio.baseUrl)}</BaseURL>
        <SegmentBase indexRange="${audio.segmentBase.indexRange}">
          <Initialization range="${audio.segmentBase.Initialization}"/>
        </SegmentBase>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>`;

  // btoa is available in React Native's Hermes JS engine
  return `data:application/dash+xml;base64,${btoa(xml)}`;
}

function escapeXml(s: string): string {
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
  • Step 2: 验证 TypeScript 无报错
cd C:/claude-code-studly/reactBilibiliApp && npx tsc --noEmit 2>&1 | head -30
  • Step 3: Commit
git add utils/dash.ts
git commit -m "feat: add buildDashDataUri utility for DASH MPD generation"

Task 4: 更新 NativeVideoPlayer 使用 DASH data URI

Files:

  • Modify: components/NativeVideoPlayer.tsx

  • Step 1: 导入 buildDashDataUri,更新 url 选取逻辑

在文件顶部 import 区域添加:

import { buildDashDataUri } from '../utils/dash';

将第 34 行的 url 计算替换为(在组件函数体内):

// 优先使用 DASH data URIAndroid降级使用 durl MP4iOS
const url = playData?.dash
  ? buildDashDataUri(playData, currentQn)
  : playData?.durl?.[0]?.url;

<Video> 组件的 source prop 更新DASH 时传入 type hint

<Video
  ref={videoRef}
  source={
    playData?.dash
      ? { uri: url!, type: 'mpd', headers: BILIBILI_HEADERS }
      : { uri: url!, headers: BILIBILI_HEADERS }
  }
  style={styles.video}
  resizeMode="contain"
  controls
  paused={false}
/>
  • Step 2: 验证 TypeScript 无报错
cd C:/claude-code-studly/reactBilibiliApp && npx tsc --noEmit 2>&1 | head -30
  • Step 3: 手动测试清单

在 Android 设备 / 模拟器上:

  1. expo run:android(需要 dev buildreact-native-video 是 native 模块)
  2. 打开任意视频详情页
  3. 确认播放器加载且有声音有画面
  4. 点击右上角清晰度按钮,检查可选项是否包含 1080P80或更高
  5. 切换清晰度,确认切换后正常播放
  6. (可选)登录后重新打开视频,确认 accept_quality 中出现更高画质选项

在 iOS如有设备

  1. 确认退化到 720P MP4 durl 路径,视频正常播放无报错
  • Step 4: Commit
git add components/NativeVideoPlayer.tsx
git commit -m "feat: play DASH streams on Android for 1080P+ quality"

完成标志

  • Android 上视频清晰度列表出现 1080Pqn=80及以上选项
  • 切换清晰度后视频带音频正常播放
  • iOS 仍可正常播放720P durl 路径)
  • npx tsc --noEmit 无类型错误