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.