++
This commit is contained in:
@@ -159,11 +159,51 @@ class CallStateManager {
|
|||||||
|
|
||||||
// 更新本地媒体状态
|
// 更新本地媒体状态
|
||||||
async updateLocalMedia(mediaType, value) {
|
async updateLocalMedia(mediaType, value) {
|
||||||
|
await this._updateLocalMediaRefactored(mediaType, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是开启视频,重新获取摄像头资源
|
async _updateLocalMediaRefactored(mediaType, value) {
|
||||||
if (mediaType === 'video' && value) {
|
if (mediaType === 'video' && value) {
|
||||||
|
await this._enableLocalVideo();
|
||||||
|
this._notifyUserListUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.session.localUser.mediaState[mediaType] = value;
|
||||||
|
this._notifyLocalMediaChange(mediaType, value);
|
||||||
|
this.emitMediaStateChange();
|
||||||
|
|
||||||
|
if (mediaType === 'video' && !value) {
|
||||||
|
this._disableLocalVideoTracks();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaType === 'audio') {
|
||||||
|
this._setLocalAudioTrackEnabled(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._notifyUserListUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _enableLocalVideo() {
|
||||||
try {
|
try {
|
||||||
// 只获取新的视频轨道,不干扰正在工作的音频
|
const newVideoTrack = await this._requestNewVideoTrack();
|
||||||
|
this._replaceLocalVideoTrack(newVideoTrack);
|
||||||
|
await this._updateOutgoingVideoTrack(newVideoTrack);
|
||||||
|
|
||||||
|
this.state.session.localUser.mediaState.video = true;
|
||||||
|
this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream: this.state.localStream });
|
||||||
|
this._notifyLocalMediaChange('video', true);
|
||||||
|
this.emitMediaStateChange();
|
||||||
|
this.startActivityDetection(this.state.localStream, { isLocal: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reopening video:', error);
|
||||||
|
this.state.session.localUser.mediaState.video = false;
|
||||||
|
this._notifyLocalMediaChange('video', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _requestNewVideoTrack() {
|
||||||
const newVideoStream = await navigator.mediaDevices.getUserMedia(VIDEO_ONLY_CONSTRAINT);
|
const newVideoStream = await navigator.mediaDevices.getUserMedia(VIDEO_ONLY_CONSTRAINT);
|
||||||
const newVideoTrack = newVideoStream.getVideoTracks()[0];
|
const newVideoTrack = newVideoStream.getVideoTracks()[0];
|
||||||
|
|
||||||
@@ -171,7 +211,10 @@ class CallStateManager {
|
|||||||
throw new Error('Failed to get video track');
|
throw new Error('Failed to get video track');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新本地流中的视频轨道(替换旧的已停止的轨道)
|
return newVideoTrack;
|
||||||
|
}
|
||||||
|
|
||||||
|
_replaceLocalVideoTrack(newVideoTrack) {
|
||||||
if (this.state.localStream) {
|
if (this.state.localStream) {
|
||||||
const oldVideoTracks = this.state.localStream.getVideoTracks();
|
const oldVideoTracks = this.state.localStream.getVideoTracks();
|
||||||
oldVideoTracks.forEach(track => {
|
oldVideoTracks.forEach(track => {
|
||||||
@@ -179,130 +222,128 @@ class CallStateManager {
|
|||||||
this.state.localStream.removeTrack(track);
|
this.state.localStream.removeTrack(track);
|
||||||
});
|
});
|
||||||
this.state.localStream.addTrack(newVideoTrack);
|
this.state.localStream.addTrack(newVideoTrack);
|
||||||
} else {
|
return;
|
||||||
// 本地流不存在时(不应该发生),使用新流
|
}
|
||||||
this.state.localStream = newVideoStream;
|
|
||||||
|
this.state.localStream = new MediaStream([newVideoTrack]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _updateOutgoingVideoTrack(newVideoTrack) {
|
||||||
|
if (!this.renderstreaming) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新WebRTC连接中的视频轨道
|
|
||||||
if (this.renderstreaming) {
|
|
||||||
console.log('Updating video track in WebRTC connection');
|
console.log('Updating video track in WebRTC connection');
|
||||||
|
|
||||||
if (this.role === 'host') {
|
if (this.role === 'host') {
|
||||||
// Host端:需要遍历所有participant的peer来替换视频轨道
|
|
||||||
const participantIds = Object.keys(this.state.remoteStreams);
|
const participantIds = Object.keys(this.state.remoteStreams);
|
||||||
for (const participantId of participantIds) {
|
for (const participantId of participantIds) {
|
||||||
const transceivers = this.renderstreaming.getTransceivers(participantId);
|
await this._updateVideoTrackForPeer(newVideoTrack, participantId);
|
||||||
if (!transceivers) continue;
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const videoTransceivers = transceivers.filter(t =>
|
await this._updateVideoTrackForPeer(newVideoTrack);
|
||||||
t.sender && t.sender.track && t.sender.track.kind === 'video'
|
}
|
||||||
|
|
||||||
|
async _updateVideoTrackForPeer(newVideoTrack, participantId = undefined) {
|
||||||
|
const transceivers = this.renderstreaming.getTransceivers(participantId);
|
||||||
|
if (!transceivers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoTransceivers = transceivers.filter(transceiver =>
|
||||||
|
transceiver.sender && transceiver.sender.track && transceiver.sender.track.kind === 'video'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (videoTransceivers.length > 0) {
|
if (videoTransceivers.length > 0) {
|
||||||
|
await this._replaceVideoTrackOnTransceivers(videoTransceivers, newVideoTrack, participantId);
|
||||||
|
} else {
|
||||||
|
this._addVideoTransceiver(newVideoTrack, participantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._scheduleVideoSenderUpdate(participantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _replaceVideoTrackOnTransceivers(videoTransceivers, newVideoTrack, participantId) {
|
||||||
for (const transceiver of videoTransceivers) {
|
for (const transceiver of videoTransceivers) {
|
||||||
try {
|
try {
|
||||||
await transceiver.sender.replaceTrack(newVideoTrack);
|
await transceiver.sender.replaceTrack(newVideoTrack);
|
||||||
console.log(`Replaced video track for participant ${participantId}`);
|
console.log(participantId
|
||||||
|
? `Replaced video track for participant ${participantId}`
|
||||||
|
: 'Successfully replaced video track');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error replacing video track for ${participantId}:`, error);
|
console.error(
|
||||||
|
participantId
|
||||||
|
? `Error replacing video track for ${participantId}:`
|
||||||
|
: 'Error replacing video track:',
|
||||||
|
error
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// 没有视频收发器,添加新的
|
|
||||||
|
_addVideoTransceiver(newVideoTrack, participantId) {
|
||||||
try {
|
try {
|
||||||
|
if (participantId) {
|
||||||
this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' }, participantId);
|
this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' }, participantId);
|
||||||
console.log(`Added new video transceiver for participant ${participantId}`);
|
console.log(`Added new video transceiver for participant ${participantId}`);
|
||||||
} catch (error) {
|
return;
|
||||||
console.error(`Error adding video transceiver for ${participantId}:`, error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置编解码器偏好
|
|
||||||
setTimeout(() => { this.setCodecPreferences(participantId); }, 100);
|
|
||||||
setTimeout(() => { this.setVideoEncodingParameters(participantId); }, 200);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Participant端:使用单一peer
|
|
||||||
const transceivers = this.renderstreaming.getTransceivers();
|
|
||||||
if (transceivers) {
|
|
||||||
const videoTransceivers = transceivers.filter(t =>
|
|
||||||
t.sender && t.sender.track && t.sender.track.kind === 'video'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (videoTransceivers.length > 0) {
|
|
||||||
for (const transceiver of videoTransceivers) {
|
|
||||||
try {
|
|
||||||
await transceiver.sender.replaceTrack(newVideoTrack);
|
|
||||||
console.log('Successfully replaced video track');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error replacing video track:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' });
|
this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' });
|
||||||
console.log('Added new video transceiver');
|
console.log('Added new video transceiver');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding video transceiver:', error);
|
console.error(
|
||||||
}
|
participantId
|
||||||
}
|
? `Error adding video transceiver for ${participantId}:`
|
||||||
}
|
: 'Error adding video transceiver:',
|
||||||
setTimeout(() => { this.setCodecPreferences(); }, 100);
|
error
|
||||||
setTimeout(() => { this.setVideoEncodingParameters(); }, 200);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新状态和通知UI
|
_scheduleVideoSenderUpdate(participantId) {
|
||||||
this.state.session.localUser.mediaState.video = true;
|
setTimeout(() => { this.setCodecPreferences(participantId); }, 100);
|
||||||
this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream: this.state.localStream });
|
setTimeout(() => { this.setVideoEncodingParameters(participantId); }, 200);
|
||||||
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: true });
|
|
||||||
this.emitMediaStateChange();
|
|
||||||
this.startActivityDetection(this.state.localStream, { isLocal: true });
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error reopening video:', error);
|
|
||||||
this.state.session.localUser.mediaState.video = false;
|
|
||||||
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: false });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 直接更新媒体状态
|
|
||||||
this.state.session.localUser.mediaState[mediaType] = value;
|
|
||||||
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType, value });
|
|
||||||
|
|
||||||
// 发送媒体状态到服务器
|
|
||||||
this.emitMediaStateChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_disableLocalVideoTracks() {
|
||||||
|
if (!this.state.localStream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是关闭视频,释放摄像头资源
|
|
||||||
if (mediaType === 'video' && !value && this.state.localStream) {
|
|
||||||
this.state.session.localUser.mediaState.video = false;
|
this.state.session.localUser.mediaState.video = false;
|
||||||
this.state.localStream.getTracks().forEach(track => {
|
this.state.localStream.getTracks().forEach(track => {
|
||||||
if (track.kind === 'video') {
|
if (track.kind === 'video') {
|
||||||
track.stop();
|
track.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 发送媒体状态到服务器
|
|
||||||
this.emitMediaStateChange();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是音频状态变化,控制本地音频轨道
|
_setLocalAudioTrackEnabled(value) {
|
||||||
if (mediaType === 'audio' && this.state.localStream) {
|
if (!this.state.localStream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.state.session.localUser.mediaState.audio = value;
|
this.state.session.localUser.mediaState.audio = value;
|
||||||
this.state.localStream.getTracks().forEach(track => {
|
this.state.localStream.getTracks().forEach(track => {
|
||||||
if (track.kind === 'audio') {
|
if (track.kind === 'audio') {
|
||||||
track.enabled = value;
|
track.enabled = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 发送媒体状态到服务器
|
|
||||||
this.emitMediaStateChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通知UI更新用户列表
|
_notifyLocalMediaChange(mediaType, value) {
|
||||||
this.notify({ type: 'USER_LIST_UPDATE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser });
|
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
_notifyUserListUpdate() {
|
||||||
|
this.notify({
|
||||||
|
type: 'USER_LIST_UPDATE',
|
||||||
|
localUser: this.state.session.localUser,
|
||||||
|
remoteUser: this.state.session.remoteUser
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user