IIS Smooth Streaming

コンテキスト データによる Silverlight ビデオ エクスペリエンスの向上

Jit Ghosh

コード サンプルのダウンロード

高解像度デジタル ビデオを Web ベースで配信する際、問題のない視聴エクスペリエンスを実現するため必要なことは主に 2 つです。1 つはビデオを配信する側の要件で、ネットワーク経由での高ビット レートのビデオ配信をサポートすることです。もう 1 つはクライアント コンピューター側の要件で、最高解像度でビデオをデコードする処理能力を絶えず維持できるようサポートすることです。

しかし、現実には、ネットワークに接続している家庭用コンピューターのネットワーク帯域幅は時間と共に大きく変化することがあり、世界中には、多くの消費者にとって高帯域幅ネットワークが非常に高価であったり、利用できない地域もあります。また、クライアント コンピューターの処理能力は、その時々の CPU 負荷によっても変化します。したがって、プレーヤーで次のビデオ フレームを表示できるだけのデータがバッファリングされるのを待機する間や、フレームをデコードするため CPU サイクルを待機する間にビデオが停止 (フリーズ) すると、消費者の視聴エクスペリエンスは瞬く間に低下します。

アダプティブ ストリーミングは、コンテンツのスムーズな配信とデコードという課題に対処するビデオ配信手法です。アダプティブ ストリーミングでは、ビデオ コンテンツがさまざまなビット レートでエンコードされ、専用のストリーミング サーバー経由で配信されます。アダプティブ ストリーミング プレーヤーは、クライアント コンピューターでのさまざまなリソース使用率の指標を絶えず監視し、その情報を使用して、その時点のリソース制約の中でクライアントが最も効果的にデコードおよび表示できる適切なビット レートを計算します。

プレーヤーからその時点に適したビット レートでエンコードされたビデオ チャンクの配信が求められると、ストリーミング サーバーはそのビット レートでエンコードされたビデオ ソースのコンテンツを返します。このため、リソース状態が低下しても、状態が改善するか、さらに悪化して異なるビット レートでの配信を求めるまで、プレーヤーは全体の解像度をわずかに下げるだけで、明らかな中断なくビデオを表示し続けることができます。

このようにプレーヤーとサーバーが継続的に連携するには、ストリーミング サーバーにも、プレーヤーを実装するクライアント ランタイムにも、特別な処理ロジックの実装が必要です。インターネット インフォメーション サービスのスムーズ ストリーミング機能 (IIS Smooth Streaming) は、マイクロソフトが提供する HTTP 経由のアダプティブ ストリーミングのサーバー側実装です。クライアント側の実装は、Microsoft Silverlight の拡張機能として提供されます。

IIS Smooth Streaming Player Development Kit (PDK) は、IIS Smooth Streaming 経由でストリーミングされるコンテンツをアプリケーションから利用できるようにする Silverlight ライブラリです。また、スムーズ ストリーミング ロジックのさまざまな側面にプログラムからアクセスできる、豊富な機能を備えた API も提供されます。

この記事ではスムーズ ストリーミングの基本を解説し、IIS Smooth Streaming Player Development Kit を使用して、ビデオに関連するリッチ ユーザー エクスペリエンスを構築する方法について説明します。具体的には、Player Development Kit を使用してストリームを利用する方法について、ストリームとトラックに関するクライアント側のデータ モデルを綿密に検証しながら説明します。次に、クローズド キャプションやアニメーションなどの追加のデータ ストリームを利用する方法、および外部のデータ ストリームを既存のプレゼンテーションにマージする方法について説明します。また、プレゼンテーション内の広告などの外部クリップのスケジュールを設定する方法、さまざまな再生レートを処理する方法、および堅牢な編集シナリオに役立つ複合マニフェストを構築する方法についても見ていきます。

スムーズ ストリーミングのしくみ

Expression Encoder 3.0 に付属するプロファイルの 1 つを使用すると、スムーズ ストリーミング用にビデオをエンコードできます。1 つのソース ビデオ ファイルにつき複数のファイルが指定したフォルダーに作成されます。図 1 は、FighterPilot.wmv というソース ビデオに対して作成される複数のファイルを示しています。


図 1 Expression Encoder によってスムーズ ストリーミング用に生成されたファイル

.ismv という拡張子の各ファイルには、特定のビット レートでエンコードされたビデオが含まれています。たとえば、FighterPilot_331.ismv には 331 Kbps のビット レートでエンコードされたビデオが含まれていて、FighterPilot_2056.ismv には 2 Mbps のビット レートでエンコードされたビデオが含まれています。

ビデオ コンテンツは、ビット レートごとに 2 秒間のフラグメントに分割され、.ismv ファイルには、これらのフラグメントが Protected Interoperable File Format (PIFF) というファイル形式で保存されます。また、.isma という拡張子の同様のファイルに、エンコードされる追加のオーディオ トラック (プレゼンテーションがオーディオのみの場合は単なるオーディオ) も作成できます。

スムーズ ストリーミング環境の準備

この記事で説明する例を試すには、開発コンピューターにスムーズ ストリーミング環境を準備する必要があります。

サーバー側の構成要素は簡単で、Microsoft Web Platform Installer を使用して、iis.net/media (英語) から IIS 7.0 向けの IIS Media Services 3.0 をダウンロードしてインストールするだけです。

スムーズ ストリーミング用ビデオを準備するには、Microsoft Expression Encoder 3.0 が必要です。Expression Encoder 3.0 の無償評価版もありますが、このバージョンではスムーズ ストリーミングがサポートされません。ビデオを独自に作成するには、ライセンスを受けた Expression Encoder が必要です。

環境の準備に関する詳細については、learn.iis.net/page.aspx/558/smooth-streaming-for-iis-70---getting-started (英語) を参照してください。

FighterPilot.ism ファイルはサーバー マニフェストです。このマニフェストは、同期マルチメディア統合言語 (SMIL) 形式で構成され、.ismv ファイルと .isma ファイルに対する品質レベルとビット レートの対応関係が記述されています。サーバーでは、サーバー マニフェストのこの対応関係を使用して、適切なディスク ファイルにアクセスし、適切なビット レートでエンコードされたコンテンツの次のフラグメントを作成してから、クライアント側の要求に応答します。図 2 に、サーバー マニフェスト ファイルからの抜粋を示します。

図 2 サーバー マニフェストのサンプル

<smil xmlns="http://www.w3.org/2001/SMIL20/Language">
  <head>
    <meta name="clientManifestRelativePath"
      content="FighterPilot.ismc" />
  </head>
  <body>
    <switch>
      <video src="FighterPilot_2962.ismv"
        systemBitrate="2962000">
        <param name="trackID"
          value="2" valuetype="data" />
      </video>
      <video src="FighterPilot_2056.ismv"
        systemBitrate="2056000">
        <param name="trackID"
          value="2" valuetype="data" />
      </video>
      ...
      <audio src="FighterPilot_2962.ismv"
        systemBitrate="64000">
        <param name="trackID"
          value="1" valuetype="data" />
      </audio>
    </switch>
  </body>
</smil>

サーバー マニフェストには、.ismc という拡張子を持つクライアント マニフェスト ファイル (この例では FighterPilot.ismc) との対応関係も記述されています。クライアント マニフェストには、Silverlight クライアントがさまざまなメディア ストリームやデータ ストリーム、およびこれらのストリームに関するメタデータ (画質レベル、利用可能なビット レート、タイミング情報、コーデック初期化データなど) にアクセスするために必要なすべての情報が含まれています。クライアント側のロジックでは、このメタデータを使用して、フラグメントをサンプリングおよびデコードし、その時点のローカル コンピューターの状態に基づいてビット レートの切り替えを要求します。

実行時には、クライアントからサーバーにクライアント マニフェストを要求することからプレゼンテーションが始まります。クライアントがマニフェストを受信すると、利用可能なビット レートをチェックし、その中で最も低いビット レートでコンテンツのフラグメントを開始するよう要求します。サーバーはこれに応答して、要求されたビット レートでエンコードされたデータを (サーバー マニフェストの対応関係を利用して) ディスク ファイルから読み取り、フラグメントを準備して送信します。その結果、クライアントでコンテンツが表示されます。

クライアントでは、リソース監視ロジックによって許容される範囲に従って少しずつ高いビット レートを要求し、最終的にその時点のリソース状態で許容される最も高いビット レートにします。このやり取りは、リソース状態が変化して適切なビット レートが低下したことをクライアントの監視ロジックが検出するまで継続されます。以降のクライアント要求は、新しいビット レートでエンコードされたメディアに対して行われ、サーバーでもこの要求に従って応答します。この処理は、プレゼンテーションが完了するか停止するまで継続されます。

Silverlight によるスムーズ ストリーミング

Silverlight でビデオを再生するのは実に簡単です。基本的なレベルで実際に必要な作業は、MediaElement 型のインスタンスを XAML ファイルに追加し、MediaElement の動作を制御する適切なプロパティを設定して、有効なメディア ソースの URI を MediaElement.Source プロパティに指定するだけです。たとえば、次の XAML では、Silverlight ページを起動すると FighterPilot.wmv ビデオが 640 x 360 ピクセルの大きさで自動再生されます。

<MediaElement AutoPlay="True" 
  Source="https://localhost/Media/FighterPilot.wmv" 
  Width="640" Height="360" />

System.Windows.Controls.MediaElement 型は API も公開し、コードから再生エクスペリエンスの動作を制御でき、再生、一時停止、シークなどの標準コントロールを備えたプレーヤーを構築できます。この手法は、プログレッシブ ダウンロードや HTTP ストリームで配信されるメディアでも適切に機能します。ただし、コンテナー形式とエンコーディングに、Silverlight ランタイム組み込みのサポートを使用している場合に限ります。

では、Silverlight によって既定でサポートされないファイル形式やコーデックを使用する場合はどうでしょう。MediaStreamSource (MSS) 型では拡張メカニズムを使用でき、カスタム パーサーとデコーダーを Silverlight のメディア パイプラインに導入することで、メディア ファイルの解析とデコードの処理を制御できます。このメカニズムを使用するには、System.Windows.Media.MediaStreamSource 抽象型を拡張する具象型を実装してから、MediaElement.SetSource メソッドを使用してそのインスタンスを MediaElement に渡す必要があります。

MSS の実装では、実際のレンダリングを除き、メディアを利用する処理のあらゆる側面を制御しなくてはなりません。たとえば、遠隔地からメディア ストリームを受信する、コンテナーとその関連メタデータを解析する、個別のオーディオ サンプルとビデオ サンプルを採取してレンダリングのために MediaElement に渡す、といった処理を制御する必要があります。

Silverlight にはスムーズ ストリーミングのデコードに必要なロジックが組み込まれていなかったため、スムーズ ストリーミングの最初のバージョン (IIS Media Services 2.0 に付属) には、すべての通信、解析、およびサンプリング ロジックを処理し、コンピューターとネットワークの状態監視機能も実装する MSS のカスタム実装が付属していました。

この方法は、ほとんどの場合のスムーズ ストリーミングに有効でしたが、いくつか欠点もありました。MSS で直接公開される唯一の API は基本的にはブラック ボックスで、何も手を加えないオーディオ サンプルやビデオ サンプルを MSS と MediaElement の間で交換しやすくすることを目的としていました。Silverlight の開発者には、開発時に MSS と直接インターフェイスを取る方法がありません。利用するコンテンツに埋め込みテキスト、アニメーション、別のカメラ アングルなどの追加データが含まれていても、またストリーミング ソリューションが可変再生レートなどでストリームをきめ細かく制御できたとしても、常に、MediaElement が公開する特定の API セットとのインターフェイスに限定されていたため、プログラムからこのような追加データに体系的にアクセスする方法がありませんでした。

スムーズ ストリーミングでは、この追加データへのアクセスが課題となりました。後で説明するように、スムーズ ストリーミングのマニフェストとワイヤ/ファイル形式では、配信できる追加コンテンツとメタデータに関する機能が非常に充実しています (これは MSS を利用する手法ではアクセスできなかった情報です)。また、スムーズ ストリーミング ソリューションをきめ細かく制御およびアクセスできる Silverlight API が必要です。

IIS Smooth Streaming Player Development Kit

では、IIS Smooth Streaming Player Development Kit について説明しましょう。Player Development Kit は、
Microsoft.Web.Media.SmoothStreaming.dll という 1 つのアセンブリから構成されます。このアセンブリの中核となるのは、Microsoft.Web.Media.SmoothStreaming.SmoothStreamingMediaElement (SSME) という型です。コードで SSME を使用する方法は、通常の MediaElement の使用方法とほぼ同じです。

<UserControl x:Class="SSPlayer.Page"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
  xmlns:ss="clr-namespace:Microsoft.Web.Media.SmoothStreaming;assembly=Microsoft.Web.Media.SmoothStreaming">
  <Grid x:Name="LayoutRoot" Background="White">
    <ss:SmoothStreamingMediaElement AutoPlay="True" 
      Width="640" Height="360"
      SmoothStreamingSource="https://localhost/SmoothStreaming/Media/FighterPilot/FighterPilot.ism/manifest"/>
  </Grid>
</UserControl>

SmoothStreamingSource プロパティは、有効なスムーズ ストリーミング プレゼンテーションを SSME に指示しています。一般に、SSME API は MediaElement API のスーパーセットですが、数少ない違いの 1 つがこのプロパティです。SSME は MediaElement と同様に Source プロパティを公開しますが、スムーズ ストリーミングに結び付ける SmoothStreamingSource プロパティも公開します。スムーズ ストリーミングと、従来の MediaElement でサポートされる他の形式のストリーミングを両方使用可能にするプレーヤーを作成する場合、SSME を使用しても問題はありませんが、メディア ソースに結び付ける適切なプロパティを設定するコードを作成する必要があるでしょう。たとえば、次のようなコードを作成します。

private void SetMediaSource(string MediaSourceUri, 
  SmoothStreamingMediaElement ssme) {

  if (MediaSourceUri.Contains(".ism"))
    ssme.SmoothStreamingSource = new Uri(MediaSourceUri); 
  else
    ssme.Source = new Uri(MediaSourceUri); 
}

注意が必要なもう 1 つの大きな違いは、SSME では MediaStreamSource 型を受け取る SetSource のオーバーロードが公開されていないことです。カスタム MSS を使用する必要がある場合は、MediaElement を使用してその処理を実行します。

ストリームとトラック

スムーズ ストリーミングのクライアント マニフェストにはプレゼンテーションに関するさまざまなメタデータが含まれています。プレーヤー アプリケーション内部でプログラムからこのようなメタデータにアクセスできると便利な場合があります。SSME は、明確に定義された API を通じて、ストリームと各ストリーム内のトラックの配置に関連してこのようなメタデータの一部を公開します。

ストリームは、特定の種類 (ビデオ、オーディオ、テキスト、広告など) のトラックに関する全体的なメタデータを表します。また、ストリームは、基になる種類が同じである複数のトラックのコンテナーとして機能します。クライアント マニフェスト (図 3 参照) では、StreamIndex エントリがそれぞれストリームを表します。StreamIndex エントリが複数あることからわかるように、プレゼンテーションには複数のストリームを含めることができます。また、同じ種類の複数のストリームを含めることもできます。その場合、ストリーム名を使用して、同じ種類の複数のストリームを区別できます。

図 3 クライアント マニフェストからの抜粋

<SmoothStreamingMedia MajorVersion="2" MinorVersion="0" 
  Duration="1456860000">
  <StreamIndex Type="video" Chunks="73" QualityLevels="8" 
    MaxWidth="1280" MaxHeight="720" 
    DisplayWidth="1280" DisplayHeight="720"
    Url="QualityLevels({bitrate})/Fragments(video={start time})">
    <QualityLevel Index="0" Bitrate="2962000" FourCC="WVC1" 
      MaxWidth="1280" MaxHeight="720"
      CodecPrivateData="250000010FD37E27F1678A27F859E80490825A645A64400000010E5A67F840" />
    <QualityLevel Index="1" Bitrate="2056000" FourCC="WVC1" 
      MaxWidth="992" MaxHeight="560" 
      CodecPrivateData="250000010FD37E1EF1178A1EF845E8049081BEBE7D7CC00000010E5A67F840" />
    ...
    <c n="0" d="20020000" />
    <c n="1" d="20020000" />
    ...
    <c n="71" d="20020000" />
    <c n="72" d="15010001" />
  </StreamIndex>
  <StreamIndex Type="audio" Index="0" FourCC="WMAP" 
    Chunks="73" QualityLevels="1" 
    Url="QualityLevels({bitrate})/Fragments(audio={start time})">
    <QualityLevel Bitrate="64000" SamplingRate="44100" Channels="2" 
      BitsPerSample="16" PacketSize="2973" AudioTag="354" 
      CodecPrivateData="1000030000000000000000000000E00042C0" />
    <c n="0" d="21246187" />
    <c n="1" d="19620819" />
    ...
    <c n="71" d="19504762" />
    <c n="72" d="14900906" />
  </StreamIndex>
  <StreamIndex Type="text" Name="ClosedCaptions" Subtype="CAPT" 
    TimeScale="10000000" ParentStreamIndex="video" 
    ManifestOutput="TRUE" QualityLevels="1" Chunks="2" 
    Url="QualityLevels({bitrate},{CustomAttributes})/Fragments(ClosedCaptions={start time})">
    <QualityLevel Index="0" Bitrate="1000" 
      CodecPrivateData="" FourCC=""/> 
    <c n="0" t="100000000">
      <f>...</f> 
    </c>
    <c n="1" t="150000000">
      <f>...</f>
    </c>
  </StreamIndex>
  ...
</SmoothStreamingMedia>

Silverlight のコードでは、StreamInfo 型がストリームを表します。SSME がクライアント マニフェストをダウンロードすると、SmoothStreamingMediaElement.ManifestReady イベントが発生します。このとき、SmoothStreamingMediaElement.AvailableStreams コレクション プロパティには、クライアント マニフェストの StreamIndex エントリごとに StreamInfo のインスタンスが保持されます。

クライアント マニフェスト内の任意のビデオ ストリームでは、ビデオ トラックが 2 秒間の多数のフラグメントに分割されるため、フラグメントのメタデータはマニフェストの各 c 要素で表されます。この例では、トラック内のフラグメントは連続しており、間隔を空けることなくビデオ トラック全体にわたって定義されています。つまり、ストリームはまばらではありません。

クローズド キャプション ストリームでは、トラックにフラグメントが 2 つだけ含まれ、各フラグメントに個別のタイミング情報 (c 要素の t 属性) が指定されます。さらに、ParentStreamIndex 属性が "video" に設定され、ビデオ ストリームがクローズド キャプション ストリームの親要素になります。このため、クローズド キャプション ストリームとビデオ ストリームのタイミング情報が同調します。つまり、クローズド キャプション ストリームは親ビデオ ストリームとまったく同じタイミングで開始して終了し、最初のキャプションはビデオ ストリームに 10 秒間表示され、次のキャプションはビデオ ストリームに 15 秒間表示されます。タイムラインが親のストリームに基づいており、しかもフラグメントが連続していないストリームは、スパース ストリームと呼ばれます。

トラックは、特定の種類 (ビデオ、オーディオ、テキストなど) のコンテンツのフラグメントのタイミングが設定されたシーケンスです。各トラックは、TrackInfo 型のインスタンスを使用して表され、ストリーム内のすべてのトラックには、StreamInfo.AvailableTracks コレクション プロパティを使用してアクセスできます。

クライアント マニフェストの各トラックは、QualityLevel 要素を介して一意に識別されます。QualityLevel は関連付けられたビット レートで識別され、TrackInfo.Bitrate プロパティを通じて公開されます。たとえば、クライアント マニフェストのビデオ ストリームには、それぞれ一意のビット レートを指定した複数の QualityLevel 要素を指定できます。各要素は同一ビデオ コンテンツの一意なトラックを表し、QualityLevel で指定されたビット レートでエンコードされます。

カスタム属性とマニフェストの出力

カスタム属性は、他のストリーム固有の情報やトラック固有の情報をマニフェストに追加する手段の 1 つです。カスタム属性は CustomAttributes 要素を使用して指定し、キーと値のペアで表される複数のデータ要素を含めることができます。各データ要素は Attribute 要素で表し、データ要素のキーとデータ要素の値をそれぞれ指定する Key 属性と Value 属性を設定します。1 つのストリーム内にトラック名とビット レートが同じトラックが複数ある場合など、個別に品質レベルを当てはめることができない場合は、カスタム属性を使用してトラックどうしを区別することもできます。図 4 にカスタム属性の使用方法の例を示します。

図 4 クライアント マニフェストでのカスタム属性の使用

<StreamIndex Type="video" Chunks="12" QualityLevels="2" 
  MaxWidth="1280" MaxHeight="720" 
  DisplayWidth="1280" DisplayHeight="720" 
  Url="QualityLevels({bitrate})/Fragments(video={start time})">
  <CustomAttributes>
    <Attribute Key="CameraAngle" Value="RoofCam"/>
    <Attribute Key="AccessLevel" Value="PaidSubscription"/>
  </CustomAttributes>
  <QualityLevel Index="0" Bitrate="2962000" FourCC="WVC1" 
    MaxWidth="1280" MaxHeight="720"
    CodecPrivateData="250000010FD37E27F1678A27F859E80490825A645A64400000010E5A67F840">
    <CustomAttributes>
      <Attribute Name = "hardwareProfile" Value = "10000" />
    </CustomAttributes>
  </QualityLevel>
...
</StreamIndex>

カスタム属性をマニフェストに追加しても、SSME の動作が自動的に影響を受けるわけではありません。カスタム属性は、プレーヤーのコードが受け取って対応できるマニフェストにカスタム データを導入する、運用環境のワークフロー向けの手段です。たとえば、図 4 では、ビデオ ストリームのカスタム属性コレクションに含まれるカスタム属性の AccessLevel キーを見て、属性の値で指定しているようにそのビデオ ストリームを有料購読者だけに公開することもできます。

StreamInfo.CustomAttributes コレクション プロパティは、ストリーム レベルで (StreamIndex 要素の直接の子である CustomAttributes 要素として) 適用されるすべてのカスタム属性について、文字列のキーと値のペアのディクショナリを公開します。TrackInfo.CustomAttributes プロパティは、トラック レベルで (QualityLevel 要素の直接の子要素として) 適用されるすべてのカスタム属性について同じ情報を公開します。

ストリーム (StreamIndex 要素) の ManifestOutput 属性に TRUE を設定すると、クライアント マニフェストに、ストリーム内のトラックの各フラグメントを表すデータを実際に含めることができます。図 5 に例を示します。

図 5 マニフェストの出力

<StreamIndex Type="text" Name="ClosedCaptions" Subtype="CAPT" 
  TimeScale="10000000" ParentStreamIndex="video" 
  ManifestOutput="TRUE" QualityLevels="1" Chunks="6" 
  Url="QualityLevels({bitrate},{CustomAttributes})/Fragments(ClosedCaptions={start time})"> 
  <QualityLevel Index="0" Bitrate="1000" CodecPrivateData="" FourCC=""/> 
  <c n="0" t="100000000">
    <f>PENhcHRpb24gSWQ9IntERTkwRkFDRC1CQzAxLTQzZjItQTRFQy02QTAxQTQ5QkFGQkJ9IiAKICAgICAgICBBY3Rp</f>
  </c>
  <c n="1" t="150000000">
    <f>PENhcHRpb24gSWQ9IntERTkwRkFDRC1CQzAxLTQzZjItQTRFQy02QTAxQTQ5QkFGQkJ9IiAKICAgI</f>
  </c>
...
</StreamIndex>

f 要素内に入れ子になったコンテンツに注目してください。各コンテンツは、そのコンテンツを含むチャンクによって指定されたタイミングで表示されるキャプション項目のデータを表しています。クライアント マニフェストの仕様では、本来のデータ項目を Base64 でエンコードした文字列としてデータを表すよう求められています。

TrackInfo.TrackData コレクション プロパティには TimelineEvent のインスタンスのリストが含まれています。このインスタンスは、トラックに対応する f 要素ごとに 1 つずつあります。TimelineEvent エントリごとに、TimelineEvent.EventTime プロパティがシーケンス内の時点を表し、TimelineEvent.EventData プロパティで Base64 でエンコードされたテキスト文字列を提供します。TrackInfo 型では、Bitrate、CustomAttributes、Index、Name、および ParentStream の各プロパティもサポートします。

ストリームとトラックの選択

ストリームとトラックに関するメタデータと API をアプリケーション コード内で使用するには、さまざまな方法があります。

ストリーム内の特定のトラックを選択して残りのトラックを除外する機能があると便利でしょう。よくあるシナリオは、購読者のアクセス レベルに応じて画質を変える視聴エクスペリエンスです。このようなシナリオでは、次のように基本レベルや無償レベルの購読者には低解像度のコンテンツを、プレミアム レベルの購読者にのみ高解像度のコンテンツを公開します。

if (subscriber.AccessLevel != "Premium") {
  StreamInfo videoStream = 
    ssme.GetStreamInfoForStreamType("video");
  List<TrackInfo> allowedTracks = 
    videoStream.AvailableTracks.Where((ti) => 
    ti.Bitrate < 1000000).ToList();
  ssme.SelectTracksForStream(
    videoStream, allowedTracks, false);
}

GetStreamInfoForStreamType メソッドは、ストリームの種類を表すリテラルを受け取り、対応する StreamInfo のインスタンスを返します。StreamInfo.AvailableTracks プロパティの LINQ クエリは、ビット レートが 1 Mbps 未満のトラックの一覧、つまり、プレミアム以外の購読者向けの標準解像度のビデオを取得します。続いて SelectTracksForStream メソッドを使用すると、このストリームに含まれるトラックのリストから、公開の対象となるトラックを限定できます。

SelectTracksForStream メソッドの最後のパラメーターを true に設定すれば、SSME に対し、先読みバッファーに格納されているすべてのデータを直ちに削除するよう指示できます。現在選択中のトラックのリストを任意の時点で取得するには、StreamInfo.SelectedTracks プロパティを使用します。一方、StreamInfo.AvailableTracks プロパティは、アクセスできるすべてのトラックを継続的に公開します。

スムーズ ストリーミングでは、同じ種類の複数のストリームをクライアント マニフェストに共存させることができます。現在ベータ版の IIS Smooth Streaming Player Development Kit では、同じ種類のストリームが複数存在する場合に、GetStreamInfoForStreamType メソッドが指定された種類の最初のストリームを返すため、想定する処理とは異なることがあります。ただし、このメソッドを使用しないで、AvailableStreams コレクションに直接照会して、適切な StreamInfo を取得すれば問題ありません。次のスニペットは、"ticker" というテキスト ストリームを取得する LINQ クエリを示しています。

StreamInfo tickerStream = 
  ssme.AvailableStreams.Where((stm) => 
  stm.Type == "text" && 
  stm.Name == "ticker").FirstOrDefault();

テキスト ストリームの使用

オーディオ/ビデオのプレゼンテーションでは、メイン ビデオ シーケンス中の特定の時点に追加コンテンツを表示する必要があることがあります。このような追加コンテンツには、クローズド キャプション、広告、ニュース テロップ、オーバーレイ アニメーションなどがあります。テキスト ストリームは、このようなコンテンツの公開に役立つ機能です。

テキスト ストリームをプレゼンテーションに含める方法の 1 つは、ビデオをエンコードするときに、ビデオ トラックに沿ってテキスト トラックを多重化し、テキスト トラックのコンテンツ チャンクが、ビデオと一緒に適切なタイミングでサーバーから配信されるようにすることです。

もう 1 つは、先ほど説明したマニフェストの出力機能を利用して、クライアント マニフェスト自体にテキスト コンテンツを作成する方法です。ここでは、この 2 つ目の方法を詳しく見てみることにしましょう。

まず、テキスト ストリームを含むクライアント マニフェストを準備します。メディア作成のワークフローには、エンコード中またはエンコード後にこのようなコンテンツをマニフェストに挿入する方法が何とおりもあります。また、広告配信プラットフォームやキャプション ジェネレーターなど、複数のソースからデータを取得できる場合もあります。しかし、ここではデータ ソースとして単純な XML データ ファイルを使用し、LINQ to XML クエリを使ってテキスト ストリームを作成して、既存のクライアント マニフェストに挿入します。

データの構造が特に複雑になるわけではありません (完全なファイルは、この記事のダウンロード コードに含まれています。ここでは説明のために抜粋を示します)。データ ファイルは、Tracks 要素から始まり、次に 2 つの ContentTrack 要素が続きます。各 ContentTrack エントリは、最終的にクライアント マニフェスト内で 1 つの独立したテキスト ストリームになります。以下に示す最初の ContentTrack 要素はキャプション用です。

<ContentTrack Name="ClosedCaptions" Subtype="CAPT">

次はアニメーション用です。

<ContentTrack Name="Animations" Subtype="DATA">

それぞれの ContentTrack 要素には複数の Event 要素があり、その time 属性に、ビデオのタイムライン中でテキスト イベントを発生させるタイミングを指定します。また、Event 要素には、CDATA セクションとして、XML で定義された実際のキャプション イベントを含めます (アニメーションの場合は XAML で定義されます)。

<Event time="00:00:10"> 
  <![CDATA[<Caption Id="{DE90FACD-BC01-43f2-A4EC-6A01A49BAFBB}" 
    Action="ADD">
    Test Caption 1
  </Caption>] ]> 
</Event>
<Event time="00:00:15"> 
  <![CDATA[<Caption Id="{DE90FACD-BC01-43f2-A4EC-6A01A49BAFBB}" 
    Action="REMOVE"/>] ]> 
</Event>

クローズド キャプション イベントの場合、イベントごとに、前に追加したキャプションを削除するタイミングを示すイベントが対応しています。クローズド キャプション イベントの CDATA セクションに含まれる Caption 要素では、"Add" または "Remove" という値を指定した Action 属性を定義することで、追加と削除の動作を指定します。

LINQ to XML コードでは、XML データをクライアント マニフェストの適切なエントリに変換し、既存のクライアント マニフェスト ファイルに挿入します。この記事のダウンロード コードには処理の例を含めていますが、例示しているデータ形式は、Smooth Streaming Player Development Kit またはスムーズ ストリーミングの仕様に含まれているものではなく、規範となるものでもありません。スムーズ ストリーミングのクライアント マニフェスト仕様で定められている適切な形式にデータ構造を変換できるのであれば、アプリケーションのニーズを満たす任意のデータ構造を定義できます。変換処理には、CDATA セクションのテキスト コンテンツを Base64 形式にエンコードする処理も含みます。

変換を実行すると、変換後のクライアント マニフェスト ファイルにはテキスト ストリームが含まれます (図 6 参照)。

図 6 テキスト コンテンツ ストリームを含むクライアント マニフェストからの抜粋

<SmoothStreamingMedia MajorVersion="2" MinorVersion="0" 
  Duration="1456860000">
  <StreamIndex Type="video" Chunks="73" QualityLevels="8" 
    MaxWidth="1280" MaxHeight="720" 
    DisplayWidth="1280" DisplayHeight="720"
    Url="QualityLevels({bitrate})/Fragments(video={start time})">
    <QualityLevel Index="0" Bitrate="2962000" FourCC="WVC1" 
      MaxWidth="1280" MaxHeight="720"
      CodecPrivateData="250000010FD37E27F1678A27F859E80490825A645A64400000010E5A67F840" />
    <QualityLevel Index="1" Bitrate="2056000" FourCC="WVC1" 
      MaxWidth="992" MaxHeight="560" 
      CodecPrivateData="250000010FD37E1EF1178A1EF845E8049081BEBE7D7CC00000010E5A67F840" />
    ...
    <c n="0" d="20020000" />
    <c n="1" d="20020000" />
    ...
    <c n="71" d="20020000" />
    <c n="72" d="15010001" />
  </StreamIndex>
  <StreamIndex Type="audio" Index="0" FourCC="WMAP" 
    Chunks="73" QualityLevels="1" 
    Url="QualityLevels({bitrate})/Fragments(audio={start time})">
    <QualityLevel Bitrate="64000" SamplingRate="44100" Channels="2" 
      BitsPerSample="16" PacketSize="2973" AudioTag="354" 
      CodecPrivateData="1000030000000000000000000000E00042C0" />
    <c n="0" d="21246187" />
    <c n="1" d="19620819" />
    ...
    <c n="71" d="19504762" />
    <c n="72" d="14900906" />
  </StreamIndex>
  <StreamIndex Type="text" Name="ClosedCaptions" Subtype="CAPT" 
    TimeScale="10000000" ParentStreamIndex="video" 
    ManifestOutput="TRUE" QualityLevels="1" Chunks="2" 
    Url="QualityLevels({bitrate},{CustomAttributes})/Fragments(ClosedCaptions={start time})">
    <QualityLevel Index="0" Bitrate="1000" 
      CodecPrivateData="" FourCC=""/> 
    <c n="0" t="100000000">
      <f>...</f> 
    </c>
    <c n="1" t="150000000">
      <f>...</f>
    </c>
  </StreamIndex>
  ...
</SmoothStreamingMedia>

ビデオ ストリームとオーディオ ストリームは 図 6 のクライアント マニフェストに既に存在しているため、ここではそれぞれ ClosedCaptions と Animations という 2 つのテキスト ストリームを追加しました。各ストリームは、ビデオ ストリームを親として使用し、ManifestOutput を TRUE に設定しています。テキスト ストリームはその性質上まばらに存在するため、ビデオ ストリームを親に設定することで、各テキスト コンテンツ エントリ (c 要素) をビデオ ストリームのタイムライン上の適切なタイミングに指定できるようになります。ManifestOutput を TRUE に設定することで、SSME はマニフェスト自体から実際のデータ (f 要素に含まれる Base64 エンコードされた文字列) を読み取るようになります。

TimelineEvent と TimelineMarker

今度は、SSME で追加のテキスト コンテンツを利用する方法を説明しましょう。SSME では、追加のテキスト ストリームが StreamInfo のインスタンスとして AvailableStreams プロパティで公開されます。各 StreamInfo には、トラック データが TrackInfo のインスタンスとして含まれています。TrackInfo.TrackData コレクション プロパティには、各テキスト トラックに含まれるテキスト イベントと同数の、TimelineEvent 型のインスタンスが含まれています。TimelineEvent.EventData プロパティでは、(Base64 エンコード形式からデコードされた) 文字列のコンテンツを表すバイト配列が公開されます。それに対して、TimelineEvent.EventTime プロパティでは、このイベントが発生するタイミングが公開されます。

プレゼンテーションの再生を開始し、これらのイベントに到達すると、SSME が TimelineEventReached イベントを発生します。図 7 に、図 6 のクライアント マニフェストに追加したクローズド キャプション トラックとアニメーション トラックを処理するサンプルを示します。

図 7 TimelineEventReached イベントの処理

ssme.TimelineEventReached += 
  new EventHandler<TimelineEventArgs>((s, e) => { 
  //if closed caption event
  if (e.Track.ParentStream.Name == "ClosedCaptions" && 
    e.Track.ParentStream.Subtype == "CAPT") {

    //base64 decode the content and load the XML fragment
    XElement xElem = XElement.Parse(
      Encoding.UTF8.GetString(e.Event.EventData,
      0, e.Event.EventData.Length));

    //if we are adding a caption
    if (xElem.Attribute("Action") != null && 
      xElem.Attribute("Action").Value == "ADD") {

      //remove the text block if it exists
      UIElement captionTextBlock = MediaElementContainer.Children.
      Where((uie) => uie is FrameworkElement && 
        (uie as FrameworkElement).Name == (xElem.Attribute("Id").Value)).
        FirstOrDefault() as UIElement;
        if(captionTextBlock != null)
          MediaElementContainer.Children.Remove(captionTextBlock);

      //add a TextBlock 
      MediaElementContainer.Children.Add(new TextBlock() {
        Name = xElem.Attribute("Id").Value,
        Text = xElem.Value,
        HorizontalAlignment = HorizontalAlignment.Center,
        VerticalAlignment = VerticalAlignment.Bottom,
        Margin = new Thickness(0, 0, 0, 20),
        Foreground = new SolidColorBrush(Colors.White),
        FontSize = 22
      });
    }
    //if we are removing a caption
    else if (xElem.Attribute("Action") != null && 
      xElem.Attribute("Action").Value == "REMOVE") {

      //remove the TextBlock
      MediaElementContainer.Children.Remove(
        MediaElementContainer.Children.Where(
        (uie) => uie is FrameworkElement && 
        (uie as FrameworkElement).Name == 
        (xElem.Attribute("Id").Value)).FirstOrDefault() 
        as UIElement);
    }
  }

  //Logic for animation event
  ...
});

TimelineEvent を処理するたびに、TextBlock を UI に挿入してキャプションを表示するか、アニメーションの XAML 文字列を読み込んでアニメーションを開始します (アニメーションの処理ロジックの詳細については、ダウンロード可能なコードを参照してください)。

テキスト コンテンツは Base64 でエンコードされているため、ここでは元の状態にデコードしていることに注意してください。また、このコードでは、Caption 要素の Action 属性を確認して、キャプションを UI に追加するか、既存のキャプションを削除するかを判断しています。アニメーション イベントの場合、アニメーションの完了ハンドラーを利用して UI からアニメーションを削除できます。

図 8 は、再生中のビデオに重ねてキャプションが表示され、楕円のアニメーションが実行されているスクリーンショットです。この手法は適切に機能しますが、使用する前に考慮すべき問題が 1 つあります。SSME の現リリースでは、TimelineEvent を 2 秒間隔で処理します。もう少しわかりやすくなるように、ビデオのタイムライン上の 15.5 秒の時点にクローズド キャプションを表示するタイミングを設定したとします。SSME は、値が 2 の倍数となる直前の時点、つまり約 14 秒の時点でこのクローズド キャプションの TimelineEventReached イベントを発生してしまいます。


図 8 テキスト コンテンツ ストリームと TimelineEvent を使用したコンテンツのオーバーレイ

もっと高い精度が求められるシナリオで、コンテンツのチャンクを 2 秒間隔に揃えて配置できない場合、TimelineEventReached を使用してコンテンツのトラックを処理することは適切な方法とは言えません。このような場合、(標準の MediaElement 型で使用するように) TimelineMarker クラスを使用してタイムラインにマーカーを追加すれば、必要な任意の精度で MarkerReached イベントを発生させることができます。この記事のダウンロード コードには、コンテンツ イベントごとに TimelineMarker を追加し、MarkerReached イベント ハンドラーでこれらのマーカーに応答する AddAndHandleMarkers メソッドの概要を含めています。

外部マニフェストのマージ

ここまでは、コンテンツの追加ストリームをクライアント マニフェストに追加する例を説明してきました。この手法は、クライアント マニフェストにアクセスできる場合は適切に機能しますが、場合によっては、クライアント マニフェストに直接アクセスして必要なストリームを追加できないことがあります。また、他の要素 (さまざまな地域と言語でのクローズド キャプション) を条件として、追加するコンテンツ ストリームが異なる場合もあります。すべての条件に対応するデータをクライアント マニフェストに追加すると、SSME でマニフェストの解析と読み込みを行う時間が長くなります。

SSME ではこうした問題に対処するために、実行時に外部マニフェスト ファイルを元のクライアント マニフェストにマージできるようにしています。外部マニフェスト ファイルをマージすれば、元のクライアント マニフェストを変更しなくても、ここまでの説明と同じように他のデータ ストリームを追加してデータを操作できます。

マニフェストをマージする例を以下に示します。

ssme.ManifestMerge += new 
  SmoothStreamingMediaElement.ManifestMergeHandler((sender) => {
  object ParsedExternalManifest = null;
  //URI of the right external manifest based on current locale
  //for example expands to 
  string UriString = 
    string.Format(
    "https://localhost/SmoothStreaming/Media/FighterPilot/{0}/CC.xml", 
    CultureInfo.CurrentCulture.Name);
  //parse the external manifest - timeout in 3 secs
  ssme.ParseExternalManifest(new Uri(UriString), 3000, 
    out ParsedExternalManifest);
  //merge the external manifest
  ssme.MergeExternalManifest(ParsedExternalManifest); 
});

このコード スニペットでは、現在のロケールを通知し、そのロケールに適した言語のクローズド キャプションを含む適切な外部マニフェスト ファイル (そのロケールの言語識別子に基づく名前のフォルダーに格納された CC.xml ファイル) を使用します。ParseExternalManifest メソッドは、外部マニフェストの場所を指す URI を受け取り、メソッドの 3 つ目の出力パラメーター経由で解析済みのマニフェストをオブジェクトとして返します。メソッドの 2 つ目のパラメーターではタイムアウト値を受け取るため、ネットワーク呼び出しが長期間ブロックされるのを回避できます。

MergeExternalManifest メソッドは、前の呼び出しから返された解析済みのマニフェスト オブジェクトを受け取り、実際のマージ処理を実行します。この結果、すべてのマージされた外部マニフェストのストリームやトラックを、StreamInfo や TrackInfo のインスタンスとしてプレーヤー コードの他の任意の場所で使用できるようになり、これまでの説明と同じようにデータを操作できます。

ParseExternalManifest メソッドと MergeExternalManifest メソッドは、ManifestMerge イベント ハンドラー内だけで呼び出すようにすることが重要です。このイベント ハンドラーのスコープ外でこれらのメソッドを呼び出すと、必ず InvalidOperationException が発生します。

また、外部マニフェストには、そのマニフェストにアクセスできる Web サーバーに登録されている MIME の種類が関連付けられた拡張子が必要なことに注意してください。そもそもコンテンツは XML なので、.xml などの一般的な拡張子を使用することをお勧めします。スムーズ ストリーミング サーバーとして機能する同一 Web サーバーから外部マニフェスト ファイルを取得する場合は、.ismc 拡張子を使用しないでください。これは、IIS Media Services ハンドラーによって .ismc ファイルへの直接アクセスが拒否され、ParseExternalManifest メソッドで外部マニフェストをダウンロードできないためです。

外部マニフェストの構造に関しては、通常のクライアント マニフェストと同様、最上位には SmoothStreamingMedia 要素があり、その内部にデータを表す適切な StreamIndex 子要素が配置されている必要があります。

クリップのスケジュール設定

プレゼンテーションの特定の時点に別のビデオ クリップを挿入する必要が生じることもあります。ほんの数例を挙げるだけでも、広告ビデオ、ニュース速報、つなぎの役割を果たすクリップなどをプレゼンテーションに挿入することが考えられます。対応すべき問題点は、2 つに分けて考えます。まず、必要なコンテンツ データを取得して、タイムライン上のどの時点に挿入するかを決定する方法です。次に、実際にクリップの再生スケジュールを設定する方法です。SSME には、両方の作業の実装を大幅に簡略化する機能が組み込まれています。

ここまで説明してきたように、クライアント マニフェストにテキスト ストリームを挿入する手法を使用して、コードからクリップ データにアクセスできるようにします。以下に、クリップのスケジュール情報に使用するデータ ソースのサンプルを示します。

<ContentTrack Name="AdClips" Subtype="DATA">
  <Event time="00:00:04">
    <![CDATA[<Clip Id="{89F92331-8501-41ac-B78A-F83F6DD4CB40}" 
    Uri="https://localhost/SmoothStreaming/Media/Robotica/Robotica_1080.ism/manifest" 
    ClickThruUri="https://msdn.microsoft.com/en-us/robotics/default.aspx" 
    Duration="00:00:20" />] ]>
  </Event>
  <Event time="00:00:10">
    <![CDATA[<Clip Id="{3E5169F0-A08A-4c31-BBAD-5ED51C2BAD21}" 
    Uri="https://localhost/ProgDownload/Amazon_1080.wmv" 
    ClickThruUri="http://en.wikipedia.org/wiki/Amazon_Rainforest" 
    Duration="00:00:25"/>] ]>
  </Event>     
</ContentTrack>

このサンプルでスケジュールが設定される各データには、コンテンツの URI、クリップのクリックスルーとしてユーザーが移動できる Web ページ のURI、およびクリップの再生時間を指定します。Event 要素の time 属性には、タイムライン上のどの時点にクリップのスケジュールを設定するかを指定します。

前述の LINQ to XML クエリと同じ手法を使用して、このデータを変換し、対応するテキスト ストリームをクライアント マニフェストに追加できます。ここでも、テキスト ストリームを StreamInfo のインスタンスとしてコードに公開します。次に、SSME のクリップのスケジュール設定用 API を使用して、これらのクリップのスケジュール設定にこの情報を活用できます。図 9 に、この情報に基づいてクリップのスケジュールを設定するメソッドを示します。

図 9 クリップのスケジュール設定

private void ScheduleClips() {
  //get the clip data stream
  StreamInfo siAdClips = ssme.AvailableStreams.Where(
    si => si.Name == "AdClips").FirstOrDefault();

  //if we have tracks
  if (siAdClips != null && siAdClips.AvailableTracks.Count > 0) {

    //for each event in that track
    foreach (TimelineEvent te in 
      siAdClips.AvailableTracks[0].TrackData) {

      //parse the inner XML fragment
      XElement xeClipData = XElement.Parse(
        Encoding.UTF8.GetString(te.EventData, 0, 
        te.EventData.Length));

      //schedule the clip
      ssme.ScheduleClip(new ClipInformation {
        ClickThroughUrl = new Uri(
        xeClipData.Attribute("ClickThruUri").Value),
        ClipUrl = new Uri(xeClipData.Attribute("Uri").Value),
        IsSmoothStreamingSource = 
        xeClipData.Attribute("Uri").Value.ToUpper().Contains("ism"), 
        Duration = TimeSpan.Parse(xeClipData.Attribute("Duration").Value)
        },
        te.EventTime, true, //pause the timeline
        null);
    }
    //set the Clip MediaElement style
    ssme.ClipMediaElementStyle = 
      this.Resources["ClipStyle"] as Style;
  }
}

実際にスケジュールを設定するのは、SSME の ScheduleClip メソッドです。
スケジュールを設定するクリップごとに、ClipInformation 型の新しいインスタンスが 1 つ、クリップのデータから派生した適切なプロパティと共にスケジュールに挿入されます。

クリップは、スムーズ ストリーミングのソースでも、Silverlight の MediaElement でサポートされているその他のソースでもかまいません。重要なのは、ClipInformation.IsSmoothStreamingSource プロパティを正しく設定して、クリップの再生に適切なプレーヤー コンポーネントが使用されるようにすることです。

ScheduleClip メソッドの 2 つ目のパラメーターは、クリップを再生するタイミングです。3 つ目のパラメーターでは、クリップの再生中にタイムラインの進行を停止するかどうかを示します。最後のパラメーターには、さまざまなクリップ関連のイベント ハンドラーで使用できる任意のユーザー データを渡します。

場合によっては、クリップのスケジュールを 1 つのシーケンスとして設定する必要があります。この場合、スケジュールを設定したすべてのクリップが連続する 1 つのシーケンスとして再生されるように、シーケンスの最初のクリップだけに開始時間の情報を適用し、残りのクリップは順次連鎖させます。ScheduleClip メソッドを使用すると、この機能も簡単になります (図 10 参照)。

図 10 スケジュール設定されたクリップを連鎖させる ClipContext の使用

private void ScheduleClips() {
  StreamInfo siAdClips = ssme.AvailableStreams.Where(
  si => si.Name == "AdClips").FirstOrDefault();

  if (siAdClips != null && siAdClips.AvailableTracks.Count > 0) {
    ClipContext clipCtx = null;
    foreach (
      TimelineEvent te in siAdClips.AvailableTracks[0].TrackData) {
      XElement xeClipData = 
        XElement.Parse(Encoding.UTF8.GetString(te.EventData, 0,
        te.EventData.Length));

      //if this is the first clip to be scheduled
      if (clipCtx == null) {
        clipCtx = ssme.ScheduleClip(new ClipInformation {
          ClickThroughUrl = new Uri(
          xeClipData.Attribute("ClickThruUri").Value),
          ClipUrl = new Uri(xeClipData.Attribute("Uri").Value),
          IsSmoothStreamingSource = 
          xeClipData.Attribute("Uri").Value.ToUpper().Contains("ism"), 
          Duration = TimeSpan.Parse(
          xeClipData.Attribute("Duration").Value)
        },
        te.EventTime, //pass in the start time for the clip
        true, null);
      }
      else { //subsequent clips
        clipCtx = ssme.ScheduleClip(new ClipInformation {
          ClickThroughUrl = new Uri(
          xeClipData.Attribute("ClickThruUri").Value),
          ClipUrl = new Uri(xeClipData.Attribute("Uri").Value),
          IsSmoothStreamingSource = 
          xeClipData.Attribute("Uri").Value.ToUpper().Contains("ism"),
          Duration = TimeSpan.Parse(
          xeClipData.Attribute("Duration").Value)
        },
        clipCtx, //clip context for the previous clip to chain
        true, null);
      }
    }
    ssme.ClipMediaElementStyle = 
      this.Resources["ClipStyle"] as Style;
  }
}

ここでは、ClipContext が存在しない (つまり、clipCtx 変数の値が null の) 最初のクリップのスケジュールだけに絶対時間を使用しています。それ以降のすべての ScheduleClip の呼び出しでは、スケジュールが設定されたクリップの状態を表す ClipContext のインスタンスが返されます。ScheduleClip メソッドには、クリップの開始時刻をスケジュール設定するのではなく、ClipContext のインスタンスを受け取り、以前にスケジュールが設定された (渡される ClipContext で表される) クリップの直後に開始するようにスケジュールを設定するオーバーロードがあります。

スケジュールが設定されたクリップを再生するときに、SSME はメイン ビデオを非表示にし、スケジュールが設定されたクリップを再生する MediaElement を生成します。この MediaElement をカスタマイズする場合は、SSME の ClipMediaElementStyle プロパティを希望する XAML スタイルに設定できます。

スケジュールが設定されたクリップの再生時に SSME で発生する興味深いイベントがいくつかあります。ClipProgressUpdate イベントを処理すると、クリップの進行状況を追跡できます。ClipPlaybackEventArgs.Progress は列挙型の ClipProgress で、クリップの進行状況を 4 分の 1 ずつに分割して表します。ClipProgressUpdate イベントは、クリップの開始時と終了時、およびクリップの長さの 25%、50%、および 75% を経過した時点で発生します。ブール型の ClipContext.HasQuartileEvents プロパティは、クリップが 25% ずつ進行するたびにイベントが発生するかどうかを示します。クリップの長さが不明な場合などは、25% 経過するたびに進行状況イベントが発生しないことがあります。

ClipClickThrough イベントは、クリップの再生中に視聴者がそのクリップをクリックすると発生します。このクリップにクリックスルーの移動先が指定されていれば、ClipEventArgs.ClipContext.ClipInformation.ClickThroughUrl によって移動先が公開されます。任意の技法 (ブラウザーを操作してポップアップ ウィンドウを開くなど) を使用して、このクリックスルー URL で指定された Web リソースを開くことができます。

また、ClipError イベントと ClipStateChanged イベントを使用すると、クリップに関するエラー条件と状態の変化をそれぞれ処理できます。

再生の速度と方向

SSME では、コンテンツの速度と方向を変えて再生できます。SmoothStreamingMediaElement.SupportedPlaybackRates プロパティは、サポートされる再生速度の一覧を double 値の一覧として返します (1.0 は既定の再生速度を表します)。現在公開されているベータ版では、この一覧には他に 0.5、4.0、8.0、-4.0、および -8.0 という値が含まれています。正の値を使用すると、2 分の 1、4 倍、および 8 倍の速度で再生でき、負の値を使用すると、逆方向の再生 (巻き戻し) を 4 倍速と 8 倍速で実行できます。

SmoothStreamingMediaElement.SetPlaybackRate メソッドを呼び出すと、再生中の任意時点で再生速度を設定できます。SetPlaybackRate メソッドは、唯一のパラメーターとして希望する再生速度を受け取ります。

再生速度の制御は、スムーズ ストリーミングのコンテンツのみで機能することに注意してください。このため、SSME を使用して、プログレッシブ ダウンロードされるコンテンツ、またはその他の技法を使用してストリームで配信されるコンテンツを再生すると、SetPlaybackRate メソッドで例外が発生します。

複合マニフェストを使用したスムーズ ストリームの編集

ときには、スムーズ ストリーミングの複数のプレゼンテーションの一部を組み合わせて、1 つの複合プレゼンテーションを作成する必要が生じることがあります。最も一般的なシナリオは、クリップを生成するマスター ソースにマークインおよびマークアウトするタイミングをユーザーが指定できるラフカット エディターなどのツールを使用して、場合によっては異なるマスター ソースからの複数のクリップを 1 つのプレゼンテーションとして連続再生することです。

SSME の複合マニフェスト機能では、クリップの複数のセグメントを含む個別のマニフェスト ドキュメントを作成することで、このような処理を実現できます。この場合、完全なプレゼンテーションの一部を開始時刻と終了時刻で範囲指定することで、クリップの各セグメントを定義します。この手法を使用する最大のメリットは、ソースとなるマテリアルをコード変換する必要なく、既存のプレゼンテーションにさまざまな編集を加えることができる点です。

複合マニフェスト名の末尾には、必ず .csm という拡張子が付きます。このようなマニフェストを使用するには、SmoothStreamingSource プロパティの値を、複合マニフェスト ファイルを指す有効な URL に設定するだけです。

ssme.SmoothStreamingSource = new Uri("https://localhost/SmoothStreaming/Media/MyCompositeSample.csm");

図 11 に、複合マニフェストからの抜粋を示します (完全なファイルは、この記事のダウンロード コードに含まれています)。

図 11 複合マニフェストのサンプル

<?xml version="1.0" encoding="utf-8"?>
<SmoothStreamingMedia MajorVersion="2" MinorVersion="0" Duration="269000000">
<Clip Url="https://localhost/SmoothStreaming/Media/AmazingCaves/Amazing_Caves_1080.ism/manifest" 
  ClipBegin="81000000" ClipEnd="250000000">
<StreamIndex Type="video" Chunks="9" QualityLevels="3"
  MaxWidth="992" MaxHeight="560"
  DisplayWidth="992" DisplayHeight="560"
  Url="QualityLevels({bitrate})/Fragments(video={start time})">
  <QualityLevel Index="0" Bitrate="2056000" FourCC="WVC1"
    MaxWidth="992" MaxHeight="560"
    CodecPrivateData="250000010FD37E1EF1178A1EF845E8049081BEBE7D7CC00000010E5A67F840" 
  />
  <QualityLevel Index="1" Bitrate="1427000" FourCC="WVC1"
    MaxWidth="768" MaxHeight="432"
    CodecPrivateData="250000010FCB6C17F0D78A17F835E8049081AB8BD718400000010E5A67F840" 
  />
  <QualityLevel Index="2" Bitrate="991000" FourCC="WVC1"
    MaxWidth="592" MaxHeight="332"
    CodecPrivateData="250000010FCB5E1270A58A127829680490811E3DF8F8400000010E5A67F840" 
  />
  <c t="80130000" />
  <c t="100150000" />
  <c t="120170000" />
  <c t="140190000" />
  <c t="160210000" />
  <c t="180230000" />
  <c t="200250000" />
  <c t="220270000" />
  <c t="240290000" d="20020000" />
</StreamIndex>
<StreamIndex Type="audio" Index="0" FourCC="WMAP"
  Chunks="10" QualityLevels="1" 
  Url="QualityLevels({bitrate})/Fragments(audio={start time})">
  <QualityLevel Bitrate="64000" SamplingRate="44100"
    Channels="2" BitsPerSample="16" PacketSize="2973"
    AudioTag="354" CodecPrivateData="1000030000000000000000000000E00042C0" />
  <c t="63506576" />
  <c t="81734240" />
  <c t="102632199" />
  <c t="121672562" />
  <c t="142106122" />
  <c t="162075283" />
  <c t="181580045" />
  <c t="202478004" />
  <c t="222447165" />
  <c t="241313378" d="20143311" />
</StreamIndex>
</Clip>
<Clip Url="https://localhost/SmoothStreaming/Media/CoralReef/Coral_Reef_Adventure_1080.ism/manifest" 
  ClipBegin="102000000" ClipEnd="202000000">
<StreamIndex Type="video" Chunks="6" QualityLevels="3"
  MaxWidth="992" MaxHeight="560"
  DisplayWidth="992" DisplayHeight="560"
  Url="QualityLevels({bitrate})/Fragments(video={start time})">
...
</Clip>
</SmoothStreamingMedia>

このマニフェストには 2 つの Clip 要素が含まれ、各要素が既存のスムーズ ストリーミング プレゼンテーションのクリップを定義 (編集) しています。URL 属性は既存のスムーズ ストリーミング プレゼンテーションを指し、ClipBegin 属性と ClipEnd 属性はクリップの範囲を指定する開始時刻と終了時刻の値を保持します。最上位レベルの SmoothStreamingMedia 要素にある Duration 属性は、マニフェストに含まれるクリップ全体の長さの合計を正確に指定する必要があります。各 Clip エントリの ClipEnd 属性と ClipBegin 属性の値の差を合計することでマニフェストの合計長を算出できます。

各 Clip 要素には、ビデオとオーディオの StreamIndex エントリとその子要素の QualityLevel エントリを含み、ソース プレゼンテーションのクライアント マニフェスト (.ismc) ファイルを反映します。ただし、各 StreamIndex エントリのチャンク メタデータ (c) エントリは、ClipBegin 属性と ClipEnd 属性の範囲を満たすの必要なチャンクだけに限定できます。つまり、ClipBegin 属性の値は、ストリームで最初の c エントリの開始時刻 (t 属性) の値以上でなければならず、ClipEnd 属性の値は、ストリームで最後の c エントリの開始時刻に長さ (d 属性) を加えた合計値以下でなければなりません。

クライアント マニフェストでチャンクを定義する際に、期間の指定とインデックス (n 属性) を付けた形式が使用されることもあります。ただし、複合マニフェストでは、開始時刻を使用してチャンクを定義する必要があります (開始時刻は、先行するチャンクの長さを合計することで簡単に算出できます)。また、各 StreamIndex エントリの Chunks 属性は、クリップ内のチャンク数を反映している必要がありますが、他のすべての属性はソース クライアント マニフェストのエントリを反映していることにも注意してください。

ライブ ストリーム

SSME では、オンデマンド ストリームとライブ ストリームの両方を再生できます。SSME を使用してライブ スムーズ ストリーミング ビデオを再生するには、次のように SSME の SmoothStreamingSource プロパティをライブの公開ポイント URL に設定します。

ssme.SmoothStreamingSource = "https://localhost/SmoothStreaming/Media/FighterPilotLive.isml/manifest";

SSME でライブ ストリームが再生されているかどうかを把握するには、IsLive プロパティをチェックします。このプロパティは、コンテンツがライブ ソースの場合は true に設定され、それ以外の場合は false に設定されます。

スムーズ ストリーミングのライブ ビデオをセットアップして配信するには、特殊なインフラストラクチャが必要になることに注意してください。ライブ ストリーミング サーバー環境のセットアップの詳細については、この記事の対象外です。ライブ ストリーミング向けに IIS Media Services 3.0 をセットアップする方法の詳細については、learn.iis.net/page.aspx/628/live-smooth-streaming/ (英語) の記事を参照してください。learn.iis.net/page.aspx/620/live-smooth-streaming-for-iis-70---getting-started/ (英語) の記事では、テスト目的でライブ ストリーミング環境のシミュレーションをセットアップする方法に関する情報を紹介しています。

まとめ

IIS Smooth Streaming は、マイクロソフトが提供する最先端のアダプティブ ストリーミング プラットフォームです。この記事で説明したように、Smooth Streaming PDK (特に SmoothStreamingMediaElement 型) は、オンデマンド ストリームとライブ ストリームの両方を使用できる Silverlight クライアントを作成するうえで欠かせない構成要素です。PDK を使用すると、スムーズ ストリームのクライアント側の動作を幅広く制御でき、単なるオーディオ ストリームやビデオ ストリームにとどまらない機能豊富で効果的なエクスペリエンスを実現できるので、データ ストリームとメディアを簡単に有意義な方法で組み合わせることができます。

スムーズ ストリーミングの操作の詳細については、この記事の対象外とします。詳細については、iis.net/media (英語) を参照することをお勧めします。Silverlight でのメディア プログラミングと Silverlight の MediaElement 型の詳細なガイダンスについては、silverlight.net/getstarted (英語) を参照してください。

 

Jit Ghosh は、マイクロソフトの開発者エバンジェリズム チームに所属するアーキテクト エバンジェリストであり、メディア業界の顧客に対して最先端のデジタル メディア ソリューションに関する助言を行っています。Ghosh は、『Silverlight Recipes』(Apress、2009 年) の共著者でもあります。ブログは blogs.msdn.com/jitghosh (英語) で公開されています。

この記事のレビューに協力してくれた技術スタッフの Vishal Sood に心より感謝いたします。