Jkvideo Bilibili React Native
Expert skill for building and extending JKVideo, a React Native Bilibili-like client with DASH playback, danmaku, WBI signing, and live streaming
- Skill ID
- reason-machines/trending-skills/jkvideo-bilibili-react-native
- Publisher
- reason-machines
- Repository
- trending-skills
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
reason-machines/trending-skills/jkvideo-bilibili-react-nativeInstalls these files- SKILL.md
What this skill tells the agent
JKVideo Bilibili React Native Client
Skill by ara.so — Daily 2026 Skills collection.
JKVideo is a full-featured third-party Bilibili client built with React Native 0.83 + Expo SDK 55. It supports DASH adaptive streaming, real-time danmaku (bullet comments), WBI API signing, QR code login, live streaming with WebSocket danmaku, and a download manager with LAN QR sharing.
Installation & Setup
Prerequisites
- Node.js 18+
- npm or yarn
- For Android: Android Studio + SDK
- For iOS: macOS + Xcode 15+
Quick Start (Expo Go — no compilation)
git clone https://github.com/tiajinsha/JKVideo.git
cd JKVideo
npm install
npx expo startScan the QR with Expo Go app. Note: DASH 1080P+ requires Dev Build.
Dev Build (Full Features — Recommended)
npm install
npx expo run:android # Android
npx expo run:ios # iOS (macOS + Xcode required)Web with Image Proxy
npm install
npx expo start --web
# In a separate terminal:
node scripts/proxy.js # Starts proxy on port 3001 to bypass Bilibili referer restrictionsInstall APK Directly (Android)
Download from Releases — enable "Install from unknown sources" in Android settings.
Project Structure
app/
index.tsx # Home (PagerView hot/live tabs)
video/[bvid].tsx # Video detail (playback + comments + danmaku)
live/[roomId].tsx # Live detail (HLS + real-time danmaku)
search.tsx # Search page
downloads.tsx # Download manager
settings.tsx # Settings (quality, logout)
components/ # UI: player, danmaku overlay, cards
hooks/ # Data hooks: video list, streams, danmaku
services/ # Bilibili API (axios + cookie interceptor)
store/ # Zustand stores: auth, download, video, settings
utils/ # Helpers: format, image proxy, MPD builderKey Technology Stack
| Layer | Technology |
|---|---|
| Framework | React Native 0.83 + Expo SDK 55 |
| Routing | expo-router v4 (file-system, Stack nav) |
| State | Zustand |
| HTTP | Axios |
| Storage | @react-native-async-storage/async-storage |
| Video | react-native-video (DASH MPD / HLS / MP4) |
| Fallback | react-native-webview (HTML5 video injection) |
| Paging | react-native-pager-view |
| Icons | @expo/vector-icons (Ionicons) |
WBI Signature Implementation
Bilibili requires WBI signing for most API calls. JKVideo implements pure TypeScript MD5 with 12h nav cache.
// utils/wbi.ts — pure TS MD5, no external crypto deps
const MIXIN_KEY_ENC_TAB = [
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35,
27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13
];
function getMixinKey(rawKey: string): string {
return MIXIN_KEY_ENC_TAB
.map(i => rawKey[i])
.join('')
.slice(0, 32);
}
export async function signWbi(
params: Record<string, string | number>,
imgKey: string,
subKey: string
): Promise<Record<string, string | number>> {
const mixinKey = getMixinKey(imgKey + subKey);
const wts = Math.floor(Date.now() / 1000);
const signParams = { ...params, wts };
// Sort params alphabetically, filter special chars
const query = Object.keys(signParams)
.sort()
.map(k => {
const val = String(signParams[k]).replace(/[!'()*]/g, '');
return `${encodeURIComponent(k)}=${encodeURIComponent(val)}`;
})
.join('&');
const wRid = md5(query + mixinKey); // pure TS md5
return { ...signParams, w_rid: wRid };
}
// Fetch and cache nav keys (12h TTL)
export async function getWbiKeys(): Promise<{ imgKey: string; subKey: string }> {
const cached = await AsyncStorage.getItem('wbi_keys');
if (cached) {
const { keys, ts } = JSON.parse(cached);
if (Date.now() - ts < 12 * 3600 * 1000) return keys;
}
const res = await api.get('/x/web-interface/nav');
const { img_url, sub_url } = res.data.data.wbi_img;
const imgKey = img_url.split('/').pop()!.replace('.png', '');
const subKey = sub_url.split('/').pop()!.replace('.png', '');
const keys = { imgKey, subKey };
await AsyncStorage.setItem('wbi_keys', JSON.stringify({ keys, ts: Date.now() }));
return keys;
}Bilibili API Service
// services/api.ts
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
export const api = axios.create({
baseURL: 'https://api.bilibili.com',
timeout: 15000,
headers: {
'User-Agent': 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36',
'Referer': 'https://www.bilibili.com',
},
});
// Inject SESSDATA cookie from store
api.interceptors.request.use(async (config) => {
const sessdata = await AsyncStorage.getItem('SESSDATA');
if (sessdata) {
config.headers['Cookie'] = `SESSDATA=${sessdata}`;
}
return config;
});
// Popular video list (WBI signed)
export async function getPopularVideos(pn = 1, ps = 20) {
const { imgKey, subKey } = await getWbiKeys();
const signed = await signWbi({ pn, ps }, imgKey, subKey);
const res = await api.get('/x/web-interface/popular', { params: signed });
return res.data.data.list;
}
// Video stream info (DASH)
export async function getVideoStream(bvid: string, cid: number, qn = 80) {
const { imgKey, subKey } = await getWbiKeys();
const signed = await signWbi(
{ bvid, cid, qn, fnval: 4048, fnver: 0, fourk: 1 },
imgKey, subKey
);
const res = await api.get('/x/player/wbi/playurl', { params: signed });
return res.data.data;
}
// Live stream URL
export async function getLiveStreamUrl(roomId: number) {
const res = await api.get('/room/v1/Room/playUrl', {
params: { cid: roomId, quality: 4, platform: 'h5' },
baseURL: 'https://api.live.bilibili.com',
});
return res.data.data.durl[0].url; // HLS m3u8
}DASH MPD Builder
ExoPlayer needs a local MPD file. JKVideo generates it from Bilibili's DASH response:
// utils/buildDashMpd.ts
export function buildDashMpdUri(dashData: BiliDashData): string {
const { duration, video, audio } = dashData;
const videoAdaptations = video.map((v) => `
<AdaptationSet mimeType="video/mp4" segmentAlignment="true">
<Representation id="${v.id}" bandwidth="${v.bandwidth}"
codecs="${v.codecs}" width="${v.width}" height="${v.height}">
<BaseURL>${escapeXml(v.baseUrl)}</BaseURL>
<SegmentBase indexRange="${v.segmentBase.indexRange}">
<Initialization range="${v.segmentBase.initialization}"/>
</SegmentBase>
</Representation>
</AdaptationSet>`).join('');
const audioAdaptations = audio.map((a) => `
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true">
<Representation id="${a.id}" bandwidth="${a.bandwidth}" codecs="${a.codecs}">
<BaseURL>${escapeXml(a.baseUrl)}</BaseURL>
<SegmentBase indexRange="${a.segmentBase.indexRange}">
<Initialization range="${a.segmentBase.initialization}"/>
</SegmentBase>
</Representation>
</AdaptationSet>`).join('');
const mpd = `<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static"
mediaPresentationDuration="PT${duration}S" minBufferTime="PT1.5S">
<Period duration="PT${duration}S">
${videoAdaptations}
${audioAdaptations}
</Period>
</MPD>`;
// Write to temp file, return file:// URI for ExoPlayer
const path = `${FileSystem.cacheDirectory}dash_${Date.now()}.mpd`;
FileSystem.writeAsStringAsync(path, mpd);
return path;
}Video Player Component
// components/VideoPlayer.tsx
import Video from 'react-native-video';
import { WebView } from 'react-native-webview';
import { useVideoStore } from '../store/videoStore';
interface VideoPlayerProps {
bvid: string;
cid: number;
autoPlay?: boolean;
}
export function VideoPlayer({ bvid, cid, autoPlay = false }: VideoPlayerProps) {More from reason-machines
Use this skill in a project
Open any RiverX project, search the Skills panel for reason-machines/trending-skills/jkvideo-bilibili-react-native, and install it in one click.
