优化目录结构
This commit is contained in:
77
client/public/call/participants/participants.js
Normal file
77
client/public/call/participants/participants.js
Normal file
@@ -0,0 +1,77 @@
|
||||
export const DEFAULT_PARTICIPANT_NAME = '参与者';
|
||||
export const DEFAULT_PARTICIPANT_AVATAR = '/images/p2.png';
|
||||
|
||||
const DEFAULT_MEDIA_STATE = Object.freeze({
|
||||
audio: false,
|
||||
video: true,
|
||||
isSpeaking: false
|
||||
});
|
||||
|
||||
function createParticipantRecord(current = {}, patch = {}) {
|
||||
return {
|
||||
id: '',
|
||||
name: DEFAULT_PARTICIPANT_NAME,
|
||||
avatar: DEFAULT_PARTICIPANT_AVATAR,
|
||||
mediaState: { ...DEFAULT_MEDIA_STATE },
|
||||
status: 'online',
|
||||
...current,
|
||||
...patch,
|
||||
mediaState: {
|
||||
...DEFAULT_MEDIA_STATE,
|
||||
...(current.mediaState || {}),
|
||||
...(patch.mediaState || {})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function upsertParticipant(participants, participantId, patch = {}) {
|
||||
if (!participantId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
participants[participantId] = createParticipantRecord(participants[participantId], patch);
|
||||
return participants[participantId];
|
||||
}
|
||||
|
||||
export function removeParticipant(participants, participantId) {
|
||||
if (!participantId || !participants[participantId]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delete participants[participantId];
|
||||
return true;
|
||||
}
|
||||
|
||||
export function omitParticipant(participants, excludedParticipantId) {
|
||||
const filtered = {};
|
||||
|
||||
for (const [participantId, participant] of Object.entries(participants || {})) {
|
||||
if (participantId !== excludedParticipantId) {
|
||||
filtered[participantId] = participant;
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
export function buildParticipantsSyncData(localUser, participants) {
|
||||
const memberList = {
|
||||
host: {
|
||||
id: localUser.id,
|
||||
name: localUser.name,
|
||||
avatar: localUser.avatar,
|
||||
mediaState: { ...localUser.mediaState },
|
||||
status: 'online',
|
||||
role: 'host'
|
||||
}
|
||||
};
|
||||
|
||||
for (const [participantId, participant] of Object.entries(participants || {})) {
|
||||
memberList[participantId] = {
|
||||
...createParticipantRecord(participant),
|
||||
role: 'participant'
|
||||
};
|
||||
}
|
||||
|
||||
return memberList;
|
||||
}
|
||||
64
client/public/call/participants/renderer-participant-grid.js
Normal file
64
client/public/call/participants/renderer-participant-grid.js
Normal file
@@ -0,0 +1,64 @@
|
||||
function createParticipantPlaceholder() {
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.className = 'participant-video-placeholder absolute inset-0 flex items-center justify-center bg-gradient-to-br from-indigo-900/80 to-purple-900/80 hidden';
|
||||
placeholder.innerHTML = `
|
||||
<div class="text-center">
|
||||
<div class="w-20 h-20 rounded-full bg-indigo-700/50 flex items-center justify-center mx-auto mb-3">
|
||||
<i class="fas fa-video-slash text-2xl text-white/70"></i>
|
||||
</div>
|
||||
<p class="text-white text-sm font-medium">\u6444\u50cf\u5934\u5df2\u5173\u95ed</p>
|
||||
</div>
|
||||
`;
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
export function createParticipantTile(connectionId, displayName) {
|
||||
const tile = document.createElement('div');
|
||||
tile.className = 'relative bg-black/60 rounded-xl overflow-hidden flex items-center justify-center';
|
||||
tile.dataset.participantId = connectionId;
|
||||
|
||||
const video = document.createElement('video');
|
||||
video.className = 'w-full h-full object-contain';
|
||||
video.autoplay = true;
|
||||
video.playsinline = true;
|
||||
video.muted = false;
|
||||
video.id = `participantVideo_${connectionId}`;
|
||||
tile.appendChild(video);
|
||||
tile.appendChild(createParticipantPlaceholder());
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'absolute bottom-3 left-3 glass px-3 py-1 rounded-full text-xs flex items-center gap-2';
|
||||
label.innerHTML = `<i class="fas fa-user text-purple-400"></i><span>${displayName || '\u53c2\u4e0e\u8005'}</span>`;
|
||||
tile.appendChild(label);
|
||||
|
||||
const liveTag = document.createElement('div');
|
||||
liveTag.className = 'absolute top-3 right-3 bg-green-500/80 px-2 py-0.5 rounded-full text-xs flex items-center gap-1';
|
||||
liveTag.innerHTML = `<span class="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></span><span>\u5728\u7ebf</span>`;
|
||||
tile.appendChild(liveTag);
|
||||
|
||||
return tile;
|
||||
}
|
||||
|
||||
export function getParticipantTile(grid, participantId) {
|
||||
return grid?.querySelector(`[data-participant-id="${participantId}"]`) || null;
|
||||
}
|
||||
|
||||
export function updateParticipantTilePlaceholder(grid, participantId, showPlaceholder) {
|
||||
const tile = getParticipantTile(grid, participantId);
|
||||
if (!tile) return;
|
||||
|
||||
const placeholder = tile.querySelector('.participant-video-placeholder');
|
||||
if (placeholder) {
|
||||
placeholder.classList.toggle('hidden', !showPlaceholder);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateParticipantTileName(grid, participantId, name) {
|
||||
const tile = getParticipantTile(grid, participantId);
|
||||
if (!tile) return;
|
||||
|
||||
const label = tile.querySelector('.absolute.bottom-3 span');
|
||||
if (label && name) {
|
||||
label.textContent = name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user