Observabilidade do cliente: Web

O SDK de vídeo da Vonage disponibiliza métricas detalhadas sobre a qualidade do fluxo por meio de uma API de estatísticas de alto nível — recomendada para a maioria dos casos de uso —, que fornece estatísticas de áudio, vídeo, rede e do lado do remetente de forma unificada e sensível à sessão, mantendo-se estável durante as transições entre conexões entre pares. Para depuração avançada, o SDK também oferece acesso ao relatório bruto de estatísticas do WebRTC, que reflete dados não processados das conexões entre pares.

O SDK também disponibiliza métricas de condição de rede que oferecem uma avaliação geral do estado da conexão, tanto para emissores quanto para assinantes. Essas métricas incluem uma pontuação de condição de rede, o motivo por trás dessa pontuação e — para os assinantes — uma fonte de degradação que indica qual lado da conexão é responsável por quaisquer problemas observados. Consulte Condição da rede e fonte de degradação para mais detalhes.

O Vonage Video Web SDK envia estatísticas periódicas de áudio, vídeo e links de mídia tanto para editores quanto para assinantes. Essas estatísticas incluem contagem de pacotes, taxas de bits, dados de taxa de quadros, métricas de pausa/congelamento, informações sobre codecs e métricas de rede no nível de transporte, como estimativa de largura de banda e pontuação das condições da rede.

Como obter estatísticas para uma editora

O Publisher.getStats() O método fornece um array de objetos que definem as estatísticas atuais de áudio e vídeo do editor. Para um editor em uma sessão roteada (aquela que utiliza o OpenTok Roteador de mídia), esse array inclui um objeto, que define as estatísticas do único fluxo de áudio e vídeo enviado ao Vonage Video Media Router. Em uma sessão retransmitida, o array inclui um objeto para cada assinante do fluxo publicado.

O código a seguir registra algumas métricas do fluxo do editor a cada segundo:

window.setInterval(() => {
  publisher.getStats((error, statsArray) => {
    if (error) {
      console.error(error);
      return;
    }

    statsArray.forEach(statsContainer => {
      const stats = statsContainer.stats;
      const connectionId = stats.connectionId || 'routed';

      console.log(`\nStats for ${connectionId}`);
      if (stats.video) {
        const video = stats.video;

        if (video.layers && video.layers.length > 0) {
          console.log(`Video layers: ${video.layers.length}`);

          video.layers.forEach((layer, index) => {
            console.log(` Layer ${index}: ${layer.width}x${layer.height}`);
            console.log(`   encodedFrameRate: ${layer.encodedFrameRate} fps`);
            console.log(`   bitrate: ${layer.bitrate} bps`);
            console.log(`   totalBitrate: ${layer.totalBitrate} bps`);
            console.log(`   codec: ${layer.codec}`);
            console.log(`   scalabilityMode: ${layer.scalabilityMode}`);
            if (layer.qualityLimitationReason) {
              console.log(`   qualityLimitationReason: ${layer.qualityLimitationReason}`);
            }
          });
        }

        console.log('transport estimated bandwidth:', stats.mediaLink.transport.connectionEstimatedBandwidth, 'bps');
        console.log('network condition:', stats.mediaLink.transport.networkCondition);
        console.log('network condition reason:', stats.mediaLink.transport.networkConditionReason);
      }
    });
  });
}, 1000);

Recebimento de eventos de qualidade de vídeo nos editores

Além das estatísticas das pesquisas com Publisher.getStats(), você pode receber notificações em tempo real quando o editor detectar uma alteração significativa na qualidade do vídeo, inscrevendo-se no videoQualityChanged evento:

publisher.on('videoQualityChanged', ({ reason, statsContainer }) => {
  console.log('Video quality change reason:', reason);

  const { stats } = statsContainer;

  if (stats.video && stats.video.layers) {
    stats.video.layers.forEach((layer) => {
      console.log(
        `Resolution: ${layer.width}x${layer.height}, FPS: ${layer.frameRate}`
      );
    });
  }
});

Recebimento de eventos de condição de rede nos editores

Para receber eventos de alteração nas condições da rede para o editor, escute o networkConditionChanged evento:

publisher.on('networkConditionChanged', ({ reason, statsContainer }) => {
  const { stats } = statsContainer;
  console.log('Network condition changed.');
  console.log(`Network Condition: ${stats.mediaLink.transport.networkCondition}, Reason: ${stats.mediaLink.transport.networkConditionReason}`);
});

Este evento é acionado quando é detectada uma mudança significativa nas condições da rede para o editor. O statsContainer O objeto inclui as estatísticas do link de mídia do assinante afetado. Em sessões retransmitidas, apenas as estatísticas do assinante afetado são incluídas no evento.

Como obter estatísticas de um assinante

O getStats() O método de um objeto assinante fornece informações sobre o fluxo do assinante.

O código a seguir registra várias métricas do fluxo do assinante a cada segundo:

window.setInterval(() => {
  subscriber.getStats((error, stats) => {
    if (error) {
      console.error('Error getting subscriber stats: ', error.message);
      return;
    }

    const video = stats.video;

    if (video) {
      console.log('video bitrate:', video.bitrate, 'bps');
      console.log('video totalBitrate:', video.totalBitrate, 'bps');
      console.log('decoded frame rate:', video.decodedFrameRate, 'fps');
      console.log('codec:', video.codec);
      console.log('res:', `${video.width}x${video.height}`);

      console.log('freezeCount:', video.freezeCount);
      console.log('totalFreezesDuration:', video.totalFreezesDuration, 'ms');
      console.log('pauseCount:', video.pauseCount);
      console.log('totalPausesDuration:', video.totalPausesDuration, 'ms');
    }
  });
}, 1000);

Recebimento de eventos de qualidade de vídeo nos assinantes

Os assinantes podem ouvir o videoQualityChanged evento para ser notificado quando forem detectadas interrupções ou alterações significativas na qualidade do vídeo.

subscriber.on('videoQualityChanged', ({ reason, stats }) => {
  if (reason === 'videoInterruption') {
    console.warn('Video playback was interrupted');

    if (stats.video.freezeCount > 0) {
      console.log(`Freeze count: ${stats.video.freezeCount}`);
    }

    if (stats.video.pauseCount > 0) {
      console.log(`Pause count: ${stats.video.pauseCount}`);
    }
  }
});

Recebimento de eventos de condição da rede nos assinantes

Para receber eventos de alteração nas condições da rede para o assinante, escute o networkConditionChanged evento:

subscriber.on('networkConditionChanged', ({ reason, stats }) => {
  console.log('Network condition changed.');
  console.log(`Degradation source: ${stats.mediaLink.networkDegradationSource}`);
  if (stats.mediaLink.networkDegradationSource === 'local') {
    console.log(`Network Condition: ${stats.mediaLink.transport.networkCondition}, Reason: ${stats.mediaLink.transport.networkConditionReason}`);
  } else if (stats.mediaLink.networkDegradationSource === 'remote') {
    console.log(`Network Condition: ${stats.mediaLink.remotePublisherTransport.networkCondition}, Reason: ${stats.mediaLink.remotePublisherTransport.networkConditionReason}`);
  }
});

Este evento é acionado quando é detectada uma alteração significativa nas condições da rede para o assinante ou para o editor remoto. O stats O objeto inclui as estatísticas do link de mídia, com métricas de transporte local e remoto e a origem da degradação.

Problemas conhecidos

Os valores e condições reais que acionam as limitações de qualidade dependem da implementação e podem variar entre navegadores e plataformas. Por exemplo:

  • O compartilhamento de tela de transmissões de vídeo nunca aciona o videoQualityChanged evento.
  • O Firefox não oferece suporte a qualityLimitationReason, portanto, essa propriedade não consta nas estatísticas do editor. Além disso, videoQualityChanged eventos com motivos bandwidth, cpu e other não são compatíveis com este navegador.
  • A codificação de vídeo acelerada por hardware e os codificadores de vídeo dedicados impedem que o macOS acione cpu limitações.

Estatística e Estruturas de Dados

Esta seção descreve as estruturas e propriedades fornecidas pela API de estatísticas de áudio e vídeo da Web. Embora todas as plataformas do SDK de vídeo apresentem o mesmo conjunto de estatísticas, pode haver pequenas diferenças na forma como cada plataforma estrutura ou nomeia os campos individuais. Essas variações refletem convenções de design específicas do SDK de cada plataforma, e não diferenças nas métricas subjacentes.

Para obter uma explicação independente de plataforma sobre as estatísticas disponíveis e o que elas representam, consulte Visão geral da observabilidade do cliente.

Estatísticas da editora (stats)

Fornece estatísticas sobre um editor.

  • connectionId — O ID exclusivo da conexão do cliente, que corresponde à propriedade id do connection propriedade do connectionCreated evento que o objeto Session enviou para o cliente remoto (disponível apenas em sessões retransmitidas).
  • subscriberId — O ID exclusivo do assinante, que corresponde à propriedade id do Subscriber objeto no aplicativo do cliente assinante (disponível apenas em sessões retransmitidas).

Estatísticas de áudio da editora (stats.audio)

Fornece estatísticas sobre a faixa de áudio de um editor.

  • bytesSent — Total de bytes de áudio enviados.
  • packetsLost — Total de pacotes de áudio que não chegaram ao assinante ou ao Media Router.
  • packetsSent — Total de pacotes de áudio enviados.
  • timestamp — Carimbo de data/hora do Unix (ms) em que as estatísticas foram coletadas.

Estatísticas de vídeo do editor (stats.video)

Esses campos representam o desempenho atual dos vídeos do editor:

  • bytesSent — Total de bytes de vídeo enviados.
  • packetsLost — Total de pacotes de vídeo que não chegaram ao assinante ou ao Media Router.
  • packetsSent — Total de pacotes de vídeo enviados.
  • layers — Uma lista ordenada das camadas de codificação de vídeo ativas, da resolução mais alta à mais baixa.

Estatísticas da camada de vídeo do editor (stats.video.layers)

Representa uma camada de transmissão simultânea ou uma camada SVC.

  • width — Largura codificada em pixels.
  • height — Altura codificada em pixels.
  • encodedFrameRate— Taxa de quadros real de codificação para esta camada.
  • bitrate — Taxa de bits da carga útil (bps).
  • totalBitrate — Taxa de bits, incluindo cabeçalhos RTP e preenchimento (bps).
  • scalabilityMode— Configuração de escalabilidade (por exemplo, “L1T3” para SVC ou “L3T3” para transmissão simultânea).
  • codec — Codec utilizado nesta camada.
  • qualityLimitationReason — Indica o motivo pelo qual o codificador ajustou a qualidade (“largura de banda”, “CPU”, “outros”).

O transport O objeto fornece métricas de estimativa de rede no nível da conexão entre pares que se aplicam ao transporte geral de áudio e vídeo, e não a faixas ou camadas individuais.

  • connectionEstimatedBandwidth — Largura de banda estimada disponível no sentido de envio para a conexão (bps).
  • networkCondition — Pontuação atual do estado da rede ("unknown", "critical", "warning", "fair", "good", ou "excellent").
  • networkConditionReason — Principal motivo que afeta o estado da rede ("none", "unknown", "bandwidth", ou "packetLoss").

Estatísticas de vídeo dos inscritos (stats.video)

Esses campos descrevem o desempenho do assinante na recepção e decodificação de vídeo em tempo real:

  • bytesReceived — Total de bytes de vídeo recebidos.
  • packetsLost — Total de pacotes de vídeo que não chegaram ao assinante.
  • packetsReceived — Total de pacotes de vídeo recebidos.
  • timestamp — Carimbo de data/hora do Unix (ms) em que as estatísticas foram coletadas.
  • decodedFrameRate — Taxa de quadros real gerada pelo decodificador (fps).
  • bitrate — Taxa de bits da carga útil em bits por segundo.
  • totalBitrate — Taxa de bits, incluindo cabeçalhos RTP e preenchimento (bps).
  • codec — Codec utilizado para este assinante.
  • pauseCount — Número de pausas em que nenhum quadro foi renderizado por ≥5 segundos.
  • totalPausesDuration — Duração acumulada (ms) de todas as pausas.
  • freezeCount — Número de pequenos congelamentos (conforme a definição das estatísticas do WebRTC).
  • totalFreezesDuration — Duração acumulada (ms) de todos os travamentos.

Estimativa do lado do remetente do assinante (stats.senderStats)

Essas métricas fornecem estimativas de largura de banda relatadas para a conexão de saída do remetente:

  • connectionMaxAllocatedBitrate — Taxa de bits máxima alocada estimada para o remetente (bps).
  • connectionEstimatedBandwidth — Largura de banda estimada atual do uplink para o remetente (bps).

O mediaLink O objeto fornece informações sobre o nível de transporte e a degradação da rede para a conexão de um assinante. Ele possui a mesma estrutura que o do editor mediaLink.transport para os transportes locais e remotos, além de um indicador de fonte de degradação:

  • transport — Estatísticas de transporte local para a conexão de downlink deste assinante (mesma estrutura que a do editor) stats.mediaLink.transport). Pode ser limitado se as estatísticas do remetente e/ou o recurso alternativo de áudio estiverem desativados.
  • remotePublisherTransport — Estatísticas de transporte do editor remoto para a conexão de uplink (mesma estrutura que a do editor) stats.mediaLink.transport). Pode ser limitado se as estatísticas do remetente e/ou o recurso alternativo de áudio estiverem desativados.
  • networkDegradationSource — Indica qual lado causou a degradação da rede, se houver. Valores possíveis: "none", "local", "remote", ou "bothOrUnclear". Pode ser limitado se as estatísticas do remetente e/ou o recurso alternativo de áudio estiverem desativados.

Monitoramento da qualidade das chamadas

Além das APIs estatísticas principais, o OpenTok.js oferece recursos adicionais para monitorar e responder a mudanças na qualidade das chamadas. Esses recursos ajudam as Applications a otimizar o desempenho, adaptando-se às limitações dos dispositivos e às condições da rede.

Monitoramento do desempenho da CPU

Os aplicativos podem ser executados em diversos dispositivos móveis e de mesa, em diferentes plataformas. Além disso, as especificações de hardware desses dispositivos não são homogêneas. Por exemplo, alguns dispositivos móveis podem ter melhor desempenho de CPU do que muitos dispositivos de mesa, e vice-versa.

O grande número de configurações de hardware possíveis — CPU(s), GPU, RAM, codificadores/decodificadores de hardware etc. — significa que pode ser necessário algum ajuste. Dispositivos com menor capacidade podem ser configurados para desativar recursos que exigem mais da CPU, enquanto dispositivos mais potentes podem ser configurados por padrão para oferecer uma experiência mais imersiva.

Detecção de alterações no desempenho da CPU

É possível detectar alterações na carga da CPU do dispositivo monitorando a sessão cpuPerformanceChanged evento. O evento contém um cpuPerformanceState propriedade, que é definida como um dos seguintes valores:

  • 'nominal' — O dispositivo pode assumir tarefas adicionais.
  • 'fair' — O dispositivo ainda pode realizar tarefas adicionais, mas a duração da bateria pode ser reduzida; além disso, em dispositivos com ventiladores, estes podem entrar em funcionamento e se tornar audíveis.
  • 'serious' — O dispositivo está sobrecarregado, portanto, pode ocorrer uma limitação dos recursos (por exemplo, da CPU).
  • 'critical' — O aparelho está sob grande pressão; se essa pressão não for aliviada, podem ocorrer problemas.

Para mais detalhes, consulte esta especificação do W3C.

Otimização de uma aplicação com base nas variações no desempenho da CPU

Em resposta a esse evento, uma aplicação pode notificar os usuários sobre o consumo de recursos ou desativar processos que exigem muitos recursos computacionais, tais como transformadores de vídeo. Veja a sessão cpuPerformanceChanged.

O código a seguir desativa a captura de vídeo quando a CPU entra em um 'critical' estado de desempenho e o reativa assim que o estado volta a 'fair' ou melhor:

let isVideoDisabledByCPU = false;

session.on('cpuPerformanceChanged', (event) => {
  if (event.cpuPerformanceState === 'critical') {
    // The application should alert the user why their video is being disabled
    publisher.publishVideo(false);
    isVideoDisabledByCPU = true;
  } else if (event.cpuPerformanceState === 'nominal' || event.cpuPerformanceState === 'fair') {
    if (isVideoDisabledByCPU) {
      publisher.publishVideo(true);
      isVideoDisabledByCPU = false;
    }
  }
})

Pontuação Média de Opinião (MOS)

A qualidade da experiência que um usuário percebe em relação a um serviço pode ser avaliada por meio de Pontuação Média de Avaliação (MOS).

O Sistema de Classificação

O MOS é expresso como um número positivo. A pontuação pode variar entre 1 (a pior qualidade) e 5 (a melhor qualidade):

  • 5 (Excelente) — Um limite máximo hipotético para a melhor qualidade que um usuário pode experimentar.
  • 4 (Bom) — Uma classificação mais acessível. Os usuários da Vonage podem esperar receber esse nível de qualidade.
  • 3 (Razoável) — A qualidade é aceitável.
  • 2 (Ruim) — A qualidade é inaceitável.
  • 1 (Ruim) — A qualidade é péssima.

O algoritmo

A classificação MOS leva em consideração vários fatores, todos os quais afetam (e podem prejudicar) a experiência do usuário. Esses fatores incluem (mas não se limitam a) os seguintes:

  • Perda de pacotes — os pacotes perdidos prejudicam a qualidade da transmissão
  • Taxa de bits — quanto maior a taxa de bits, maior a fidelidade potencial do arquivo de mídia
  • Latência de rede — pacotes que chegam com atraso podem ser descartados, o que pode causar falhas no áudio e/ou instabilidade no vídeo

Otimização de uma aplicação com base nas variações na qualidade das chamadas

Use o Assinante qualityScoreChanged evento para monitorar mudanças na qualidade de áudio e vídeo. No entanto, observar as mudanças na qualidade da mídia não é suficiente. Dada a natureza em tempo real dos aplicativos de chamada, também é necessário responder às mudanças observadas, ajustando seu aplicativo para oferecer continuamente a melhor experiência ao usuário.

A seguir, é apresentada uma heurística simples. Um aplicativo é otimizado dinamicamente, com base nas restrições de recursos.

// We want to know if the CPU is overloaded. If it is, then we
// can disable certain features so that the best call possible
// can still take place.
let isCpuOverloaded = false;

session.on('cpuPerformanceChanged', (event) => {
  isCpuOverloaded = event.cpuPerformanceState === 'critical';
});

// We monitor for changes in call quality. This allows us to
// tune our application, taking into account multiple factors
// (inlined below)
subscriber.on('qualityScoreChanged', (event) => {
  const { qualityScore } = event;
  const isVideoQualityBad = qualityScore.video < 2;

  if (!isVideoQualityBad && !isCpuOverloaded) {
    // Subscribe to the highest quality video since the CPU isn't taxed
    // and the quality received is good
    subscriber.setPreferredResolution('1280x720');
    subscriber.setPreferredFrameRate(30);
  }
  else if (isVideoQualityBad && !isCpuOverloaded) {
    // Even though the CPU isn't taxed, the video quality received is
    // bad. This might be due to (hopefully) intermittent network issues, so
    // we subscribe to lower quality video.
    subscriber.setPreferredResolution('320x180');
    subscriber.setPreferredFrameRate(7);
  }
  else if (isVideoQualityBad && isCpuOverloaded) {
    // The video quality received is bad and the CPU not being overloaded.
    // Let's disable video for now.
    // We can enable video once conditions improve. See statement below.
    subscriber.subscribeToVideo(false);
  }
  else {
    // Enable video
    subscriber.subscribeToVideo(true);
  }
});

Estimativa da qualidade da chamada em um teste pré-chamada

Você pode usar o Vonage Video Biblioteca de testes de rede para a Web para verificar se o cliente suporta a publicação de áudio e vídeo e para informar as pontuações MOS estimadas de áudio e vídeo para o fluxo publicado pelo cliente. Essa biblioteca utiliza a Publisher.getRtcStatsReport() e Subscriber.subscriber.getStats() métodos para calcular a pontuação MOS.

Estatísticas do lado do remetente

Veja o Visão geral das estatísticas do lado do remetente.

Ativação das estatísticas do remetente

As estatísticas do remetente são recebidas pelos assinantes. Para receber essas estatísticas, habilite-as para o editor do fluxo, passando o publishSenderStats propriedade definida como true no OT.initPublisher chamada:

const publisher = OT.initPublisher({
  publishSenderStats: true
});

Se publishSenderStats Se não estiver habilitado, nenhum canal de estatísticas do remetente será publicado para esse editor. O valor padrão é false.

Recebimento de estatísticas do lado do remetente

Se o editor tiver ativado as estatísticas do remetente, os assinantes as recebem automaticamente por meio de Subscriber.getStats() descrito acima. O senderStats A propriedade no objeto de estatísticas retornado fornece duas métricas:

  • connectionMaxAllocatedBitrate — A taxa de bits máxima que pode ser estimada para a conexão (bps)
  • connectionEstimatedBandwidth — A largura de banda estimada atual da conexão (bps)

Essas métricas são calculadas por pacote de áudio e vídeo; portanto, os mesmos valores aparecem tanto nas estatísticas de vídeo quanto nas de áudio. Observe que a primeira chamada para getStats pode não incluir estatísticas do remetente devido à latência da rede.

subscriber.getStats((stats) => {
  if (stats.senderStats) {
    console.log(`Connection max allocated bitrate: ${stats.senderStats.connectionMaxAllocatedBitrate} bps`);
    console.log(`Connection current estimated bandwidth: ${stats.senderStats.connectionEstimatedBandwidth} bps`);
  }
});

Problemas conhecidos

Em alguns casos, quando a sessão é retransmitida — ou em certas configurações de roteamento com apenas dois participantes — e o Editor usa o Firefox, as estatísticas do lado do remetente podem não estar disponíveis devido a limitações do navegador.

Condição da rede e fonte da degradação

O SDK fornece métricas em tempo real sobre as condições da rede tanto para editores quanto para assinantes, incluindo uma pontuação de condição, o motivo por trás dessa pontuação e a origem da degradação para os assinantes. Para obter uma explicação completa sobre o modelo de condições da rede, as pontuações, os motivos e como ativá-lo, consulte o Visão geral da observabilidade do cliente.

Os dados sobre o estado da rede estão disponíveis por meio de dois canais:

  • Estatísticas periódicas: As estatísticas de eventos de links de mídia incluem métricas de transporte com connectionEstimatedBandwidth, networkCondition e networkConditionReason. As estatísticas dos assinantes também revelam remotePublisherTransport e networkDegradationSource.
  • Eventos relacionados a alterações nas condições da rede: Eventos específicos em ambos editora e assinante são acionados quando é detectada uma mudança significativa nas condições da rede.

O exemplo a seguir mostra como usar os dados de condição da rede do assinante para identificar a origem da degradação:

subscriber.on('networkConditionChanged', ({ reason, stats }) => {
  console.log('Network condition changed.');
  console.log(`Degradation source: ${stats.mediaLink.networkDegradationSource}`);
  if (stats.mediaLink.networkDegradationSource === 'local') {
    console.log(`Network Condition: ${stats.mediaLink.transport.networkCondition}, Reason: ${stats.mediaLink.transport.networkConditionReason}`);
  } else if (stats.mediaLink.networkDegradationSource === 'remote') {
    console.log(`Network Condition: ${stats.mediaLink.remotePublisherTransport.networkCondition}, Reason: ${stats.mediaLink.remotePublisherTransport.networkConditionReason}`);
});

Relatório de Estatísticas da RTC

Para obter estatísticas de conexão entre pares de baixo nível para um editor, use o Publisher.getRtcStatsReport() método. Ele retorna uma promessa que, em caso de sucesso, é resolvida com um RtcStatsReport objeto para o fluxo assinado:

publisher.getRtcStatsReport()
  .then((stats) => stats.forEach(console.log))
  .catch(console.log);

Para obter estatísticas de conexão entre pares de baixo nível para um assinante, use o Subscriber.getRtcStatsReport() método. Ele retorna uma promessa que, em caso de sucesso, é resolvida com um RtcStatsReport objeto para o fluxo assinado:

subscriber.getRtcStatsReport()
  .then((stats) => stats.forEach(console.log))
  .catch(console.log);