FASTチャンネルはコマーシャルブレークで収益を上げます。このビジネスモデル全体は、たった一つの仮定に基づいています。チャンネルが「広告に切り替え」と指示したとき、その指示を聞いているすべてのダウンストリームシステム(広告サーバー、SSAIスプライサー、スマートTVプレーヤー)が、全く同じタイミングで、全く同じ期間でそれを聞くということです。SCTE-35は、そのメッセージを伝える標準プロトコルです。これを静かに間違えると(そして、静かに失敗するのがデフォルトのモードです)、広告は表示されないか、間違ったフレームで表示されるか、または間違った長さで表示されます。視聴者にはスレートが表示されます。収益は全く発生しません。
これは、mStudioのFASTチャンネルが、オペレーターフレンドリーな入力から標準に準拠したSCTE-35キューをどのように発行するのか、そしてなぜ1つではなく1ブレークあたり3つのキューとして構築したのか、という技術的な話です。
概要
| 側面 | 詳細 |
|---|---|
| ドメイン | AWS MediaLive FASTチャンネル向けのSCTE-35広告ブレークシグナリング |
| オペレーターワークフロー | プログラムごとの広告ブレークエディター — 秒単位の位置、秒単位の期間 |
| 1ブレークあたりに発行されるキュー | 3つ — 広告開始 TimeSignal、SpliceInsert、広告終了 TimeSignal |
| 期間制約 | 10〜120秒、データレイヤーで検証済み |
| ダウンストリームコンシューマー | プレーヤー、広告サーバー、およびAWS MediaTailor(設計済みだが未接続)などが例です。 |
| ステータス | SCTE-35キュー発行は本番稼働中。SSAI統合は設計中。 |
ビジネス課題
FASTチャンネルのすべてのコマーシャルブレークは、収益イベントです。チャンネルは「ここに広告が挿入され、30秒間続きます。準備してください」というマーカーを発行します。ダウンストリームシステム(Google Ad Manager、AWS MediaTailor、地域広告サーバー、スマートTVプレーヤーSDKなど)は、このマーカーを読み取り、何を挿入するかを決定します。
マーカーが正しければ、広告ブレークはスムーズに実行され、インプレッションはカウントされ、オペレーターは支払いを受けます。マーカーが誤っている場合(期間、形式、フォーマットが誤っている場合)、広告システムはそれを拒否するか(広告が配信されない)、または誤って受け入れます(長さが誤った広告、または次のプログラムにオーバーランする広告)。どちらの結果も実際のお金を失い、どちらも静かに失敗します。チャンネルはストリーミングを続けます。視聴者にはスレートまたは不自然なカットが表示されます。収益化パイプラインは、低い数字を生成するだけで、追跡すべきエラーメッセージは生成されません。
エンジニアリングの目標は「広告をサポートする」ことではありません。それは、「オペレーターが定義するすべての広告ブレークが、オペレーターが選択した正確なフレームで、常に標準に準拠したSCTE-35シグナルとしてすべてのダウンストリームコンシューマーに到達すること」です。
SCTE-35の実際の姿(90秒バージョン)
SCTE-35は、ビデオストリームにキューメッセージを挿入するための標準です。キューは広告コンテンツを伝送するのではなく、シグナルを伝送します。「広告ブレークはここから始まる」、「広告ブレークはここで終わる」、「このセグメントはN秒間」といったものです。ダウンストリームシステムはこれらのシグナルを読み取り、それに基づいて動作します。SSAIレイヤーは実際の広告クリエイティブをHLSマニフェストにスプライスし、スマートTVプレーヤーはオーバーレイをトリガーし、広告サーバーはスロットの機会をログに記録します。
私たちのユースケースで重要な2つのキュー形式があります。
- SpliceInsert — 元祖SCTE-35キュー。SpliceEventId、秒単位の期間、および「アウトオブネットワーク」フラグを伝送します。ほとんどの従来の広告サーバーがこれを読み取ります。
- TimeSignal — より新しく、より表現力豊かなキュー。SegmentationDescriptorとSegmentationTypeId(52 = Provider Ad Start、53 = Provider Ad End)、および90,000ティック単位のSegmentationDurationを伝送します。SSAIシステムや最新のプレーヤーは、開始と終了をきれいにペアリングできるため、この形式を好みます。
どちらの形式も正しいSCTE-35です。異なるダウンストリームコンシューマーは異なる方を好みます。一方の形式だけを発行すると、収益機会を逃すことになります。
これが重要な理由。素朴な実装では、1つの広告ブレークにつき1つのSpliceInsertを発行して完了とします。その場合、広告ブレークはレガシープレーヤーでは機能しますが、SSAIでは静かに失敗します。広告インベントリの半分が収益化され、半分はそうなりません。収益が低い数字で現れるまで、どちらがどちらかを知ることはできません。
素朴なアプローチが失敗する理由
ショートカットは、どれもほとんど機能するため魅力的です。
- 「エンコード時に元のビデオに広告を挿入する。」柔軟性が全くありません。オペレーターは配置をA/Bテストしたり、デプロイ後に広告を変更したり、地域別またはオーディエンスターゲット広告を配信したりできません。FASTチャンネルが存在する主な理由は、同じコンテンツを複数の方法で収益化することです。エンコード時に広告を焼き付けることは、その可能性を奪います。
- 「クライアントサイド広告挿入を利用する。」広告ブロックが簡単になります。デバイスごとに異なるコードパスが必要になります。サーバーサイドのパーソナライゼーションはできません。そして、SSAIを完全にバイパスするため、CPMが低くなります。
- 「1ブレークあたり1つのSpliceInsertを発行するだけにする。」最も一般的な本番環境での間違いです。一部のプレーヤーでは機能しますが、開始/終了のペアリングのためにTimeSignalキューを必要とするSSAIシステムでは静かに無視されます。部分的な収益化しかできず、調査すべきエラーも発生しません。
- 「仕様に記載されているからといって、すべてを90 kHzティックで表現する。」仕様はそれほど単純ではありません。SpliceInsert.Durationは秒単位です。SegmentationDurationは90 kHzティック単位です。これらを混同すると、スキーマ検証は通過するものの、プレーヤー側で形式が誤った期間として失敗するキューが生成され、出荷されてしまいます。エンコーダーはそれを検出せず、プレーヤーは単にブレークをスキップします。
- 「MediaLiveに確認を許可する。」MediaLiveはそれを行いません。MediaLiveは、重複するブレーク、ゼロ期間のブレーク、不可能なセグメンテーション期間を苦情なしに受け入れます。確認はMediaLiveがアクションを見る前に存在する必要があります。
私たちが持っていたレバーは、オペレーターUI(広告ブレークが単なる{position, duration}ペアである場所)と、すべてのキューが完璧でなければならないMediaLiveスケジュールとの間の境界でした。
私たちのソリューション
広告ブレークを最初から最後までファーストクラスのデータオブジェクトとして扱います。オペレーターは可能な限りシンプルな用語でそれを定義します。バックエンドはスキーマレイヤーで一度検証します。Lambdaは、デプロイ時にそれを3つのキューのスタックに変換し、各キューはそれぞれの仕様が要求するタイムベースで表現されます。スタック内の他の何もSCTE-35について知る必要はありません。変換は、他のすべてのスケジュールアクションを所有する同じLambda内の1つの関数で行われます。
図1 · エンドツーエンド広告マーカーパイプライン

アーキテクチャ
- フロントエンド広告ブレークエディター — オペレーターは、プログラム開始からのオフセット位置(秒)と期間(秒)を指定することで、プログラムごとに広告ブレークを追加します。バンパースロットはデータモデルの一部であり、プレイアウトレイヤーで使用する準備ができています。
- AdMarker コレクション (MongoDB) — 広告ブレークを持つプログラムごとに1つのドキュメントがあり、ビデオを参照しています。adBreaks[]配列は{position, duration}を保存し、Mongooseは保存時に10 ≤ duration ≤ 120を強制します。バンパーはダウンストリームのペアリングのためにadBreakId参照を保持します。
- NestJSバックエンド (schedule.service.ts) — デプロイ時に、各スケジュールをそのAdMarkerに結合し、フィールドをLambdaのコントラクトに合わせてリネームします(position → offsetSeconds、duration → durationSeconds)。1回のバルクフェッチで、N+1問題は発生しません。
- Lambdaオーケストレーター (fastChannel-lambda-fun/index.js) — SCTE-35変換を所有します。各広告ブレークについて、入力スイッチとウォーターマークを伝送する同じBatchUpdateScheduleCommandに3つのキューのスタックを発行します。アクション名はプログラムごとにバージョン管理されているため、部分的なデプロイ後にオーファン掃討がそれらをペアリングできます。
- AWS MediaLive — アクションを受信し、オペレーターが選択した秒数でHLSマニフェストに#EXT-SCTE35マーカーを発行します。
主要な技術的決定
1. 1ブレークあたり3つのキュー、1つではない
各広告ブレークは、プログラム内の同じ論理的瞬間に3つの異なるアクションを発行します。
図2 · 単一広告ブレークのライフサイクル

開始と終了のTimeSignalキューは同じSegmentationUpidを共有するため、ダウンストリームシステムはそれらを確定的にペアリングできます。SpliceInsertは、プレーヤーレベルでの重複排除のために一意のSpliceEventIdを伝送します。異なるコンシューマーが異なるキューを読み取ります。3つすべてを発行することで、今日私たちが関心を持つすべての契約、そして将来的に考えられるすべての契約をカバーできます。
よくある本番環境での間違い。1つのSpliceInsertを発行し、残りのエコシステムがそれを解決すると仮定すること。SSAIシステムは、一致するTimeSignalキューがないブレークを静かにドロップし、従来の広告サーバーはTimeSignalのみのシグナルを無視します。3つすべてがなければ、すべての広告ブレークはダウンストリームスタックの一部によって収益化され、残りの部分によって見逃されます。そして、それはログではなく収益レポートから判明します。
2. 2つのタイムベース、1つの関数で調整
SpliceInsert.Durationは秒単位です。TimeSignalディスクリプター内のSegmentationDurationは90,000ティック単位です。論理的な期間は同じですが、エンコーディングは2つあります。そして、最も一般的なSCTE-35のバグはこれらを混同することです。
図3 · 時間変換ロジック

変換はbuildProgramActionsにのみ存在します。キューの期間が間違っている場合に参照すべき正準な場所が1つあり、仕様が変更された場合に変更すべき場所が1つあります。
意図的に行ったトレードオフ。AdMarkerドキュメントに両方のタイムベースを保存し、Lambdaにそれらをコピーさせることもできましたが、意図的にそうしませんでした。秒単位の値のみを保存するということは、オペレーターが設定する数字は1つだけであり、検証する数字も1つだけであり、間違っている可能性がある数字も1つだけであることを意味します。90 kHzの値は派生されるものであり、保存されることはありません。派生データはずれを生じません。
3. 位置は相対的、発行時刻は絶対的
オペレーターは「プログラム開始から720秒の広告ブレーク」と言います。Lambdaは、実際のUTC発行時刻をprogramStartTime + offsetSeconds × 1000として計算し、そのアクションにスタンプします。他のすべてのスケジュールアクション(入力切り替え、ウォーターマークオン、プログラム終了)は、同じオフセットからタイムスタンプへのパイプラインを使用します。広告キューは、タイムラインの所有者がどのクロックであるかについて曖昧さなく、他のすべてと同じフレーム境界に着地します。
4. データレイヤーでの検証、ワイヤーではない
AdMarkerスキーマは、Mongooseの保存時にduration ∈ [10, 120]秒を強制します。範囲外のブレークはLambdaに到達しません。プログラム終了を越えてしまう可能性のある広告ブレークは、拒否されるのではなくビルド時にクリップされます。オペレーターは単一の誤ったブレークのために作業を失うことはありません。発生しうるバグよりも、発生しえないバグの種類の方が興味深いものです。
5. キュー発行とSSAIの分離
今日出荷しているのはシグナリングレイヤーです。AWS MediaTailorを介したサーバーサイド広告挿入(これらのキューを消費して実際の広告クリエイティブをHLSマニフェストにスプライスするもの)は完全に設計されていますが、まだ接続されていません。意図的な順序は、SSAIを有効にする前に、キューレイヤーをまず正しく、本番環境で、実際のチャンネルで使用されるようにすることです。統合が開始されるときには、キューはすでにそこにある状態です。ダウンストリームシステムは初日からクリーンな契約を受け取ることができます。
この順序が重要な理由。キューが間違っている場合、SSAIのデバッグは非常に困難です。なぜなら、すべての失敗がキューが原因であるにもかかわらず、SSAIの失敗に見えるからです。まずキューレイヤーを単独で出荷し、実際のプレーヤーや広告サーバーに対して検証することで、統合に関する混乱の全体的なカテゴリが発生する前に排除しました。
結果
- オペレーターが定義するすべての広告ブレークは、チャンネルのHLSマニフェストにおいて、適切な秒数で、適切なタイムベースで、標準に準拠した3つのキューからなるSCTE-35スタックを発行します。
- 変換レイヤーは1つの関数に集約されています。仕様変更や新しいダウンストリームコンシューマーへの対応は、コードベース全体を探索するのではなく、1つのファイルを変更するだけで済みます。
- 期間の検証は、キューがエンコーダーに到達する前にデータレイヤーで実行されます。オペレーターの誤りは、放送時に静かに発生するのではなく、UIに表示されます — 形式の誤ったキューはMediaLiveに到達できません。
- キュー生成は決定的です。同じオペレーター入力は同じSCTE-35アクションを生成するため、同じブレークはすべてのデプロイとすべてのチャンネルで同じように動作します。
- シグナリングレイヤーは出荷され、稼働中です。SSAIコンシューマー(MediaTailor)は設計されており、接続する準備ができています — 接続されるときには、既存のすべてのチャンネルのキューがすでにそこで待機しています。
技術スタック: AWS MediaLive · AWS Lambda · NestJS · MongoDB · TypeScript · Node 18 · SCTE-35 (Scte35TimeSignalSettings, Scte35SpliceInsertSettings) · AWS SDK v3

