📽️

videoのバッファーを開始前に読み込む【React】

2 min read

仕様

reactでコンポーネントマウント時にvideoのソースにapiからとってきたurlを入れて読み込み開始しておいて、全てのバッファーを読み込んだら再生ボタンを押せるような仕様。
ユーザー体験を考えると動画をすべて読みこんでからじゃないと再生できないというのは、少し良くない感じがするかもしれませんがアプリの都合上、途中で動画が止まるほうがもっと良くないので上記のような仕様になりました。
一定時間で全て読み込めなかった場合、再生はスキップさせます。

preload属性を追加する

今回実装した方法はreact-helmetを使ってpreload属性を追加したlinkタグで読み込む動画のソースを入れておく方法でした。

import { useEffect } from 'react';
import Helmet from 'react-helmet';
import useVideoData from './path/to/useVideoData';

export const App = () => {   
    const { videoState, fetchVideo } = useVideoData();
    
    useEffect(() => {
        fetchVideo();
    }, [])
    
    return (
        <div>
           <Helmet>
                <link rel='preload' href={videoState.url} as='video' />
            </Helmet> 
            <video src={videoState.url} />
        </div>
    )
}

上のソースではマウント時にプリロードしてくれるんですが、このままだとバッファーを全て読み込んでいない状態でも再生可能になっているかと思います。
今回の実装では全てのバッファーを読み込んだ段階で再生できるようにします。

bufferがdurationと同じでなければvideoを非表示

setIntervalでvideoがどこまで読み込まれたかを確認し、読み込まれたか判断するステートを用意してそれを使って表示非表示を制御する。

import { useEffect, useRef, useState } from 'react';
import Helmet from 'react-helmet';
import useVideoData from './path/to/useVideoData';

export const App = () => {   
    const { videoState, fetchVideo } = useVideoData();
    const [isCompleted, setIsCompleted] = useState(false);
    const videoRef = useRef(null);
    
    useEffect(() => {
        fetchVideo();
    }, [])

    useEffect(() => {
    if (!isCompleted) {
        const intervalId = setInterval(() => {
            const videoBuf = videoRef.current.buffered;
            const videoDuration = videoBuf.duration;
            const videoBufEnd = videoBuf.end(0);

            if (videoBufEnd / videoDuration === 1 ) {
                setIsCompleted(true);
                clearInterval(intervalId);
            }
        }, 1000);

        setTimeout(() => {
            clearInterval(intervalId);
        }, 10000);
    }
    }, [isCompleted])
    
    return (
        <div>
           <Helmet>
                <link rel='preload' href={videoState.url} as='video' />
            </Helmet>
            { isCompleted && <video src={videoState.url} ref={videoRef} /> }
        </div>
    )
}

おわり

実際の実装ではここから更に色々調整をしましたが、基本的なロジックはこんな感じだったかなと思います。