一键导入
mobile-audio-video
Audio playback, recording, TTS, video handling with expo-av and expo-speech. VSTEP exam audio rules. Load when implementing audio/video features.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Audio playback, recording, TTS, video handling with expo-av and expo-speech. VSTEP exam audio rules. Load when implementing audio/video features.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Project UI design system — read this before building any page or component. Defines visual style, color tokens, typography, and constraints.
Design tokens, Tailwind classes, card patterns, spacing, colors, radius, typography, gamification patterns (Duolingo). Load when working on UI, components, styling.
Phân tích và refactor codebase. Load khi cần review code structure, tìm vấn đề thiết kế, tách component, dọn dead code.
Tạo, cập nhật, hoặc implement RFC cho frontend-v2. Use when user says 'tạo rfc', 'viết rfc', 'implement rfc', 'update rfc', 'rfc status', or when a cross-cutting UI/UX change needs formal spec before coding.
TanStack Query patterns, data fetching, cache invalidation, route loaders. Load when working with server state, queries, mutations.
TypeScript patterns, type conventions, naming, code style. Load when writing complex types, reviewing code style, or refactoring.
| name | mobile-audio-video |
| description | Audio playback, recording, TTS, video handling with expo-av and expo-speech. VSTEP exam audio rules. Load when implementing audio/video features. |
import { Audio } from "expo-av";
// Load and play audio ONCE
const { sound } = await Audio.Sound.createAsync(
{ uri: audioUrl },
{ shouldPlay: true, isLooping: false }
);
// Track progress
sound.setOnPlaybackStatusUpdate((status) => {
if (status.isLoaded) {
const progress = status.positionMillis / status.durationMillis;
setProgress(progress);
}
});
Key Rules:
await sound.unloadAsync() on unmountimport { Audio } from "expo-av";
// Start recording
const { recording } = await Audio.Recording.createAsync(
Audio.RecordingOptionsPresets.HIGH_QUALITY
);
// Stop and get file
await recording.stopAndUnloadAsync();
const uri = recording.getURI();
Key Rules:
Audio.requestPermissionsAsync()recording.unlinkAsync() if user cancelsas any — use proper FormData typing for uploadimport * as Speech from "expo-speech";
// Speak text
Speech.speak(text, {
language: "en-US",
pitch: 1.0,
rate: 0.9,
});
// Stop
Speech.stop();
// Check if speaking
Speech.isSpeakingAsync().then((speaking) => {
if (speaking) setSpeakingState(true);
});
Key Rules:
rate: 0.7 | 0.8 | 0.9 | 1.0import { Audio } from "expo-av";
async function checkPermissions() {
const { status } = await Audio.requestPermissionsAsync();
if (status !== "granted") {
Alert.alert(
"Microphone permission required",
"Please grant microphone access in settings to use the speaking practice feature.",
[{ text: "OK" }]
);
return false;
}
return true;
}
// Progress bar
<Slider
value={progress}
onValueChange={(value) => {
// Only allow seek during practice, not exam
if (!isExam) sound.setPositionAsync(value * duration);
}}
disabled={isExam}
/>
// Play/Pause button
<HapticTouchable onPress={isPlaying ? pause : play}>
<Ionicons name={isPlaying ? "pause" : "play"} size={24} />
</HapticTouchable>
type AudioState =
| { kind: "idle" }
| { kind: "loading" }
| { kind: "ready"; duration: number }
| { kind: "playing"; position: number; duration: number }
| { kind: "paused"; position: number; duration: number }
| { kind: "ended" };
Use discriminated union for audio state — no as casts, exhaustive switch.