Flags de recurso e modo canário

O Media3 apresenta novas otimizações, melhorias de comportamento e melhorias de arquitetura em todas as bibliotecas. Embora essas mudanças sejam testadas e verificadas minuciosamente antes do lançamento, o Media3 inclui uma infraestrutura centralizada de flags de recursos (androidx.media3.common.Flags) centrada no modo canário para que os aplicativos controlem como e quando novos comportamentos são adotados.

Nesta página, explicamos como o modo canário e as flags de recurso funcionam e como usá-los no aplicativo para reduzir os riscos de upgrade entre as versões, executar implantações controladas e configurar reversões de emergência.

Visão geral

O modo canário agrupa as melhorias de biblioteca recém-introduzidas e verificadas em um botão global unificado:

  • Ativado por padrão: nas versões padrão do Media3, o modo Canary é ativado por padrão (true). Isso garante que seu app se beneficie automaticamente de melhorias de desempenho, otimizações e novos recursos seguros.
  • Capacidade de desativação e reversão: os aplicativos podem desativar o modo canário globalmente ou substituir flags de recursos específicas individualmente no ambiente de execução.
  • Zero complexidade de fiação: as flags são gerenciadas pela classe central Flags no módulo common, eliminando a necessidade de setters personalizados ou objetos de configuração em diferentes componentes.

Por que usar sinalizações no app?

Embora o modo canário esteja ativo por padrão, integrar o Flags à configuração ou ao framework de experimentos do app oferece vários benefícios:

1. Como reduzir o risco de upgrade de versão

O upgrade de versões principais ou secundárias da biblioteca na produção pode introduzir interações inesperadas em dispositivos OEM específicos ou pipelines de mídia personalizados. Ao atualizar o Media3, use o modo canário e a configuração remota para um fluxo de upgrade mais seguro entre as versões:

  1. Envie a atualização com o modo canário desativado: o lançamento de uma nova versão do Media3 com o modo canário desativado preserva o conjunto de atributos da versão anterior (com o modo canário ativado), permitindo que você verifique a estabilidade da biblioteca de referência e as correções de bugs gerais na produção antes de adotar novos comportamentos.
  2. Ativar o modo canário progressivamente: use a configuração remota para ativar o modo canário em etapas em coortes de usuários (por exemplo, 5% → 25% → 100%) ou execute experimentos A/B para medir o impacto das métricas.
  3. Reverter se necessário: se ocorrer uma regressão em dispositivos específicos, desative remotamente o modo canário ou o ID de flag específico sem precisar de uma versão de emergência do app.
  4. Adotar como padrão: depois de verificado, deixe o modo canário ativado por padrão até o próximo upgrade de versão, em que os recursos canário verificados anteriormente se tornam permanentes.

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. Lançamentos graduais controlados e testes A/B

Em vez de expor 100% dos usuários aos novos comportamentos da biblioteca de uma só vez, você pode conectar Flags a uma configuração remota ou a um sistema de experimentos.

É possível ativar o modo canário em etapas (por exemplo, 5% dos usuários → 25% → 100%) ou realizar experimentos A/B para medir o impacto de novas otimizações nas principais métricas de reprodução, como latência de inicialização, taxa de armazenamento em buffer e falhas de reprodução.

3. Interruptor de emergência remoto e reversão rápida

Se ocorrer uma regressão na produção em um modelo de dispositivo ou versão do SO específico, você poderá desativar remotamente o modo canário (ou o ID da flag responsável) usando a configuração do lado do servidor. Isso reverte imediatamente a biblioteca para o comportamento de fallback estável e legado para os usuários afetados, sem exigir um hotfix de emergência do app ou uma versão binária.

Como configurar o modo canário e as flags

A API Flags oferece métodos para controlar o modo canário global e as substituições de flags individuais.

Modo canário global

Para ativar ou desativar todas as melhorias cobertas pelo modo canário:

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);

Como substituir flags individuais

Também é possível ativar ou desativar explicitamente flags específicas, independentemente da configuração global do modo 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);

As substituições explícitas sempre têm precedência sobre o padrão global do modo canário.

Verificar o status da sinalização

Para verificar se o modo canário ou uma flag específica está ativa no seu código:

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);

Ordem de resolução de flags

Quando o Media3 verifica se um recurso está ativado no momento da execução, ele resolve o estado na seguinte ordem:

  1. Substituição explícita do ambiente de execução: se enableFlag ou disableFlag tiver sido chamado para o ID da flag, o valor de substituição será retornado.
  2. Estado da biblioteca estática: se a flag estiver desativada estaticamente (por exemplo, um recurso de desenvolvimento inicial que ainda não está pronto para o modo canário) ou ativada estaticamente (um recurso que saiu do modo canário e manteve um substituto temporário de desativação), esse valor estático será retornado.
  3. Estado do modo canário: caso contrário, a flag segue a configuração global do modo canário (ativada se o modo canário for true, desativada se for false).

Requisitos de inicialização

Configure as flags antes de instanciar qualquer componente do Media3, como ExoPlayer, Transformer ou MediaSession:

  • Configuração antecipada: configure o Flags durante a inicialização do aplicativo (por exemplo, em Application.onCreate) ou antes de inicializar a camada de mídia do app.
  • Concorrência segura: os componentes do Media3 leem flags durante a instanciação e propagam esses valores para as linhas de execução internas de reprodução e processamento. Verifique se a configuração da flag ocorre antes de criar componentes do Media3. Não é possível modificar as flags enquanto os players ou transformadores estão em execução.

Como fazer a integração com a configuração remota

Uma arquitetura recomendada é expor três parâmetros na plataforma de configuração remota:

  • media3_canary_mode_enabled (booleano): alternância global para o modo canário (o padrão é true).
  • media3_force_enable_flags (lista de números inteiros): IDs de sinalização específicos para forçar a ativação de testes segmentados.
  • media3_force_disable_flags (lista de números inteiros): IDs de sinalizações específicas para desativar se um problema for descoberto.

Aplique esses valores durante a inicialização antes de criar instâncias do player:

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);
  }
}

Como testar com flags

Ao escrever testes de unidade ou do Robolectric para caminhos de código afetados por flags de recurso, use androidx.media3.test.utils.Media3FlagsRule (disponível no módulo media3-test-utils) para garantir que os testes não poluam o estado estático uns dos outros.

Limpeza automática com Media3FlagsRule

Adicionar Media3FlagsRule garante que todas as substituições de flag e modificações do modo Canary sejam redefinidas automaticamente após cada teste:

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...
  }
}

Teste parametrizado

É possível combinar Media3FlagsRule com as anotações @BindFlag e @BindCanaryMode para executar testes parametrizados em estados de flag:

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.
  }
}

Diagnóstico e geração de registros

O Media3 oferece ferramentas de diagnóstico para verificar as flags ativas e os estados do modo Canary durante a inicialização da reprodução e no momento da execução.

Registros de inicialização

Quando uma instância ExoPlayer é inicializada, ela mostra o estado ativo do Canary e as substituições explícitas no logcat, além da versão da biblioteca:

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

Se as substituições estiverem ativas, somente os desvios do estado padrão serão listados:

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

Inspeção programática

É possível recuperar a string de diagnóstico formatada ou as listas de substituição de consulta de forma programática (por exemplo, para incluir o estado da flag nos relatórios de falhas ou análises do app):

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();

Ciclo de vida da sinalização

As flags no Media3 são projetadas para serem temporárias:

  1. Desenvolvimento: uma nova otimização ou recurso é adicionado por trás de um ID de flag em Flags.java.
  2. Modo canário: depois de verificado e pronto para uso geral, o recurso é ativado no modo canário em todas as versões.
  3. Promoção e limpeza: após um período de teste que comprove a estabilidade na produção, o recurso é promovido para ativação permanente na biblioteca, e a constante de flag, os caminhos de código substitutos e as verificações condicionais são removidos.

Se você encontrar um problema com uma flag de recurso, registre um relatório no Issue Tracker do GitHub do Media3 do AndroidX para que a equipe possa resolvê-lo antes da promoção permanente.