今回は、Vue.jsを利用してプレビュー動画を表示するようなコンポーネントを開発していた際にvideoの読み込みが行われないという問題があったので、その問題の対処方法について紹介します。

前提条件

  • Nuxt.jsの2.15.3

下記のような動画を読み込んで、読み込んだ動画をプレビュー表示するようなコンポーネントを開発しようと思います。

該当部分のソースコードを示します。

<template>
  <div>
     <!-- ファイル入力 -->
     <input accept="video/mp4"  @change="fileSelect" />
     <!-- プレビューとして表示 -->
     <video controls >
       <source :src="src" type="video/mp4">
    </video>
  </div>
</template>
<script lang="ts">
import {
  defineComponent,
  ref,
} from '@nuxtjs/composition-api'

export default defineComponent({
  setup(_props: any, _context: any) {
    const src = ref()

   // ファイルを読みんでDataURL(Base64文字列)を返す
   const readFileAsDataURL = (file: any) => {
     return new Promise((resolve, reject) => {
       const reader = new FileReader()
       reader.onload = (evt: any) => {
        resolve(evt.target.result)
      } 
      reader.onerror = reject;
      reader.readAsDataURL(file)
    })
  }

    // ファイル選択
    const fileSelect = async  (_event: any) => {
      const file = event.target.files[0]
       if (!file || !file.type.match('video/*')) {
          return
        }
        const data = await readFileAsDataURL(file)
       // 選択された動画を見えるようにする。
        src.value = data
    }

    return {
      src,
      fileSelect,
    }
  },
})
</script>

このとき、動画ファイルを2回読み込むと、読み込んだファイルが更新されません。

対策例

Videoタグは、HTMLMediaElementというエレメントにあたります。ドキュメントを引用すると

HTMLMediaElement の load() メソッドは、メディア要素をその初期状態にリセットし、再生を開始する準備としてメディアソースを選択してメディアを読み込むプロセスを開始します。 プリフェッチされるメディアデータの量は、要素の preload 属性の値によって決まります。

とあります。load()をコールすれ強制的の再生開始準備ができるようなので、srcを更新した後にコールするようにします。

先程のコードを下記のように変更します。

<template>
  <div>
     <!-- ファイル入力 -->
     <input accept="video/mp4"  @change="fileSelect" />
     <!-- プレビューとして表示 -->
     <video controls ref="previewVideo">
       <source :src="src" type="video/mp4">
    </video>
  </div>
</template>
<script lang="ts">
import {
  defineComponent,
  ref,
} from '@nuxtjs/composition-api'

export default defineComponent({
  setup(_props: any, _context: any) {
    const src = ref()
    const previewVideo = ref()

   // ファイルを読みんでDataURL(Base64文字列)を返す
   const readFileAsDataURL = (file: any) => {
     return new Promise((resolve, reject) => {
       const reader = new FileReader()
       reader.onload = (evt: any) => {
        resolve(evt.target.result)
      } 
      reader.onerror = reject;
      reader.readAsDataURL(file)
    })
  }

    // ファイル選択
    const fileSelect = async  (_event: any) => {
      const file = event.target.files[0]
       if (!file || !file.type.match('video/*')) {
          return
        }
        const data = await readFileAsDataURL(file)
       // 選択された動画を見えるようにする。
        src.value = data
        // 強制的にロードする
        previewVideo.load()
    }

    return {
      src,
      fileSelect,
      previewVideo,
    }
  },
})
</script>

vue.jsのcomposition apiでは、従来のthis.$refs.hogehogeという形での
Dom参照する変数をref()を使って、定義することで参照できます。

この対応を入れることで無事問題解決しました。

リアクティブなVue.jsやNuxt.jsに慣れてくるとデータを変えたら、いい感じに画面が切り替わるので、意図的にイベントを拾って更新しないといけないElementもあるようです。