Modernizando a interface do usuário de reprodução na Web

de Corey Grunewald e Matt Jaquish

Netflix Technology Blog Blocked Unblock Seguir Seguindo 11 de dezembro de 2018

Desde 2013, a experiência do usuário de reproduzir vídeos no site da Netflix mudou muito pouco. Durante esse período, as equipes da Netflix lançaram incríveis recursos de reprodução de vídeo , mas o design visual e os controles de usuário da interface de reprodução continuaram os mesmos.

O design visual e os controles de usuário da reprodução são os mesmos desde 2013.

Nos últimos dois anos, a equipe de interface da Web teve uma meta de longo prazo para modernizar a experiência do usuário de reprodução para nossos membros. A reprodução consiste em três telas principais:

  • Pre Play: Um vídeo a ser mostrado aos membros antes do conteúdo principal (por exemplo, uma recapitulação da temporada).
  • Reprodução de vídeo: o membro está assistindo ao conteúdo selecionado e tem acesso aos controles para pausar o vídeo, alterar o volume etc.
  • Post Play: quando o conteúdo selecionado termina, os membros são apresentados com o próximo episódio de uma série ou recomendações sobre o que assistir em seguida.

Por meio de testes AB e aprendizado subsequente, lançamos uma experiência de interface de reprodução moderna e atualizada para nossos membros. Queremos compartilhar nossa jornada de como chegamos onde estamos hoje.

A nova e moderna interface de reprodução

Se no começo você não tiver sucesso …

A partir de 2016, nossa principal prioridade para o esforço de modernização foi começar a usar o React para criar e renderizar os componentes da interface do usuário de reprodução. Enquanto o restante do site passou a usar o React for the UI no verão de 2015, a interface do usuário de reprodução continuou a usar uma estrutura JavaScript personalizada e personalizada. Apenas alguns engenheiros da equipe tinham experiência com a estrutura, o que poderia criar gargalos ao trabalhar com correções ou recursos. Ao mudar para o React, permitiríamos que mais desenvolvedores contribuíssem para a construção de uma experiência melhor, devido à familiaridade e à ergonomia fornecidas.

Além de melhorar o rendimento do desenvolvedor, precisamos eliminar a intrincada ponte que criamos entre a estrutura personalizada e os componentes React existentes usados no site. Construir a interface de reprodução com o React significava que poderíamos nos livrar dessa complexidade.

Como na maioria das alterações de produto na Netflix, tratamos a modernização da interface de reprodução como um teste AB. Com nossos parceiros de design visual e análise de dados, elaboramos um projeto de teste AB. Nossa célula de controle seria nosso design visual atual e conjunto de recursos usando a estrutura personalizada para renderizar a interface do usuário. Nosso tratamento experimental seria um novo design visual para todas as telas de reprodução (pré-reprodução, reprodução de vídeo e pós-reprodução), com os componentes da interface do usuário criados com o React.

Nós estávamos animados para começar. Havia um campo verde à nossa frente! No entanto, logo percebemos que estávamos muito empolgados para começar a construir e projetar, e não gastamos tempo suficiente pensando se devíamos .

Trabalhamos durante meses criando novos componentes do React, portando sobre a lógica e reescrevendo o CSS para o novo design visual. No verão de 2016, estávamos prontos para lançar o teste. Drumroll por favor… nós falhamos.

Fazendo muito de uma vez

Os membros que usam o novo design de player criado no React estavam transmitindo menos conteúdo. Ficamos confusos de que o teste não foi uma vitória. Presumimos que os usuários não teriam problemas com o novo design visual, pois agora ele estava alinhado com o restante do website e com as interfaces de usuário de reprodução de outras plataformas. Tivemos que investigar onde estávamos prejudicando a experiência do usuário – e encontramos alguns lugares.

Isolando Mudanças

Nosso projeto de teste inicial teve uma falha fatal – mudamos o design visual e a arquitetura de interface do usuário subjacente. Quando obtivemos os resultados negativos do teste, foi difícil determinar qual alteração causava impacto. Foi o design, a arquitetura da interface do usuário ou ambos? Depois de uma análise mais detalhada das métricas, descobrimos que tanto o novo design visual quanto a mudança para o React estavam impactando os membros de maneiras diferentes. Isso se tornou uma lição difícil de garantir que todas as suas variáveis de teste sejam isoladas.

Perda de desempenho de renderização

Ao mudar para o React, alteramos fundamentalmente a arquitetura da interface do usuário para cada tela de reprodução. Ao analisar as métricas de desempenho do teste AB, descobrimos que nossa abordagem específica ao uso do React para criar componentes estava realmente fazendo com que a inicialização da reprodução demorasse mais tempo do que a nossa estrutura personalizada, além de descartar mais quadros de vídeo.

Histograma de tempos de carregamento de reprodução usando a estrutura personalizada (verde), sobreposta com React (vermelho).

Ficamos surpresos com essa descoberta, mas depois de uma comparação mais profunda entre nossa implementação de framework personalizado e nosso uso do React, entendemos por que havia uma lacuna. Nossa estrutura personalizada foi vinculada diretamente aos eventos do player de vídeo para obter o estado da interface do usuário. Cada classe de componente criaria um nó DOM, aguardaria a emissão de um evento do player de vídeo e atualizaria os atributos no nó DOM com base nos dados do evento. Enquanto isso, no React, nós utilizamos o fluxo de dados unidirecional fazendo com que um componente raiz recebesse todo o estado do player de vídeo, o que então passaria esse estado para todos os componentes filhos. A nova renderização para cada alteração de estado do player de vídeo desse componente raiz contribuiu para o delta de desempenho.

Tente, tente novamente

Armados para saber o que foi negativamente afetado no teste inicial, fomos encarregados de corrigir esses problemas e limpar nosso projeto de teste AB.

Design de Teste

Decidimos que o próximo teste AB precisava focar apenas nas alterações de arquitetura da interface do usuário. Mudar para o React não significa que o design visual da interface de reprodução precisa mudar também. O plano era replicar o design visual existente em cima de nossos componentes React.

Necessito de velocidade

Para corrigir a lacuna na velocidade de inicialização, primeiro tínhamos que medir onde o tempo estava sendo gasto durante a renderização da interface do usuário. A instrumentação foi adicionada para rastrear o tempo que levou para atingir os principais marcos da reprodução. Esta instrumentação foi na forma de usar marcadores de desempenho em todos os nossos componentes:

 // Inside Player.jsx ... 

componentDidMount () {
performance.mark (
`playbackMilestone: start: $ { this .state.playbackView}`
);
}

componentDidUpdate (prevProps, prevState) {
if (prevState.playbackView! == this .state.playbackView) {
performance.mark (
`playbackMilestone: end: $ { this .state.playbackView}`
);
performance.mark (
`playbackMilestone: start: $ { this .state.playbackView}`
);
}
 if ( this .state.playbackView === PlaybackViews.PLAYBACK) { 
// serializar e registrar dados de desempenho de reprodução.
}
}

Os dados do marco mostraram que renderizar a visualização de carregamento inicial no React estava demorando muito mais tempo em comparação com nossa célula de controle. Descobrimos que estávamos renderizando o componente de controles de reprodução em paralelo com o componente de carregamento. Ao garantir que os controles fossem processados somente quando a reprodução de vídeos estivesse pronta, melhoramos nossos tempos de renderização enquanto o player de vídeo estava sendo carregado.

O próximo passo foi evitar a queda de quadros durante a reprodução de vídeo. As ferramentas de desempenho incorporadas do React foram usadas para definir o perfil do tempo de renderização do componente. Nós fizemos várias etapas para melhorar os tempos de renderização:

  • Reaja com as melhores práticas: Garantimos que os componentes de interface do usuário estavam implementando as práticas recomendadas ao usar o React, ou seja, usando o ciclo de vida shouldComponentUpdate quando necessário.
  • Menos HOCs : Sempre que possível, migramos do uso de componentes de ordem mais alta, fazendo a transição para o uso de funções de utilitário ou movendo a lógica para um componente pai
  • Prop Spreads e Collapsing Props: Spreading props faz com que o tempo seja gasto na iteração de objetos. Recolher vários suportes em um único objeto, quando possível, ajuda a reduzir o tempo de comparação no ciclo de vida shouldComponentUpdate.
  • Observabilidade: retirando uma página do manual do framework personalizado, introduzimos a observação do estado do player de vídeo em componentes que precisam ser renderizados com mais freqüência. Isso ajudou a reduzir os ciclos de renderização em nosso componente raiz.

Com o design visual e as alterações de desempenho realizadas, um novo teste AB foi lançado. Depois de pacientemente esperar, os resultados estavam dentro, outro drumroll por favor … membros transmitiram a mesma quantidade com a interface de reprodução do React em comparação com o framework personalizado! No verão de 2017, lançamos o React em reprodução para todos os membros.

Sob o capô: simplificando a lógica de reprodução

Além de usar o React para tornar a camada do componente de UI mais acessível e fácil de desenvolver em várias equipes, queríamos fazer o mesmo para a lógica de negócios relacionada ao player. Temos várias equipes trabalhando em diferentes tipos de lógica de reprodução ao mesmo tempo, como: títulos interativos (em que o usuário faz escolhas para participar da história), reprodução de filmes e episódios, visualizações de vídeo na experiência de navegação e conteúdo exclusivo durante Pós-jogo

Optamos por usar o Redux para fornecer uma única fonte e encapsular a lógica de negócios de reprodução complexa. O Redux é uma biblioteca / padrão bem conhecido na engenharia da interface do usuário da Web e facilita a separação de interesses de maneiras que atendam aos nossos objetivos. Ao combinar o Redux com a normalização de dados, permitimos o desenvolvimento paralelo entre as equipes, além de fornecer formas padronizadas e previsíveis de expressar lógica de negócios complexa.

Separando o ciclo de vida de vídeo do ciclo de vida da interface do usuário

Permitir que a árvore de componentes de UI controle a lógica referente ao ciclo de vida do vídeo real pode resultar em uma experiência de usuário lenta. As árvores de componentes de UI geralmente têm seu ciclo de vida representado em um conjunto padronizado de métodos, como componentDidMount do React, componentDidUpdate etc. Quando a lógica para criar uma nova reprodução de vídeo é oculta em um método de ciclo de vida da interface do usuário que está dentro de uma árvore de componentes, o usuário deve esperar até que esse componente específico seja chamado antes que a reprodução possa ser iniciada. Após ser iniciado, o usuário deve esperar até que a reprodução seja carregada o suficiente para começar a visualizar o vídeo.

Quando a interface do usuário é processada no servidor, o DOM inicial é enviado para o cliente. Este DOM não inclui um vídeo carregado ou nenhum dado em buffer necessário para iniciar a reprodução. No caso do React, a UI do cliente precisa se reconstruir sobre esse DOM inicial e depois passar por uma sequência de ciclo de vida para começar a carregar o vídeo.

No entanto, se a lógica para gerenciar a reprodução de vídeo existir fora da árvore de componentes da interface do usuário, ela poderá ser executada em qualquer lugar dentro do aplicativo, como antes da árvore da interface do usuário ser renderizada durante a sequência inicial de carregamento do aplicativo. Ao iniciar a criação de um vídeo em paralelo com a renderização da interface do usuário, ele fornece mais tempo para o aplicativo criar, inicializar e armazenar em buffer a reprodução de vídeo para que, quando a interface terminar a renderização, o usuário possa começar a reproduzir o vídeo mais rapidamente.

Padronizando a representação de dados de uma reprodução de vídeo

Como a reprodução de vídeo é composta de uma série de eventos dinâmicos, ela pode representar um problema quando há partes diferentes de um aplicativo que se importam com o estado de uma reprodução de vídeo. Por exemplo, uma parte de um aplicativo pode ser responsável pela criação de um vídeo, outra parte responsável por configurá-lo com base nas preferências do usuário e ainda outro responsável por gerenciar o controle em tempo real de reprodução, pausa e busca.

Para encapsular o conhecimento de uma reprodução de vídeo, criamos uma estrutura de dados padronizada para representá-lo. Fomos então capazes de criar um único local central para armazenar a estrutura de dados para cada reprodução de vídeo, de modo que tanto a lógica de negócios quanto a interface do usuário pudessem acessá-las. Isso possibilitou regras inteligentes que governam as reproduções de vídeo, várias interfaces de usuário que operam em um único conjunto de dados e testes mais fáceis.

A estrutura de dados de reprodução padronizada pode ser criada a partir de qualquer fonte de vídeo: uma biblioteca de vídeos personalizada ou um elemento de vídeo HTML padrão. Usar os dados normalizados libera a interface do usuário de ter que saber sobre a implementação de vídeo específica.

Adicionando suporte para várias reproduções de vídeo

Quando temos os dados de reprodução para cada vídeo existente de fonte única no aplicativo independente da interface do usuário, ele permite que o aplicativo defina regras de lógica de negócios que coordenam reproduções únicas ou múltiplas de vídeo. Se cada vídeo estivesse oculto dentro de uma determinada instância de um componente de interface do usuário, e os componentes existissem em áreas completamente diferentes da interface do usuário, seria difícil coordenar e forçaria os componentes de interface do usuário a se conhecerem quando provavelmente não deveriam .

Algumas áreas da lógica que se tornam mais fáceis com os dados de reprodução independentes da interface do usuário e vários players são:

  • Controle de volume e mudo.
  • Reproduzir e pausar o controle.
  • Precedência de reprodução para reprodução automática.
  • Restrições no número de jogadores que podem coexistir.

Uma implementação do estado do aplicativo

A fim de fornecer um local bem estruturado para o estado independente da interface, decidimos aproveitar novamente o Redux. No entanto, também sabíamos que precisaríamos permitir que várias equipes trabalhassem na base de código à medida que adicionassem e removessem a lógica que seria independente e não exigida em todos os casos de uso. Como resultado, criamos uma camada extremamente fina sobre o núcleo do Redux que nos permitiu empacotar arquivos relacionados a domínios específicos da lógica e, então, compor os aplicativos Redux desses domínios.

Um domínio em nosso sistema é simplesmente um objeto estático que contém os seguintes itens:

  • Estrutura de dados do estado.
  • Redutor de estado.
  • Ações.
  • Middleware.
  • API personalizada para consultar o estado.

Um aplicativo pode escolher se compor fora de domínios ou não usá-los. Quando um domínio é usado para criar um aplicativo, as partes individuais do domínio são automaticamente vinculadas ao seu próprio estado de domínio; ele não terá acesso a nenhuma outra parte do estado do aplicativo fora do que foi definido. O bom é que a API externa final do aplicativo é a mesma, quer use domínios ou não, graças ao poder da composição.

Nós capacitamos dois casos de uso: um aplicativo Redux padrão de nível único, em que cada parte conhece todo o estado ou um aplicativo em que cada domínio é imposto para gerenciar apenas sua própria parte do subestado do aplicativo. O benefício de identificar áreas de lógica que podem ser encapsuladas em um domínio lógico é que o domínio pode ser facilmente adicionado, removido ou transferido para qualquer outro aplicativo sem quebrar mais nada.

Ativando a lógica Plug & Play

Aproveitando nosso conceito de domínios, pudemos continuar trabalhando nos principais recursos de interface do usuário de reprodução, enquanto outras equipes implementaram domínios lógicos personalizados para conectar-se ao nosso aplicativo de player, como lógica para títulos interativos. Os títulos interativos têm lógica de reprodução personalizada, bem como interfaces de usuário personalizadas para permitir que o usuário faça escolhas de histórias durante a reprodução. Agora que tínhamos a interface do usuário bem encapsulada (via React) e o estado com lógica associada (via Redux e nossos domínios), tínhamos um sistema para gerenciar a complexidade em várias frentes. Como continuamente testamos muitos recursos, o encapsulamento consistente da lógica facilita muito a adição e remoção de lógica com base em dados de teste AB ou em sinalizadores de recursos. Ter uma estrutura reforçada e consistente, pensando em termos de domínios lógicos, também nos ajudou a identificar e formalizar áreas de nossa aplicação que antes eram inconsistentes. Ao adicionar estrutura e previsibilidade e desistir da absoluta liberdade de fazer qualquer coisa de qualquer forma, isso realmente nos liberou e a outras equipes para adicionar mais recursos, realizar mais testes e criar códigos de maior qualidade.

Um novo casaco de tinta com um motor melhor

Com padrões de gerenciamento e desenvolvimento de estado novos e aprimorados para os engenheiros colegas usarem, nossa etapa final na jornada de modernização foi atualizar o design visual da interface do usuário.

De nosso aprendizado anterior sobre isolamento de mudanças, o plano era criar um teste AB que focalizasse apenas a atualização da interface do usuário na experiência de reprodução de vídeo e a não modificação de outras telas de reprodução ou da arquitetura.

Ao utilizar nossa implementação do Redux e estender os componentes existentes do React, ficou mais fácil do que nunca transformar nossos protótipos de projeto em código de produção para o teste AB. Lançamos o teste no verão de 2018. Recuperamos os resultados no outono e descobrimos que os membros preferiam o design visual moderno junto com novos controles de player de vídeo que permitiam que eles procurassem para frente e para trás ou pausassem o vídeo simplesmente clicando na tela.

Este teste AB final na jornada de modernização foi fácil de implementar e analisar. Ao cometer erros e aprender com eles, construímos a intuição e as melhores práticas para garantir que você não esteja tentando fazer muitas coisas ao mesmo tempo.