리액트에서 오픈비두를 사용하여 화상미팅을 구현하는 것은 매우 간단하다.
우선 오픈비두 라이브러리를 install한다.
npm i openvidu-browser
이러면 오픈비두를 사용할 모든 준비를 마쳤다. (여기서는 오픈비두 서버를 띄우는 것은 하지 않겠다)
오픈비두 라이브러리를 사용하여 화상 미팅을 구현하는 단계는 다음과 같다.
Publisher
OpenVidu 객체 생성 → session 생성 → token 생성 → user media 객체 생성 → publish
Subscriber
OpenVidu 객체 생성 → session 생성 → 메세지 수신 이벤트 정의 → 수신된 이벤트 처리
State
const [OV, setOV] = useState<OpenVidu>();
const [session, setSession] = useState<Session>();
const [initUserData, setInitUserData] = useState({
mySessionId: channelId,
myUserName: userInfo.nickname,
});
const [publisher, setPublisher] = useState<Publisher | null>(null);
const [subscribers, setSubscribers] = useState<Array<StreamManager>>([]);
Publish 과정
- OpenVidu 객체 생성
- Session 객체 생성
- state에 OpenVidu 객체와 Session 객체 담기 (unmount 되었을 때 session을 disconnect 하기 위해)
- connection 함수 정의
- token 생성
- user media 객체 생성
- publish
const joinSession = () => {
// 1. openvidu 객체 생성
const newOV = new OpenVidu();
// socket 통신 과정에서 많은 log를 남기게 되는데 필요하지 않은 log를 띄우지 않게 하는 모드
newOV.enableProdMode();
// 2. initSesison 생성
const newSession = newOV.initSession();
// 3. 미팅을 종료하거나 뒤로가기 등의 이벤트를 통해 세션을 disconnect 해주기 위해 state에 저장
setOV(newOV);
setSession(newSession);
// 4. session에 connect하는 과정
const connection = () => {
// 4-a token 생성
getToken(initUserData.mySessionId!).then((token: any) => {
newSession
.connect(token, { clientData: initUserData.myUserName })
.then(async () => {
// 4-b user media 객체 생성
newOV
.getUserMedia({
audioSource: false,
videoSource: undefined,
resolution: '1280x720',
frameRate: 10,
})
.then((mediaStream) => {
var videoTrack = mediaStream.getVideoTracks()[0];
var newPublisher = newOV.initPublisher(
initUserData.myUserName,
{
audioSource: undefined,
videoSource: videoTrack,
publishAudio: isAudioOn,
publishVideo: isVideoOn,
// resolution: '1280x720',
// frameRate: 10,
insertMode: 'APPEND',
mirror: true,
}
);
// 4-c publish
newPublisher.once('accessAllowed', () => {
newSession.publish(newPublisher);
setPublisher(newPublisher);
});
});
})
.catch((error) => {
console.warn(
'There was an error connecting to the session:',
error.code,
error.message
);
});
});
};
connection();
};
getToken
const getToken = async (sessionId: string) => {
return createSession(sessionId).then((sessionId: any) =>
createToken(sessionId)
);
};
createSession
export const createSession = (sessionId: string) => {
const data = JSON.stringify({ customSessionId: sessionId });
return new Promise(async (resolve, reject) => {
await openviduInstance
.post(`/openvidu/api/sessions`, data)
.then((response) => {
resolve(response.data.id);
})
.catch((response) => {
const err = Object.assign({}, response);
if (err?.response?.status === 409) {
resolve(sessionId);
} else {
console.warn(
'No connection to OpenVidu Server. This may be a certificate error at ' +
process.env.REACT_APP_OPENVIDU_SERVER_URL
);
if (
window.confirm(
'No connection to OpenVidu Server. This may be a certificate error at "' +
process.env.REACT_APP_OPENVIDU_SERVER_URL +
'"\\n\\nClick OK to navigate and accept it. ' +
'If no certificate warning is shown, then check that your OpenVidu Server is up and running at "' +
process.env.REACT_APP_OPENVIDU_SERVER_URL +
'"'
)
) {
window.location.assign(
process.env.REACT_APP_OPENVIDU_SERVER_URL + '/accept-certificate'
);
}
}
});
});
};
createToken
export const createToken = (sessionId: string) => {
const data = {};
return new Promise(async (resolve, reject) => {
await openviduInstance
.post(`/openvidu/api/sessions/${sessionId}/connection`, data)
.then((response) => {
resolve(response.data.token);
})
.catch((error) => reject(error));
});
};
getDevices 와 getUserMedia
미디어 정보를 담은 객체를 생성할 때 두가지 방법이 존재하였다.
getDevices 메소드를 사용하는 방법과 getUserMedia를 사용하는 방법이 있는데
getDevices 메소스를 사용하여 구현을 하였을 때 웹에서 카메라를 허용하는지 묻지 않는 문제가 있었다. 권한 설정에서 강제로 허용을 해주면 해결되는 문제였지만 사용자 입장에서 권한을 허용할지 묻는 것이 사용성이 더 뛰어날 것이라고 생각하여 getUserMedia 메소드를 사용하였다.
Subscribe 과정
subscribe 과정은 선언한 session에 메세지 수신 이벤트를 처리해주는 것만으로 간단하게 이루어진다.
- Session에 메세지 수신 이벤트 처리
- 수신한 이벤트에 따라 로직 구현
...
const connection = () => {
...
// 1. connection 메소드 내부에 이벤트 수신 처리
// 1-1 session에 참여한 사용자 추가
newSession.on('streamCreated', (event) => {
const newSubscriber = newSession.subscribe(
event.stream,
JSON.parse(event.stream.connection.data).clientData
);
const newSubscribers = subscribers;
newSubscribers.push(newSubscriber);
setSubscribers([...newSubscribers]);
});
// 1-2 session에서 disconnect한 사용자 삭제
newSession.on('streamDestroyed', (event) => {
if (event.stream.typeOfVideo === 'CUSTOM') {
deleteSubscriber(event.stream.streamManager);
} else {
setDestroyedStream(event.stream.streamManager);
setCheckMyScreen(true);
}
});
// 1-3 예외처리
newSession.on('exception', (exception) => {
console.warn(exception);
});
...
}
...
deleteSubscriber
const deleteSubscriber = (streamManager: StreamManager) => {
const prevSubscribers = subscribers;
let index = prevSubscribers.indexOf(streamManager, 0);
if (index > -1) {
prevSubscribers.splice(index, 1);
setSubscribers([...prevSubscribers]);
}
};
'PROJECT' 카테고리의 다른 글
OpenVidu 라이브러리 (0) | 2022.06.20 |
---|---|
DOTORE 프로젝트 회고 (0) | 2022.06.11 |
DRAWING DREAM 프로젝트 회고 (4) | 2022.02.23 |