PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

就業型インターン参加レポート: ライブ配信基盤の裏側に迫る一ヶ月

こんにちは!

プラットフォーム本部 技術基盤部 配信基盤グループ インターン生の浦山です。今回は、1ヶ月の就業型インターンの参加レポートをお届けします。

私が配属されたのは、動画配信サービスを支える基盤を開発・運用しているSTREAKSチームです。 1ヶ月の間、ライブ配信を支える機能開発に、フロントエンドからインフラまで携わることができました!

普段Webアプリケーションを触っているだけでは出会わない「動画が視聴者の手元に届くまで」の未知の世界に、足を踏み入れた1ヶ月でした。 今回はその中身をできるだけ具体的に書いてみます。

インターンについて

期間・所属

  • 期間:2026/8/3〜2026/9/4(約1ヶ月)
  • 稼働形態:週5日 / フルタイム
  • 配属:プラットフォーム本部 技術基盤部 配信基盤グループ

参加のきっかけ

一言でいえば、本番で動いている動画配信のインフラ構成を、自分の目で見て設計に関わってみたかったからです。

もともと私は、エンジニアリングの中でも設計(構造)を考えている時間がいちばん楽しいと感じるタイプです。トレードオフを比べたり、どこにレバレッジを効かせるかを考えたりするのが好きで、将来的にはプロダクト・組織・事業の構造まで設計できるエンジニアになれればなぁ、なんて思っていたりします笑

一方で当時の自分は、コードレベルの設計はある程度考えられても、クラウドインフラまで含めた設計をレビューしてもらった経験がほとんどない状態でした。そこで興味を持ったのが、動画配信のインフラ構成です。大量の映像を、遅延を抑えたまま途切れさせずに視聴者へ届ける。その要求が実際のクラウド構成としてどう組まれているのかが気になっていました。

今回の目標

インターンの開始時に、目標をひとつ立てました。

issueだけ与えられた状態で、インフラまで含めた設計を自分で組めるようにする。

「実装できるようになる」ではなく「設計を自分で組めるようになる」に置いたのが自分にとってのポイントでした。そのうえで足りていないのは手を動かす量ではなく、全体像を掴む手順と、自分の外にある知識を取りに行く速さだと考え、意識する行動を3つ決めました。

  1. コードを書く前に、整理のためにノートへ書き出す
  2. 5分本気で悩んで答えのとっかかりが掴めないなら、すぐ聞く
  3. 知識をアウトプットする機会を自分で創出する

Day1:ライブ配信の仕組みを教わる

タスクに入る前に、初日にメンターの方からライブ配信がどうやって視聴者の手元に届いているのかを一通り説明してもらう時間が1時間ほどありました。私のノートに書き込みながら構造を説明していただいたおかげで、ライブ配信の解像度が一気に上がりました!

以下は、そのとき教わった内容を自分なりに描き直したものです。

ライブ配信はどうやって視聴者に届いているのか

ライブ配信構成図

全体は大きく3つのブロックに分かれます。現地(Broadcast Side)/ OVP (Online Video Platform) /視聴者(Client Side) です。目的を一言でいえば、現地の映像をプレイヤーが読める形に変えて配ること。この一文が、途中に出てくる装置すべての存在理由になっています。

1. Broadcast Side:現地から圧縮して送る

出発点はカメラです。カメラが出力する映像の生データは非圧縮なので、そのままネットワークに流せる大きさではありません。そこで現地に置かれたコントリビューションエンコーダが映像を圧縮し、TSストリームの形でクラウドへ送ります。

ここで言う「コントリビューション」は、視聴者に配るための圧縮ではなく、クラウドまで運ぶための圧縮です。この後クラウド側でもう一度エンコードし直されるので、ここでは品質を落としすぎないことが優先されます。

2. OVP (Online Video Platform):配信できる形に加工する

受け取った映像に対して、クラウド側では3つの装置が順番に仕事をします。

エンコーダ(トランスコード) は、届いた1本の映像を 1080p / 720p / 360p といった複数の画質に変換します。図のとおり、出力は「映像1080p + 音声」のように画質と音声がセットになっています。視聴者の回線状況はそれぞれ違うので、あらかじめ何段階かの画質を用意しておき、プレイヤーが自分に合うものを選べるようにしておくわけです。

パッケージャ は、複数画質がまとまった1本のストリームとして受け取り、HLS / DASH / MSS といった配信形式への変換や、DRMの暗号化等を行います。届け先の環境によって求められるフォーマットもDRMも違うので、ここで動的に組み立ています。

配信オリジン は、パッケージャが変換したものを HLS / DASH / MSS それぞれのエンドポイントとして公開し、CDNからのリクエストに応える配信元です。

CDN は、そのエンドポイントの手前に立ってキャッシュを配る役割です。視聴者からのリクエストを直接オリジンに通さず、CDNが持っているものはCDNが返します。

3. Client Side:プレイヤーが小さな動画ファイルをこまめに取りに来る

最後にプレイヤーです。図のピンクの矢印のとおり、起点はプレイヤーからのリクエストで、CDNがキャッシュを返す(なければオリジンに取りに行く)という流れになります。

ここはかなり意外でした。 これまで私は、プレイヤーが映像のストリームを掴み続けている(一度繋いだら、その上を映像が流れてくる)というイメージを持っていたのですが、実際は小さな動画ファイルをこまめに取得しているだけでした。数秒ぶんの映像が入ったファイルを取得し、再生している間に次を取得する。ライブ映像が流れ続けているように見えているものの正体は、大量の細かいリクエストの積み重ねです。

そして小さな静的ファイルであれば、Webでおなじみの仕組みがそのまま効きます。同じ瞬間に何万人が見ていても、同じファイルを求めている限りオリジンに届くリクエストはごく一部で済む。CDNが成立するのは、配信するものをこの形に加工してあるからでした。

業務内容

1ヶ月のあいだ、2件の機能開発を中心に担当しました。

  1. SSAI設定画面へのスレート動画アップロード機能の追加 広告挿入時の代替映像(スレート)を、運用者が管理画面からアップロードして使えるようにする改修です。本記事のメインとして後述します。
  2. ライブフィードの出力先を2つ以上選択できるようにする エンコーダの1つの出力から2つの出力先へ同時に送出できるようにし、冗長構成(たすき掛け)を組めるようにする改修です。データ構造の変更を伴いました。

このほか、設定項目の説明の修正といった細かな改善も担当しました。

MISSION1: スレート動画をアップロード可能にせよ

最初に担当したのが、SSAI設定画面にスレート動画をアップロードできるようにする機能です。

SSAI(Server-Side Ad Insertion/サーバサイド広告挿入) は、広告を配信サーバ側で本編に繋いでしまう方式です。プレイヤー側で本編を止めて広告を取得してから再生する CSAI と違い、サーバ側で本編と広告を1本のストリームとして配信するため、プレイヤーからは広告も「ただの続きの動画」に見えます。広告ブロッカーに遮断されにくく、切り替わりで再生が途切れることもありません。

問題は、広告枠が必ず埋まるとは限らないことです。 広告サーバから広告が返ってこない、返ってきた広告の尺が枠に足りないといったことは普通に起こります。埋まらなかった時間に何も流さなければ、視聴者の画面はそのまま黒くなります。その穴をあらかじめ用意した映像で埋めるのがスレートです。

スレート動画とは

私が担当したのは、このスレートを運用者が管理画面からアップロードして使えるようにする機能です。これまでは、スレートとして流したい動画のURLを運用者がコピーして貼り付ける必要があり、やや使いにくい機能でした。

課題はアップロードした動画を広告挿入サーバから読めるようにすること

やること自体は「ファイルを保存して、そのURLを設定に持たせる」だけに見えます。

ところがアップロード先のストレージはprivateなので、このままでは広告挿入サーバがスレート動画を読めず、余った広告枠を埋められません。そこで、ストレージの手前にプロキシサーバを挟みました。

従来:   運用者が入力したURL                    → ◯ 読める
そのまま: アップロード → ストレージ(private)のURL → ✗ 読めない
今回:   アップロード → ストレージ → プロキシURL   → ◯ 読める

従来のURL直貼りも引き続き使えるので、入力されたURLはそのまま使い、アップロードされたものだけプロキシ経由のURLに変換する切り分けを入れています。

MISSION2: 1本の映像を、2つの出力先へたすき掛けで届けよ

2つめは、ライブフィードの出力先を2つ以上選べるようにする改修です。ライブフィードは、クラウドのエンコーダで作った映像を、RTMPやSRTといったプロトコルで別のサーバへ送り出す機能です。ここでは送り先を、お客様側で映像を受け取る配信オリジンサーバとして話を進めます。

もう一段先の冗長構成にするために

前提として、クラウドのエンコーダは同じ映像を2系統(パイプライン0 / パイプライン1)で並行処理しています。改修前は、この2系統と出力先が1対1で結ばれていました。パイプライン0は出力先Aへ、パイプライン1は出力先Bへ。

これはこれで冗長化されています。ただし、それぞれの出力先に映像を届けているパイプラインは1つだけです。パイプライン0が止まってしまえば、出力先Aへの映像はそのまま途絶えてしまいます。 系全体としては生きていても、出力先という単位で見ると一重のままでした。

そこで、1つのパイプラインから出力先AとBの両方へ配信し、片方のパイプラインが落ちても出力に影響が出ないようにします。

あとは、AとBの受け口をどう埋めるかです。受け取る側のサーバには受け口が2つ用意されていて、普段使う main と、mainが途切れたときに切り替わる sub に分かれています。Aのmainをパイプライン0で埋めるなら、Aのsubはパイプライン1で埋める。Bはその逆にする。この組み方がたすき掛けです。

  • パイプライン0 → 配信オリジンサーバAの main + 配信オリジンサーバBの sub
  • パイプライン1 → 配信オリジンサーバBの main + 配信オリジンサーバAの sub

たすきがけ構成

要求を一文にすれば、エンコーダの1つの出力から2つの出力先へ同時に送れるようにする(ライブチャンネル全体で最大4出力)。シンプルに見えて、難しかったことが2つありました。

難所1:URLが、設定を保存する時点ではまだ存在しない

出力先が2つから4つに増えるのは、素直に考えればフロントエンドの入力欄が4つに増えるだけの話です。ここまで見てきたような外部のサーバが相手なら、実際それで済みます。

違ったのが、出力先にSTREAKSのライブインジェスト(配信ハブサーバ)を選んだ場合です。ライブインジェストの受け口URLは、ライブインジェストが起動して初めて確定します。つまり、設定を保存する時点では、入力してもらうべきURLがまだ存在しません。そこで「ライブインジェストAのmain」といった役割だけを設定として持たせ、実際のURLはチャンネル作成時に動的に解決する形にしました。画面上は同じ機能に見えるのに、出力先の種類によって、設定として保存するものの粒度そのものが変わるというのが厄介でした。

難所2:エンコーダの仕様とデータ構造のギャップ

もう1つは、エンコーダの仕様と既存のデータ構造のギャップです。

エンコーダは、出力先ごとに並べたURLの並び順でパイプラインを識別します。改修前のSTREAKS側もそれに合わせ、URLをただの配列で持ち「配列の何番目か=パイプライン番号」という暗黙のルールで運用していました。出力先が1つなら、これで足ります。

その前提が崩れたのが今回のたすき掛けです。本来は「どの出力先の、どのパイプラインか」という2軸なのに、1次元の配列では片方しか表現できません。並び順に意味を持たせている限り、2つ目の出力先は書きようがありませんでした。

そこで、並び順に意味を持たせるのをやめ、URLごとにどのパイプラインの(pipeline)、どの出力先への(slot)出力なのかを明示的に持たせました。

改修前: [ {url, stream_name}, {url, stream_name} ]
        ↑ 配列の並び順がパイプライン番号を兼ねている

改修後: [ {url, stream_name, pipeline: 0, slot: 0},   ← 出力先Aのmain
         {url, stream_name, pipeline: 1, slot: 0},   ← 出力先Aのsub
         {url, stream_name, pipeline: 1, slot: 1},   ← 出力先Bのmain
         {url, stream_name, pipeline: 0, slot: 1} ]  ← 出力先Bのsub

エンコーダへ渡すときは、slotごとに出力先を組み立て、その中をpipeline順に並べ直します。意味は自分たちのデータ構造で持ち、相手の仕様である「並び順」へ翻訳するのは境界の一箇所だけ、という切り分けです。

目標の振り返り

冒頭に立てた目標の振り返りです。

issueだけ与えられた状態で、インフラまで含めた設計を自分で組めるようにする。

issueに書いてあるのは「やること」です。その下にある「満たすべき条件」を先に確かめてから設計を決める、というのが1ヶ月で身についたことでした。

MISSION2の依頼は「出力先を2つ以上選べるようにする」でしたが、満たすべき条件は「片方のパイプラインが落ちても、出力先が映像を受け取り続けられること」でした。文面どおりに入力欄を4つに増やすだけでは、この条件は満たせません。MISSION1も同じで、「動画をアップロードできるようにする」だけでは足りず、その先に「広告挿入サーバがその動画を読めること」という条件がありました。issueの背景まで想像できたからこそ、プロキシを挟む設計を選べたのだと思います。

決めた3つの行動はどうだったか

1. コードを書く前に、整理のためにノートへ書き出す

一番効果があったのはこれでした。先に構造を書き出しておかないと、目の前の実装に思考が閉じて全体感を見失う、というのが元の意図でしたが、実際には書き出したものがそのまま設計の土台になるという使い方に落ち着きました。この記事に載せたライブ配信の構成図も、たすき掛けの図も、元をたどればノートに描いたものです。

2. 5分本気で悩んで答えのとっかかりが掴めないなら、すぐ聞く

このルールを決めておいて正解でした。詰まる原因の多くは、調べても出てこないPLAY独自の前提知識だったからです。MISSION2の難所1がまさにそれで、「ライブインジェストの受け口URLは、起動して初めて確定する」という前提は、一人でコードを追っていても確信が持てるものではありませんでした。5分で線を引いたことで、悩む対象を「自分で解けるもの」に絞れたのが大きかったと思います。

3. 知識をアウトプットする機会を自分で創出する

勉強会での発表、AIレビュー用に自作したClaude Code Skillのチームへの共有、そしてこの記事。人に説明できる形にしようとすると、自分が「なんとなく分かったつもり」でいた場所がはっきり浮かび上がってきます。特にライブ配信の仕組みは、図に描き起こそうとして初めて、自分が矢印の向きすら曖昧なまま理解したつもりでいたことに気づきました。

最後に

長期の就業型インターンは初めてだったので、自分の力が通用するのかという不安もありました。しかもライブ配信の技術は全く知らない状態からのスタートで、動画配信特有のインフラ構成も、初めて聞く言葉と仕組みばかりでした。それでも1ヶ月走り抜けられたのは、最初に決めた3つの行動を最後まで続けられたことと、何より社員の皆さんに手厚くサポートしていただいたおかげです。

振り返ると、チームの雰囲気が本当に温かかったです! 自前のAIレビュー用のClaudeCode Skillを共有したらその場でチームに採用され、勉強会では発表の機会までいただき、ランチもほぼ毎日連れて行っていただきました。インターン生が持ち込んだものがそのまま採用される距離感が、本当に嬉しかったです。

インターン開始前からサポートしてくださった人事の方、マネージャーや開発チームの皆さん、メンターさん、心の底から感謝しております!!! ありがとうございました!