1import { Controller } from "@hotwired/stimulus" 2 3// O VÃDEO DA LIÃÃO â começa sozinho, sem som, em laço. Ou não começa. 4// 5// Por que autoplay e laço são LIGADOS AQUI e não por atributo no HTML: 6// 7// · `prefers-reduced-motion: reduce` é um pedido, não um enfeite. CSS não 8// interrompe reprodução â só o JavaScript consegue não começar. Com 9// `<video autoplay loop>` no HTML, quem pede menos movimento receberia o 10// laço mesmo assim, e a preferência viraria decoração. 11// · sem JavaScript, o resultado passa a ser pôster parado com controles à 12// mão, que é um estado bom. Nunca uma tela preta. 13// 14// O AUTOPLAY PODE SER RECUSADO pelo navegador, e isso não é erro: `play()` 15// devolve uma promessa que rejeita, e o pôster com o botão de play continua ali. 16// Engolir a rejeição em silêncio é o certo â o que não pode é virar um erro 17// vermelho no console do cliente. 18export default class extends Controller { 19 static targets = ["midia", "estado", "moldura"] 20 static values = { id: String, movimentoReduzido: String, toqueParaComecar: String, naoCarregou: String } 21 22 connect() { 23 if (!this.hasMidiaTarget) return 24 25 // A faixa que veio no HTML é o "Carregandoâ¦", e ela pode sair sozinha. Um 26 // RECADO é outra coisa: alguém decidiu escrever ali. Ver `pronto()`. 27 this.recado = false 28 this.midiaTarget.muted = true 29 30 // O EVENTO PODE TER PASSADO ANTES DE NÃS. Turbo troca o palco e o Stimulus 31 // conecta um instante depois; num vÃdeo em cache o `loadeddata` já 32 // aconteceu, e esperar por ele deixaria a faixa "Carregandoâ¦" pousada para 33 // sempre sobre um vÃdeo que já está rodando. `readyState >= 2` é a mesma 34 // pergunta feita de outro jeito: "já há quadro para mostrar?". 35 if (this.midiaTarget.readyState >= 2) this.pronto() 36 37 if (this.movimentoReduzido) { 38 // Quem pediu menos movimento fica com o pôster e o texto. O vÃdeo 39 // continua ali, à mão, para quem mudar de ideia e apertar play. 40 this.mostrarEstado(this.movimentoReduzidoValue) 41 return 42 } 43 44 this.midiaTarget.loop = true 45 this.tocar() 46 } 47 48 disconnect() { 49 // O Turbo Frame troca o palco inteiro ao mudar de lição; parar aqui evita 50 // que um elemento a caminho do lixo continue baixando bytes. 51 if (this.hasMidiaTarget) this.midiaTarget.pause() 52 } 53 54 tocar() { 55 const promessa = this.midiaTarget.play() 56 if (promessa && typeof promessa.catch === "function") { 57 promessa.catch(() => { 58 this.mostrarEstado(this.toqueParaComecarValue) 59 }) 60 } 61 } 62 63 // Primeiro quadro decodificado: a faixa de CARREGAMENTO sai. à `loadeddata` 64 // e não `canplaythrough` de propósito â em conexão ruim o segundo pode nunca 65 // chegar, e a faixa ficaria girando sobre um vÃdeo que já está rodando. 66 // 67 // E Sà A FAIXA DE CARREGAMENTO SAI. Antes isto apagava o que estivesse ali,
68 // fosse o que fosse: quem pediu menos movimento lia "VÃdeo parado: você 69 // pediu menos movimento ao sistema." e via a frase evaporar no instante em 70 // que o primeiro quadro decodificava, ficando diante de um vÃdeo parado, com 71 // "sem som / 22 s" escrito em cima e explicação nenhuma. O mesmo acontecia 72 // com "Toque no vÃdeo para começar" quando o navegador recusa o autoplay â 73 // apagar a instrução é apagar a única saÃda da tela. 74 pronto() { 75 this.element.classList.add("curso-video--pronto") 76 if (this.hasEstadoTarget && !this.recado) this.estadoTarget.hidden = true 77 } 78 79 // ESTà TOCANDO: nenhum dos três recados vale mais. Não é "toque para 80 // começar" (já começou), não é "vÃdeo parado" (não está) e não é "não 81 // carregou" (carregou). Só aqui um recado deliberado é retirado. 82 tocando() { 83 this.recado = false 84 if (this.hasEstadoTarget) this.estadoTarget.hidden = true 85 } 86 87 falhou() { 88 this.element.classList.add("curso-video--falhou") 89 this.mostrarEstado(this.naoCarregouValue) 90 } 91 92 // TELA CHEIA A UM TOQUE. A tela gravada é de 1440 de largura e a moldura da 93 // lição tem menos da metade disso no computador, e um quarto no celular: o 94 // texto da tela gravada não se lê ali, e mandar a pessoa caçar o botãozinho 95 // dos controles nativos é fingir que se lê. O `<video>` tem `controls`, mas 96 // este botão é o convite explÃcito. 97 // 98 // No iPhone o `requestFullscreen` do elemento não existe: o Safari só abre 99 // vÃdeo em tela cheia pelo `webkitEnterFullscreen`, e ele mora no próprio 100 // <video>, nunca na moldura. 101 telaCheia(evento) { 102 evento?.preventDefault() 103 104 const moldura = this.hasMolduraTarget ? this.molduraTarget : this.element 105 const pedido = moldura.requestFullscreen || moldura.webkitRequestFullscreen 106 107 if (pedido) { 108 const resposta = pedido.call(moldura) 109 if (resposta && typeof resposta.catch === "function") resposta.catch(() => {}) 110 return 111 } 112 113 if (this.midiaTarget.webkitEnterFullscreen) this.midiaTarget.webkitEnterFullscreen() 114 } 115 116 mostrarEstado(texto) { 117 if (!this.hasEstadoTarget) return 118 119 this.recado = true 120 this.estadoTarget.hidden = false 121 this.estadoTarget.textContent = texto 122 } 123 124 get movimentoReduzido() { 125 return window.matchMedia("(prefers-reduced-motion: reduce)").matches 126 } 127}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.