フィーチャー トグルとカナリアモード

Media3 では、ライブラリ全体で新しい最適化、動作の改善、アーキテクチャの強化が導入されています。これらの変更は、リリース前に徹底的にテストおよび検証されますが、Media3 には、カナリアモードを中心とした一元化されたフィーチャー トグル インフラストラクチャ(androidx.media3.common.Flags)が含まれており、アプリケーションは新しい動作の採用方法とタイミングを制御できます。

このページでは、カナリアモードとフィーチャー トグルの仕組みと、アプリケーションでこれらを使用して、リリース全体でアップグレードのリスクを軽減し、制御されたロールアウトを実行し、緊急ロールバックを構成する方法について説明します。

概要

カナリアモードでは、新たに導入された検証済みのライブラリの改善が、統合されたグローバル トグルにグループ化されます。

  • デフォルトで有効: 標準の Media3 リリースでは、カナリアモードがデフォルトで有効になっています(true)。これにより、アプリは安全なパフォーマンスの向上、最適化、新機能のメリットを自動的に享受できます。
  • オプトアウトとロールバック機能: アプリケーションは、カナリアモードをグローバルに無効にしたり、実行時に特定のフィーチャー トグルを個別にオーバーライドしたりできます。
  • 配線の複雑さがゼロ: フラグは common モジュールの Flags クラスで一元管理されるため、異なるコンポーネント間でカスタムのセッターや構成オブジェクトを使用する必要がありません。

アプリでフラグを使用する理由

カナリアモードはデフォルトで有効になっていますが、Flags をアプリの構成またはテスト フレームワークに統合すると、次のようなメリットがあります。

1. バージョン アップグレードのリスクを軽減する

本番環境でライブラリのメジャー バージョンまたはマイナー バージョンをアップグレードすると、特定の OEM デバイスやカスタム メディア パイプラインで予期しないインタラクションが発生する可能性があります。Media3 を更新する際は、カナリアモードとリモート構成を使用することで、リリース全体でより安全なアップグレード フローを実現できます。

  1. カナリアモードを無効にしてアップデートをリリースする: カナリアモードをオフにして新しい Media3 バージョンをリリースすると、以前のリリース(カナリアモードが有効)の機能セットがほぼ維持されます。これにより、新しい動作を採用する前に、本番環境でベースライン ライブラリの安定性と一般的なバグの修正を確認できます。
  2. カナリアモードを段階的に有効にする: リモート構成を使用して、ユーザー コホート全体でカナリアモードを段階的に有効にするか(5% → 25% → 100% など)、A/B テストを実施して指標への影響を測定します。
  3. 必要に応じてロールバックする: 特定のデバイスで回帰が発生した場合は、緊急のアプリ リリースを必要とせずに、カナリアモードまたは特定のフラグ ID をリモートで無効にします。
  4. デフォルトとして採用: 検証が完了したら、次のバージョン アップグレードまでカナリアモードをデフォルトで有効のままにします。これにより、以前に検証されたカナリア機能が永続化されます。

Kotlin

// Enable or disable Canary mode globally (defaults to true).
Flags.setCanaryModeEnabled(false)

Java

// Enable or disable Canary mode globally (defaults to true).
Flags.setCanaryModeEnabled(false);

2. 管理された段階的なリリースと A/B テスト

すべてのユーザーに新しいライブラリの動作を一度に公開するのではなく、Flags をリモート構成システムまたはテストシステムに接続できます。

カナリアモードを段階的に有効にしたり(例: 5% のユーザー → 25% → 100%)、A/B テストを実施して、新しい最適化が起動レイテンシ、リバッファ率、再生エラーなどのコア再生指標に与える影響を測定したりできます。

3. リモート キルスイッチと迅速なロールバック

特定のデバイスモデルまたは OS バージョンの本番環境で回帰が発生した場合は、サーバーサイドの構成を使用して、カナリアモード(または原因となっている特定のフラグ ID)をリモートで無効にできます。これにより、影響を受けるユーザーに対して、ライブラリは安定した以前のフォールバック動作にすぐに戻ります。緊急のアプリ ホットフィックスやバイナリ リリースは必要ありません。

カナリアモードとフラグの構成

Flags API には、グローバルなカナリアモードと個々のフラグのオーバーライドを制御するメソッドが用意されています。

グローバル カナリアモード

Canary モードの対象となるすべての拡張機能を有効または無効にするには:

Kotlin

// Enable or disable Canary mode globally (defaults to true).
Flags.setCanaryModeEnabled(false)

Java

// Enable or disable Canary mode globally (defaults to true).
Flags.setCanaryModeEnabled(false);

個々のフラグのオーバーライド

グローバルな Canary モードの設定とは別に、特定のフラグを明示的に有効または無効にすることもできます。

Kotlin

// Explicitly enable an experimental flag.
Flags.enableFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION)

// Explicitly disable a flag.
Flags.disableFlag(Flags.FLAG_DYNAMIC_SCHEDULING)

Java

// Explicitly enable an experimental flag.
Flags.enableFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION);

// Explicitly disable a flag.
Flags.disableFlag(Flags.FLAG_DYNAMIC_SCHEDULING);

明示的なオーバーライドは、常にグローバルなカナリアモードのデフォルトよりも優先されます。

フラグのステータスを確認する

コードで Canary モードまたは特定のフラグが有効になっているかどうかを確認するには:

Kotlin

// Query whether Canary mode is active globally.
val isCanaryActive = Flags.isCanaryModeEnabled()

// Query whether a specific flag is active.
val isFeatureActive = Flags.isEnabled(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION)

Java

// Query whether Canary mode is active globally.
boolean isCanaryActive = Flags.isCanaryModeEnabled();

// Query whether a specific flag is active.
boolean isFeatureActive = Flags.isEnabled(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION);

フラグの解決順序

Media3 は、実行時に機能が有効になっているかどうかを確認する際、次の順序で状態を解決します。

  1. 明示的なランタイム オーバーライド: フラグ ID に対して enableFlag または disableFlag が呼び出された場合、そのオーバーライド値が返されます。
  2. 静的ライブラリの状態: フラグが静的に無効になっている場合(Canary モードの準備がまだできていない初期開発機能など)や、静的に有効になっている場合(Canary モードから移行した機能で、一時的なオプトアウト フォールバックを保持している場合)は、その静的な値が返されます。
  3. Canary モードの状態: それ以外の場合、フラグはグローバル Canary モードの設定に従います(Canary モードが true の場合は有効、false の場合は無効)。

初期化の要件

Media3 コンポーネント(ExoPlayer、Transformer、MediaSession など)をインスタンス化する前にフラグを構成します。

  • 早期セットアップ: アプリケーションの起動時(Application.onCreate 内など)またはアプリのメディアレイヤの初期化前に Flags を構成します。
  • スレッド セーフティ: Media3 コンポーネントは、インスタンス化中にフラグを読み取り、それらの値を内部の再生スレッドと処理スレッドに伝播します。フラグ構成が Media3 コンポーネントの作成前に行われるようにします。プレーヤーまたはトランスフォーマーがアクティブに実行されているときにフラグを変更することはサポートされていません。

リモート構成との統合

推奨されるアーキテクチャは、リモート構成プラットフォームで次の 3 つのパラメータを公開することです。

  • media3_canary_mode_enabled(ブール値): カナリアモードのグローバル切り替え(デフォルトは true)。
  • media3_force_enable_flags(整数のリスト): ターゲット トライアルで強制有効化する特定のフラグ ID。
  • media3_force_disable_flags(整数のリスト): 問題が検出された場合に強制的に無効にする特定のフラグ ID。

プレーヤー インスタンスを作成する前に、起動時に次の値を適用します。

Kotlin

fun initializeMedia3Flags(
  canaryModeEnabled: Boolean,
  forceEnabledFlags: List<Int>,
  forceDisabledFlags: List<Int>,
) {
  // 1. Set global Canary mode state.
  Flags.setCanaryModeEnabled(canaryModeEnabled)

  // 2. Apply explicit force-enable overrides.
  for (flagId in forceEnabledFlags) {
    Flags.enableFlag(flagId)
  }

  // 3. Apply explicit force-disable overrides (remote kill switch).
  for (flagId in forceDisabledFlags) {
    Flags.disableFlag(flagId)
  }
}

Java

public static void initializeMedia3Flags(
    boolean canaryModeEnabled,
    List<Integer> forceEnabledFlags,
    List<Integer> forceDisabledFlags) {
  // 1. Set global Canary mode state.
  Flags.setCanaryModeEnabled(canaryModeEnabled);

  // 2. Apply explicit force-enable overrides.
  for (int flagId : forceEnabledFlags) {
    Flags.enableFlag(flagId);
  }

  // 3. Apply explicit force-disable overrides (remote kill switch).
  for (int flagId : forceDisabledFlags) {
    Flags.disableFlag(flagId);
  }
}

フラグを使用したテスト

フィーチャー トグルの影響を受けるコードパスの単体テストまたは Robolectric テストを作成する場合は、androidx.media3.test.utils.Media3FlagsRule(media3-test-utils モジュールで使用可能)を使用して、テストが互いの静的状態を汚染しないようにします。

Media3FlagsRule による自動クリーンアップ

Media3FlagsRule を追加すると、各テストの後にすべてのフラグ オーバーライドとカナリアモードの変更が自動的にリセットされます。

Kotlin

class MyPlayerTest {

  @get:Rule val flagsRule = Media3FlagsRule(this)

  @Test
  fun playback_withFeatureFlagEnabled() {
    Flags.enableFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION)
    // Run test with the flag enabled...
  }
}

Java

public static class MyPlayerTest {

  @Rule public final Media3FlagsRule flagsRule = new Media3FlagsRule(this);

  @Test
  public void playback_withFeatureFlagEnabled() {
    Flags.enableFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION);
    // Run test with the flag enabled...
  }
}

パラメータ化テスト

Media3FlagsRule を @BindFlag アノテーションおよび @BindCanaryMode アノテーションと組み合わせると、フラグの状態全体でパラメータ化されたテストを実行できます。

Kotlin

class MyParameterizedPlayerTest {

  @BindCanaryMode var canaryMode: Boolean = false

  @BindFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION) var perStreamProgression: Boolean = false

  @get:Rule val flagsRule = Media3FlagsRule(this)

  @Test
  fun playback() {
    // Flags are automatically set for each test parameter combination.
  }
}

Java

public static class MyParameterizedPlayerTest {

  @BindCanaryMode public boolean canaryMode;

  @BindFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION)
  public boolean perStreamProgression;

  @Rule public final Media3FlagsRule flagsRule = new Media3FlagsRule(this);

  @Test
  public void playback() {
    // Flags are automatically set for each test parameter combination.
  }
}

診断とロギング

Media3 には、再生の初期化時と実行時に、有効なフラグと Canary モードの状態を確認するための診断ツールが用意されています。

初期化ログ

ExoPlayer インスタンスが初期化されると、アクティブな Canary 状態と明示的なオーバーライドが、ライブラリ バージョンとともに logcat に出力されます。

ExoPlayerImpl: Init 59a18ab [AndroidXMedia3/1.10.1] [flame, Pixel 4, Google, 33] [Canary:ON]

オーバーライドが有効になっている場合は、デフォルトの状態からの逸脱のみが一覧表示されます。

ExoPlayerImpl: Init 59a18ab [AndroidXMedia3/1.10.1] [flame, Pixel 4, Google, 33] [Canary:ON, Overrides: enabled=[1], disabled=[3]]

プログラムによる検査

フォーマットされた診断文字列またはクエリ オーバーライド リストをプログラムで取得できます(たとえば、アプリのクラッシュ レポートや分析にフラグの状態を含める場合)。

Kotlin

// Get formatted string representing active Canary state and overrides.
val flagLogString = Flags.getLogString()

// Query explicit override IDs if needed.
val enabledFlags = Flags.getEnabledOverrides()
val disabledFlags = Flags.getDisabledOverrides()

Java

// Get formatted string representing active Canary state and overrides.
String flagLogString = Flags.getLogString();

// Query explicit override IDs if needed.
int[] enabledFlags = Flags.getEnabledOverrides();
int[] disabledFlags = Flags.getDisabledOverrides();

フラグのライフサイクル

Media3 のフラグは一時的なものとして設計されています。

  1. 開発: 新しい最適化または機能が、Flags.java のフラグ ID の背後に追加されます。
  2. カナリアモード: 検証が完了し、一般的に使用できる状態になると、この機能はリリース全体でカナリアモードで有効になります。
  3. プロモーションとクリーンアップ: 本番環境での安定性が証明されるソーク期間が経過すると、機能はライブラリで永続的に有効化され、フラグ定数、フォールバック コードパス、条件チェックは削除されます。

フィーチャー トグルで問題が発生した場合は、AndroidX Media3 GitHub Issue Tracker で報告してください。チームが恒久的なプロモーションの前に対応します。