블로그 목록

expo-video로 영상 플레이어 붙이기 — expo-av는 이제 그만

앱에 짧은 영상 하나 재생시키려고 예전 습관대로 expo-av를 깔았는데, 콘솔에 deprecated 경고가 떴다. 찾아보니 Expo가 오디오는 expo-audio로, 비디오는 expo-video로 쪼개서 새로 만들었고, expo-av는 SDK 54부터 정리 대상이었다. 그냥 새 걸 쓰기로 했다.

왜 갈아탔나

expo-avVideo 컴포넌트는 재생 상태를 onPlaybackStatusUpdate 콜백으로 계속 받아서 useState에 밀어넣는 구조였다. 재생/일시정지 버튼 하나 만들려고 해도 상태 동기화 코드가 지저분해졌다.

expo-video는 플레이어를 useVideoPlayer 훅으로 만들고, 컴포넌트는 그 플레이어를 화면에 붙이기만 한다. 재생 로직과 렌더링이 분리돼서 훨씬 깔끔하다.

설치

npx expo install expo-video

app.json 플러그인 배열에 추가해주고 dev client를 다시 빌드해야 네이티브 모듈이 들어간다. Expo Go에서도 웬만한 건 돌아간다.

{
  "plugins": ["expo-video"]
}

기본 재생

import { useVideoPlayer, VideoView } from 'expo-video';
import { useEffect } from 'react';

const source = 'https://example.com/sample.mp4';

export default function Player() {
  const player = useVideoPlayer(source, (p) => {
    p.loop = true;
    p.play();
  });

  return (
    <VideoView
      style={{ width: '100%', height: 220 }}
      player={player}
      allowsFullscreen
      allowsPictureInPicture
    />
  );
}

useVideoPlayer의 두 번째 인자는 플레이어가 처음 만들어질 때 한 번 실행되는 설정 콜백이다. 여기서 loop, muted, play() 같은 초기 상태를 잡아주면 된다. 플레이어 정리(release)는 훅이 알아서 해준다.

재생 상태 구독

버튼 UI를 플레이어 상태와 맞추고 싶으면 useEvent로 이벤트를 구독한다. 콜백으로 상태를 직접 관리하던 방식과 비교하면 훨씬 선언적이다.

import { useEvent } from 'expo';

const { isPlaying } = useEvent(player, 'playingChange', {
  isPlaying: player.playing,
});

// isPlaying 값으로 버튼 아이콘만 바꿔주면 끝
<Pressable onPress={() => (isPlaying ? player.pause() : player.play())}>
  <Text>{isPlaying ? '일시정지' : '재생'}</Text>
</Pressable>;

player.playing을 직접 읽지 않고 이벤트로 받는 이유는, 플레이어가 네이티브 쪽에서 상태가 바뀌어도 리렌더가 자동으로 안 걸리기 때문이다. useEvent가 그 간극을 메워준다.

정리

expo-av에서 넘어오면서 헷갈렸던 건 딱 하나였다. 컴포넌트에 소스를 넘기는 게 아니라 훅으로 만든 플레이어 객체를 넘긴다는 것. 이 구조가 익숙해지면 재생 로직을 커스텀 훅으로 빼기도 쉽고, 리스트에서 여러 영상 다룰 때도 관리가 편하다. 새 프로젝트라면 고민할 것 없이 expo-video다.