1. はじめに
みなさん、こんにちは! 株式会社ヤプリでiOSエンジニアをしています白数 (@cychow_app) です。
前回の記事では、空間ビデオプレイヤーを構築する上で、まず空間ビデオとは何なのか、どのようなメタデータを保持しているのかに焦点を当てて解説しました。
もしご興味がありましたら、前回の記事も一読いただけると嬉しいです。
今回は、本題の空間ビデオのプレイヤーの実装についてご紹介していければと思います。
2. 空間ビデオ再生プレイヤーの実装
2.1 VideoPlayerComponent と視聴モード
再生プレイヤーを実装していく前に、まずどのようなコンポーネントを使用していくかについて見ていきたいと思います。
今回メインで使用していくのが、RealityKit内部で提供されている VideoPlayerComponent です。
VideoPlayerComponent は、iOSアプリでも使用する AVPlayer を、visionOSのEntityに対してつなぎ込む役割を担っています。
このComponent自体は、visionOS 1.0+から利用することができるため、手軽にvisionOS向けの動画再生プレイヤーを実装することができます。
また、空間ビデオを再生するにあたり、重要となってくるものが VideoPlayerComponent.ImmersiveViewingMode と VideoPlayerComponent.SpatialVideoMode です。
ImmersiveViewingMode
VideoPlayerComponent.ImmersiveViewingMode は、没入体験用のメディアコンテンツを「どの見せ方で再生するか」を指定する列挙型です。
ImmersiveViewingMode のパターンは「portal」、「full」、「progressive」の3つとなっています。
- portal:没入体験用のメディアコンテンツを「窓」や「ポータル」のように表示するモード。
- full:視野全体を覆う没入表示モード。没入表示として最も強い見せ方で、完全にコンテンツ側へ入り込む体験になります。
- progressive:段階的に没入度を深めるモード。Apple Vision Pro上部の Digital Crown で没入度を調整でき、100% では full 相当になります。(ただし 空間ビデオでは通常 progressive は使用しません。)
SpatialVideoMode
続いて VideoPlayerComponent.SpatialVideoMode は、空間ビデオを「通常の立体スクリーン映像として出すか」それとも「空間ビデオらしい奥行き表現付きで出すか」を指定する列挙型です。
パターンは「screen」、「spatial」の2つがあります。
- screen:空間ビデオを、従来型のステレオビデオをスクリーンに貼る形で表示するモード。つまり、「空間ビデオ特有の奥行きのある映像体験」を無効としているモードとなっています。また、このモードでは
ImmersiveViewingModeは効きません。 - spatial:「空間ビデオ特有の奥行きのある映像体験」を有効にするモード。このモードでは
ImmersiveViewingModeの portal / full が利用可能となります。
2.2 いざ実装へ
今回は「空間ビデオ特有の奥行きのある映像体験」をより感じることができる、「窓」や「ポータル」のように表示するportalモードと、完全にコンテンツ側へ入り込むことができるfullモードの2つの再生プレイヤーを構築していきます。
以下の実装ポイントに焦点を当てつつ、実際のソースコードを用いながら解説していければと思います。
【実装ポイント】
① Immersiveモード起動時の VideoPlayerComponent をEntityに対して設定する方法
② VideoPlayerComponent の生成と ImmersiveViewingMode 及び SpatialVideoMode の指定方法
③ ビデオプレイヤーのモード変更などを検知する VideoPlayerEvents について
① Immersiveモード起動時の VideoPlayerComponent をEntityに対して設定する方法
まずは、「Immersiveモード起動時の VideoPlayerComponent をEntityに対して設定する方法」です。
はじめに ImmersiveSpace(id:) 部分と、その内部について見ていきたいと思います。
@main struct EntryPoint: App { @State private var appModel = AppModel() var body: some Scene { ... ImmersiveSpace(id: appModel.immersiveSpaceID) { ImmersiveView() .environment(appModel) .onAppear { appModel.immersiveSpaceState = .open } .onDisappear { appModel.immersiveSpaceState = .closed } } ... } }
今回の場合ですと、 ImmersiveSpace 内部で ImmersiveView というViewを新たに定義しています。
ImmersiveView 内部では、 RealityKit の RealityView で構成されています。さらにその内部で、 ImmersiveSpace 内部で設置する空間ビデオ再生用のEntityを定義していきます。
import RealityKit import SwiftUI struct ImmersiveView: View { @Environment(AppModel.self) private var appModel @State private var videoEntity: Entity? ... var body: some View { RealityView { content in ... let videoEntity = Entity() videoEntity.position = [0, 1.35, -1.8] videoEntity.components[VideoPlayerComponent.self] = appModel.makeVideoPlayerComponent() content.add(videoEntity) self.videoEntity = videoEntity ... } ... } ... }
これにより、VideoPlayerComponent を適用するためのEntityが生成され、Immersiveモード起動時にSpace上に再生画面を表示するようになります。
次に、再生画面の詳細設定について見ていきます。詳細の設定は appModel.makeVideoPlayerComponent() 部分で設定しています。
@MainActor @Observable class AppModel { ... func makeVideoPlayerComponent() -> VideoPlayerComponent { preparePlayerIfNeeded() var component = VideoPlayerComponent(avPlayer: player) ... return component } ... }
上記のコードを見ていても分かる通り、ここで VideoPlayerComponent を呼び出しています。
② VideoPlayerComponent の生成と ImmersiveViewingMode 及び SpatialVideoMode の指定方法
続いて、2つ目のポイントである「 VideoPlayerComponent の生成と ImmersiveViewingMode 及び SpatialVideoMode の指定方法」についてです。
makeVideoPlayerComponent() 内で生成した VideoPlayerComponent に対して、以下の設定を追加していきます。
func makeVideoPlayerComponent() -> VideoPlayerComponent { preparePlayerIfNeeded() var component = VideoPlayerComponent(avPlayer: player) component.desiredImmersiveViewingMode = .full component.desiredSpatialVideoMode = .spatial return component }
新たに追加した desiredImmersiveViewingMode 及び desiredSpatialVideoMode の設定項目についてそれぞれ見ていきたいと思います。
まず desiredImmersiveViewingMode は、没入体験用のメディアコンテンツを「どの見せ方で再生するか」を指定するプロパティとなっています。
つまり、冒頭で説明した VideoPlayerComponent.ImmersiveViewingMode のパターンを設定する箇所となっています。
次にdesiredSpatialVideoMode は、空間ビデオを「通常の立体スクリーン映像として出すか」それとも「空間ビデオらしい奥行き表現付きで出すか」を指定するプロパティとなっています。
つまり、 VideoPlayerComponent.SpatialVideoMode のパターンを設定する箇所です。
上記では、 desiredImmersiveViewingMode に .full 、desiredSpatialVideoMode に .spatial が設定されているため、下記のような見え方で空間ビデオが再生されます。
(本来は没入感があり、また奥行きのある映像コンテンツが再生されているのですが、実際にApple Vision Proを装着して見ないと伝わりづらい...)

また、 desiredImmersiveViewingMode には .portal 、desiredSpatialVideoMode には .spatial が設定されている場合は下記のような見え方で空間ビデオが再生されます。

上記の2つのポイントをおさえておくと、比較的簡単に空間ビデオの再生画面を実装することができます。
③ ビデオプレイヤーのモード変更などを検知する VideoPlayerEvents について
そして、3つ目のポイントとして挙げている「ビデオプレイヤーのモード変更などを検知する VideoPlayerEvents について」も最後にご紹介したいと思います。
VideoPlayerEvents は、主に VideoPlayerComponent に何らかの変更が発生した際に、それを検知するためのイベント群です。
| Event | 概要 |
|---|---|
ImmersiveViewingModeWillTransition |
VideoPlayerComponent の Immersive モードが .full へ切り替わる前に飛んでくるイベント |
ImmersiveViewingModeDidChange |
VideoPlayerComponent.ImmersiveViewingMode が .full、.portal、.progressive のいずれかへ変更されたタイミングで飛んでくるイベント |
ImmersiveViewingModeDidTransition |
VideoPlayerComponent の Immersive モードが .full への切り替えを完了したときに飛んでくるイベント |
RenderingStatusDidChange |
VideoPlayerComponent のレンダリング状態が変化したときに飛んでくるイベント |
VideoComfortMitigationDidOccur |
没入型ビデオの再生中に、システム側の Comfort Mitigation が発動したことを知らせるイベント |
ContentTypeDidChange |
VideoPlayerComponent が扱っているビデオの種類が変わったときに飛んでくるイベント |
ViewingModeDidChange |
VideoPlayerComponent.ViewingMode が変更されたときに飛んでくるイベント |
VideoSizeDidChange |
VideoPlayerComponent が表示しているビデオのサイズ情報が変わったときに飛んでくるイベント |
SpatialVideoModeDidChange |
VideoPlayerComponent.SpatialVideoMode が変更されたときに飛んでくるイベント |
上記のEventを下記のように購読することで、検知できます。
これによって、 VideoPlayerComponent に対する様々な変更を検知し、それに応じて簡単に適切な処理を実行することができます。
struct ImmersiveView: View { ... var body: some View { RealityView { content in ... _ = content.subscribe(to: VideoPlayerEvents.ImmersiveViewingModeDidChange.self, on: videoEntity) { event in Task { @MainActor in appModel.actualPlayerImmersiveMode = event.currentMode } } } ... } ... }
3. まとめ
今回は、 VideoPlayerComponent を使って visionOS 上で空間ビデオを再生するための基本的な実装方法を見てきました。
やること自体はそこまで複雑ではなく、 ImmersiveSpace 内で Entity を用意し、その Entity に VideoPlayerComponent を設定することで、空間ビデオ再生の土台を作ることができます。
また、 desiredImmersiveViewingMode と desiredSpatialVideoMode を組み合わせることで、「ポータルっぽく見せるのか」「しっかり没入させるのか」「空間ビデオとして奥行きを有効にするのか」といった体験を切り替えられるのも、 VideoPlayerComponent の分かりやすいポイントです。
空間ビデオ再生というと少し身構えてしまいますが、まずは .portal と .full を試しながら挙動の違いを見ていくと、かなり理解しやすいと思います。
さらに、実装してみると意外と大事なのが VideoPlayerEvents です。
Immersive モードの変化だけでなく、レンダリング状態、 SpatialVideoMode 、コンテンツ種別などもイベントとして拾えるため、「今プレイヤーがどういう状態なのか」をアプリ側で把握しやすくなります。
このあたりを押さえておくと、表示切り替えに合わせたUI更新や状態管理もしやすくなり、プレイヤー実装がかなり組み立てやすくなります。
空間ビデオは見た目のインパクトが強い一方で、実装としては AVPlayer と RealityKit をうまくつないでいく感覚に近いので、visionOSアプリ作成の入口としてもかなり触りやすい内容になっているかなと思います。
この記事が、これから空間ビデオ再生に触れてみたい方の最初の一歩になれば嬉しいです。