mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
snapshot user threshold..
This commit is contained in:
@@ -339,6 +339,34 @@ function getChatTargetForSocket(socketId) {
|
||||
};
|
||||
}
|
||||
|
||||
function getParticipantSocketIds() {
|
||||
/*
|
||||
PTZ has no roverManager record, so services that need a global "how many
|
||||
controllable users are online" count need a tiny PTZ-owned participant list.
|
||||
The operator and queue are the only users attached to this controllable
|
||||
camera target; spectators merely viewing snapshots/live video are excluded.
|
||||
*/
|
||||
return Array.from(new Set([
|
||||
state.operatorSocketId,
|
||||
...state.queue,
|
||||
].filter(Boolean)));
|
||||
}
|
||||
|
||||
function countControllableUsers() {
|
||||
const ids = new Set();
|
||||
io.sockets.sockets.forEach((candidate) => {
|
||||
if (!candidate?.id || getRole(candidate) === 'spectator') return;
|
||||
if (roverManager.getRoversForSocket(candidate.id).length > 0) {
|
||||
ids.add(candidate.id);
|
||||
}
|
||||
});
|
||||
getParticipantSocketIds().forEach((socketId) => {
|
||||
const socket = io.sockets.sockets.get(socketId);
|
||||
if (socket && getRole(socket) !== 'spectator') ids.add(socketId);
|
||||
});
|
||||
return ids.size;
|
||||
}
|
||||
|
||||
function canSpeakThroughPtz(socket) {
|
||||
/*
|
||||
PTZ chat uses roverId for identity, but the camera has its own queue rather
|
||||
@@ -1305,7 +1333,10 @@ function canRequestLiveVideo(socket) {
|
||||
*/
|
||||
return local || !shouldUseSnapshotsForExternalSpectatorVideo();
|
||||
}
|
||||
if (canUsePtzFeature(socket) && !shouldUseSnapshotsForNonTurnVideo()) {
|
||||
if (
|
||||
canUsePtzFeature(socket) &&
|
||||
!shouldUseSnapshotsForNonTurnVideo({ controllableUserCount: countControllableUsers() })
|
||||
) {
|
||||
/*
|
||||
Verified/VIP users who can queue or claim the camera are PTZ "turn"
|
||||
participants even before they become operator. When non-turn video is set
|
||||
@@ -1577,6 +1608,7 @@ module.exports = {
|
||||
ptzCameraEvents: events,
|
||||
getPublicState,
|
||||
getChatTargetForSocket,
|
||||
getParticipantSocketIds,
|
||||
canSpeakThroughPtz,
|
||||
speakText,
|
||||
canRequestLiveVideo,
|
||||
|
||||
@@ -42,6 +42,7 @@ const { getFeatureFlags } = require('../../helpers/features');
|
||||
const {
|
||||
canUseExternalSpectatorAccess,
|
||||
getBandwidthSavingsPolicy,
|
||||
shouldUseSnapshotsForNonTurnVideo,
|
||||
} = require('../../helpers/bandwidthSavings');
|
||||
const {
|
||||
getFeatureState,
|
||||
@@ -79,12 +80,17 @@ function hasExternalSpectatorGrant(socket) {
|
||||
return Boolean(state?.external);
|
||||
}
|
||||
|
||||
function buildBandwidthSavingsSessionState(socket) {
|
||||
function buildBandwidthSavingsSessionState(socket, controllableUserCount = 0) {
|
||||
const policy = getBandwidthSavingsPolicy();
|
||||
const local = isLocalNetwork(getSocketIp(socket));
|
||||
const granted = hasExternalSpectatorGrant(socket);
|
||||
return {
|
||||
...policy,
|
||||
nonTurnVideo: {
|
||||
...policy.nonTurnVideo,
|
||||
controllableUserCount,
|
||||
snapshotsActive: shouldUseSnapshotsForNonTurnVideo({ controllableUserCount }),
|
||||
},
|
||||
/*
|
||||
These derived fields let browser routes make clear UI choices without
|
||||
re-implementing IP/admin/grant logic. The server still enforces the same
|
||||
@@ -100,6 +106,24 @@ function buildBandwidthSavingsSessionState(socket) {
|
||||
};
|
||||
}
|
||||
|
||||
function countControllableUsers(userEntries = []) {
|
||||
const ids = new Set();
|
||||
userEntries.forEach((entry) => {
|
||||
const role = String(entry?.role || '');
|
||||
if (role === 'spectator') return;
|
||||
const socketId = String(entry?.socketId || '').trim();
|
||||
const roverId = String(entry?.roverId || '').trim();
|
||||
/*
|
||||
buildUserEntry already maps PTZ queued/operators to the PTZ pseudo-rover
|
||||
id and normal drivers to their physical rover. Counting entries after that
|
||||
normalization gives the browser the same conceptual "controllable users"
|
||||
count it shows in the user/queue panels without duplicating PTZ UI logic.
|
||||
*/
|
||||
if (socketId && roverId) ids.add(socketId);
|
||||
});
|
||||
return ids.size;
|
||||
}
|
||||
|
||||
function buildUserEntry(socket) {
|
||||
if (!socket) return null;
|
||||
const role = getRole(socket);
|
||||
@@ -124,19 +148,22 @@ function buildUserEntry(socket) {
|
||||
function buildSession(socket) {
|
||||
const overseerVote = getOverseerVoteStatus();
|
||||
const features = getFeatureFlags();
|
||||
const users = Array.from(io.sockets.sockets.values())
|
||||
const userEntries = Array.from(io.sockets.sockets.values())
|
||||
.map((sock) => buildUserEntry(sock))
|
||||
.filter(Boolean)
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
/*
|
||||
PTZ is intentionally not a roverManager record, so the normal physical
|
||||
rover visibility filter would erase the user's PTZ chat target. Preserve
|
||||
it here because getPtzChatTargetForSocket already applied the PTZ access
|
||||
and queue/operator rules before buildUserEntry returned it.
|
||||
*/
|
||||
roverId: entry.roverId === PTZ_CAMERA_ID ? entry.roverId : filterVisibleRoverId(socket, entry.roverId),
|
||||
}));
|
||||
.filter(Boolean);
|
||||
const controllableUserCount = countControllableUsers(userEntries);
|
||||
const users = userEntries.map((entry) => ({
|
||||
...entry,
|
||||
/*
|
||||
PTZ is intentionally not a roverManager record, so the normal physical
|
||||
rover visibility filter would erase the user's PTZ chat target. Preserve
|
||||
it here because getPtzChatTargetForSocket already applied the PTZ access
|
||||
and queue/operator rules before buildUserEntry returned it.
|
||||
*/
|
||||
roverId: entry.roverId === PTZ_CAMERA_ID
|
||||
? entry.roverId
|
||||
: filterVisibleRoverId(socket, entry.roverId),
|
||||
}));
|
||||
const roster = roverManager.getRosterForSocket(socket);
|
||||
const assignment = assignmentService.describeAssignment(socket?.id || '');
|
||||
const assignmentRoverId = filterVisibleRoverId(socket, assignment?.roverId);
|
||||
@@ -148,7 +175,7 @@ function buildSession(socket) {
|
||||
role: getRole(socket),
|
||||
mode: getMode(),
|
||||
isLocalNetwork: isLocalNetwork(getSocketIp(socket)),
|
||||
bandwidthSavings: buildBandwidthSavingsSessionState(socket),
|
||||
bandwidthSavings: buildBandwidthSavingsSessionState(socket, controllableUserCount),
|
||||
/*
|
||||
Features is the single UI contract for optional server capabilities. A
|
||||
disabled feature should be absent from navigation/layout decisions even
|
||||
|
||||
@@ -30,6 +30,7 @@ const { canAccessStream } = createVideoAuthPolicy({
|
||||
ptzCameraService,
|
||||
getSocketIp,
|
||||
isLocalNetwork,
|
||||
io,
|
||||
});
|
||||
|
||||
registerVideoAuthRoute({
|
||||
|
||||
@@ -19,8 +19,31 @@ function createVideoAuthPolicy(deps) {
|
||||
ptzCameraService,
|
||||
getSocketIp,
|
||||
isLocalNetwork,
|
||||
io,
|
||||
} = deps;
|
||||
|
||||
function countControllableUsers() {
|
||||
const ids = new Set();
|
||||
io.sockets.sockets.forEach((candidate) => {
|
||||
if (!candidate?.id || getRole(candidate) === 'spectator') return;
|
||||
/*
|
||||
MediaMTX can ask for authorization after a browser has already received
|
||||
a token, so this count intentionally mirrors videoSocketService instead
|
||||
of trusting the client-visible session policy snapshot.
|
||||
*/
|
||||
if (roverManager.getRoversForSocket(candidate.id).length > 0) {
|
||||
ids.add(candidate.id);
|
||||
}
|
||||
});
|
||||
if (typeof ptzCameraService.getParticipantSocketIds === 'function') {
|
||||
ptzCameraService.getParticipantSocketIds().forEach((socketId) => {
|
||||
const socket = io.sockets.sockets.get(socketId);
|
||||
if (socket && getRole(socket) !== 'spectator') ids.add(socketId);
|
||||
});
|
||||
}
|
||||
return ids.size;
|
||||
}
|
||||
|
||||
function canView(socket) {
|
||||
const mode = getMode();
|
||||
if (!socket) return false;
|
||||
@@ -78,7 +101,11 @@ function createVideoAuthPolicy(deps) {
|
||||
if (!roverManager.isDriver(roverId, socket)) {
|
||||
return false;
|
||||
}
|
||||
if (!isAudio && shouldUseSnapshotsForNonTurnVideo() && !turnService.canDrive(roverId, socket)) {
|
||||
if (
|
||||
!isAudio &&
|
||||
shouldUseSnapshotsForNonTurnVideo({ controllableUserCount: countControllableUsers() }) &&
|
||||
!turnService.canDrive(roverId, socket)
|
||||
) {
|
||||
/*
|
||||
This mirrors videoSocketService's token gate. MediaMTX can ask auth
|
||||
after a token has been issued, so the active-turn bandwidth rule must
|
||||
|
||||
@@ -85,6 +85,29 @@ function canViewRoomCamera(socket) {
|
||||
return passesMode(socket);
|
||||
}
|
||||
|
||||
function countControllableUsers() {
|
||||
const ids = new Set();
|
||||
io.sockets.sockets.forEach((candidate) => {
|
||||
if (!candidate?.id || getRole(candidate) === 'spectator') return;
|
||||
/*
|
||||
Rover drivers and PTZ participants are both "controllable" users for this
|
||||
bandwidth decision because either group can create a non-turn video view.
|
||||
Counting unique socket ids prevents someone who is transitioning between
|
||||
rover and PTZ from being counted twice.
|
||||
*/
|
||||
if (roverManager.getRoversForSocket(candidate.id).length > 0) {
|
||||
ids.add(candidate.id);
|
||||
}
|
||||
});
|
||||
if (typeof ptzCameraService.getParticipantSocketIds === 'function') {
|
||||
ptzCameraService.getParticipantSocketIds().forEach((socketId) => {
|
||||
const socket = io.sockets.sockets.get(socketId);
|
||||
if (socket && getRole(socket) !== 'spectator') ids.add(socketId);
|
||||
});
|
||||
}
|
||||
return ids.size;
|
||||
}
|
||||
|
||||
function normalizeRequest(payload = {}) {
|
||||
if (!payload) return null;
|
||||
if (payload.type && payload.id) {
|
||||
@@ -129,7 +152,7 @@ io.on('connection', (socket) => {
|
||||
!isAudio &&
|
||||
role !== 'spectator' &&
|
||||
!isAdmin(socket) &&
|
||||
shouldUseSnapshotsForNonTurnVideo() &&
|
||||
shouldUseSnapshotsForNonTurnVideo({ controllableUserCount: countControllableUsers() }) &&
|
||||
!turnService.canDrive(baseId, socket)
|
||||
) {
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user