audio-components
sync-store 기반 오디오 플레이어 컴포넌트와 상태
앱 전체에서 HTMLAudioElement 하나를 공유하는 싱글턴 store(audioStore)가 재생 상태를
들고, 컴포넌트는 useAudioStore로 필요한 slice만 구독합니다. 볼륨과 반복 여부는
localStorage에 저장됩니다.
설치
pnpm add @cbcruk/audio-components react브라우저 전용입니다. 내부적으로 @cbcruk/sync-store, @cbcruk/utils, @use-gesture/react를
사용합니다.
Tailwind CSS 설정
CastAudioPlayer는 CSS 파일 없이 Tailwind 유틸리티 클래스(bg-blue-600, min-w-[3rem] 등)로만
스타일을 입힙니다. Tailwind는 이 패키지의 의존성이 아니므로, 앱에 Tailwind v4를 설정하고 패키지
빌드 결과물을 스캔 대상에 넣어야 스타일이 생성됩니다. Tailwind v4의 자동 소스 감지는
node_modules를 건너뛰므로 @source로 직접 지정합니다.
/* app.css — 경로는 이 CSS 파일 기준 상대 경로 */
@import 'tailwindcss';
@source '../node_modules/@cbcruk/audio-components/dist';Tailwind를 쓰지 않는 앱에서는 CastAudioPlayer가 스타일 없이 렌더링됩니다. 이 경우
AudioPlayerSlider와 useAudioStore / audioActions로 UI를 직접 만드세요.
사용법
플레이어 컴포넌트
AudioManager를 앱에 한 번 마운트해 오디오 엘리먼트를 만들고 이벤트를 store에
연결한 뒤, 플레이어를 원하는 만큼 렌더링합니다.
import { AudioManager, CastAudioPlayer } from '@cbcruk/audio-components'
function App() {
return (
<>
<AudioManager />
<CastAudioPlayer src="/audio/episode-1.mp3" />
<CastAudioPlayer src="/audio/episode-2.mp3" />
</>
)
}다른 src의 플레이어에서 재생하면 공유 오디오의 소스가 교체됩니다. 재생 상태·현재 시간·
길이는 지금 재생 중인 src의 플레이어에만 표시되고, 볼륨과 반복 여부는 모든 플레이어가
공유합니다.
직접 UI 만들기
import {
audioActions,
formatDuration,
useAudioStore,
} from '@cbcruk/audio-components'
function MiniPlayer({ src }: { src: string }) {
const currentSrc = useAudioStore((state) => state.src)
const storeIsPlaying = useAudioStore((state) => state.isPlaying)
const storeCurrentTime = useAudioStore((state) => state.currentTime)
const isPlaying = currentSrc === src && storeIsPlaying
const currentTime = currentSrc === src ? storeCurrentTime : 0
return (
<button
onClick={() =>
isPlaying ? audioActions.pause() : audioActions.play(src)
}
>
{isPlaying ? '일시정지' : '재생'} {formatDuration(currentTime)}
</button>
)
}액션은 React 밖에서도 호출할 수 있습니다. 단, AudioManager가 마운트되어 오디오
엘리먼트가 등록되기 전에는 재생 관련 액션이 아무것도 하지 않습니다.
API
<AudioManager />
new Audio()(preload = 'metadata')를 만들어 audioActions.setAudio로 등록하고,
play / pause / ended / timeupdate / loadedmetadata / loadstart / canplay /
error 이벤트를 store에 반영합니다. store의 src가 없을 때 발생한 error 이벤트는
무시합니다. 아무것도 렌더링하지 않으며, 언마운트 시 재생을 멈추고 소스를 비운 뒤
setAudio(null)로 등록을 해제해 재생 상태를 초기화합니다.
<CastAudioPlayer src />
재생/일시정지, 진행 바, 현재·전체 시간, 볼륨 슬라이더, 반복 토글이 있는 플레이어입니다. 스타일은 Tailwind 유틸리티 클래스로 작성되어 있어 위의 "Tailwind CSS 설정"이 필요합니다. 이전/다음 버튼은 비활성 상태로만 렌더링됩니다.
재생 상태·진행 바·시간은 store의 src가 이 플레이어의 src와 같을 때만 반영되며,
다른 플레이어의 진행 바를 끌어도 재생 위치는 바뀌지 않습니다. 반복 토글은
aria-pressed로 상태를 나타내고 라벨은 반복 켜짐 / 반복 꺼짐입니다.
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | 재생할 오디오 URL |
<AudioPlayerSlider value onChange />
드래그로 0~1 값을 고르는 가로 슬라이더입니다. 스타일이 없는 마크업만 렌더링하므로
[data-scope], [data-part="rail" | "track" | "handle"] 선택자로 직접 꾸며야 합니다.
track의 width와 handle의 left는 인라인 %로 지정됩니다.
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | 현재 값(0~1) |
onChange | (value: number) => void | — | 드래그 위치 비율(0~1) |
audioStore
persist로 만든 싱글턴 Store<AudioState>입니다. volume, isLooping만
localStorage의 audio-storage 키에 저장됩니다.
interface AudioState {
audio: HTMLAudioElement | null
src: string | null
isPlaying: boolean
currentTime: number // 초
duration: number // 초
isLoading: boolean
error: string | null
volume: number // 0~1, 기본 1
isLooping: boolean
}useAudioStore(selector?)
useStore(audioStore, selector)의 얇은 래퍼입니다. selector를 생략하면 상태 전체를
반환합니다.
audioActions
| Action | 설명 |
|---|---|
setAudio(audio) | 오디오 엘리먼트를 등록하고 저장된 volume / isLooping을 반영. null이면 등록 해제 후 재생 상태 초기화 |
play(src) | src가 현재와 다르면 소스를 교체해 재생, 같으면 이어서 재생. 로딩 중이면 무시. 재생이 시작되거나 실패하면 isLoading 해제 |
togglePlay() | isPlaying에 따라 재생/일시정지. 현재 src가 없으면 무시 |
pause() | 일시정지 |
stop() | 정지 후 src 속성을 제거하고, src, 재생 위치, 길이, 로딩·에러 상태 초기화 |
seek(time) | 재생 위치(초) 이동 |
setVolume(volume) | 0~1로 clamp해 반영 |
toggleLoop() | 반복 여부 토글 |
setIsPlaying 등 | setIsPlaying, setCurrentTime, setDuration, setIsLoading, setError — 해당 필드만 갱신 |
재생 실패 시 error에 한국어 메시지가 들어갑니다. pause()·stop()으로 play()가 중단된
경우(AbortError)는 실패로 보지 않습니다.
formatDuration(seconds)
초를 mm:ss 문자열로 바꿉니다. 시간 단위로 올리지 않습니다(3665 → '61:05').
formatCount(count) (deprecated)
오디오와 관계없는 범용 포맷터라 @cbcruk/utils로 옮겼습니다. 기존 코드가 깨지지 않도록
@cbcruk/utils의 formatCount를 그대로 다시 내보내며, 새 코드에서는
import { formatCount } from '@cbcruk/utils'를 사용하세요. 반올림 결과가 다음 단위에 닿으면
단위를 올립니다(1500 → '1.5K', 999999 → '1.0M').