0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Dexie.js実践編:ブラウザで録音→ブラウザ内にデータ保存

Posted at

今回実現したいこと

  • ブラウザで録音する
  • ブラウザ内で録音データを保存する

あくまでもクライアント側で保存することを目指します。
ブラウザ上で保存する場合、localStoragesessionStorage のような WEB Storage API を使い簡単に実装できますが、大量のデータは扱えず(約5MB)で、且つ文字列しか保存できないため音声データはそのままでは保存できません。

リサーチの結果、画像データや音声データなどのBlobやファイルもそのままの値で保存でき、大量のデータをブラウザ上に保存することができるIndexedDBに保存することにしました。

IndexedDBをそのまま扱うのは初学者にはハードル高そう・・・ということで、よりシンプルな構文で扱うことのできる、ラッパーライブラリDexie.jsを使って実装しました。

実行環境

完成DEMO

完成DEMOは、以下にてご確認下さい。
録音データは、AddボタンでIndexedDBへ保存ができます。
IndexedDBに保存した音声データはリロード後も保存されます。
DownLoad(.wav)から、録音データの直ダウンロードも可能です

解説

1. データベースの準備 

db.jsファイルを作成し、データベースを定義します。

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 に変換してからデータに追加します。

AddVoiceData.jsx
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()を使います。

GetVoiceData.jsx
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()を使ってテーブル内のデータを一括削除します。

ClearDatabaseButton.jsx
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と同様の実装を組み込んで、音声クイズアプリを制作しました。
ぜひミュートを解除して、音出して遊んでみてください🎶

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?