PageSourceSearch

https://novo.gerenciamentopolitico.com.br/assets/controllers/curso_video_controller-f70936bb.js

js gerenciamentopolitico.com.br collected 2026-10-08 18:21:08 UTC 5,307 bytes, 127 lines download raw bytes

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.