SinatraでHTTP Live Streaming

動画をダイレクトにサーバから視聴する方法は他にもストリーミング系の仕組みがあるらしいのですが、Sinatraがストリーミングにうまく対応できてないような感じなので、ガチガチの配信ではなく「なんちゃってストリーミング動画配信」をする為のメモです。Appleが考案していてiOSとも親和性が高い技術なので採用しました。
# 環境
- Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-99-generic x86_64)
- Sinatra 2.0.5 (bundlerで起動する前提です)
- ffmpeg N-97994-g83fa39e

UbuntuとSinatraは既に環境に配置されているとします。
また、インストール作業用フォルダとffmpegのバイナリを格納するフォルダをhomeに作成します。

- ffmpeg_sources
- ffmpeg_build
- bin

# ffmpegのインストール
ffmpegをaptでインストールするとlibfdk-aacが含まれていませんので、自分でカスタマイズインストールしなければなりません。libfdk-aacはHTTP Live Streamingを行うために必要な動画を細切れに分割してプレイリストファイルを作るのに必要です。

## ffmpegで利用するコーデック群のライブラリをインストールする為に事前に必要なツール群のインストール

```console:console
sudo apt-get update -qq && sudo apt-get -y install \
  autoconf \
  build-essential \
  cmake \
  git-core \
  libass-dev \
  libfreetype6-dev \
  libgnutls28-dev \
  libsdl2-dev \
  libtool \
  libva-dev \
  libvdpau-dev \
  libvorbis-dev \
  libxcb1-dev \
  libxcb-shm0-dev \
  libxcb-xfixes0-dev \
  pkg-config \
  texinfo \
  wget \
  yasm \
  zlib1g-dev
```

## ffmpegで利用するコーデック群のライブラリのインストール

```console:console
sudo apt-get install nasm
sudo apt-get install libx264-dev
sudo apt-get install libx265-dev libnuma-dev
sudo apt-get install libvpx-dev
sudo apt-get install libfdk-aac-dev
sudo apt-get install libmp3lame-dev
sudo apt-get install libopus-dev
```

## ffmpegのビルド

```console:console
cd ~/ffmpeg_sources && \
  wget -O ffmpeg-snapshot.tar.bz2 https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2 && \
  tar xjvf ffmpeg-snapshot.tar.bz2 && \
  cd ffmpeg && \
  env PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \
    --prefix="$HOME/ffmpeg_build" \
    --pkg-config-flags="--static" \
    --extra-cflags="-I$HOME/ffmpeg_build/include" \
    --extra-ldflags="-L$HOME/ffmpeg_build/lib" \
    --extra-libs="-lpthread -lm" \
    --bindir="$HOME/bin" \
    --enable-gpl \
    --enable-libass \
    --enable-libfdk-aac \
    --enable-libfreetype \
    --enable-libmp3lame \
    --enable-libopus \
    --enable-libvorbis \
    --enable-libvpx \
    --enable-libx264 \
    --enable-libx265 \
    --enable-nonfree && \
  env PATH="$HOME/bin:$PATH" make && \
  make install

```
※gnutlsとlibaomはなんかエラーが出たので（今回は必要ないので）除外

# テスト
今回は人気アニメ「ミンクの刃」をストリーミング配信で観てみましょう。

## mp4形式に変換
```console:console
sudo ffmpeg -i 'ミンクの刃 01.mkv' -vcodec copy 'ミンクの刃 01.mp4'
```

## mp4形式からストリーミング配信用のHLS形式へ変換
```console:console
sudo /home/ユーザ名/bin/ffmpeg -i 'ミンクの刃 01.mp4' -codec copy -vbsf h264_mp4toannexb -map 0 -f segment -segment_format mpegts -segment_time 5 -segment_list 'ミンクの刃 01'/playlist.m3u8 'ミンクの刃 01'/a%03d.ts
```

## 配信用サイトの作成
```ruby:test.rb
require 'sinatra'
require 'uri'
set :environment, :production

get '/' do
  erb(:test);
end

get '/:file_name' do |file_name|
  sendFilePath = "disk/live/minknoyaiba/#{file_name}";
  if file_name.match(/.+m3u8$/) then
    send_file(sendFilePath, :filename => file_name, :type => "application/vnd.apple.mpegurl");
  else
    send_file(sendFilePath, :filename => file_name, :type => "video/mp2t");
  end
end

```
/source/onlineフォルダ内にtest.rbがあり、同フォルダ内にdiskフォルダが存在する環境です。minknoyaibaフォルダ内にはHLS用のプレイリスト(*.m3u8)や分割動画ファイル(*.ts)が入っていると思ってください。
先ほど「ミンクの刃 01」フォルダにHLSのファイル一式を変換したのに何故「minknoyaiba」フォルダに入れるのか？それはSinatraのsend_fileが日本語フォルダをうまく認識してくれないからです。
また外部から接続する為に`set :environment, :production`しています。

```erb:test.erb
<!DOCTYPE html>
<html>
  <head>
    <title>HTTP Live Streaming Example</title>
  </head>
  <body>
    <video src="/playlist.m3u8" width="400" height="300" controls></video>
  </body>
</html>

```

## 配信用サイトの起動
```console:console
bundle exec ruby -I /source/class -C/source/online test.rb
```
この時点で以下にアクセスするとサイトで動画が見れます。
`http://テスト用サーバのIP:4567/`
次にiOS側で動画を観てみましょう。

## iOSアプリの作成
```objective_c:MinkTubeBase.h
// global require
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>

// local require

@interface MagiTubeBase : AVPlayerViewController {
    
}

```
AVKitとAVFoundationをプロジェクトに追加しておいてください。

```objective_c:MinkTubeBase.m
@implementation MagiTubeBase

-(void)viewDidLoad {
    [super viewDidLoad];
    
    __strong NSURL *url = [NSURL URLWithString:@"http://テスト用サーバのIP:4567/playlist.m3u8"];
    __strong AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
    __strong AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
    self.player = player;    
}

@end

```

# おしまい
ミンクの刃面白いですよね！
最後に溶鉱炉の中に沈むミンクが最高にカッコイイ！🥺