[0.9.1] FORK_IA_UX v7: Karvan calls Fase 1 — video/mic UI + media over the existing WebRTC

Adds a call panel to the Karvan room (local video + remote gallery + call/mic/cam/hang-up
buttons) and grafts getUserMedia + addTrack + ontrack onto the peer connections we already
use for the text data-channel (perfect negotiation handles the renegotiation). No backend,
no new !important. Verified: getUserMedia path attaches audio+video tracks to the local
video and activates the panel (chromium fake device). getUserMedia will fail gracefully on
the current wrapper (no camera/mic permission) with a clear message — the chat keeps working.
Fases 2 (SSB cross-device signaling) and 3 (wrapper permissions) pending.
This commit is contained in:
s1to 2026-08-07 19:44:37 +02:00
parent 423e973f02
commit e2598ab747
3 changed files with 134 additions and 3 deletions

View file

@ -3,7 +3,7 @@
// autodestrucción) y, si conectan pares, por data-channel WebRTC (instantáneo). El <script> solo
// se carga en la página de sala; el WebRTC usa data-channel (sin cámara/mic => sin permisos extra).
const { div, section, h2, h3, p, a, form, input, button, span, ul, li, script, label } =
const { div, section, h2, h3, p, a, form, input, button, span, ul, li, script, label, video } =
require("../server/node_modules/hyperaxe");
const { template, i18n } = require("./main_views");
@ -80,6 +80,22 @@ exports.karvanRoomView = (room, messages, params = {}) =>
span({ class: "kl-dot" }),
span({ class: "kl-txt", id: "karvan-live-txt" }, i18n.karvanRelay || "server relay")
),
// Fase 1: panel de videollamada (media P2P sobre el mismo WebRTC del chat). La galería
// aparece al iniciar; los botones cablean cámara/micro. En el móvil actual getUserMedia
// fallará hasta que el wrapper conceda permisos (Fases 2/3) → se avisa en #kc-status.
div({ class: "karvan-call", id: "karvan-call" },
div({ class: "karvan-videos", id: "karvan-videos" },
video({ id: "karvan-local", class: "kc-video kc-local", muted: "muted", autoplay: "autoplay", playsinline: "playsinline" }),
div({ class: "kc-remote", id: "karvan-remote" })
),
div({ class: "karvan-call-bar" },
button({ type: "button", class: "kc-btn kc-btn-start", id: "kc-start" }, "📞 " + (i18n.karvanCall || "Call")),
button({ type: "button", class: "kc-btn", id: "kc-mic", "aria-label": "mic" }, "🎤"),
button({ type: "button", class: "kc-btn", id: "kc-cam", "aria-label": "camera" }, "🎥"),
button({ type: "button", class: "kc-btn kc-btn-stop", id: "kc-stop", "aria-label": "hang up" }, "⛔")
),
div({ class: "kc-status", id: "kc-status" }, "")
),
ul({ class: "karvan-msgs", id: "karvan-msgs" }, ...(messages || []).map(renderMsg)),
form({ class: "karvan-compose", method: "POST", action: "/karvan/" + room.id + "/msg", id: "karvan-form" },
input({ type: "hidden", name: "from", value: params.selfId || "" }),