media-state-changed 消息,如果状态没变化的话,不更新状态
This commit is contained in:
@@ -80,7 +80,9 @@ export default {
|
|||||||
],
|
],
|
||||||
|
|
||||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||||
// moduleNameMapper: {},
|
moduleNameMapper: {
|
||||||
|
"^/module/(.*)$": "<rootDir>/src/$1"
|
||||||
|
},
|
||||||
|
|
||||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
// modulePathIgnorePatterns: [],
|
// modulePathIgnorePatterns: [],
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ import { MeetingRecorder } from './media/meeting-recorder.js';
|
|||||||
import { ServerRecordingPeer } from './media/server-recording-peer.js';
|
import { ServerRecordingPeer } from './media/server-recording-peer.js';
|
||||||
|
|
||||||
const logger = createLogger('store');
|
const logger = createLogger('store');
|
||||||
|
const MEDIA_STATE_KEYS = ['audio', 'video', 'screenShare', 'recording', 'isSpeaking'];
|
||||||
|
|
||||||
|
function hasMediaStateChanged(current = {}, next = {}) {
|
||||||
|
if (!next || typeof next !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MEDIA_STATE_KEYS.some(key => (
|
||||||
|
Object.prototype.hasOwnProperty.call(next, key)
|
||||||
|
&& Boolean(current?.[key]) !== Boolean(next[key])
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
class CallStateManager {
|
class CallStateManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -932,29 +945,34 @@ class CallStateManager {
|
|||||||
_handleMediaStateChangedMessage(data) {
|
_handleMediaStateChangedMessage(data) {
|
||||||
logger.debug('收到媒体状态更新:', data.data, 'from participant:', data.participantId);
|
logger.debug('收到媒体状态更新:', data.data, 'from participant:', data.participantId);
|
||||||
if (this.role === 'host') {
|
if (this.role === 'host') {
|
||||||
if (data.participantId && this.state.participants[data.participantId]) {
|
const participantChanged = this._updateParticipantMediaStateIfChanged(data.participantId, data.data);
|
||||||
this._upsertParticipant(data.participantId, {
|
const remoteChanged = this._updateRemoteMediaIfChanged(data.data, data.participantId);
|
||||||
mediaState: data.data
|
if (participantChanged) {
|
||||||
});
|
this._notifyParticipantsUpdate();
|
||||||
|
this.broadcastParticipantsList();
|
||||||
|
}
|
||||||
|
if (!participantChanged && !remoteChanged) {
|
||||||
|
logger.debug('媒体状态未变化,跳过更新:', data.participantId);
|
||||||
}
|
}
|
||||||
this.updateRemoteMedia(data.data, data.participantId);
|
|
||||||
this._notifyParticipantsUpdate();
|
|
||||||
this.broadcastParticipantsList();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.participantId && data.participantId !== this.selfParticipantId && this.state.participants[data.participantId]) {
|
if (data.participantId && data.participantId !== this.selfParticipantId && this.state.participants[data.participantId]) {
|
||||||
this._upsertParticipant(data.participantId, {
|
if (this._updateParticipantMediaStateIfChanged(data.participantId, data.data)) {
|
||||||
mediaState: data.data
|
this._notifyParticipantsUpdate();
|
||||||
});
|
} else {
|
||||||
this._notifyParticipantsUpdate();
|
logger.debug('媒体状态未变化,跳过参与者更新:', data.participantId);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.participantId === this.selfParticipantId) {
|
if (data.participantId === this.selfParticipantId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.debug('Received media-state-changed from Host, updating remoteUser:', data.data);
|
logger.debug('Received media-state-changed from Host, updating remoteUser:', data.data);
|
||||||
this.updateRemoteMedia(data.data, data.participantId);
|
if (this._updateRemoteMediaIfChanged(data.data, data.participantId)) {
|
||||||
this._notifyParticipantsUpdate();
|
this._notifyParticipantsUpdate();
|
||||||
|
} else {
|
||||||
|
logger.debug('媒体状态未变化,跳过远端用户更新:', data.participantId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_handleUserInfoMessage(data) {
|
_handleUserInfoMessage(data) {
|
||||||
logger.debug('收到用户信息:', data.data, 'from participant:', data.participantId);
|
logger.debug('收到用户信息:', data.data, 'from participant:', data.participantId);
|
||||||
@@ -1046,6 +1064,25 @@ class CallStateManager {
|
|||||||
_upsertParticipant(participantId, patch = {}) {
|
_upsertParticipant(participantId, patch = {}) {
|
||||||
return upsertParticipant(this.state.participants, participantId, patch);
|
return upsertParticipant(this.state.participants, participantId, patch);
|
||||||
}
|
}
|
||||||
|
_updateParticipantMediaStateIfChanged(participantId, mediaState) {
|
||||||
|
if (!participantId || !this.state.participants[participantId]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasMediaStateChanged(this.state.participants[participantId].mediaState, mediaState)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this._upsertParticipant(participantId, {
|
||||||
|
mediaState
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_updateRemoteMediaIfChanged(mediaState, participantId) {
|
||||||
|
if (!hasMediaStateChanged(this.state.session.remoteUser.mediaState, mediaState)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.updateRemoteMedia(mediaState, participantId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
_removeParticipant(participantId) {
|
_removeParticipant(participantId) {
|
||||||
return removeParticipant(this.state.participants, participantId);
|
return removeParticipant(this.state.participants, participantId);
|
||||||
}
|
}
|
||||||
|
|||||||
88
client/test/unit/store-media-state.test.js
Normal file
88
client/test/unit/store-media-state.test.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
const { default: store } = await import('../../public/call/store.js');
|
||||||
|
|
||||||
|
function mediaState(overrides = {}) {
|
||||||
|
return {
|
||||||
|
audio: true,
|
||||||
|
video: true,
|
||||||
|
screenShare: false,
|
||||||
|
recording: false,
|
||||||
|
isSpeaking: false,
|
||||||
|
...overrides
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetStore() {
|
||||||
|
store.role = 'host';
|
||||||
|
store.selfParticipantId = 'host';
|
||||||
|
store.renderstreaming = {
|
||||||
|
sendMessage: jest.fn()
|
||||||
|
};
|
||||||
|
store.state = {
|
||||||
|
session: {
|
||||||
|
duration: 0,
|
||||||
|
localUser: {
|
||||||
|
id: 'host-user',
|
||||||
|
name: 'Host',
|
||||||
|
avatar: '/images/p1.png',
|
||||||
|
mediaState: mediaState()
|
||||||
|
},
|
||||||
|
remoteUser: {
|
||||||
|
id: 'remote-user',
|
||||||
|
name: 'Remote',
|
||||||
|
avatar: '/images/p2.png',
|
||||||
|
status: 'online',
|
||||||
|
mediaState: mediaState()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
participants: {
|
||||||
|
'participant-1': {
|
||||||
|
id: 'participant-user',
|
||||||
|
name: 'Participant',
|
||||||
|
avatar: '/images/p2.png',
|
||||||
|
mediaState: mediaState(),
|
||||||
|
status: 'online'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
store.notify = jest.fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('media-state-changed handling', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
resetStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips updates when participant media state is unchanged', () => {
|
||||||
|
store._handleMediaStateChangedMessage({
|
||||||
|
participantId: 'participant-1',
|
||||||
|
data: {
|
||||||
|
userId: 'participant-user',
|
||||||
|
...mediaState()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.notify).not.toHaveBeenCalled();
|
||||||
|
expect(store.renderstreaming.sendMessage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates and broadcasts when participant media state changes', () => {
|
||||||
|
store._handleMediaStateChangedMessage({
|
||||||
|
participantId: 'participant-1',
|
||||||
|
data: {
|
||||||
|
userId: 'participant-user',
|
||||||
|
...mediaState({ audio: false })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.state.participants['participant-1'].mediaState.audio).toBe(false);
|
||||||
|
expect(store.notify).toHaveBeenCalledWith({
|
||||||
|
type: 'PARTICIPANTS_UPDATE',
|
||||||
|
participants: store.state.participants
|
||||||
|
});
|
||||||
|
expect(store.renderstreaming.sendMessage).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
type: 'participants-sync'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user