Add PostCSS configuration and skills lock file
- Created a new PostCSS configuration file to integrate Tailwind CSS. - Added a skills lock file containing various Expo skills with their respective source and computed hashes.
This commit is contained in:
160
.agents/skills/upgrading-expo/references/expo-av-to-video.md
Normal file
160
.agents/skills/upgrading-expo/references/expo-av-to-video.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Migrating from expo-av to expo-video
|
||||
|
||||
## Imports
|
||||
|
||||
```tsx
|
||||
// Before
|
||||
import { Video, ResizeMode } from 'expo-av';
|
||||
|
||||
// After
|
||||
import { useVideoPlayer, VideoView, VideoSource } from 'expo-video';
|
||||
import { useEvent, useEventListener } from 'expo';
|
||||
```
|
||||
|
||||
## Video Playback
|
||||
|
||||
### Before (expo-av)
|
||||
|
||||
```tsx
|
||||
const videoRef = useRef<Video>(null);
|
||||
const [status, setStatus] = useState({});
|
||||
|
||||
<Video
|
||||
ref={videoRef}
|
||||
source={{ uri: 'https://example.com/video.mp4' }}
|
||||
style={{ width: 350, height: 200 }}
|
||||
resizeMode={ResizeMode.CONTAIN}
|
||||
isLooping
|
||||
onPlaybackStatusUpdate={setStatus}
|
||||
/>
|
||||
|
||||
// Control
|
||||
videoRef.current?.playAsync();
|
||||
videoRef.current?.pauseAsync();
|
||||
```
|
||||
|
||||
### After (expo-video)
|
||||
|
||||
```tsx
|
||||
const player = useVideoPlayer('https://example.com/video.mp4', player => {
|
||||
player.loop = true;
|
||||
});
|
||||
|
||||
const { isPlaying } = useEvent(player, 'playingChange', { isPlaying: player.playing });
|
||||
|
||||
<VideoView
|
||||
player={player}
|
||||
style={{ width: 350, height: 200 }}
|
||||
contentFit="contain"
|
||||
/>
|
||||
|
||||
// Control
|
||||
player.play();
|
||||
player.pause();
|
||||
```
|
||||
|
||||
## Status Updates
|
||||
|
||||
### Before (expo-av)
|
||||
|
||||
```tsx
|
||||
<Video
|
||||
onPlaybackStatusUpdate={status => {
|
||||
if (status.isLoaded) {
|
||||
console.log(status.positionMillis, status.durationMillis, status.isPlaying);
|
||||
if (status.didJustFinish) console.log('finished');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### After (expo-video)
|
||||
|
||||
```tsx
|
||||
// Reactive state
|
||||
const { isPlaying } = useEvent(player, 'playingChange', { isPlaying: player.playing });
|
||||
|
||||
// Side effects
|
||||
useEventListener(player, 'playToEnd', () => console.log('finished'));
|
||||
|
||||
// Direct access
|
||||
console.log(player.currentTime, player.duration, player.playing);
|
||||
```
|
||||
|
||||
## Local Files
|
||||
|
||||
### Before (expo-av)
|
||||
|
||||
```tsx
|
||||
<Video source={require('./video.mp4')} />
|
||||
```
|
||||
|
||||
### After (expo-video)
|
||||
|
||||
```tsx
|
||||
const player = useVideoPlayer({ assetId: require('./video.mp4') });
|
||||
```
|
||||
|
||||
## Fullscreen and PiP
|
||||
|
||||
```tsx
|
||||
<VideoView
|
||||
player={player}
|
||||
allowsFullscreen
|
||||
allowsPictureInPicture
|
||||
onFullscreenEnter={() => {}}
|
||||
onFullscreenExit={() => {}}
|
||||
/>
|
||||
```
|
||||
|
||||
For PiP and background playback, add to app.json:
|
||||
|
||||
```json
|
||||
{
|
||||
"expo": {
|
||||
"plugins": [
|
||||
["expo-video", { "supportsBackgroundPlayback": true, "supportsPictureInPicture": true }]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Mapping
|
||||
|
||||
| expo-av | expo-video |
|
||||
|---------|------------|
|
||||
| `<Video>` | `<VideoView>` |
|
||||
| `ref={videoRef}` | `player={useVideoPlayer()}` |
|
||||
| `source={{ uri }}` | Pass to `useVideoPlayer(uri)` |
|
||||
| `resizeMode={ResizeMode.CONTAIN}` | `contentFit="contain"` |
|
||||
| `isLooping` | `player.loop = true` |
|
||||
| `shouldPlay` | `player.play()` in setup |
|
||||
| `isMuted` | `player.muted = true` |
|
||||
| `useNativeControls` | `nativeControls={true}` |
|
||||
| `onPlaybackStatusUpdate` | `useEvent` / `useEventListener` |
|
||||
| `videoRef.current.playAsync()` | `player.play()` |
|
||||
| `videoRef.current.pauseAsync()` | `player.pause()` |
|
||||
| `videoRef.current.replayAsync()` | `player.replay()` |
|
||||
| `videoRef.current.setPositionAsync(ms)` | `player.currentTime = seconds` |
|
||||
| `status.positionMillis` | `player.currentTime` (seconds) |
|
||||
| `status.durationMillis` | `player.duration` (seconds) |
|
||||
| `status.didJustFinish` | `useEventListener(player, 'playToEnd')` |
|
||||
|
||||
## Key Differences
|
||||
|
||||
- **Separate player and view**: Player logic decoupled from the view—one player can be used across multiple views
|
||||
- **Time in seconds**: Uses seconds, not milliseconds
|
||||
- **Event system**: Uses `useEvent`/`useEventListener` from `expo` instead of callback props
|
||||
- **Video preloading**: Create a player without mounting a VideoView to preload for faster transitions
|
||||
- **Built-in caching**: Set `useCaching: true` in VideoSource for persistent offline caching
|
||||
|
||||
## Known Issues
|
||||
|
||||
- **Uninstall expo-av first**: On Android, having both expo-av and expo-video installed can cause VideoView to show a black screen. Uninstall expo-av before installing expo-video
|
||||
- **Android: Reusing players**: Mounting the same player in multiple VideoViews simultaneously can cause black screens on Android (works on iOS)
|
||||
- **Android: currentTime in setup**: Setting `player.currentTime` in the `useVideoPlayer` setup callback may not work on Android—set it after mount instead
|
||||
- **Changing source**: Use `player.replace(newSource)` to change videos without recreating the player
|
||||
|
||||
## API Reference
|
||||
|
||||
https://docs.expo.dev/versions/latest/sdk/video/
|
||||
Reference in New Issue
Block a user