YouTubeとローカルPCの混合メディアを同時に再生できるハイブリッドメディアプレイヤーを作ってみた


私はPCで作業しているとき、バックグラウンドで音楽を再生していることがよくある。このときに再生する曲は、アルバムCDからインポートしたオーディオメディアのときもあれば、YouTubeの動画だったりとさまざまだ。メディアの再生環境のバリエーションが多様なため、ローカルのオーディオソースを再生する場合はPCにインストールされているメディア・プレイヤーを使い、YouTubeの動画の場合はブラウザからアクセスしている。それはいたって一般的な方法ではあるのだが、PC上のメディアとお気に入りのYouTube動画を別々にしか取り扱えない不自由さには常々不満があった。

──両方のメディアを一元管理して、プレイヤーを切り替えることなく再生できればなぁ──

この要望を満たすソリューションは既に世の中にはいくつか存在している。それは例えば「CherryPlayer」とか「FreeTube」などだ。ただ、前者はWindowsでしか使えないうえ、私のPCでは挙動が安定せずエラーが頻発して使いものにならなかった。一方、後者はインストールしてみたもののUIが煩雑でとても使いづらく、これも継続的に使い続けるのには抵抗があったので諦めた。もっとシンプルな機能と、直感的な操作感で、OSとかの環境を選ばずに手軽に利用できるアプリケーションが欲しかったのだが、結局、私の要望を満たすようなソリューションは見つけられずじまい……。
そして気づいた。YouTubeの埋め込みIframeプレイヤーと `<audio>` や `<video>` などのHTMLメディアタグを組み合わせたWebアプリケーションであれば、YouTubeの動画とPCのメディアファイルの両方を同時に再生できるんじゃないか？！
よーし、それなら自分で作ってしまおう。なんか簡単そうだし……(笑)

そうして出来たのが「Ambient」だ。

![ambient_light_mobile_size.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64077/5e2c7d86-2bc1-de3e-2189-6ab6aeaa904f.png)

![ambient_dark_pc_size.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64077/ba9c4560-12a8-b769-2705-5e23dae2a77d.jpeg)

なお、下記URLにてクラウド用デモ版を公開しているので、興味のある人はぜひ触ってみて欲しい。

[https://amp.ka2.org](https://amp.ka2.org/)

何気に「Ambient」を使い出したら他のメディアプレイヤーはCDの音源取り込みぐらいでしか使わなくなってしまった……我ながら良いアプリ作ったものである。

## Ambientの特長

「Ambient」の特長的な機能を紹介すると、下記のようになる。

* OS環境を問わずブラウザでアクセスできる（※ ローカルのホストコンピュータ上にインストールする場合にはある程度の技術的な知識が必要）
* ローカルのホストコンピュータ上のメディアファイル（動画およびオーディオ）とYouTubeの動画を同時に再生できる
* 各メディアごとに頭出し（再生開始時間）やトリム（再生終了時間）の指定ができる
* 各メディアごとにフェードイン／フェードアウトのエフェクト処理を設定できる
* 拡張性の高いJSON形式のプレイリスト
* プレイリスト内でメディアをカテゴライズできる
* ローカルのホストコンピュータ上のメディアファイル格納フォルダへのシンボリックリンクを作成し、既存メディアパスを変更せずにメディアソースをマウントできる
* プレイヤー上でメディアをプレイリストに追加でき、更新したプレイリストをダウンロードできる
* ランダム再生とシャッフル再生
* YouTube動画を広告なしで連続再生できる（※ 私自身が「YouTube Premium」に加入済みなのでこの恩恵については未検証だが…）
* 対応するローカルメディアファイルの種類が多い（※ iTunesでインポートしたm4a形式に対応していないのは数少ない難点の一つだ…）
* プレイヤーのUIの表示言語を切り替えられる（※ 翻訳ファイルはJSON形式）
* フロントエンドのスタイルは TailwindCSS + Flowbite を採用しているため拡張が容易である
* MITライセンスのオープンソースで、恒久的に無料である（※ もし将来的に自律アプリ版を作っても、面倒なので有償版とか作る気は全くない）

──改めて書き出してみて、結構実装機能があったことに驚いた（笑）

## Ambientの導入方法

「Ambient」はローカルPC上にインストールした時に真価を発揮する。
クラウド版の「Ambient」ではローカルのメディアファイルの取り扱いや管理が制限されてしまうのだ。「Ambient」は自身のインストール場所から相対的にプレイリストやメディア等を検索するように出来ているため、再生対象のメディアファイルも同じホスト上に配置しなければならない。不特定多数に公開されるようなクラウド上のホストの場合、そのホストにメディアファイルを同居させることはメディアファイルの再配布と変わらず著作権的にNGだ。またメディアファイルはサイズが大きいため、クラウド上のストレージにはコスト的な制限も発生してくるだろう。したがって、クラウド版の「Ambient」でローカルメディアを取り扱うのはおよそ現実的ではない（YouTube動画のプレイヤーとして使う分には有用である）。
そもそも「Ambient」自体が私がローカルPC上で個人利用するために開発したものなので、ローカルPC上にインストールして利用するのが正しい使い方と言える。

さて、ローカルPCで「Ambient」を使うには、PHPが動作する環境とApacheのようなWEBサーバ環境が必要になる。
この時点で知見がない人のハードルはだいぶ上がってしまうが、Windowsであれば「[XAMPP](https://www.apachefriends.org/jp/)」、Macであれば「[MAMP](https://www.mamp.info/)」を導入すればそこまで難しくなく導入できるのではないだろうか。
知見がある人であれば、DockerやWSLで仮想WEBサーバ環境を構築してそこにインストールしても良いし、WEBサーバについてはApacheではなくNginx等でも動作可能だ（URLのリライト設定を導入すれば動作する）。
PHPのバージョンは7.4以降であれば動くはずだが、8.1以降を推奨したい。

「Ambient」のインストールは、[GitHub](https://github.com/ka215/ambient) から最新のパッケージリソースを取得するだけと簡単だ。
コマンドラインやターミナルで、インストールしたいパス (例えば、WEBサーバのドキュメントルート直下等)に移動して、次のコマンドを実行すればよい。

```bash
git clone https://github.com/ka215/ambient.git ambient
```

これにより、インストールパスにambientディレクトリが作成され、その中にパッケージ一式がインストールされる。
上記のコマンドを使用してドキュメントルート直下にインストールした場合、ブラウザのURL欄に`localhost/ambient`と入力すれば起動できるはずだ。
もしWEBサーバ上に仮想ホストを指定している場合なら、 `http://<ホスト名>/ambient` のURLへのアクセスでもOkである。

コマンドを使用しない場合は、[Ambient Release Packages](https://github.com/ka215/ambient/releases) の各リリースバージョンからZIPファイルをダウンロードし、目的のインストール場所に解凍することでもインストールできる。
ちなみに、2023/12/2現在の最新バージョンは 1.2.0 だ。

## Ambientのプレイリスト

「Ambient」で再生したいメディアはプレイリストで管理される。
プレイリストはJSON形式で記述し、インストールディレクトリ下の`assets`ディレクトリ内に配置しておく必要がある。「Ambient」のパッケージには空のプレイリストとして`assets/PlayList.json`が同梱しているので、それをひな形にして再生したいメディアデータを追加して行くのが良いだろう。
参考までに、プレイリストのJSONスキーマを公開しているのでそのURLを貼っておく。

[https://ka2.org/schemas/ambient.json](https://ka2.org/schemas/ambient.json)

JSONスキーマだけではわかりづらいので、実際のプレイリストの例も示しておこう。最近デモ用に作成した [メメントモリ・ラメントMV集のプレイリスト](https://gist.github.com/ka215/e1685ad07a3419740bb3661b4f9e5423) をGistに公開しているので、これを参考にした方が理解しやすいかもしれない。

### プレイリストのデータ構造

プレイリストのJSONデータの大まかな構造は下記のようになっている。

```json
{
    "カテゴリー名": [
        {
            "title": "メディアのタイトル",
            "videoid": "YouTubeの動画ID"
        },
        ...
    ],
    "カテゴリー名": [
        (メディアデータ...)
    ],
    "options": {
        "autoplay":   true,
        "seek":       true,
        "dark":       false
    }
}
```

### メディアデータ

最上位のプロパティレイヤーである「カテゴリー名」には属するメディアデータを配列形式で設定することになる。各メディアデータはオブジェクト型となり、有効なプロパティは下記の通りだ。

| プロパティ名 | 値の型 | 値について |
|:------------:|:------:|:---------|
| `title` | `string` | 再生メディアのタイトル名。これが指定されていないか、もしくは空文字の場合、そのメディアは無効なデータとしてAmbient側で再生できない |
| `file` | `string` | 再生メディアのファイルパス。Ambientインストールディレクトリ下の`assets/media`フォルダからの相対パスで指定する。指定パスにメディアファイルがない場合は再生されない |
| `videoid` | `string` | 再生メディアのYouTubeでのVIDEO ID（YouTubeの動画URLのv=～で表されるパラメータ値）。この`videoid`と`file`の両方が定義されたメディアについては`videoid`で指定されたYouTubeの動画再生が優先される |
| `desc` | `string` | 再生メディアの説明やサブタイトルなど。Ambient側のキャプション欄などの出力に利用される |
| `artist` | `string` | 再生メディアのアーティスト名。Ambient側のキャプション欄などの出力に利用される |
| `image` | `string` | 再生メディアのサムネイル画像のファイルパス。Ambientインストールディレクトリ下の`assets/images`フォルダからの相対パスで指定する。そのメディアがYouTube動画の場合はYouTube側からサムネイルを自動で取得するので、ローカルのメディアファイル用のサムネイル指定である。指定パスに画像ファイルがない場合はサムネイルは表示されない |
| `volume` | `integer` | 再生メディアの初期ボリューム。未指定の場合はオプションの`volume`値に準じる。疑似フェーダーのエフェクト処理のベース音量としても使われる |
| `start` | `string` `integer` | メディアの再生開始時間（秒）。オプションにて「シーク再生」が有効になっている場合、これが指定されているメディアは指定の秒数までシークしてから再生が開始される。秒数の整数値、もしくは`H:MM:SS`の形式で指定できる |
| `end` | `string` `integer` | メディアの再生終了時間（秒）。オプションにて「シーク再生」が有効になっている場合、これが指定されているメディアは再生時間が指定の秒数に達した時点で再生が終了する。秒数の整数値、もしくは`H:MM:SS`の形式で指定できる |
| `fadein` | `integer` `float` | フェードインのエフェクト範囲の時間（秒数）。オプションにて「疑似フェーダー」が有効になっている場合、この設定秒数分フェードインが行われる |
| `fadeout` | `integer` `float` | フェードアウトのエフェクト範囲の時間（秒数）。オプションにて「疑似フェーダー」が有効になっている場合、この設定秒数分フェードアウトが行われる |
| `fs` | `boolean` | YouTubeメディアのみ有効で、再生メディアのプレイヤーにフルスクリーンモードを追加するかどうか |
| `cc` | `boolean` | YouTubeメディアのみ有効で、再生メディアのプレイヤーに字幕モードを追加するかどうか |

なお、メディアデータのプロパティは自由に拡張可能で、上記以外にもアルバム名やトラック番号など独自のプロパティを追加可能だ。追加したプロパティについては「Ambient」側でプレースホルダー`%{プロパティ名}%`を使って参照することができる。

### プレイヤーオプション

最上位のプロパティレイヤーにはカテゴリー名の他に「Ambient」自身の初期設定を指定できる（特に初期設定が必要ない場合は省略可能）。その場合は`options`というプロパティにオブジェクト型で指定する。このため、カテゴリー名に「options」というフレーズは使えないので注意が必要だ。

| プロパティ名 | 値の型 | 初期値 | 値について |
|:------------:|:------:|:-----:|:---------|
| `autoplay` | `boolean` | `true` | 自動再生用のフラグ。この値を`true`に設定していてもスマホの場合は省電力モードが有効だと自動再生が始まらないので注意 |
| `random` | `boolean` | `false` | ランダム再生をするかどうか |
| `shuffle` | `boolean` | `false` | シャッフル再生をするかどうか |
| `seek` | `boolean` | `false` | シーク再生を有効化するかどうか |
| `fader` | `boolean` | `false` | 疑似フェーダーを有効化するかどうか |
| `volume` | `integer` | `100` | 再生するメディアの初期音量（0~100）。もしメディアデータ側に個別に音量指定がある場合はそちらが優先される |
| `dark` | `boolean` | `false` | AmbientのUIをダークモードにするかどうか |
| `background` | `string` | `` | AmbientのUIに背景画像を表示する場合の画像ファイルパス。Ambientインストールディレクトリ下の`assets/images`フォルダからの相対パスで指定する |
| `caption` | `string` | `%artist% - %title% - %desc%` | Ambientのキャプション欄に出力するメディアデータの書式フォーマット。`%{プロパティ名}%`のプレースホルダーを使うことでメディアデータに定義したプロパティ値を参照できる。HTMLタグも利用可能 |
| `playlist` | `string` | `%artist% - %title%` | Ambientのプレイリスト一覧（左側ドロワー）に表示されるメディアデータの書式フォーマット。`%{プロパティ名}%`のプレースホルダーを使うことでメディアデータに定義したプロパティ値を参照できる。HTMLタグも利用可能 |

## 疑似フェーダーについて

一般的なメディア・プレイヤーにはない機能として、「Ambient」は「疑似フェーダー」という機能を持っている（バージョン 1.2.0で実装した）。この機能は、再生するメディアごとに音量のフェードイン／フェードアウトのエフェクト処理をかけることができるものだ。
実際のメディア自体のマスターボリュームはそのままに、再生中のメディア・プレイヤー側で音量調整を自動で行うことで「疑似的に」フェードイン／フェードアウトを実現しているため、「疑似フェーダー」という名称にしている。
疑似フェーダーによる音量の変遷を視覚化したものが下記の図だ。

![疑似フェーダーによる音量の変遷](https://ka2.org/assets/2023/11/ambient_fader.png)

フェードインは再生開始時間(`start`が指定されている場合はその時間)から`fadein`プロパティで設定された時間（秒数）分行われ、その間再生音量が0から既定音量（初期ボリューム値）まで増加する。
フェードアウトは再生終了時間（`end`が指定されている場合はその時間、未指定時はメディアの再生終端時間）から`fadeout`プロパティで設定された時間（秒数）分遡った時点から行われ、その間再生音量が既定音量（初期ボリューム値）から0まで減少する。フェードイン後からフェードアウト開始時点まではプレイヤー側での音量変更が可能だが、フェードイン／フェードアウト中は100ミリ秒単位で自動で音量変更が行われているため、結果的にユーザー側からの音量変更が無視されるので注意が必要である。

なお、「疑似フェーダー」はYouTubeの動画およびローカルメディアのどちらにも使用できる。

## まとめ

「Ambient」は出来る限り実装機能を削ぎ落して、直感的かつ速やかにメディアの再生が開始できるメディアプレイヤーというコンセプトで作ったものである。そのため、既存のメディアプレイヤーと較べると決してリッチな機能を持っているわけではない。でも、その名の通り**周囲の環境に溶け込むようにメディアを垂れ流す**ためのプレイヤーとしては最適解なアプリだと思っている。
なお「Ambient」は現行の最新バージョン 1.2.0 で私が欲しかった機能はすべて実装したので、ひとまず開発はひと段落という状態だ。「Ambient」については、自分にとってかなり有益なアプリが作れたのでとても満足している。
しいて言えば、各セッションごとにメモリ上へプレイリストをアップロードする機能を付けると、クラウド版でも自分のプレイリストが再生できるようになって汎用性が高まって良いかもしれないなぁ……とか、Flutterでリファクタリングして完全な自律アプリ版を作ってみようか……とか、潜在的なアイデアは尽きないが。

あとこれは蛇足だが、私の個人ブログに「Ambient」の詳細仕様やカスタマイズのやり方等の記事を掲載しているので、興味があれば読んでみて欲しい。

* [YouTubeと自分のPC内メディアを同時に再生できるメディアプレイヤーを作ってみた](https://ka2.org/ambient-media-player)
* [Ambientメディアプレイヤーのクラウド版デモを公開](https://ka2.org/released_ambient_demo_version)
* [Ambientでメメントモリのラメントプレイヤーを作る](https://ka2.org/ambient_playlist_customize)

最後に総括──

かなり久しぶり（約1年半ぶり）に個人開発をしてみた感想として、やはり**外部バイアスが微塵もない私利私欲な個人開発ってとても愉しい**ものだと、改めて感じた次第。
言うなれば、個人開発ってエンジニアの**DIY**である。そのアプローチは基本的に「愉しい」ので、高いモチベーションが維持できる。仕事だと既定された枠組みの中で活動を制限されることが多いが、DIYは趣味なので際限なく自由に手を動かしてトライ＆エラーが出来るのだ。結果、学びの幅も非常に広くなる。
例えそのDIYが自己満足を満たすだけのものだったとしても、エンジニアの精神衛生上の平安を保つ一助には確実になっているんじゃないだろうか。

- - -

明日の個人開発Advent Calendarは@RyoTNさんの「ストリートビュー(360度写真)を撮影するのを少し楽にするツールを作った」です。よろしくお願いします！

▼個人開発Advent Calendar2023

https://qiita.com/advent-calendar/2023/personal-developement