やりたいこと
React TypeScript で開発していて、埋め込み動画を用意して、自動再生させたい!
やった事の顛末をストーリー風に書いてみました。
あらすじ
- 動画読込
- 自動再生
- リプレイ機能
Video系のライブラリっている?
Reactで開発していて動画を再生したい!というとき、探すとそれっぽいライブラリが出てきます。これってライブラリ使わなきゃいけない理由でもあるの?と思ったので、無しでやったらどうなるのか試してみました。
結論から言うと、ブラウザの user agent stylesheet のおかげで割とリッチに仕上がるので、用途によってはライブラリ不要ですね。また、今回の手法は背景動画を仕込むときにも使えそうな方法です。ここからlazyload
とかの技術を足していくと良さそうです。
コード
いきなりサブコンポーネントですが、こんな感じになりました。
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してみる
import video_mp4 from "../movie/sample.mp4"
import video_webm from "../movie/sample.webm"
で動画をソースに含めます。
TS(2307)
このとき、mp4
、 webm
のimport
部分でts(2307)
のエラーが出ます。
モジュール ../movie/sample.mp4
またはそれに対応する型宣言が見つかりません。ts(2307)
そこで、react-app-env.d.ts
にdeclare
文を追記します。
/// <reference types="react-scripts" />
declare module '*.mp4' {
const src: string;
export default src;
}
declare module '*.webm' {
const src: string;
export default src;
}
videoタグを設置
<video>
を定義して、先ほどの動画を表示させます。
<video controls muted ref={videoRef} >
controls
を書くと、user agent stylesheet の再生ボタンとかを設定してくれます。
背景動画とかを置くなら controls
は消せばOKです。
ref
には、useRef()
で参照を設定しています。これは後で解説します。
<source src={video_mp4} type="video/mp4" />
ここでは、先ほどソースに含めた動画をvideoの中で代替動画一覧に指定しています。ユーザー画面では、sourceのいずれかが実際に使用されます。
自動再生
しかし、sourceに動画を指定しただけでは再生が行われません。<video>
にautoPlay
属性を指定すると、自動再生されます。しかし今回は使用せず、代わりに、先ほどuseRef()
で参照を設定して置いたものを活用しましょう。
useEffect(() => {
videoRef.current?.play();
}, []);
useEffect()
でレンダリング終了後に、HtmlVideoElement
のplay()
をトリガーします。
追加要望でリプレイボタンを付けたい!
「まさか動画があるなんて!最初から見せてよ!」
「再生コントロールでスライダーをマウスでドラッグしろ?面倒なこと言わないで!」
という要望にお応えして、リプレイボタンを設置することになりました。
ボタンをクリックしたら、動画の最初に戻るような機能を加えてみましょう。
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
を操作する処理を入れます。
const StartReplay = () => {
videoRef.current?.currentTime = 0;
}
TS(2779)
さて、ここで問題発生です。currentTime
のところがエラーになります。
代入式の左辺には、省略可能なプロパティ アクセスを指定できません。ts(2779)
解決方法は、「存在する」と確実に分かる状況を作り出すことです。
const StartReplay = () => {
if (videoRef.current?.currentTime) {
videoRef.current.currentTime = 0;
}
}
これでエラーは解決です。
始めに戻っただけ
これで良し!と思いますが、残念。これだと、動画が終了後にリプレイボタンは機能しません。動画内のどの時刻にカーソルがあるかを示すcurrentTime
と、再生と停止を司るplay()
は別の問題なのです。
const StartReplay = () => {
if (videoRef.current?.currentTime) {
videoRef.current.currentTime = 0;
}
videoRef.current?.play();
}
currentTime
をゼロにした後に、play()
で再生を開始させましょう。
まとめ
最後にまとめてみましょう。
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
{
"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!