今回実現したいこと
- ブラウザで録音する
- ブラウザ内で録音データを保存する
あくまでもクライアント側で保存することを目指します。
ブラウザ上で保存する場合、localStorage や sessionStorage のような WEB Storage API を使い簡単に実装できますが、大量のデータは扱えず(約5MB)で、且つ文字列しか保存できないため音声データはそのままでは保存できません。
リサーチの結果、画像データや音声データなどのBlobやファイルもそのままの値で保存でき、大量のデータをブラウザ上に保存することができるIndexedDBに保存することにしました。
IndexedDBをそのまま扱うのは初学者にはハードル高そう・・・ということで、よりシンプルな構文で扱うことのできる、ラッパーライブラリDexie.jsを使って実装しました。
実行環境
- React
- Dexie.js
- react-media-recorder
完成DEMO
完成DEMOは、以下にてご確認下さい。
録音データは、Add
ボタンでIndexedDBへ保存ができます。
IndexedDBに保存した音声データはリロード後も保存されます。
DownLoad(.wav)から、録音データの直ダウンロードも可能です
解説
1. データベースの準備
db.js
ファイルを作成し、データベースを定義します。
import Dexie from "dexie";
// IndexedDB を定義
export const db = new Dexie("DemoDB");
db.version(1).stores({
voicelist: "++id, name, voice" // Primary key and indexed props
});
2. IndexedDBにデータを追加
db.voicelist.add({dataObject...}) でデータを追加します。
今回録音に使用したライブラリreact-media-recorderでは、mediaBlobUrl
で録音データにアクセスできますが、mediaBlobUrl
は、Blob URLを返しているため、Blob URL → Blob に変換してからデータに追加します。
import { db } from "../db";
import { useReactMediaRecorder } from "react-media-recorder";
import { useEffect, useState } from "react";
import "regenerator-runtime";
import axios from "axios";
export function AddVoiceData() {
const [name, setName] = useState("");
const [dbStatus, setDbStatus] = useState("");
const [voice, setVoice] = useState("");
async function addVoiceData() {
try {
// DBにデータを追加
const id = await db.voicelist.add({
name,
voice
});
setDbStatus(`Voicedata ${name} successfully added. Got id ${id}`);
setName("");
} catch (error) {
setDbStatus(`Failed to add ${name}: ${error}`);
}
}
// react-media-recorderのhooksを使用 録音関連
const {
status,
startRecording,
stopRecording,
mediaBlobUrl
} = useReactMediaRecorder({
video: false,
audio: true,
echoCancellation: true
});
// 音声データ Blob URL → Blobに変換する
useEffect(() => {
axios
.get(mediaBlobUrl, {
responseType: "blob"
})
.then(({ data }) => {
setVoice(data);
console.log(data); // → Blob
})
.catch((err) => {
console.error(err);
});
}, [mediaBlobUrl]);
console.log(voice);
console.log("url", mediaBlobUrl);
return (
<>
<h2>
🎤 Recording:
<a href="https://www.npmjs.com/package/react-media-recorder">
react-media-recorder
</a>
</h2>
<div>
<div style={{ display: "block", margin: "1.5rem 0" }}>
<audio src={mediaBlobUrl} controls></audio>
</div>
<div>
<div
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
<p class="status">{status}</p>
<div>
<button
onClick={() => {
startRecording();
}}
>
Record Start
</button>
<button
onClick={() => {
stopRecording();
}}
>
Record Stop
</button>
</div>
<div>
<a download href={mediaBlobUrl}>
Download(.wav)
</a>
</div>
</div>
</div>
</div>
<h2>
🧰 Add to IndexedDB:
<a href="https://dexie.org/docs/Tutorial/React#2-install-dependencies">
Dexie.js
</a>
</h2>
<p class="status">{dbStatus}</p>
Name:
<input
type="text"
value={name}
onChange={(ev) => setName(ev.target.value)}
/>
<button onClick={addVoiceData}>Add</button>
</>
);
}
3. データを取得するコンポーネント
React用のHooksである、useLiveQuery を使って、コンポーネントの中でDBのデータを読み込みます。
ブラウザからデータを取得する際には、追加時と逆で、Blob → Blob URL に変換します。
変換には、URL.createObjectURL()を使います。
import { db } from "../db";
import { useLiveQuery } from "dexie-react-hooks";
export function GetVoiceData() {
// DBからデータを取得
const IndexedData = useLiveQuery(async () => {
const allVoiceList = await db.voicelist.toArray();
// // 音声データ Blob → Blob URLに変換する
const convertVoicesData = allVoiceList.map((list) => {
if (list.voice) {
const url = URL.createObjectURL(list.voice);
return { ...list, voiceUrl: url };
} else {
return { ...list, voiceUrl: "" };
}
});
console.log(convertVoicesData);
return convertVoicesData;
});
return (
<>
<h2>🎧 Get data from IndexedDB</h2>
<ul>
{IndexedData?.map((data) => (
<li key={data.id}>
{data.name}
<audio controls src={data.voiceUrl}></audio>
</li>
))}
</ul>
</>
);
}
4. テーブル内のデータ全てを削除するコンポーネント
table.clear()
を使ってテーブル内のデータを一括削除します。
import { db } from "../db";
import React from "react";
export function ClearDatabaseButton() {
return (
<button
onClick={() => {
db.transaction("rw", db.tables, async () => {
await Promise.all(db.tables.map((table) => table.clear()));
});
}}
>
Clear Database
</button>
);
}
参考サイト
Get started with Dexie in React
【React】Dexie.jsでIndexedDBを使ってみました
個人開発アプリに組み込む
DEMOと同様の実装を組み込んで、音声クイズアプリを制作しました。
ぜひミュートを解除して、音出して遊んでみてください🎶