LoginSignup
13
9

More than 1 year has passed since last update.

React TypeScriptでvideoタグだけで動画を読み込む

Last updated at Posted at 2021-09-06

やりたいこと

React TypeScript で開発していて、埋め込み動画を用意して、自動再生させたい!

やった事の顛末をストーリー風に書いてみました。

あらすじ

  1. 動画読込
  2. 自動再生
  3. リプレイ機能

Video系のライブラリっている?

Reactで開発していて動画を再生したい!というとき、探すとそれっぽいライブラリが出てきます。これってライブラリ使わなきゃいけない理由でもあるの?と思ったので、無しでやったらどうなるのか試してみました。

結論から言うと、ブラウザの user agent stylesheet のおかげで割とリッチに仕上がるので、用途によってはライブラリ不要ですね。また、今回の手法は背景動画を仕込むときにも使えそうな方法です。ここからlazyloadとかの技術を足していくと良さそうです。

コード

いきなりサブコンポーネントですが、こんな感じになりました。

sample.tsx
import React, { useRef, useEffect } from 'react'
import video_mp4 from "../movie/sample.mp4"
import video_webm from "../movie/sample.webm"

export function Translate() {
    const videoRef = useRef<HTMLVideoElement>(null);
    useEffect(() => {
        videoRef.current?.play();
    }, []);
    return (
        <React.StrictMode>
                    <video controls muted ref={videoRef} >
                        <source src={video_mp4} type="video/mp4" />
                        <source src={video_webm} type="video/webm" />
                        <p>Your browser doesn't support HTML5 video.</p>
                    </video>
        </React.StrictMode>
    );
}

動画をimportしてみる

sample.tsx
import video_mp4 from "../movie/sample.mp4"
import video_webm from "../movie/sample.webm"

で動画をソースに含めます。

TS(2307)

このとき、mp4webmimport部分でts(2307)のエラーが出ます。

モジュール ../movie/sample.mp4 またはそれに対応する型宣言が見つかりません。ts(2307)

そこで、react-app-env.d.tsdeclare文を追記します。

./src/react-app-env.d.ts
/// <reference types="react-scripts" />
declare module '*.mp4' {
    const src: string;
    export default src;
}
declare module '*.webm' {
    const src: string;
    export default src;
}

videoタグを設置

<video>を定義して、先ほどの動画を表示させます。

sample.tsx
 <video controls muted ref={videoRef} >

controls を書くと、user agent stylesheet の再生ボタンとかを設定してくれます。
背景動画とかを置くなら controlsは消せばOKです。
refには、useRef()で参照を設定しています。これは後で解説します。

sample.tsx
<source src={video_mp4} type="video/mp4" />

ここでは、先ほどソースに含めた動画をvideoの中で代替動画一覧に指定しています。ユーザー画面では、sourceのいずれかが実際に使用されます。

自動再生

しかし、sourceに動画を指定しただけでは再生が行われません。<video>autoPlay属性を指定すると、自動再生されます。しかし今回は使用せず、代わりに、先ほどuseRef()で参照を設定して置いたものを活用しましょう。

sample.tsx
useEffect(() => {
  videoRef.current?.play();
}, []);

useEffect()でレンダリング終了後に、HtmlVideoElementplay()をトリガーします。

追加要望でリプレイボタンを付けたい!

「まさか動画があるなんて!最初から見せてよ!」
「再生コントロールでスライダーをマウスでドラッグしろ?面倒なこと言わないで!」
という要望にお応えして、リプレイボタンを設置することになりました。

ボタンをクリックしたら、動画の最初に戻るような機能を加えてみましょう。

sample.tsx
import React, { useRef, useEffect } from 'react'
import video_mp4 from "../movie/sample.mp4"
import video_webm from "../movie/sample.webm"
+ import { Button } from '@material-ui/core';

export function Translate() {
    const videoRef = useRef<HTMLVideoElement>(null);
    useEffect(() => {
        videoRef.current?.play();
    }, []);
+    const StartReplay = () => {
+       videoRef.current?.currentTime = 0;
+    }
    return (
        <React.StrictMode>
                    <video controls muted ref={videoRef} >
                        <source src={video_mp4} type="video/mp4" />
                        <source src={video_webm} type="video/webm" />
                        <p>Your browser doesn't support HTML5 video.</p>
                    </video>
+                    <Button onClick={StartReplay} children="replay" variant="contained" color="primary" />
        </React.StrictMode>
    );
}

currentTimeで振り出しに

コードの説明を見ていきましょう。

HtmlVideoElementのプロパティcurrentTimeをゼロにすると、動画の最初に戻ることは分かっています。そこで、StartReplay関数を用意します。その中で、currentTimeを操作する処理を入れます。

sample.tsx
const StartReplay = () => {
   videoRef.current?.currentTime = 0;
}

TS(2779)

さて、ここで問題発生です。currentTimeのところがエラーになります。

代入式の左辺には、省略可能なプロパティ アクセスを指定できません。ts(2779)

解決方法は、「存在する」と確実に分かる状況を作り出すことです。

sample.tsx
const StartReplay = () => {
    if (videoRef.current?.currentTime) {
        videoRef.current.currentTime = 0;
    }
}

これでエラーは解決です。

始めに戻っただけ

これで良し!と思いますが、残念。これだと、動画が終了後にリプレイボタンは機能しません。動画内のどの時刻にカーソルがあるかを示すcurrentTimeと、再生と停止を司るplay()は別の問題なのです。

sample.tsx
const StartReplay = () => {
    if (videoRef.current?.currentTime) {
        videoRef.current.currentTime = 0;
    }
    videoRef.current?.play();
}

currentTimeをゼロにした後に、play()で再生を開始させましょう。

まとめ

最後にまとめてみましょう。

sample.tsx
import React, { useRef, useEffect } from 'react'
import video_mp4 from "../movie/sample.mp4"
import video_webm from "../movie/sample.webm"
+ import { Button } from '@material-ui/core';

export function Translate() {
    const videoRef = useRef<HTMLVideoElement>(null);
    useEffect(() => {
        videoRef.current?.play();
    }, []);
+    const StartReplay = () => {
+       if (videoRef.current?.currentTime) {
+        videoRef.current.currentTime = 0;
+        }
+        videoRef.current?.play();
+    }
    return (
        <React.StrictMode>
                    <video controls muted ref={videoRef} >
                        <source src={video_mp4} type="video/mp4" />
                        <source src={video_webm} type="video/webm" />
                        <p>Your browser doesn't support HTML5 video.</p>
                    </video>
+                    <Button onClick={StartReplay} children="replay" variant="contained" color="primary" />
        </React.StrictMode>
    );
}

環境

  • create-react-app : 4.0.3
  • react : 17
  • typescript : 4
./tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

関連公式

Excelsior!

13
9
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
13
9