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