chat
This commit is contained in:
1 parent
104d781614
commit
d604c01114
145 files changed
+13678
-2169
No files matched your search
@@ -0,0 +1,206 @@
|
||||
window.BackOneAddons = window.BackOneAddons || {};
|
||||
|
||||
window.BackOneAddons.video = function videoAddon() {
|
||||
let pc = null;
|
||||
let picker = null;
|
||||
let selectedPeer = null;
|
||||
let inCall = false;
|
||||
let localStream = null;
|
||||
let micEnabled = true;
|
||||
let camEnabled = true;
|
||||
let root = null;
|
||||
|
||||
function networkLabel() {
|
||||
const n = BackOneNetworkSession.getActiveNetwork();
|
||||
return n ? (n.name || n.id) : BackOneNetworkSession.getNetworkId();
|
||||
}
|
||||
|
||||
function updateConnectionBar() {
|
||||
const bar = root?.querySelector('#video-conn-bar');
|
||||
if (!bar || !window.BackOneCommLayout) return;
|
||||
bar.innerHTML = BackOneCommLayout.connectionBarHtml(
|
||||
selectedPeer,
|
||||
BackOneNetworkSession.getActiveNetwork()
|
||||
);
|
||||
}
|
||||
|
||||
function updateHeadStatus() {
|
||||
const statusEl = root?.querySelector('#video-head-status');
|
||||
const breadcrumb = root?.querySelector('#video-breadcrumb-peer');
|
||||
if (!statusEl) return;
|
||||
if (inCall && selectedPeer) {
|
||||
statusEl.innerHTML = `<span class="connected">Connected</span> · ${selectedPeer.latency != null ? selectedPeer.latency + 'ms' : '—'}`;
|
||||
if (breadcrumb) breadcrumb.textContent = selectedPeer.peerId;
|
||||
} else if (selectedPeer) {
|
||||
statusEl.textContent = 'Ready';
|
||||
if (breadcrumb) breadcrumb.textContent = selectedPeer.peerId;
|
||||
} else {
|
||||
statusEl.textContent = '';
|
||||
if (breadcrumb) breadcrumb.textContent = '—';
|
||||
}
|
||||
}
|
||||
|
||||
function updateCallButtons() {
|
||||
const callBtn = root?.querySelector('#video-call');
|
||||
if (callBtn) callBtn.disabled = !selectedPeer || !BackOneNetworkSession.getNetworkId() || inCall;
|
||||
const selectedLabel = root?.querySelector('#video-selected-label');
|
||||
if (selectedLabel) {
|
||||
selectedLabel.innerHTML = selectedPeer
|
||||
? `Selected: <strong>${selectedPeer.peerId.slice(0, 10)}…</strong>`
|
||||
: 'Selected: <strong>—</strong>';
|
||||
}
|
||||
updateHeadStatus();
|
||||
updateConnectionBar();
|
||||
}
|
||||
|
||||
function showPlaceholder(show) {
|
||||
const ph = root?.querySelector('#video-placeholder');
|
||||
const remote = root?.querySelector('#video-remote');
|
||||
if (ph) ph.style.display = show ? 'block' : 'none';
|
||||
if (remote) remote.style.display = show ? 'none' : 'block';
|
||||
}
|
||||
|
||||
function hangUp() {
|
||||
if (pc) { pc.close(); pc = null; }
|
||||
if (localStream) {
|
||||
localStream.getTracks().forEach(t => t.stop());
|
||||
localStream = null;
|
||||
}
|
||||
const local = root?.querySelector('#video-local');
|
||||
const remote = root?.querySelector('#video-remote');
|
||||
if (local) local.srcObject = null;
|
||||
if (remote) remote.srcObject = null;
|
||||
inCall = false;
|
||||
micEnabled = true;
|
||||
camEnabled = true;
|
||||
showPlaceholder(true);
|
||||
updateCallButtons();
|
||||
window.BackOneRingingPopup?.dismiss();
|
||||
}
|
||||
|
||||
return {
|
||||
mount(container) {
|
||||
root = container;
|
||||
container.innerHTML = `
|
||||
<div class="comm-layout">
|
||||
<aside class="comm-sidebar">
|
||||
<div class="comm-sidebar-head">
|
||||
<h2>Video Call P2P</h2>
|
||||
<p class="comm-sidebar-sub">BackOne core · same network as backone-cli</p>
|
||||
</div>
|
||||
<div class="comm-sidebar-body">
|
||||
<div id="video-picker"></div>
|
||||
</div>
|
||||
<div class="comm-sidebar-actions">
|
||||
<div id="video-selected-label" class="comm-selected-label">Selected: <strong>—</strong></div>
|
||||
<button type="button" id="video-call" class="btn-primary" disabled>Video Call</button>
|
||||
<button type="button" id="video-hangup" class="btn-secondary">Hang Up</button>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="comm-main">
|
||||
<div class="comm-main-head">
|
||||
<div class="comm-breadcrumb">Video Call P2P > <span id="video-breadcrumb-state">${inCall ? 'Connected to' : 'Select peer'}</span> <strong id="video-breadcrumb-peer">—</strong></div>
|
||||
<div id="video-head-status" class="comm-head-status"></div>
|
||||
</div>
|
||||
<div class="comm-stage">
|
||||
<div class="comm-remote-tag"><span class="dot"></span> Remote</div>
|
||||
<div id="video-placeholder" class="comm-remote-placeholder">
|
||||
<div class="avatar">👤</div>
|
||||
<div class="peer-id" id="video-ph-peer">—</div>
|
||||
<div>Remote Peer</div>
|
||||
</div>
|
||||
<video id="video-remote" autoplay playsinline style="display:none"></video>
|
||||
<div class="video-local-pip">
|
||||
<video id="video-local" autoplay muted playsinline></video>
|
||||
</div>
|
||||
<div class="comm-call-controls">
|
||||
<button type="button" id="video-mic">Mic</button>
|
||||
<button type="button" id="video-cam">Camera</button>
|
||||
<button type="button" id="video-fs">Fullscreen</button>
|
||||
<button type="button" id="video-hangup-stage" class="danger">Hang Up</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="video-conn-bar" class="comm-connection-bar"></div>`;
|
||||
|
||||
const callBtn = container.querySelector('#video-call');
|
||||
|
||||
picker = BackOnePeerPicker(container.querySelector('#video-picker'), {
|
||||
layout: 'sidebar',
|
||||
showActionButton: false,
|
||||
onSelect(peer) {
|
||||
selectedPeer = peer;
|
||||
const phPeer = root.querySelector('#video-ph-peer');
|
||||
if (phPeer) phPeer.textContent = peer.peerId;
|
||||
updateCallButtons();
|
||||
},
|
||||
onNetworkChange() {
|
||||
if (!inCall) selectedPeer = null;
|
||||
updateCallButtons();
|
||||
}
|
||||
});
|
||||
|
||||
BackOneNetworkSession.subscribe(() => updateCallButtons());
|
||||
|
||||
callBtn.onclick = async () => {
|
||||
const peer = selectedPeer || picker.getSelectedPeer();
|
||||
const networkId = BackOneNetworkSession.getNetworkId();
|
||||
if (!peer || !networkId) return;
|
||||
try {
|
||||
pc = new RTCPeerConnection();
|
||||
pc.ontrack = (ev) => {
|
||||
root.querySelector('#video-remote').srcObject = ev.streams[0];
|
||||
showPlaceholder(false);
|
||||
};
|
||||
localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
||||
root.querySelector('#video-local').srcObject = localStream;
|
||||
localStream.getTracks().forEach(t => pc.addTrack(t, localStream));
|
||||
const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
await BackOneAuth.fetchWithAuth('/controller/comms/call', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
type: 'video',
|
||||
networkId,
|
||||
targetId: peer.peerId,
|
||||
sdp: offer.sdp
|
||||
})
|
||||
});
|
||||
inCall = true;
|
||||
root.querySelector('#video-breadcrumb-state').textContent = 'Connected to';
|
||||
updateCallButtons();
|
||||
} catch (err) {
|
||||
hangUp();
|
||||
alert(err.message || 'Call failed');
|
||||
}
|
||||
};
|
||||
|
||||
container.querySelector('#video-hangup').onclick = hangUp;
|
||||
container.querySelector('#video-hangup-stage').onclick = hangUp;
|
||||
|
||||
container.querySelector('#video-mic').onclick = () => {
|
||||
if (!localStream) return;
|
||||
micEnabled = !micEnabled;
|
||||
localStream.getAudioTracks().forEach(t => { t.enabled = micEnabled; });
|
||||
container.querySelector('#video-mic').textContent = micEnabled ? 'Mic' : 'Mic off';
|
||||
};
|
||||
|
||||
container.querySelector('#video-cam').onclick = () => {
|
||||
if (!localStream) return;
|
||||
camEnabled = !camEnabled;
|
||||
localStream.getVideoTracks().forEach(t => { t.enabled = camEnabled; });
|
||||
container.querySelector('#video-cam').textContent = camEnabled ? 'Camera' : 'Camera off';
|
||||
};
|
||||
|
||||
container.querySelector('#video-fs').onclick = () => {
|
||||
const stage = container.querySelector('.comm-stage');
|
||||
if (stage.requestFullscreen) stage.requestFullscreen();
|
||||
};
|
||||
|
||||
showPlaceholder(true);
|
||||
updateCallButtons();
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user