SHA256
Добавить базовую поддержку видеозвонка
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
acceptIncomingCall,
|
||||
declineIncomingCall,
|
||||
hangupActiveCall,
|
||||
setCameraEnabled,
|
||||
setMicMuted,
|
||||
subscribeCallState,
|
||||
toggleSpeakerMode,
|
||||
@@ -15,10 +16,16 @@ let panelEl = null;
|
||||
let headerEl = null;
|
||||
let titleEl = null;
|
||||
let statusEl = null;
|
||||
let mediaStageEl = null;
|
||||
let remoteVideoEl = null;
|
||||
let remoteVideoPlaceholderEl = null;
|
||||
let localPreviewWrapEl = null;
|
||||
let localVideoEl = null;
|
||||
let incomingActionsEl = null;
|
||||
let activeActionsEl = null;
|
||||
let speakerBtn = null;
|
||||
let muteBtn = null;
|
||||
let cameraBtn = null;
|
||||
let acceptBtn = null;
|
||||
let declineBtn = null;
|
||||
let hangupBtn = null;
|
||||
@@ -28,6 +35,7 @@ let barTextEl = null;
|
||||
let barActionsEl = null;
|
||||
let barSpeakerBtn = null;
|
||||
let barMuteBtn = null;
|
||||
let barCameraBtn = null;
|
||||
let barHangupBtn = null;
|
||||
let unbind = null;
|
||||
let tickerId = 0;
|
||||
@@ -87,6 +95,12 @@ function getIconSvg(name) {
|
||||
if (name === 'speaker-off') {
|
||||
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 10H4v4h4l5 4V6z" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"/><path d="M16 8l4 8" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/><path d="M20 8l-4 8" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>';
|
||||
}
|
||||
if (name === 'camera-on') {
|
||||
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 8.5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M16 10.2 20 8v8l-4-2.2z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>';
|
||||
}
|
||||
if (name === 'camera-off') {
|
||||
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 8.5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M16 10.2 20 8v8l-4-2.2z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M5 5l14 14" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -113,6 +127,15 @@ function updateSpeakerButtons(enabled) {
|
||||
setButtonIcon(barSpeakerBtn, iconName, label);
|
||||
}
|
||||
|
||||
function updateCameraButtons(enabled) {
|
||||
const iconName = enabled ? 'camera-on' : 'camera-off';
|
||||
const label = enabled ? 'Выключить камеру' : 'Включить камеру';
|
||||
setButtonIcon(cameraBtn, iconName, label);
|
||||
setButtonIcon(barCameraBtn, iconName, label);
|
||||
if (cameraBtn) cameraBtn.dataset.enabled = enabled ? '1' : '0';
|
||||
if (barCameraBtn) barCameraBtn.dataset.enabled = enabled ? '1' : '0';
|
||||
}
|
||||
|
||||
async function handleMuteClick(event) {
|
||||
stopEvent(event);
|
||||
const nowMuted = event.currentTarget?.dataset?.muted === '1';
|
||||
@@ -134,6 +157,14 @@ async function handleSpeakerClick(event) {
|
||||
await toggleSpeakerMode();
|
||||
}
|
||||
|
||||
async function handleCameraClick(event) {
|
||||
stopEvent(event);
|
||||
const enabled = event.currentTarget?.dataset?.enabled === '1';
|
||||
try {
|
||||
await setCameraEnabled(!enabled);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function setMinimized(nextValue) {
|
||||
minimized = Boolean(nextValue);
|
||||
render();
|
||||
@@ -185,12 +216,17 @@ function ensureUi() {
|
||||
barMuteBtn.className = 'call-icon-btn call-icon-btn--bar call-icon-btn--secondary';
|
||||
barMuteBtn.addEventListener('click', handleMuteClick);
|
||||
|
||||
barCameraBtn = document.createElement('button');
|
||||
barCameraBtn.type = 'button';
|
||||
barCameraBtn.className = 'call-icon-btn call-icon-btn--bar call-icon-btn--secondary';
|
||||
barCameraBtn.addEventListener('click', handleCameraClick);
|
||||
|
||||
barHangupBtn = document.createElement('button');
|
||||
barHangupBtn.type = 'button';
|
||||
barHangupBtn.className = 'call-icon-btn call-icon-btn--bar call-icon-btn--danger';
|
||||
barHangupBtn.addEventListener('click', handleEndCallClick);
|
||||
|
||||
barActionsEl.append(barSpeakerBtn, barMuteBtn, barHangupBtn);
|
||||
barActionsEl.append(barSpeakerBtn, barMuteBtn, barCameraBtn, barHangupBtn);
|
||||
barEl.append(barTextEl, barActionsEl);
|
||||
|
||||
overlayEl = document.createElement('div');
|
||||
@@ -219,6 +255,31 @@ function ensureUi() {
|
||||
statusEl = document.createElement('div');
|
||||
statusEl.className = 'call-overlay-status';
|
||||
|
||||
mediaStageEl = document.createElement('div');
|
||||
mediaStageEl.className = 'call-video-stage';
|
||||
|
||||
remoteVideoEl = document.createElement('video');
|
||||
remoteVideoEl.className = 'call-remote-video';
|
||||
remoteVideoEl.autoplay = true;
|
||||
remoteVideoEl.playsInline = true;
|
||||
remoteVideoEl.muted = true;
|
||||
|
||||
remoteVideoPlaceholderEl = document.createElement('div');
|
||||
remoteVideoPlaceholderEl.className = 'call-video-placeholder';
|
||||
remoteVideoPlaceholderEl.textContent = 'Собеседник пока без видео';
|
||||
|
||||
localPreviewWrapEl = document.createElement('div');
|
||||
localPreviewWrapEl.className = 'call-local-preview';
|
||||
|
||||
localVideoEl = document.createElement('video');
|
||||
localVideoEl.className = 'call-local-video';
|
||||
localVideoEl.autoplay = true;
|
||||
localVideoEl.playsInline = true;
|
||||
localVideoEl.muted = true;
|
||||
|
||||
localPreviewWrapEl.append(localVideoEl);
|
||||
mediaStageEl.append(remoteVideoEl, remoteVideoPlaceholderEl, localPreviewWrapEl);
|
||||
|
||||
incomingActionsEl = document.createElement('div');
|
||||
incomingActionsEl.className = 'call-overlay-controls call-overlay-controls--incoming';
|
||||
|
||||
@@ -235,6 +296,11 @@ function ensureUi() {
|
||||
muteBtn.className = 'call-icon-btn call-icon-btn--secondary';
|
||||
muteBtn.addEventListener('click', handleMuteClick);
|
||||
|
||||
cameraBtn = document.createElement('button');
|
||||
cameraBtn.type = 'button';
|
||||
cameraBtn.className = 'call-icon-btn call-icon-btn--secondary';
|
||||
cameraBtn.addEventListener('click', handleCameraClick);
|
||||
|
||||
acceptBtn = document.createElement('button');
|
||||
acceptBtn.type = 'button';
|
||||
acceptBtn.className = 'call-accept-btn';
|
||||
@@ -258,9 +324,9 @@ function ensureUi() {
|
||||
hangupBtn.addEventListener('click', handleEndCallClick);
|
||||
|
||||
incomingActionsEl.append(acceptBtn, declineBtn);
|
||||
activeActionsEl.append(speakerBtn, muteBtn, hangupBtn);
|
||||
activeActionsEl.append(speakerBtn, muteBtn, cameraBtn, hangupBtn);
|
||||
headerEl.append(titleEl, minimizeBtn);
|
||||
panelEl.append(headerEl, statusEl, incomingActionsEl, activeActionsEl);
|
||||
panelEl.append(headerEl, statusEl, mediaStageEl, incomingActionsEl, activeActionsEl);
|
||||
overlayEl.append(panelEl);
|
||||
rootEl.append(barEl, overlayEl);
|
||||
resolveAppShell().append(rootEl);
|
||||
@@ -277,6 +343,7 @@ function applyExpandedState(snapshot) {
|
||||
const muted = Boolean(snapshot.muted);
|
||||
updateMuteButtons(muted);
|
||||
updateSpeakerButtons(Boolean(snapshot.speakerEnabled));
|
||||
updateCameraButtons(Boolean(snapshot.cameraEnabled));
|
||||
|
||||
minimizeBtn.hidden = incomingStage;
|
||||
minimizeBtn.disabled = incomingStage;
|
||||
@@ -290,9 +357,21 @@ function applyExpandedState(snapshot) {
|
||||
muteBtn.hidden = !snapshot.canMute;
|
||||
muteBtn.disabled = !snapshot.canMute;
|
||||
|
||||
cameraBtn.hidden = !snapshot.canToggleCamera;
|
||||
cameraBtn.disabled = !snapshot.canToggleCamera;
|
||||
|
||||
acceptBtn.hidden = !incomingStage || !snapshot.canAnswer;
|
||||
declineBtn.hidden = !incomingStage || !snapshot.canDecline;
|
||||
hangupBtn.hidden = !snapshot.canHangup;
|
||||
|
||||
mediaStageEl.hidden = incomingStage;
|
||||
const remoteStream = snapshot.remoteMediaStream || null;
|
||||
const localStream = snapshot.localPreviewStream || null;
|
||||
if (remoteVideoEl.srcObject !== remoteStream) remoteVideoEl.srcObject = remoteStream;
|
||||
if (localVideoEl.srcObject !== localStream) localVideoEl.srcObject = localStream;
|
||||
remoteVideoEl.hidden = !snapshot.hasRemoteVideo;
|
||||
remoteVideoPlaceholderEl.hidden = snapshot.hasRemoteVideo;
|
||||
localPreviewWrapEl.hidden = !snapshot.hasLocalVideo;
|
||||
}
|
||||
|
||||
function applyMinimizedState(snapshot) {
|
||||
@@ -303,15 +382,18 @@ function applyMinimizedState(snapshot) {
|
||||
const muted = Boolean(snapshot.muted);
|
||||
updateMuteButtons(muted);
|
||||
updateSpeakerButtons(Boolean(snapshot.speakerEnabled));
|
||||
updateCameraButtons(Boolean(snapshot.cameraEnabled));
|
||||
barSpeakerBtn.hidden = incomingStage || !snapshot.canToggleSpeaker;
|
||||
barSpeakerBtn.disabled = !snapshot.canToggleSpeaker;
|
||||
barMuteBtn.hidden = incomingStage || !snapshot.canMute;
|
||||
barMuteBtn.disabled = !snapshot.canMute;
|
||||
barCameraBtn.hidden = incomingStage || !snapshot.canToggleCamera;
|
||||
barCameraBtn.disabled = !snapshot.canToggleCamera;
|
||||
const canEnd = !incomingStage && snapshot.canHangup;
|
||||
barHangupBtn.hidden = !canEnd;
|
||||
barHangupBtn.disabled = !canEnd;
|
||||
setButtonIcon(barHangupBtn, 'hangup', 'Положить трубку');
|
||||
barActionsEl.hidden = incomingStage || (!snapshot.canToggleSpeaker && !snapshot.canMute && !canEnd);
|
||||
barActionsEl.hidden = incomingStage || (!snapshot.canToggleSpeaker && !snapshot.canMute && !snapshot.canToggleCamera && !canEnd);
|
||||
}
|
||||
|
||||
function syncShellState() {
|
||||
|
||||
Reference in New Issue
Block a user