(function () { if (typeof BridgeSwarmExamples === 'undefined') return; var logEl = document.getElementById('log'); function log(msg, type) { BridgeSwarmExamples.logLine(logEl, msg, type); } var live = document.getElementById('live'); var clip = document.getElementById('clip'); var out = document.getElementById('out'); var poster = document.getElementById('poster'); var recState = document.getElementById('recState'); var progress = document.getElementById('progress'); var captureStream = null; var recorder = null; var chunks = []; var recordedBlob = null; var lastJobPath = null; var lastOutPath = null; function setRec(label, livePill) { recState.textContent = label || ''; recState.className = 'bs-pill' + (livePill ? ' bs-pill--live' : ''); } function blobToBase64(blob) { return new Promise(function (resolve, reject) { var reader = new FileReader(); reader.onload = function () { var s = String(reader.result); var i = s.indexOf(','); resolve(i >= 0 ? s.slice(i + 1) : s); }; reader.onerror = reject; reader.readAsDataURL(blob); }); } async function uploadBlob(blob, filename) { var CHUNK = 500000; // ~base64 under NMH when chunked via multiple writeInput var buf = await blob.arrayBuffer(); var bytes = new Uint8Array(buf); var jobId = 'clip_' + Date.now(); var offset = 0; var first = true; while (offset < bytes.length) { var end = Math.min(offset + CHUNK, bytes.length); var slice = bytes.subarray(offset, end); var binary = ''; for (var i = 0; i < slice.length; i++) binary += String.fromCharCode(slice[i]); var dataBase64 = btoa(binary); await window.BridgeSwarm.media.writeInput({ jobId: jobId, filename: filename, dataBase64: dataBase64, append: !first, }); first = false; offset = end; progress.textContent = 'Upload ' + Math.round((offset / bytes.length) * 100) + '%'; } return { jobId: jobId, path: jobId + '/' + filename }; } async function startCapture(kind) { stopCapture(); try { if (kind === 'screen') { captureStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false }); } else { captureStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); } live.srcObject = captureStream; document.getElementById('btnRecord').disabled = false; document.getElementById('btnStopCapture').disabled = false; log('Capture started (' + kind + ')', 'val'); captureStream.getVideoTracks()[0].addEventListener('ended', function () { stopCapture(); }); } catch (e) { log(e.message, 'err'); } } function stopCapture() { if (recorder && recorder.state !== 'inactive') { try { recorder.stop(); } catch (_) {} } if (captureStream) { captureStream.getTracks().forEach(function (t) { t.stop(); }); captureStream = null; } live.srcObject = null; document.getElementById('btnRecord').disabled = true; document.getElementById('btnStopCapture').disabled = true; document.getElementById('btnStopRecord').disabled = true; setRec(''); } function startRecord() { if (!captureStream) return; chunks = []; recordedBlob = null; var mime = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' : 'video/webm'; try { recorder = new MediaRecorder(captureStream, { mimeType: mime }); } catch (e) { recorder = new MediaRecorder(captureStream); } recorder.ondataavailable = function (e) { if (e.data && e.data.size) chunks.push(e.data); }; recorder.onstop = function () { recordedBlob = new Blob(chunks, { type: recorder.mimeType || 'video/webm' }); clip.src = URL.createObjectURL(recordedBlob); document.getElementById('clipStage').hidden = false; document.getElementById('btnProcess').disabled = false; document.getElementById('btnPoster').disabled = false; setRec('Recorded ' + Math.round(recordedBlob.size / 1024) + ' KB'); log('Recording ready (' + recordedBlob.size + ' bytes)', 'val'); }; var maxMs = (Number(document.getElementById('maxSec').value) || 8) * 1000; recorder.start(250); document.getElementById('btnRecord').disabled = true; document.getElementById('btnStopRecord').disabled = false; setRec('Recording…', true); log('Recording up to ' + (maxMs / 1000) + 's', 'sys'); setTimeout(function () { if (recorder && recorder.state === 'recording') stopRecord(); }, maxMs); } function stopRecord() { if (!recorder || recorder.state === 'inactive') return; recorder.stop(); document.getElementById('btnRecord').disabled = !captureStream; document.getElementById('btnStopRecord').disabled = true; } BridgeSwarmExamples.waitForBridgeSwarm().then(async function () { var has = await window.BridgeSwarm.capabilities.has('media'); if (!has) { document.getElementById('missing').hidden = false; log('Media capability missing', 'err'); return; } log('Media pack ready', 'val'); document.getElementById('btnScreen').onclick = function () { startCapture('screen'); }; document.getElementById('btnCamera').onclick = function () { startCapture('camera'); }; document.getElementById('btnStopCapture').onclick = stopCapture; document.getElementById('btnRecord').onclick = startRecord; document.getElementById('btnStopRecord').onclick = stopRecord; document.getElementById('btnProcess').onclick = async function () { if (!recordedBlob) return; try { progress.textContent = 'Uploading…'; var up = await uploadBlob(recordedBlob, 'clip.webm'); lastJobPath = up.path; log('Uploaded to ' + up.path, 'sys'); progress.textContent = 'Transcoding…'; var r = await window.BridgeSwarm.media.transcode( { path: up.path, format: 'webm', width: 640, height: 360, jobId: up.jobId + '_x' }, { timeoutMs: 300000, onProgress: function (p) { if (p.bytes) progress.textContent = Math.round(p.bytes / 1024) + ' KB encoded'; }, } ); lastOutPath = r.path; progress.textContent = 'done'; log('Transcode → ' + (r.path || ''), 'val'); if (r.path) { var read = await window.BridgeSwarm.media.readOutput({ path: r.path, mimetype: 'video/webm' }); out.src = 'data:video/webm;base64,' + read.dataBase64; document.getElementById('outStage').hidden = false; document.getElementById('btnDrive').disabled = false; } } catch (e) { progress.textContent = ''; log(e.message, 'err'); } }; document.getElementById('btnPoster').onclick = async function () { if (!recordedBlob && !lastJobPath) return; try { var path = lastJobPath; var jobId; if (!path) { var up = await uploadBlob(recordedBlob, 'clip.webm'); path = up.path; jobId = up.jobId; lastJobPath = path; } progress.textContent = 'Extracting frame…'; var r = await window.BridgeSwarm.media.extractFrame({ path: path, frameIndex: 0, mimetype: 'image/webp', jobId: (jobId || 'poster') + '_f', }); poster.src = 'data:' + (r.mimetype || 'image/webp') + ';base64,' + r.dataBase64; document.getElementById('posterStage').hidden = false; progress.textContent = ''; log('Poster frame ready', 'val'); } catch (e) { progress.textContent = ''; log(e.message, 'err'); } }; document.getElementById('btnDrive').onclick = async function () { if (!lastOutPath) return; try { var read = await window.BridgeSwarm.media.readOutput({ path: lastOutPath, mimetype: 'video/webm' }); await window.BridgeSwarm.request('drivePut', { path: '/clips/' + Date.now() + '.webm', base64: read.dataBase64, }); log('Saved to Hyperdrive /clips/…', 'val'); } catch (e) { log(e.message, 'err'); } }; }).catch(function (e) { log(e.message, 'err'); }); })();