🙄

Expo VideoでHLS再生してみよう

2024/09/27に公開

最近アプリ開発に興味があって動画再生をアプリ内でやるには?というのを試したくなったので試してみたお話です。

もともとはアプリ開発のノウハウを得たかった

メンバーに1ヶ月でアプリのノウハウをできるだけ得るにはどうしたら良い?という雑なアドバイスを求めたら「動画プレイヤー作ってみ」という意見を頂いたので、何もわからんけどとりあえず動画再生してみっか!というわけで作ってみることにしました。
ちなみに他にも「オンラインマルチプレイテトリス」「ソシャゲ」「Blueskyクローン」といった意見も頂きました。(1ヶ月...🤔)

Expoを始める

5,6年ほど前に一度React Nativeを触ったことがあったので、今回もReact Nativeを使うことは決めていたのですが、今どきはExpoを使う方が良いぞとこれまたアドバイスを頂いたので、Expoで始めることにしました。
たしかにセットアップが楽すぎる...気づいたらiOS Simulatorでサンプルアプリが動いてました。

React Nativeでの動画再生について調べる

早速React Nativeで動画を再生するには?ということで調べ始めました。
react-native-videoというライブラリがよく出てきますね。
どうやらExpoにも便利なモジュールが存在しているらしく、expo-avを使えばすぐ動画再生できそうだぞ?ということに気づきました。
なんならSnackで動いているの見えますし、目的は達成(?)されたようなものです。
ただ、ここで終わると何も得られずドキュメント眺めてふーんで終わってしまうので、もう少し調べることにしました。

expo-videoとの出会い

Expo Videoでもう少し調べると Expo Video(expo-av)Expo Video の同じようなタイトルで異なる2つのページが出てきました。
同じものかと思いきや、どうやらexpo-avのVideoコンポーネントをよりモダンで信頼性の高い実装に置き換えようとしているようで、それがexpo-videoという形で提供されているそうです。
ただし、まだExperimentalなので、安定するまではexpo-avの利用が推奨されています。
モダン、って良いですよね。みんな大好きモダン。
私はすぐにexpo-videoに心惹かれるようになりました。
これが私とexpo-videoの出会いです。

さすがExpoのドキュメントで、これまたSnackですぐに動作が確認でき、動画が再生されていることが確認できました。
ただ、これではまたドキュメント眺めてexpo-avのソースと見比べてふーんで終わってしまいそうなので、少し手を加えてみようと考えました。

そうだ、HLSを再生しよう

HLS(HTTP Live Streaming)とは、動画をストリーミング配信するための規格です。Apple社が開発したんですね。
m3u8という拡張子のプレイリストファイルに、特定の秒数ごとに区切った動画ファイルのパスを指定して再生秒数と再生動画ファイル(tsファイル)の指定をマッピングした情報が記載して、それを読み込んで動画を連続再生します。
以下のような形ですね。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:17
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10.000000, // 再生開始〜10秒はこの部分でseg.000.tsを再生
seg.000.ts
#EXTINF:8.0000000, // 次の8秒はこの部分でseg.001.tsを再生
seg.001.ts
#EXTINF:10.0000000, // 次の10秒はこの部分でseg.002.tsを再生
seg.002.ts
#EXT-X-ENDLIST

m3u8に対応した動画プレイヤーでこういったファイルを読み込むと、動画が再生されます。

Expoでの実装

ドキュメントではmp4ファイルを再生していましたが、HLSに対応しているかどうかは不明でした。
そのため再生できるかドキドキしつつ以下のように試してみました。

/app/(tabs)/video.tsx
import { VideoPlayer } from "~/components/VideoPlayer";
import type { VideoView } from "expo-video";
import { useRef, useState } from "react";
import { PixelRatio, StyleSheet, View, Button } from "react-native";

const videoSource =
  "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/hls/BigBuckBunny.m3u8";

export default function VideoScreen() {
  const ref = useRef<VideoView>(null);

  return (
    <View style={styles.contentContainer}>
      <VideoPlayer source={videoSource} videoRef={ref} />
    </View>
  );
}

const styles = StyleSheet.create({
  contentContainer: {
    flex: 1,
    padding: 10,
    alignItems: "center",
    justifyContent: "center",
    paddingHorizontal: 50,
  },
  controlsContainer: {
    padding: 10,
  },
});
/components/VideoPlayer.tsx
import { useVideoPlayer, VideoView } from "expo-video";
import { StyleSheet } from "react-native";

type VideoPlayerProps = {
  source: string;
  videoRef?: React.RefAttributes<VideoView>["ref"];
};

export function VideoPlayer({ source, videoRef }: VideoPlayerProps) {
  const player = useVideoPlayer(source, (player) => {
    player.loop = true;
    player.play();
  });

  return (
    <VideoView
      ref={videoRef}
      style={styles.video}
      player={player}
      allowsFullscreen
      allowsPictureInPicture
    />
  );
}

const styles = StyleSheet.create({
  video: {
    width: "100%",
    height: 240,
  },
});

...はい、お気づきの方もいらっしゃると思いますが、コンポーネントをファイル分割して、動画のソースをm3u8の指定に変更しただけです。
これで無事HLSが再生されました。

expo-videoはHLSにも対応されているんですね〜。素晴らしい。ありがたい。
ついでにフルスクリーンでの再生や、倍速再生といった機能にも対応されていました。

まとめ

アプリ開発のノウハウを得るはずでしたが、expo-videoの便利さに感動して終わってしまいました。
ただExpoが便利な機能をたくさん提供してくれていたり、EASによるビルドで非常に簡単だったりとExpo自体の便利さがとくに感じられたので、もう少しExpoについての理解を深めながらアプリ開発を試していきたいと思います。
次に作るのはBlueskyのクローンだな〜。

株式会社モニクル

Discussion