0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

HLS形式の再生中動画から画像キャプチャを取得する

Posted at

目的

HLS形式の再生中動画から画像キャプチャを取得したいのでその方法を検証する

使うもの

Docker
VSCode(Remote Development拡張機能使用)
Vue(Vite)

開発環境の準備

ローカルには何も入れないで仮想環境上で開発していきます。

VSCode で開発用の空フォルダを開く

image.png

フォルダ名は hls_capture としました

仮想環境用の Dockerfile を用意

Dockerfile
FROM node:lts

image.png

VSCode で Reopen in Container, From 'Dockerfile' を実行して仮想環境を起動

image.png

image.png

image.png

ターミナルを起動し node バージョン確認

image.png

image.png

vue インストール

今回は vite を使いました。

terminal
yarn create vite . --template vue

途中の上書き確認は y, Package name には package.json と入力します。

image.png

フォルダを作りたくないので . を指定しましたが、そうすると Dockerfile が消えてしまうので、再作成しておきます。

Dockerfile
FROM node:lts

一度ローカルに戻って再度仮想環境を開き直します。
image.png

image.png

yarn で必要なモジュールをインストールします。

terminal
yarn

image.png

開発サーバ起動

NPM SCRIPTS の dev の起動ボタンを押します。
※ NPM SCRIPTS が表示されていない場合は package.json ファイルを選択すると表示されます

image.png

ブラウザで確認

image.png

起動すると Open in Browser ボタンが表示されるので押してブラウザを開きます。

image.png

拡張機能追加

仮想環境で使う vue の拡張機能(Volar)を devcontainer.json に追加します。

.devcontainer/devcontainer.json変更前
"extensions": []
.devcontainer/devcontainer.json変更後
"extensions": ["johnsoncodehk.volar"]

Rebuild Container を実行して拡張機能をインストールします。

image.png

これで開発環境の準備が完了です。

HLSの再生に対応したコンポーネント作成

トップページの編集

ビデオ表示枠と画像表示枠、画像ダウンロードボタンを配置します。

src/App.vue
<script setup>
</script>

<template>
  <h1>HLSライブ動画のキャプチャ</h1>
  <article>
    <section>
      <video src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8" autoplay playsinline muted />
      <button type="button">キャプチャ</button>
    </section>
    <section>
      <img />
      <button type="button">ダウンロード</button>
    </section>
  </article>
</template>

<style>
h1 {
  text-align: center;
}
article {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}
section {
  display: flex;
  gap: 10px;
  flex-direction: column;
  justify-content: center;
}
video, img {
  width: 684px;
  height: 360px;
  border: 1px solid silver;
}
</style>

サンプル動画は HLS.js のものを使わせていただいています。
ブラウザで確認すると以下のようになります。
2022/2現在、mac だと Safari では再生されますが、Chrome, Edge, Firefox では再生されません。

image.png

HlsVideo.vue ファイルを作成

src/components/HlsVideo.vue
<template>
  <video/>
</template>

image.png

HlsVideo コンポーネントを読み込みます

src/App.vue
<script setup>
import HlsVideo from './components/HlsVideo.vue';
</script>

<template>
  <h1>HLSライブ動画のキャプチャ</h1>
  <article>
    <section>
      <HlsVideo src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8" autoplay playsinline muted />
      <button type="button">キャプチャ</button>
    </section>
    <section>
      <img />
      <button type="button">ダウンロード</button>
    </section>
  </article>
</template>

(以下省略)

import 行と videoタグを HlsVideo に置き換えました。
ブラウザでの表示は変わらないはずです。

HLS.js を追加

対応していないブラウザでも再生できるように HLS.js というライブラリを組み込みます。

まずターミナルから hls.js を追加します。

terminal
yarn add hls.js

image.png

HlsVideo.vue 編集

HLS.js を使ってビデオを読み込む処理を追加します。

src/components/HlsVideo.vue
<template>
  <video ref="video" />
</template>

<script setup>
import { onMounted, onBeforeUnmount, ref, watch } from "vue"
import Hls from "hls.js"

const props = defineProps({
  src: String
})

const video = ref()
const hls = new Hls()
const updateSrc = (src) => {
  hls.detachMedia()
  if (!src) {
    return
  }
  if (video.value.canPlayType && video.value.canPlayType('application/vnd.apple.mpegurl')) {
    video.value.src = src
  } else if (Hls.isSupported()) {
    hls.loadSource(src)
    hls.attachMedia(video.value)
  }
}

onMounted(() => updateSrc(props.src))

watch(() => props.src, src => updateSrc(src))

onBeforeUnmount(() => updateSrc())
</script>

video.value.canPlayType でブラウザが対応しているか調べて対応していればそのまま video の src をセットします。
HLS.js に対応しているブラウザの場合は hls を介して再生されるよう video を設定しています。

これで Chrome, Edge, FireFox でも動画が再生されるようになりました。

画像キャプチャ機能追加

HlsVideo コンポーネントに capture メソッドを追加します。

src/components/HlsVideo.vue
<template>
  <video ref="video" />
</template>

<script setup>
import { onMounted, onBeforeUnmount, ref, watch } from "vue"
import Hls from "hls.js"

const props = defineProps({
  src: String
})

const video = ref()
const hls = new Hls()
const updateSrc = (src) => {
  hls.detachMedia()
  if (!src) {
    return
  }
  if (video.value.canPlayType && video.value.canPlayType('application/vnd.apple.mpegurl')) {
    video.value.src = src
  } else if (Hls.isSupported()) {
    hls.loadSource(src)
    hls.attachMedia(video.value)
  }
}
const capture = (then, format = 'image/png') => {
  const width = video.value.videoWidth
  const height = video.value.videoHeight
  const canvas = document.createElement('canvas')
  canvas.width = width
  canvas.height = height
  canvas.getContext('2d').drawImage(video.value, 0, 0, width, height)
  then(canvas.toDataURL(format))
}
defineExpose({ capture })

onMounted(() => updateSrc(props.src))

watch(() => props.src, src => updateSrc(src))

onBeforeUnmount(() => updateSrc())
</script>

キャプチャボタン、ダウンロードボタンの処理を追加します。

src/App.vue
<script setup>
import HlsVideo from './components/HlsVideo.vue'
import { ref } from 'vue'

const video = ref()
const image = ref()
const download = () => {
  const a = document.createElement('a')
  a.download = 'capture.png'
  a.href = image.value.src
  a.click()
}
</script>

<template>
  <h1>HLSライブ動画のキャプチャ</h1>
  <article>
    <section>
      <HlsVideo ref="video" src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8" autoplay playsinline muted crossorigin="anonymous" />
      <button type="button" @click="video.capture(url => image.src = url)">キャプチャ</button>
    </section>
    <section>
      <img ref="image" />
      <button type="button" @click="download">ダウンロード</button>
    </section>
  </article>
</template>

<style>
h1 {
  text-align: center;
}
article {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}
section {
  display: flex;
  gap: 10px;
  flex-direction: column;
  justify-content: center;
}
video, img {
  width: 684px;
  height: 360px;
  border: 1px solid silver;
}
</style>

これでキャプチャとダウンロードができるようになりました。

image.png

ただし、動作確認すると、 Chrome, Edge, FireFox は問題ないですが、 Safari だけキャプチャ画像が透明になってしまうようです。
https://github.com/video-dev/hls.js/issues/1806
ISSUEを見るとブラウザの動作なのでライブラリ側ではどうしようもないようです。

仕方がないのでキャプチャ画像が透明な場合はクリップボードの画像を貼り付ける処理をいれて、
Safari の場合は Shift + Command + 4 で手動でクリップボードでキャプチャしたものを貼り付けられるようにしておきます。

src/components/HlsVideo.vue
//(省略)
const capture = (then, format = 'image/png') => {
  const width = video.value.videoWidth
  const height = video.value.videoHeight
  const canvas = document.createElement('canvas')
  canvas.width = width
  canvas.height = height
  const context = canvas.getContext('2d')
  context.drawImage(video.value, 0, 0, width, height)
  if (context.getImageData(0, 0, 1, 1).data.every(i => i == 0)) {
    navigator.clipboard.read().then(items => {
      const getImage = items.map(i => {
        const type = i.types.find(j => j.startsWith('image'))
        return type && (() => i.getType(type))
      })[0]
      if (getImage) {
        getImage().then(image => then(URL.createObjectURL(image)))
      }
    })
  } else {
    then(canvas.toDataURL(format))
  }
}
//(省略)

これで一応目的は達成できました。
safari が HLS ビデオの画像キャプチャに対応する日が来るのでしょうか。

今回のソース

0
3
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
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?