我认为这与 webrtc 无关,而是反应状态更新和重新渲染,但上下文涉及 webrtc。
我一直在开发一个带有简单对等点和套接字 io 的视频聊天应用程序,用于后端的多点对等聊天。
我正在使用 redux 来处理我的状态。
const intialState = {
unseenMessages: [],
peers: [],
peerStreams: [],
currMessages: [],
chatID: null,
meet: null,
isChatActive: false,
isPeopleActive: false,
userVideoStream: null,
cameraStream: null,
screenOn: false,
deck_limit: 2,
mediaState: {
videoPaused: false,
muted: false,
},
userDeckOn: false,
};
这是初始的 redux 套接字状态。
我的每个对等流对象看起来像这样
{
muted:false,
user:{}
videoPaused:false,
peer:Peer object
peerID:userID
userOnDeck:boolean if user is on main deck
isPinned: boolean if user is pinned,
stream:MediaStream of peer
}
我有一个 setmediaState redux 操作,当我停止视频时,它会向会议内的所有对等方发出带有我的用户 ID 的套接字事件,通知他们该用户已停止视频。音频也是如此。
/**
* handle change in audio or video state of the peer
* if peerchanged is current user broacast set metdia state to whole meeting
* change mediaValues of user in peers and peerStreams array
* @param {*} userID user to add
*/
export const setmediaState = (mediaState, peerID, socket = null, meetID = null) => {
return async (dispatch, getState) => {
try {
console.log("updating media state of peer", peerID, socket, meetID);
let peerStreams = getState().socket.peerStreams;
let peerIndex = peerStreams.findIndex((p) => p.peerID == peerID);
if (socket) {
socket.emit("change_media_state", {
mediaState,
meetID,
});
dispatch({
type: actionTypes.SET_MEDIA_STATE,
payload: mediaState,
});
}
if (peerIndex != -1) {
peerStreams[peerIndex] = {
...peerStreams[peerIndex],
...mediaState,
};
dispatch({
type: actionTypes.UPDATE_PEER_STREAMS,
payload: peerStreams,
});
}
} catch (err) {
dispatch(
setNotification(
"Warning:you may be disconnected",
err.message + "\nPlease try refreshing",
"warning"
)
);
}
};
};
现在的问题是当我停止视频时,音频也以某种方式暂停。我说这可能是状态/重新渲染问题的原因是当我从上述函数中评论以下代码时。视频停止正常工作/我可以听视频暂停时的音频。
if (peerIndex != -1) {
peerStreams[peerIndex] = {
...peerStreams[peerIndex],
...mediaState,
};
dispatch({
type: actionTypes.UPDATE_PEER_STREAMS,
payload: peerStreams,
});
}
基本上,当我没有在更改 mediaState 时更新同行时,视频停止正常工作。
这是我的视频元素
import { Microphone16, MicrophoneFilled16, MicrophoneFilled20, MicrophoneOff16, MicrophoneOffFilled16, MicrophoneOffFilled20, Pin16, PinFilled16, PinFilled20, Undo16, Undo20, UserAvatar20, VideoChat20, VideoOffFilled20 } from '@carbon/icons-react';
import { Column } from 'carbon-components-react';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { SocketContext } from '../../context/GlobalSocketContext';
import { pinUser, unPinUser } from '../../store/actions/socket';
import "./_styles.css"
/**
* This is ther user video compoent of meet page
* @component
* @param {*} peerObj
*/
function CallVideo({peerObj}) {
const ref = useRef();
const peerStream= useSelector(state => state.socket.peerStreams.find(p=>p.peerID==peerObj.peerID))
const userID = useSelector(state=>state.auth.userID);
const userDeckopen = useSelector(state=>state.socket.userDeckOn)
useEffect(()=>{
if(peerStream&&peerStream.stream)console.log(peerStream.stream.getTracks())
if(peerStream&&ref.current)ref.current.srcObject=peerStream.stream;
},[peerStream])
/**
* Check is video stream is valid or not
* by active trakcs and mediaState object of peer
*/
const checkVideo = ()=>{
return (peerStream&&peerStream.stream&&
peerStream.stream.active
&&peerStream.stream.getVideoTracks()[0]?.enabled&&
!(peerStream.videoPaused)
)
}
/**
* Check is audio stream is valid or not
* by active trakcs and mediaState object of peer
*/
const checkAudio = ()=>{
return (peerStream&&peerStream.stream&&
peerStream.stream.active
&&peerStream.stream.getAudioTracks()[0]?.enabled&&
(!peerStream.muted)
)
}
const dispatch = useDispatch()
/**
* check if user has proper image or not
*/
const checkUser = ()=>{
return (
peerObj&&peerObj.user&&peerObj.user.image
);
}
/**
* user gizmos :pin unpin user
*/
const pinPeer = ()=>{
dispatch(pinUser(peerObj.peerID))
}
const unPinPeer = ()=>{
dispatch(unPinUser(peerObj.peerID))
}
const handlePin = ()=>{
if(peerObj.isPinned)unPinPeer();
else pinPeer()
}
/**
* set scale of userVideo based on the status of
* peer
* depending on if userBar is open or not,or if user
* is pinned or not
*/
let colLg = ()=>{
if(peerObj.isPinned&&userDeckopen)return 12;
else if(peerObj.isPinned)return 14;
else return 7;
}
let colmd= ()=>{
if(peerObj.isPinned&&userDeckopen)return 6;
else if(peerObj.isPinned)return 7;
else return 6;
}
return (
<Column sm={4} md={colmd()} lg={colLg()} key={peerObj.peerID} className="video__col">
<p style={{color:"white"}}>{
peerObj?.peerID==userID?"You":peerObj?.user?.username
}</p>
<div className="peer__video">
{checkVideo()?<video autoPlay
playsInline ref={ref} className={"videoplayer "+
(peerObj.isPinned?"pinned":"")
+(peerObj.isPinned&&userDeckopen?" pinned_deck":"")
}
muted={peerObj.peerID==userID}/>:
<div className={"user__tile " +
(peerObj.isPinned?"pinned":"")
+(peerObj.isPinned&&userDeckopen?" pinned_deck":"")
}>
{
checkUser()?(<img src={peerObj?.user?.image} className="user__avatar"/>):
(<UserAvatar20 className="user__avatar"/>)
}
</div>
}
<div className="user_video_gizmo user__gizmos__active" onClick={()=>{
handlePin()
}}>
{peerObj.isPinned?<Undo20 />:<PinFilled20/>}
</div>
<div className="media__state">
{checkAudio()?<MicrophoneFilled20/>:
<MicrophoneOffFilled20 className="media__off"/>}
{checkVideo()?<VideoChat20/>:<VideoOffFilled20 className="media__off"/>}
</div>
</div>
</Column>
);
}
export default CallVideo
视频控件
import { Chat20, EventsAlt20, FaceActivated20, FaceActivatedAdd20, MicrophoneFilled20, MicrophoneOffFilled20, PhoneBlockFilled20, Screen20, ScreenOff20, Share20,VideoFilled20, VideoOffFilled20 } from '@carbon/icons-react'
import React, { useContext, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useHistory, useParams } from 'react-router-dom';
import { SocketContext } from '../../context/GlobalSocketContext';
import { setmediaState } from '../../store/actions/socket';
import * as actionTypes from "../../store/constants/socket"
import "./_style.css"
/**
* VideoControls
* Display user video chat gizmos inside meet
* @component
*/
function VideoControls() {
const mediaState = useSelector(state => state.socket.mediaState)
const screenOn = useSelector(state=>state.socket.screenOn);
const {meetID}= useParams()
const context = useContext(SocketContext)
const userVideoStream = useSelector(state=>state.socket.userVideoStream);
const dispatch = useDispatch()
const userID = useSelector(state=>state.auth.userID);
const history = useHistory()
/**start video */
const handleVideoOff = (e)=>{
if(userVideoStream)console.log(userVideoStream.getVideoTracks()[0])
if(userVideoStream)userVideoStream.getVideoTracks()[0].enabled=true;
dispatch(setmediaState({videoPaused:false},userID,context.socket,meetID))
}
/**stop video */
const handleVideoOn =()=>{
if(userVideoStream)console.log(userVideoStream.getVideoTracks()[0])
if(userVideoStream)userVideoStream.getVideoTracks()[0].enabled=false;
dispatch(setmediaState({videoPaused:true},userID,context.socket,meetID))
}
/**stop audio */
const handleAudioOn = (e)=>{
if(userVideoStream&&
userVideoStream.getAudioTracks()[0])userVideoStream.getAudioTracks()[0].enabled=false;
dispatch(setmediaState({muted:true},userID,context.socket,meetID))
}
/**start audio */
const handleAudioOff = (e)=>{
if(userVideoStream
&&userVideoStream.getAudioTracks()[0])userVideoStream.getAudioTracks()[0].enabled=true;
dispatch(setmediaState({muted:false},userID,context.socket,meetID))
}
/**stop screen */
const handleScreenOn = (e)=>{
context.stopScreenShare();
}
/**start screen */
const handleScreenOff = (e)=>{
context.startScreenShare()
}
/**share link to users */
const shareLink = (e)=>{
prompt(
"Copy this link and send it to people you want to meet with",
window.location.href
);
}
/**open meetpeople sidebar */
const openPeople = (e)=>{dispatch({
type:actionTypes.PEOPLE_ACTIVE
})}
/**open chat sidebar */
const openChat = (e)=>{dispatch({
type:actionTypes.CHAT_ACTIVE
})}
/**exit meet */
const leaveMeet = (e)=>{
history.push("/dashboard")
}
return(
<div className="video__controls">
<div>
{!mediaState.videoPaused?<VideoFilled20 onClick={e=>handleVideoOn(e)}/>:
<VideoOffFilled20 onClick={e=>handleVideoOff(e)}/>}
</div>
<div>
{!mediaState.muted?<MicrophoneFilled20 onClick={e=>handleAudioOn(e)}/>:
<MicrophoneOffFilled20 onClick={e=>handleAudioOff(e)}/>}
</div>
<div>
{screenOn?<ScreenOff20 onClick={e=>handleScreenOn(e)}/>:
<Screen20 onClick={e=>handleScreenOff(e)}/>}
</div>
<div onClick={(e=>openChat(e))}>
<Chat20/>
</div>
<div onClick={(e)=>openPeople(e)}>
<EventsAlt20/>
</div>
<div onClick={(e)=>shareLink(e)}>
<FaceActivatedAdd20 />
</div>
<div className="end__call" onClick={e=>leaveMeet()}>
<PhoneBlockFilled20 />
</div>
</div>
)
}
export default VideoControls
如果有人愿意,我也可以提供 github 链接。