🎂

iOS(safari)でcanplaythroughイベントが発火されなかった時に解決した方法【React】

2021/05/29に公開

実装時の仕様

コンポーネントマウント時にvideoをプリロードしておいて、そのコンポーネント内のボタンを押下するとモーダルが表示され、その中でvideoが再生されます。
ボタンが押下可能になるのはvideoが最後まで再生されるするのに十分なデータが読み込まれた時のみです。canplaythroughイベントを使います。スタイルはemotionで当てています。

canplaythrough (MDN Web Docs)

全体コード

import React, { useState, useRef } from 'react';
import Movie from './path/to/sample.mp4';
import { css } from '@emotion/css';

export const PreloadPage = () => {
    const videoRef = useRef();
    const video = videoRef.current;
    const [isOpen, setIsOpen] = useState(false);
    const [isAvailable, setIsAvailable] = useState(false);

    const playMovie = () => {
        if(isAvailable) {
            setIsOpen(true);
            video.play();
        }
    }
    const endMovie = () => {
        setIsOpen(false);
        video.currentTime = 0;
    }

    const videoStyle = css`
        visibility: ${ isOpen ? 'visible' : 'hidden' };
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%:
        & video {
            object-fit: cover;
        }
    `;

    return (
        <div>
            <button onClick={playMovie}>START</button>
            <div css={videoStyle}>
                <video
                    ref={videoRef}
                    src={Movie}
                    onCanPlayThrough={() => setIsAvailable(true)}
                    onEnded={endMovie}
                    playsInline/>
            </div>
        </div>
    )
}

まず上記のコードの感じで実装していました。
ただこれではisAvailableがtureにならず、動画が再生できません。
解決方法は割と簡単でした。以下のコードになります。

解決

解決方法は簡単で、video.load()を追加するだけでした。
以下に記載します。

import React, { useState, useRef, useEffect } from 'react';
import Movie from './path/to/sample.mp4';
import { css } from '@emotion/css';

export const PreloadPage = () => {
    const videoRef = useRef();
    const video = videoRef.current;
    const [isOpen, setIsOpen] = useState(false);
    const [isAvailable, setIsAvailable] = useState(false);

    const playMovie = () => {
        if(isAvailable) {
            setIsOpen(true);
            video.play();
        }
    }
    const endMovie = () => {
        setIsOpen(false);
        video.currentTime = 0;
    }

    const videoStyle = css`
        visibility: ${ isOpen ? 'visible' : 'hidden' };
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%:
        & video {
            object-fit: cover;
        }
    `;

    // add
    useEffect(() => {
        video.load();
    }, [])

    return (
        <div>
            <button onClick={playMovie}>START</button>
            <div css={videoStyle}>
                <video
                    ref={videoRef}
                    src={Movie}
                    onCanPlayThrough={() => setIsAvailable(true)}
                    onEnded={endMovie}
                    playsInline/>
            </div>
        </div>
    )
}

useEffectを使ってvideo.load()と記述することによってビデオがロードされ再生が可能になりました。iOS(safari)以外はそこらへんはよしなにやってくれるらしいです。実機端末での確認は大切ですね。

Discussion