PageSourceSearch

https://ocupat.es/assets/controllers/video_recorder_controller-4454b8aa.js

js ocupat.es collected 2026-09-27 10:11:27 UTC 3,003 bytes, 106 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2import { DirectUpload } from "@rails/activestorage"
3
4// Graba una videoentrevista con la cámara del navegador y la sube
5// directamente a S3 (ActiveStorage direct upload), sin pasar el archivo
6// por un worker de Puma.
7export default class extends Controller {
8  static targets = ["video", "startButton", "stopButton", "submitButton", "signedIdField", "status"]
9  static values = { directUploadUrl: String }
10
11  connect() {
12    this.chunks = []
13    this.stream = null
14    this.mediaRecorder = null
15    this.blob = null
16    this.stopButtonTarget.disabled = true
17    this.submitButtonTarget.disabled = true
18  }
19
20  disconnect() {
21    this.releaseCamera()
22  }
23
24  async start() {
25    try {
26      this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
27    } catch (error) {
28      this.setStatus("No se pudo acceder a la cámara/micrófono: " + error.message)
29      return
30    }
31
32    this.videoTarget.srcObject = this.stream
33    this.videoTarget.muted = true
34    this.videoTarget.play()
35
36    this.chunks = []
37    this.mediaRecorder = new MediaRecorder(this.stream, { mimeType: "video/webm" })
38    this.mediaRecorder.ondataavailable = (event) => {
39      if (event.data.size > 0) this.chunks.push(event.data)
40    }
41    this.mediaRecorder.onstop = () => this.handleStop()
42    this.mediaRecorder.start()
43
44    this.startButtonTarget.disabled = true
45    this.stopButtonTarget.disabled = false
46    this.setStatus("Grabando…")
47  }
48
49  stop() {
50    if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") {
51      this.mediaRecorder.stop()
52    }
53  }
54
55  handleStop() {
56    this.releaseCamera()
57
58    this.blob = new Blob(this.chunks, { type: "video/webm" })
59    this.videoTarget.srcObject = null
60    this.videoTarget.src = URL.createObjectURL(this.blob)
61    this.videoTarget.muted = false
62    this.videoTarget.controls = true
63
64    this.stopButtonTarget.disabled = true
65    this.submitButtonTarget.disabled = false
66    this.setStatus("Grabación lista. Revísala y pulsa Enviar.")
67  }
68
69  releaseCamera() {
70    if (this.stream) {
71      this.stream.getTracks().forEach((track) => track.stop())
72      this.stream = null
73    }
74  }
75
76  upload(event) {
77    event.preventDefault()
78
79    if (!this.blob) {
80      this.setStatus("Graba una respuesta antes de enviarla.")
81      return
82    }
83
84    this.submitButtonTarget.disabled = true
85    this.setStatus("Subiendo grabación…")
86
87    const file = new File([this.blob], "videoentrevista.webm", { type: "video/webm" })
88    const upload = new DirectUpload(file, this.directUploadUrlValue)
89
90    upload.create((error, blob) => {
91      if (error) {
92        this.setStatus("No se pudo subir la grabación: " + error)
93        this.submitButtonTarget.disabled = false
94        return
95      }
96
97      this.signedIdFieldTarget.value = blob.signed_id
98      this.setStatus("Enviando…")
99      this.element.requestSubmit()
100    })
101  }
102
103  setStatus(message) {
104    this.statusTarget.textContent = message
105  }
106}

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.