Files
BridgeSwarm/examples/clip-studio/app.js
T
Raven Scott 906422d0a5
CI / Build & Test (push) Successful in 8m12s
Updates
2026-07-26 23:23:52 -04:00

236 lines
8.2 KiB
JavaScript

(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');
});
})();