Expo Tailwind Setup
CherryHQ/cherry-studio-app
Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling
Expert skill for building and extending JKVideo, a React Native Bilibili-like client with DASH playback, danmaku, WBI signing, and live streaming
$ npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills jkvideo-bilibili-react-native --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/jkvideo-bilibili-react-native .claude/skills/jkvideo-bilibili-react-native && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "jkvideo-bilibili-react-native" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/jkvideo-bilibili-react-native into .claude/skills/jkvideo-bilibili-react-native/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "jkvideo-bilibili-react-native", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/jkvideo-bilibili-react-nativeType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills jkvideo-bilibili-react-native --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/jkvideo-bilibili-react-native .agents/skills/jkvideo-bilibili-react-native && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "jkvideo-bilibili-react-native" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/jkvideo-bilibili-react-native into .agents/skills/jkvideo-bilibili-react-native/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "jkvideo-bilibili-react-native", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills jkvideo-bilibili-react-native --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/jkvideo-bilibili-react-native .cursor/skills/jkvideo-bilibili-react-native && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "jkvideo-bilibili-react-native" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/jkvideo-bilibili-react-native into .cursor/skills/jkvideo-bilibili-react-native/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "jkvideo-bilibili-react-native", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/LeoYeAI/openclaw-master-skills.git --path skills/jkvideo-bilibili-react-native--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills jkvideo-bilibili-react-native --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/jkvideo-bilibili-react-native .gemini/skills/jkvideo-bilibili-react-native && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "jkvideo-bilibili-react-native" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/jkvideo-bilibili-react-native into .gemini/skills/jkvideo-bilibili-react-native/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "jkvideo-bilibili-react-native", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install LeoYeAI/openclaw-master-skills jkvideo-bilibili-react-nativeInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/jkvideo-bilibili-react-native .github/skills/jkvideo-bilibili-react-native && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "jkvideo-bilibili-react-native" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/jkvideo-bilibili-react-native into .github/skills/jkvideo-bilibili-react-native/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "jkvideo-bilibili-react-native", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills jkvideo-bilibili-react-native --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/jkvideo-bilibili-react-native .opencode/skills/jkvideo-bilibili-react-native && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "jkvideo-bilibili-react-native" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/jkvideo-bilibili-react-native into .opencode/skills/jkvideo-bilibili-react-native/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "jkvideo-bilibili-react-native", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
jkvideo-bilibili-react-nativeExpert skill for building and extending JKVideo, a React Native Bilibili-like client with DASH playback, danmaku, WBI signing, and live streaming
Jkvideo Bilibili React Native is an agent skill from LeoYeAI/openclaw-master-skills. Expert skill for building and extending JKVideo, a React Native Bilibili-like client with DASH playback, danmaku, WBI signing, and live streaming
Its SKILL.md is about 4.9k tokens, which your agent loads only when the skill is triggered. The skill folder holds 1 other file (for example `_meta.json`).
It sits in Mobile, covering Cross-platform mobile apps. It works with Bilibili, React Native, Expo and Android. The repository describes itself as: 🧠 Curated collection of 1209+ best OpenClaw skills — weekly updated by MyClaw.ai. The licence is MIT.
Read from SKILL.md and the folder at commit e5199b5. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npxnpmnodegitFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
bilibili.comgithub.comapi.bilibili.comapi.live.bilibili.comAlso links to:
ara.soFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Jkvideo Bilibili React Native loads about 4.9k tokens when it runs. Until then it costs about 44 tokens; SKILL.md has 436 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from LeoYeAI/openclaw-master-skills at commit e5199b5, republished under its MIT licence (© LeoYeAI). 436 words, ~4,940 tokens.
.claude/skills/jkvideo-bilibili-react-native/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.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.
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.
npm install
npx expo run:android # Android
npx expo run:ios # iOS (macOS + Xcode required)npm install
npx expo start --web
# In a separate terminal:
node scripts/proxy.js # Starts proxy on port 3001 to bypass Bilibili referer restrictionsDownload from Releases — enable "Install from unknown sources" in Android settings.
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 builder| 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) |
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;
}// 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
}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;
}// 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) {
const [mpdUri, setMpdUri] = useState<string | null>(null);
const [useFallback, setUseFallback] = useState(false);
const { setCurrentVideo } = useVideoStore();
useEffect(() => {
loadStream();
}, [bvid, cid]);
async function loadStream() {
try {
const stream = await getVideoStream(bvid, cid);
if (stream.dash) {
const uri = await buildDashMpdUri(stream.dash);
setMpdUri(uri);
} else {
setUseFallback(true);
}
} catch {
setUseFallback(true);
}
}
if (useFallback) {
// WebView fallback for Expo Go / Web
return (
<WebView
source={{ uri: `https://www.bilibili.com/video/${bvid}` }}
allowsInlineMediaPlayback
mediaPlaybackRequiresUserAction={false}
/>
);
}
return (
<Video
source={{ uri: mpdUri! }}
style={{ width: '100%', aspectRatio: 16 / 9 }}
controls
paused={!autoPlay}
resizeMode="contain"
onLoad={() => setCurrentVideo({ bvid, cid })}
/>
);
}// hooks/useDanmaku.ts
export function useDanmaku(cid: number) {
const [danmakuList, setDanmakuList] = useState<Danmaku[]>([]);
useEffect(() => {
fetchDanmaku(cid);
}, [cid]);
async function fetchDanmaku(cid: number) {
const res = await api.get(`/x/v1/dm/list.so?oid=${cid}`, {
responseType: 'arraybuffer',
});
// Parse XML danmaku
const xml = new TextDecoder('utf-8').decode(res.data);
const items = parseXmlDanmaku(xml); // parse <d p="time,...">text</d>
setDanmakuList(items);
}
return danmakuList;
}
// components/DanmakuOverlay.tsx — 5-lane floating display
const LANE_COUNT = 5;
export function DanmakuOverlay({ danmakuList, currentTime }: Props) {
const activeDanmaku = danmakuList.filter(
d => d.time >= currentTime - 0.1 && d.time < currentTime + 0.1
);
return (
<View style={StyleSheet.absoluteFillObject} pointerEvents="none">
{activeDanmaku.map((d, i) => (
<DanmakuItem
key={d.id}
text={d.text}
color={d.color}
lane={i % LANE_COUNT}
/>
))}
</View>
);
}// hooks/useLiveDanmaku.ts
const LIVE_WS = 'wss://broadcastlv.chat.bilibili.com/sub';
export function useLiveDanmaku(roomId: number) {
const [messages, setMessages] = useState<LiveMessage[]>([]);
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
const ws = new WebSocket(LIVE_WS);
wsRef.current = ws;
ws.onopen = () => {
// Send join room packet
const body = JSON.stringify({ roomid: roomId, uid: 0, protover: 2 });
ws.send(buildPacket(body, 7)); // op=7: enter room
startHeartbeat(ws);
};
ws.onmessage = async (event) => {
const packets = await decompressPacket(event.data);
packets.forEach(packet => {
if (packet.op === 5) {
const msg = JSON.parse(packet.body);
handleCommand(msg);
}
});
};
return () => ws.close();
}, [roomId]);
function handleCommand(msg: any) {
switch (msg.cmd) {
case 'DANMU_MSG':
setMessages(prev => [{
type: 'danmaku',
text: msg.info[1],
user: msg.info[2][1],
isGuard: msg.info[7] > 0, // 舰长标记
}, ...prev].slice(0, 200));
break;
case 'SEND_GIFT':
setMessages(prev => [{
type: 'gift',
user: msg.data.uname,
gift: msg.data.giftName,
count: msg.data.num,
}, ...prev].slice(0, 200));
break;
}
}
return messages;
}// store/videoStore.ts
import { create } from 'zustand';
interface VideoState {
currentVideo: { bvid: string; cid: number } | null;
isMiniplayer: boolean;
quality: number; // 80=1080P, 112=1080P+, 120=4K
setCurrentVideo: (video: { bvid: string; cid: number }) => void;
setMiniplayer: (val: boolean) => void;
setQuality: (q: number) => void;
}
export const useVideoStore = create<VideoState>((set) => ({
currentVideo: null,
isMiniplayer: false,
quality: 80,
setCurrentVideo: (video) => set({ currentVideo: video }),
setMiniplayer: (val) => set({ isMiniplayer: val }),
setQuality: (q) => set({ quality: q }),
}));
// store/authStore.ts
interface AuthState {
sessdata: string | null;
isLoggedIn: boolean;
login: (sessdata: string) => Promise<void>;
logout: () => Promise<void>;
}
export const useAuthStore = create<AuthState>((set) => ({
sessdata: null,
isLoggedIn: false,
login: async (sessdata) => {
await AsyncStorage.setItem('SESSDATA', sessdata);
set({ sessdata, isLoggedIn: true });
},
logout: async () => {
await AsyncStorage.removeItem('SESSDATA');
set({ sessdata: null, isLoggedIn: false });
},
}));// hooks/useQrLogin.ts
export function useQrLogin() {
const { login } = useAuthStore();
const [qrUrl, setQrUrl] = useState('');
const [qrKey, setQrKey] = useState('');
const pollRef = useRef<ReturnType<typeof setInterval>>();
async function generateQr() {
const res = await api.get('/x/passport-login/web/qrcode/generate');
const { url, qrcode_key } = res.data.data;
setQrUrl(url);
setQrKey(qrcode_key);
startPolling(qrcode_key);
}
function startPolling(key: string) {
pollRef.current = setInterval(async () => {
const res = await api.get('/x/passport-login/web/qrcode/poll', {
params: { qrcode_key: key },
});
const { code } = res.data.data;
if (code === 0) {
// Extract SESSDATA from Set-Cookie header
const setCookie = res.headers['set-cookie'] ?? [];
const sessdataCookie = setCookie
.find((c: string) => c.includes('SESSDATA='));
const sessdata = sessdataCookie?.match(/SESSDATA=([^;]+)/)?.[1];
if (sessdata) {
await login(sessdata);
clearInterval(pollRef.current);
}
}
}, 2000);
}
useEffect(() => () => clearInterval(pollRef.current), []);
return { qrUrl, generateQr };
}// store/downloadStore.ts
import * as FileSystem from 'expo-file-system';
export const useDownloadStore = create((set, get) => ({
downloads: [] as Download[],
startDownload: async (bvid: string, quality: number) => {
const stream = await getVideoStream(bvid, /* cid */ 0, quality);
const url = stream.durl?.[0]?.url ?? stream.dash?.video?.[0]?.baseUrl;
const path = `${FileSystem.documentDirectory}downloads/${bvid}_${quality}.mp4`;
const task = FileSystem.createDownloadResumable(url, path, {
headers: { Referer: 'https://www.bilibili.com' },
}, (progress) => {
// Update progress in store
set(state => ({
downloads: state.downloads.map(d =>
d.bvid === bvid ? { ...d, progress: progress.totalBytesWritten / progress.totalBytesExpectedToWrite } : d
),
}));
});
set(state => ({
downloads: [...state.downloads, { bvid, quality, path, progress: 0, task }],
}));
await task.downloadAsync();
},
}));
// LAN HTTP server for QR sharing (scripts/proxy.js pattern)
// Built-in HTTP server serves downloaded file, generates QR with local IP
import { createServer } from 'http';
import { networkInterfaces } from 'os';
function getLanIp(): string {
const nets = networkInterfaces();
for (const name of Object.keys(nets)) {
for (const net of nets[name]!) {
if (net.family === 'IPv4' && !net.internal) return net.address;
}
}
return 'localhost';
}// app/video/[bvid].tsx
import { useLocalSearchParams } from 'expo-router';
export default function VideoDetail() {
const { bvid } = useLocalSearchParams<{ bvid: string }>();
// ...
}
// Navigate to video
import { router } from 'expo-router';
router.push(`/video/${bvid}`);
// Navigate to live room
router.push(`/live/${roomId}`);
// Navigate back
router.back();Bilibili images block direct loading in browsers via Referer header. Use the bundled proxy:
// scripts/proxy.js (port 3001)
// Usage in components:
function proxyImage(url: string): string {
if (Platform.OS === 'web') {
return `http://localhost:3001/proxy?url=${encodeURIComponent(url)}`;
}
return url; // Native handles Referer correctly
}| Code | Quality |
|---|---|
| 16 | 360P |
| 32 | 480P |
| 64 | 720P |
| 80 | 1080P |
| 112 | 1080P+ (大会员) |
| 116 | 1080P60 (大会员) |
| 120 | 4K (大会员) |
// services/api.ts
export async function getVideoInfo(bvid: string) {
const { imgKey, subKey } = await getWbiKeys();
const signed = await signWbi({ bvid }, imgKey, subKey);
const res = await api.get('/x/web-interface/view', { params: signed });
return res.data.data;
}// app/history.tsx — automatically becomes /history route
import { Stack } from 'expo-router';
export default function HistoryScreen() {
return (
<>
<Stack.Screen options={{ title: '历史记录' }} />
{/* screen content */}
</>
);
}// store/settingsStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
export const useSettingsStore = create(
persist(
(set) => ({
defaultQuality: 80,
danmakuEnabled: true,
setDefaultQuality: (q: number) => set({ defaultQuality: q }),
toggleDanmaku: () => set(s => ({ danmakuEnabled: !s.danmakuEnabled })),
}),
{
name: 'jkvideo-settings',
storage: createJSONStorage(() => AsyncStorage),
}
)
);| Issue | Solution |
|---|---|
| DASH not playing in Expo Go | Use Dev Build: npx expo run:android |
| Images not loading on Web | Run node scripts/proxy.js and ensure web uses proxy URLs |
| API returns 412 / risk control | Ensure WBI signature is fresh; clear cached keys via AsyncStorage |
| 4K/1080P+ not available | Login with a Bilibili Premium (大会员) account |
| Live stream fails | App auto-selects HLS; FLV is not supported by ExoPlayer/HTML5 |
| QR code expired | Close and reopen the login modal to regenerate |
| Cookie not persisting | Check AsyncStorage permissions; SESSDATA key must match interceptor |
| WebSocket danmaku drops | Increase heartbeat frequency; check packet decompression (zlib) |
| Build fails on iOS | Run cd ios && pod install then rebuild |
bili_jct CSRF token — not yet implemented© LeoYeAI, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in skills/jkvideo-bilibili-react-native of LeoYeAI/openclaw-master-skills.
Open the folder on GitHubat commit e5199b5
Jkvideo Bilibili React Native next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Jkvideo Bilibili React Native this skillLeoYeAI/openclaw-master-skills | 2.2k | — | ~4.9k | Automated safety check: Pass | MIT | |
| Expo Tailwind SetupCherryHQ/cherry-studio-app | 4k | 8 repos | ~3k | Automated safety check: Pass | MIT | |
| Screenmapaleqsio/screenmap | 244 | — | ~7.2k | Automated safety check: Pass | MIT | |
| Expo Brownfield Integrationmweinbach/agent-coworker | 156 | 2 repos | ~900 | Automated safety check: Notes | Custom licence | |
| Expo UIarcboxlabs/linkcode | 155 | — | ~1.2k | Automated safety check: Pass | MIT | |
| Simfleetentropyconquers/simfleet | 118 | — | ~1.5k | Automated safety check: Pass | MIT |
CherryHQ/cherry-studio-app
Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling
aleqsio/screenmap
Generate a visual navigation map of an Expo / React Native or NativeScript app.
mweinbach/agent-coworker
Helps add Expo and React Native to an existing native iOS or Android app, and choose between a prebuilt AAR or XCFramework and a fully integrated build.
arcboxlabs/linkcode
Framework (OSS). An agent skill from arcboxlabs/linkcode.
entropyconquers/simfleet
Operate a simfleet device fleet: slimmed iOS simulators and Android emulators, parallel React Native worktree lanes with leased Metro ports, the native build cache, and per-agent device claims.
talsec/Free-RASP-ReactNative
Performs strict code reviews on React Native plugin projects covering TypeScript, the Kotlin (Android) and Swift (iOS) native bridges, and the Expo config plugin.
LeoYeAI/openclaw-master-skills
Manages pipelines on a DevOps quality and efficiency platform through its OpenAPI: list workspaces and templates, create, update, run and cancel pipelines, and read run records.
LeoYeAI/openclaw-master-skills
Patches OpenClaw's Feishu extension so an edited document triggers an isolated agent session that reads the doc and replies inline, turning it into a live chat space.
LeoYeAI/openclaw-master-skills
Multi-context memory management system for OpenClaw agents with group-isolated storage, global shared memory, workspace organization, and group-specific skills isolation.
LeoYeAI/openclaw-master-skills
Runs a brand's AI-search visibility work end to end: diagnosing how AI platforms represent it, repositioning it, producing AI-optimized content and monitoring ongoing mentions.
LeoYeAI/openclaw-master-skills
Installs and authenticates the gws CLI, then automates Gmail, Drive, Sheets, Calendar, Docs, Chat and Tasks with ready-made recipes, persona bundles and security audits.
LeoYeAI/openclaw-master-skills
Runs four advisor roles, a fitness coach, nutritionist, data analyst and TCM practitioner, to build a health profile and track workouts, diet and wellness over time.
Works with
Categories
Expert skill for building and extending JKVideo, a React Native Bilibili-like client with DASH playback, danmaku, WBI signing, and live streaming. Jkvideo Bilibili React Native is an agent skill from LeoYeAI/openclaw-master-skills.
Jkvideo Bilibili React Native fits situations like: tasks that involve Cross-platform mobile apps.
Run `npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a claude-code`. Or copy the skill folder (skills/jkvideo-bilibili-react-native in LeoYeAI/openclaw-master-skills) into .claude/skills/jkvideo-bilibili-react-native in your project. Claude Code loads it when a task matches its description.
Run `npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a codex`. Or copy the skill folder (skills/jkvideo-bilibili-react-native in LeoYeAI/openclaw-master-skills) into .agents/skills/jkvideo-bilibili-react-native in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add LeoYeAI/openclaw-master-skills --skill jkvideo-bilibili-react-native -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/jkvideo-bilibili-react-native, .gemini/skills/jkvideo-bilibili-react-native, .github/skills/jkvideo-bilibili-react-native and .opencode/skills/jkvideo-bilibili-react-native in your project.
Going by SKILL.md and its folder, Jkvideo Bilibili React Native needs the command-line tools its instructions call (npx, npm, node and git). Our summary lists: Node.js.
SKILL.md names 5 domains. In commands or code: bilibili.com, github.com, api.bilibili.com and api.live.bilibili.com; the agent is likely to contact these when it follows the instructions. As links in the text: ara.so. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Jkvideo Bilibili React Native is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 4.9k tokens (SKILL.md is roughly 20k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Jkvideo Bilibili React Native: Expo Tailwind Setup (CherryHQ/cherry-studio-app, 4k stars), Screenmap (aleqsio/screenmap, 244 stars), Expo Brownfield Integration (mweinbach/agent-coworker, 156 stars) and Expo UI (arcboxlabs/linkcode, 155 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
LeoYeAI (a GitHub user) maintains it in LeoYeAI/openclaw-master-skills, which has 2,161 GitHub stars. The repository holds 1,235 skills in this directory. The repository was last updated on July 20, 2026.
Source: LeoYeAI/openclaw-master-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.