😎

ReactでS3へのマルチアップロードを実装する (動画サイト構築までの道③)

2022/11/23に公開

はじめに

ここまで 3 本記事でしばらく連載してきました「動画サイト構築までの道」最後となります
本日は React と AWS SDK を使って動画ファイルをアップロードする仕組みを作成していきます。動画ファイルはサイズが大きいことが予想されるのでマルチパートアップロードを利用していきます。

ここまで内容を利用すれば React を利用した動画サイトが作成できるかと思います

過去のシリーズ

環境

VSCode
Ubuntu 20.04 (WSL2)
Docker 20.10.12
docker-compose version v2.2.3
git version 2.25.1

前準備

今回はこのような画面を作成します

この画面ですが以下の動画を参考に作らせていただきました。プログラミングチュートリアルさんいつも本当にお世話になっております

https://www.youtube.com/watch?v=_PZ1kCjoiSk

また今回は動画とは少し変えてtailwindCSSreact-dropzoneを利用しています

今回のハンズオンに必要なライブラリをインストールします
以下のリポジトリをクローンして行っていきます

$ git clone https://github.com/jinwatanabe/streaming_aws_handson
$ cd streaming_aws_handson/chapter1
$ yarn

まずはtailwindCSSからいれていきます

公式サイト通りに設定していきます

$ yarn add tailwindcss postcss autoprefixer]
$ yarn tailwindcss init -p

次にtailwind.config.jsを以下にします

/chapter1/tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

次にincex.ccsを以下にします

/chapter1/src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;

これで設定完了です。
次にドロップゾーン用のライブラリをインストールします

$ yarn add react-dropzone

AWS SDK

今回は以下の記事と動画を参考に S3 へのアップロードの関数を作成します

https://www.youtube.com/watch?v=_TPIddmgjv4&list=LL&index=3

https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_lib_storage.html

AWS SDK とそれに関するライブラリが必要になるためインストールします

$  yarn add @aws-sdk/client-s3 @aws-sdk/lib-storage aws-sdk

S3 の用意

AWS コンソールから「S3」と調べてクリックします

「バケットを作成」をクリック

「バケット」にバケット名をいれます。ここでは「hls-handson」としています。
バケット名はすべてでユニークでないとエラーになるため、ここからは読み替えてハンズオンを進めてください

「バケットを作成」をクリックします」

作成したバケット名をクリックします

ここからはCross-Origin Resource Sharing (CORS)の設定をしていきます。
今のままでは React から S3 にアクセスすると不正なアクセスとしてエラーになり、アップロードができないのでそこを許可するものだと思って大丈夫です

ここは以下の記事を参考に設定していきます

https://zenn.dev/knaka0209/articles/1048c1fa83c268

タブメニューから「アクセス許可」をクリック

「Cross-Origin Resource Sharing (CORS)」から「編集」をクリック

以下のコードを張り付けて「変更の保存」をクリック

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "HEAD",
            "GET",
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [
            "ETag"
        ]
    }
]

これで S3 の準備ができました

IAM の認証情報

ここからは S3 にアクセスするためのアクセスキーを取得します
既にある方はスキップして大丈夫です

AWS コンソールから「IAM」と検索してクリックします

左メニューから「ユーザー」をクリック

アクセスに使用するユーザーをクリック(S3 のアクセス権限 orAdmin 権限があるもの)

タブメニューから「認証情報」をクリック

「アクセスキー」の「アクセスキー作成」をクリックすると「アクセスキー」と「シークレットアクセス」が取得できるのでメモしておきましょう
今後も利用するのでファイルでダウンロードして保存しておくとよいです

React の実装

実際に動画アップロードフォームを作成します

以下のコードをApp.tsxにはりつけます

/chapter1/src/App.tsx
import React, { useCallback, useRef, useState } from "react";
import { useDropzone } from "react-dropzone";
import { Credentials } from "aws-sdk";
import { Upload } from "@aws-sdk/lib-storage";
import { S3Client } from "@aws-sdk/client-s3";

import ImageLogo from "./image.svg";

function App() {
  const inputRef = useRef<HTMLInputElement>(null);
  const [isLoading, setIsLoading] = useState(false);

  const onDrop = useCallback(async (acceptedFiles: File[]) => {
    setIsLoading(true);
    const file = acceptedFiles[0];
    const creds = new Credentials(
      "[AWSのアクセスキー]",
      "[AWSのシークレットアクセスアクセスキー]"
    );

    try {
      const parallelUploads3 = new Upload({
        client: new S3Client({ region: "ap-northeast-1", credentials: creds }),
        params: { Bucket: "[バケット名]", Key: file.name, Body: file },
        leavePartsOnError: false,
      });

      parallelUploads3.on("httpUploadProgress", (progress) => {
        console.log(progress);
      });

      await parallelUploads3.done();
      await setIsLoading(false);
    } catch (e) {
      console.log(e);
    }
  }, []);

  const { getRootProps, getInputProps } = useDropzone({
    onDrop,
  });

  const fileUpload = () => {
    if (inputRef.current == null) return;
    inputRef.current.click();
  };

  const onFileInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files == null) return;
    console.log(event.target.files[0]);
  };

  return (
    <div className="App">
      <div className="flex justify-center items-center h-screen">
        {isLoading ? (
          <h1>アップロード中・・・</h1>
        ) : (
          <div
            className="rounded shadow-lg w-1/2"
            style={{ maxWidth: "400px", minWidth: "300px" }}
          >
            <div className="flex justify-center">
              <div className="my-8 flex justify-center grid">
                <h1 className="font-bold text-gray-500 col-span-3 text-center">
                  動画アップロード
                </h1>
                <div className="text-gray-500 col-span-3 text-center mt-2 mb-4 text-xs">
                  MP4の動画ファイルを選択
                </div>
                <div
                  className="border-dashed border-2 border-gray-500 grid flex justify-cente p-4 mb-2 cursor-pointer"
                  style={{ minWidth: "200px" }}
                  {...getRootProps()}
                >
                  <input {...getInputProps()} />
                  <div className="container flex justify-center mb-2">
                  </div>
                  <div className="container flex justify-center text-xs text-gray-500">
                    <p>ここにドラッグ&ドロップしてね</p>
                  </div>
                </div>
                <div className="col-span-3 text-center my-1 text-gray-500">
                  または
                </div>
                <div className="col-span-3 flex justify-center w-full">
                  <button
                    onClick={fileUpload}
                    className="bg-blue-500 rounded text-white font-bold py-2 px-4 w-full"
                  >
                    ファイルを選択
                  </button>
                  <input
                    type="file"
                    className="hidden"
                    accept=".mp4"
                    ref={inputRef}
                    onChange={onFileInputChange}
                  />
                </div>
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

export default App;

React を起動して確認します

$ yarn start

localhost:3000にアクセスして以下の画面が表示されました

次にアップロードのために以下の部分を各自変更します

今回はドロップゾーンにのみ S3 のアップロード機能をつけています
ボタンからのアップロードにも同じ要領でぜひ追加してみてください

それでは実際に試してみます

今回はtest.mp4をあげてみます (リポジトリにあります)

ドロップすると以下の画面になります

アップロード中はuseStateであるisLogintrueにしてアップロード画面に切り替えています。すべてのアップロードが終わるとfalseに戻して先ほどの画面に戻ります

画面が戻ったら、作成した S3 を開きます。するとファイルがアップロードできていることが確認できます

おわりに

ここまで長いハンズオンお疲れさまでした
3 回にわたって動画サイト構築に必要な AWS のハンズオンを行ってきましたがいかがでしたでしょうか

思ったよりも簡単な技術で裏の仕組みが作成できるので、あとは CMS などを利用してデータ回りの保存ができるようになれば動画サイトは作れるかと思います

ぜひともこの先もチャレンジしてみてください

参考

GitHubで編集を提案

Discussion