取り組みの背景 — なぜ音楽プレイヤーアプリなのか
音楽プレイヤーは、モバイルアプリ開発において学びの宝庫です。再生・一時停止といった基本操作から、シークバーの実装、プレイリスト管理、バックグラウンド再生まで、アプリ開発に必要なスキルが凝縮されています。
Rork を使えば、AI の力を借りながら React Native ベースのオーディオプレイヤーアプリを効率的に構築できます。Expo のオーディオライブラリ expo-av を活用して、実用的な音楽プレイヤーアプリを一から作る方法を順を追って整理していきます。
この記事で学べること:
- expo-av を使ったオーディオファイルの読み込みと再生制御
- シークバー・音量調整などの UI コンポーネントの実装
- プレイリストの管理と曲の切り替え機能
- バックグラウンド再生の設定方法
対象読者は、Rork でのアプリ開発に少し慣れてきた方や、React Native の基本的な概念を理解している方です。
環境準備と expo-av のセットアップ
まず、Rork で新しいプロジェクトを作成します。Rork のプロンプトに「音楽プレイヤーアプリを作りたい」と伝えれば、基本的なプロジェクト構造を自動で生成してくれます。
expo-av は Expo SDK に含まれるオーディオ・ビデオ再生ライブラリで、Rork のプロジェクトでそのまま利用できます。手動でインストールする場合は以下のコマンドを実行します。
# expo-av のインストール
npx expo install expo-av次に、オーディオ再生の初期設定を行います。アプリ起動時に Audio.setAudioModeAsync を呼び出して、再生モードを構成します。
// hooks/useAudioSetup.ts
import { Audio } from 'expo-av';
import { useEffect } from 'react';
export function useAudioSetup() {
useEffect(() => {
async function configureAudio() {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
staysActiveInBackground: true, // バックグラウンド再生を有効化
playsInSilentModeIOS: true, // サイレントモードでも再生
shouldDuckAndroid: true, // 他のアプリの音声を一時的に下げる
});
}
configureAudio();
}, []);
}
// 期待される動作:
// - アプリ起動時にオーディオモードが自動設定される
// - バックグラウンドでも音楽が途切れない
// - iOS のサイレントスイッチをオンにしても音が出るこの設定は一度だけ実行すれば十分です。staysActiveInBackground: true を指定することで、ユーザーがアプリを閉じても音楽が再生され続けます。
オーディオ再生の基本 — 読み込みから再生・停止まで
expo-av の Audio.Sound オブジェクトを使って、オーディオファイルの読み込みと再生を制御します。ここでは、カスタムフックとして再利用しやすい形にまとめます。
// hooks/useAudioPlayer.ts
import { useState, useCallback, useRef } from 'react';
import { Audio, AVPlaybackStatus } from 'expo-av';
interface Track {
id: string;
title: string;
artist: string;
uri: string; // ローカルまたはリモートの音声ファイルURL
artwork?: string; // アルバムアートのURL
}
interface PlayerState {
isPlaying: boolean;
isLoaded: boolean;
positionMillis: number;
durationMillis: number;
}
export function useAudioPlayer() {
const soundRef = useRef<Audio.Sound | null>(null);
const [playerState, setPlayerState] = useState<PlayerState>({
isPlaying: false,
isLoaded: false,
positionMillis: 0,
durationMillis: 0,
});
// 再生状態の更新コールバック
const onPlaybackStatusUpdate = useCallback((status: AVPlaybackStatus) => {
if (status.isLoaded) {
setPlayerState({
isPlaying: status.isPlaying,
isLoaded: true,
positionMillis: status.positionMillis,
durationMillis: status.durationMillis ?? 0,
});
}
}, []);
// トラックの読み込み
const loadTrack = useCallback(async (track: Track) => {
// 既存のサウンドオブジェクトを解放
if (soundRef.current) {
await soundRef.current.unloadAsync();
}
const { sound } = await Audio.Sound.createAsync(
{ uri: track.uri },
{ shouldPlay: false },
onPlaybackStatusUpdate
);
soundRef.current = sound;
}, [onPlaybackStatusUpdate]);
// 再生 / 一時停止の切り替え
const togglePlayPause = useCallback(async () => {
if (!soundRef.current) return;
if (playerState.isPlaying) {
await soundRef.current.pauseAsync();
} else {
await soundRef.current.playAsync();
}
}, [playerState.isPlaying]);
// 指定位置へシーク
const seekTo = useCallback(async (positionMillis: number) => {
if (!soundRef.current) return;
await soundRef.current.setPositionAsync(positionMillis);
}, []);
return { playerState, loadTrack, togglePlayPause, seekTo };
}ポイントは onPlaybackStatusUpdate コールバックです。expo-av は再生位置が変わるたびにこのコールバックを呼び出すため、UI のリアルタイム更新が可能になります。
シークバーと再生コントロール UI の実装
音楽プレイヤーの操作性を左右するのが、シークバーと再生コントロールの UI です。ユーザーが直感的に操作できるインターフェースを実装しましょう。
// components/PlayerControls.tsx
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import Slider from '@react-native-community/slider';
import { Ionicons } from '@expo/vector-icons';
interface PlayerControlsProps {
isPlaying: boolean;
positionMillis: number;
durationMillis: number;
onTogglePlayPause: () => void;
onSeek: (position: number) => void;
onPrevious: () => void;
onNext: () => void;
}
// ミリ秒を "MM:SS" 形式にフォーマットする関数
function formatTime(millis: number): string {
const totalSeconds = Math.floor(millis / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
export function PlayerControls({
isPlaying,
positionMillis,
durationMillis,
onTogglePlayPause,
onSeek,
onPrevious,
onNext,
}: PlayerControlsProps) {
return (
<View style={styles.container}>
{/* シークバー */}
<Slider
style={styles.slider}
minimumValue={0}
maximumValue={durationMillis}
value={positionMillis}
onSlidingComplete={onSeek}
minimumTrackTintColor="#1DB954"
maximumTrackTintColor="#535353"
thumbTintColor="#1DB954"
/>
{/* 経過時間 / 残り時間 */}
<View style={styles.timeRow}>
<Text style={styles.timeText}>
{formatTime(positionMillis)}
</Text>
<Text style={styles.timeText}>
-{formatTime(durationMillis - positionMillis)}
</Text>
</View>
{/* 再生コントロールボタン */}
<View style={styles.controls}>
<TouchableOpacity onPress={onPrevious}>
<Ionicons name="play-skip-back" size={32} color="#fff" />
</TouchableOpacity>
<TouchableOpacity
onPress={onTogglePlayPause}
style={styles.playButton}
>
<Ionicons
name={isPlaying ? 'pause-circle' : 'play-circle'}
size={64}
color="#1DB954"
/>
</TouchableOpacity>
<TouchableOpacity onPress={onNext}>
<Ionicons name="play-skip-forward" size={32} color="#fff" />
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 20 },
slider: { width: '100%', height: 40 },
timeRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: -8,
},
timeText: { color: '#b3b3b3', fontSize: 12 },
controls: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 32,
marginTop: 16,
},
playButton: { marginHorizontal: 24 },
});
// 期待される表示:
// - 緑色のシークバーが曲の進行に合わせて動く
// - 経過時間と残り時間が "M:SS" 形式で表示される
// - 前の曲・再生/停止・次の曲の3つのボタンが並ぶ@react-native-community/slider はスムーズなドラッグ操作を提供してくれるライブラリです。onSlidingComplete を使うことで、ユーザーがスライダーを離したタイミングでシーク処理を実行できます。
プレイリスト管理と曲の切り替え
複数の曲を順番に再生するプレイリスト機能は、音楽プレイヤーの核となる部分です。現在再生中の曲のインデックスを管理し、次の曲・前の曲への切り替えを実装します。
// hooks/usePlaylist.ts
import { useState, useCallback } from 'react';
interface Track {
id: string;
title: string;
artist: string;
uri: string;
artwork?: string;
}
export function usePlaylist(tracks: Track[]) {
const [currentIndex, setCurrentIndex] = useState(0);
const [isShuffled, setIsShuffled] = useState(false);
const [repeatMode, setRepeatMode] = useState<'off' | 'all' | 'one'>('off');
const currentTrack = tracks[currentIndex] ?? null;
// 次の曲へ進む
const playNext = useCallback(() => {
setCurrentIndex((prev) => {
if (repeatMode === 'one') return prev; // 1曲リピート
const next = prev + 1;
if (next >= tracks.length) {
return repeatMode === 'all' ? 0 : prev; // 全曲リピートまたは停止
}
return next;
});
}, [tracks.length, repeatMode]);
// 前の曲へ戻る
const playPrevious = useCallback(() => {
setCurrentIndex((prev) => {
const prevIndex = prev - 1;
if (prevIndex < 0) {
return repeatMode === 'all' ? tracks.length - 1 : 0;
}
return prevIndex;
});
}, [tracks.length, repeatMode]);
// シャッフル切り替え
const toggleShuffle = useCallback(() => {
setIsShuffled((prev) => !prev);
}, []);
// リピートモード切り替え(off → all → one → off)
const toggleRepeat = useCallback(() => {
setRepeatMode((prev) => {
if (prev === 'off') return 'all';
if (prev === 'all') return 'one';
return 'off';
});
}, []);
return {
currentTrack,
currentIndex,
isShuffled,
repeatMode,
playNext,
playPrevious,
toggleShuffle,
toggleRepeat,
};
}リピートモードは「オフ → 全曲リピート → 1曲リピート」の3段階で切り替わります。シャッフル機能を本格的に実装する場合は、Fisher-Yates アルゴリズムでトラック配列をランダムに並べ替える処理を追加するとよいでしょう。
Rork アプリでのアニメーション実装について詳しく知りたい方は、Rork アニメーション実装ガイド も参考になります。
バックグラウンド再生と通知コントロール
音楽プレイヤーアプリにとって、バックグラウンド再生は必須機能です。ユーザーがアプリを閉じても音楽が途切れないようにするには、いくつかの追加設定が必要です。
iOS の設定
app.json(または app.config.js)の ios.infoPlist に、バックグラウンドモードの指定を追加します。
{
"expo": {
"ios": {
"infoPlist": {
"UIBackgroundModes": ["audio"]
}
}
}
}Android の設定
Android ではフォアグラウンドサービスを使ってバックグラウンド再生を維持します。expo-av の staysActiveInBackground: true 設定に加えて、app.json に以下を追加します。
{
"expo": {
"android": {
"permissions": ["FOREGROUND_SERVICE"]
}
}
}ロック画面の再生コントロール
ロック画面やコントロールセンターに再生情報を表示するには、expo-av の Audio.Sound が自動的にシステムのメディアセッションと連携します。ただし、アルバムアートやトラック情報をロック画面に表示したい場合は、expo-media-library や react-native-track-player のような専用ライブラリの導入を検討してください。
// より高度なメディアコントロールが必要な場合の参考構成
// react-native-track-player を使う場合:
//
// import TrackPlayer from 'react-native-track-player';
//
// await TrackPlayer.setupPlayer();
// await TrackPlayer.add({
// id: 'track-1',
// url: 'https://example.com/song.mp3',
// title: '曲のタイトル',
// artist: 'アーティスト名',
// artwork: 'https://example.com/artwork.jpg',
// });
// await TrackPlayer.play();react-native-track-player は、ロック画面コントロール・通知バー・Apple CarPlay / Android Auto など、プラットフォーム固有のメディア機能をフルサポートしています。本格的な音楽プレイヤーアプリを目指す場合は、こちらへの移行をおすすめします。
再利用可能なカスタムフックの設計パターンについては、Rork カスタムフック活用ガイド で詳しく解説しています。
まとめ
ここではRork と expo-av を使って音楽プレイヤーアプリを構築する方法を解説しました。オーディオの初期設定から再生制御、シークバー UI の実装、プレイリスト管理、バックグラウンド再生まで、音楽プレイヤーに必要な機能を網羅的にカバーしました。
ここで紹介したカスタムフック(useAudioPlayer や usePlaylist)はそのまま再利用できるので、ぜひ自分のプロジェクトに取り入れてみてください。UI のデザインやアニメーションをさらに磨きたい方には、Rork アプリの UX デザインパターン完全ガイド がおすすめです。