Skip to content

Commit 0f727e1

Browse files
Merge pull request #266 from Front-line-dev/fix/voice-chat
보이스 채팅 audio태그, 연결 해제 및 재연결, 태그 중복 생성
2 parents 4636845 + f9dea0e commit 0f727e1

File tree

7 files changed

+164
-91
lines changed

7 files changed

+164
-91
lines changed

src/backend/sockets/exitRoom.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ function exitRoom() {
1313

1414
if (game.getState() === GAME_STATE.WAITING) {
1515
socket.in(roomID).emit('exit player', passedData);
16+
socket.in(roomID).emit('voice disconnected', passedData);
1617
} else {
1718
// READY 상태일때 나가면 답이 없음
1819
socket.in(roomID).emit('game terminated', {})

src/backend/sockets/voiceChat.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1-
function onConnectVoice(id) {
1+
function onConnectVoice() {
22
const socket = this;
33
const { game } = socket;
4-
socket.in(game.roomID).emit('another voice connected', id);
54

6-
socket.on('disconnect', () => {
7-
socket.in(game.roomID).emit('voice disconnected', id);
5+
if (!game) return;
6+
7+
socket.in(game.roomID).emit('another voice connected', {
8+
socketID: socket.id,
9+
});
10+
}
11+
12+
function onDisconnectVoice() {
13+
const socket = this;
14+
const { game } = socket;
15+
16+
if (!game) return;
17+
18+
socket.in(game.roomID).emit('voice disconnected', {
19+
socketID: socket.id,
820
});
921
}
1022

1123
export default function onVoiceChat(socket) {
12-
socket.on('player connect voice channel', onConnectVoice);
24+
socket.on('player connect voice', onConnectVoice);
25+
socket.on('player disconnect voice', onDisconnectVoice);
1326
}

src/frontend/game/LeftTab.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import './left.scss';
22
import { $id } from '@utils/dom';
33
import DuckLeftTabObject from '@engine/DuckLeftTabObject';
44
import PlayerManager from '@utils/PlayerManager';
5-
import initVoiceChat from '@utils/voiceChat';
5+
import { activateVoiceChat, deactivateVoiceChat } from './voiceChat';
66

77
const createDuck = (duckInfo) => {
88
const { socketID, color, nickname } = duckInfo;
@@ -13,14 +13,28 @@ const createDuck = (duckInfo) => {
1313
class LeftTab {
1414
constructor() {
1515
this.ducks = [];
16+
this.voiceActive = false;
1617
PlayerManager.onInitialize.push(this.initializePlayers.bind(this));
1718
PlayerManager.onUpdate.push(this.updateDuck.bind(this));
1819
PlayerManager.onDelete.push(this.deletePlayer.bind(this));
20+
$id('microphone-controller').addEventListener(
21+
'click',
22+
this.toggleVoiceChat,
23+
);
1924
}
2025

2126
initializePlayers() {
2227
this.render();
23-
initVoiceChat();
28+
}
29+
30+
toggleVoiceChat() {
31+
if (this.voiceActive) {
32+
this.voiceActive = false;
33+
deactivateVoiceChat();
34+
} else {
35+
this.voiceActive = true;
36+
activateVoiceChat();
37+
}
2438
}
2539

2640
findDuck(socketID) {

src/frontend/game/game.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313
<body>
1414
<div class="game-layout">
1515
<div id="left-tab" class="left-tab">
16-
<div id="video-grid"></div>
16+
<div id="voice-chat-audio-container"></div>
1717
<div class="left-title-wrapper">
1818
<span class="left-title-text">전체 참여자</span>
1919
<span id="participants-count" class="left-title-number">0</span>
2020
</div>
2121
<div id="participants-wrapper" class="participants-wrapper"></div>
22-
<div class="microphone-controller">
22+
<div class="microphone-controller" id="microphone-controller">
2323
<span>마이크 연결</span>
2424
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"/></svg>
2525
</div>

src/frontend/game/voiceChat.js

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import socket from '@utils/socket';
2+
import Peer from 'peerjs';
3+
import {
4+
getAudioStream,
5+
setAnswerBehavior,
6+
connectToNewUser,
7+
} from '@utils/voiceChatUtil';
8+
9+
let myPeerJSClient = null;
10+
let myAudioStream = null;
11+
12+
const peerMap = new Map();
13+
14+
const deleteOtherPeer = (socketID) => {
15+
if (peerMap.has(socketID)) {
16+
peerMap.get(socketID).mediaConnection.close();
17+
peerMap.delete(socketID);
18+
}
19+
};
20+
21+
const isConnectedToVoiceChat = () => {
22+
return myPeerJSClient && myAudioStream;
23+
};
24+
25+
// 내가 보이스 채팅 접속
26+
const activateVoiceChat = () => {
27+
myPeerJSClient = new Peer(socket.id);
28+
myPeerJSClient.on('open', async () => {
29+
try {
30+
// 마이크 연결
31+
myAudioStream = await getAudioStream();
32+
setAnswerBehavior({
33+
stream: myAudioStream,
34+
peerMap,
35+
peer: myPeerJSClient,
36+
});
37+
} catch (err) {
38+
console.log('Get Media error: ', err);
39+
}
40+
41+
socket.emit('player connect voice');
42+
});
43+
};
44+
45+
// 내가 보이스 채팅 접속을 끊었을 때
46+
const deactivateVoiceChat = () => {
47+
socket.emit('player disconnect voice');
48+
myPeerJSClient.destroy();
49+
myPeerJSClient = null;
50+
myAudioStream.getTracks().forEach((track) => {
51+
track.stop();
52+
});
53+
54+
peerMap.forEach((_, socketID) => {
55+
deleteOtherPeer(socketID);
56+
});
57+
};
58+
59+
// 연결된 유저가 보이스 채팅 접속을 끊었을 때
60+
socket.on('voice disconnected', ({ socketID }) => {
61+
if (!isConnectedToVoiceChat()) return;
62+
63+
deleteOtherPeer(socketID);
64+
});
65+
66+
socket.on('another voice connected', ({ socketID }) => {
67+
if (!isConnectedToVoiceChat()) return;
68+
69+
connectToNewUser({
70+
peer: myPeerJSClient,
71+
socketID,
72+
stream: myAudioStream,
73+
peerMap,
74+
});
75+
});
76+
77+
export { activateVoiceChat, deactivateVoiceChat };

src/frontend/utils/voiceChat.js

-82
This file was deleted.

src/frontend/utils/voiceChatUtil.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const audioContainer = document.getElementById('voice-chat-audio-container');
2+
3+
const addAudioStream = ({ mediaConnection, peerMap }) => {
4+
const socketID = mediaConnection.peer;
5+
6+
if (peerMap.has(socketID)) return;
7+
8+
const audioElement = document.createElement('audio');
9+
10+
mediaConnection.on('stream', (stream) => {
11+
// eslint-disable-next-line no-param-reassign
12+
audioElement.srcObject = stream;
13+
audioElement.addEventListener('loadedmetadata', () => {
14+
audioElement.play();
15+
});
16+
audioContainer.append(audioElement);
17+
});
18+
19+
mediaConnection.on('close', () => {
20+
audioElement.remove();
21+
});
22+
23+
peerMap.set(socketID, {
24+
mediaConnection,
25+
audioElement,
26+
});
27+
};
28+
29+
// socket을 통해 다른 사람이 접속한걸 받았을 때
30+
// 다른 사람에게 mediaConnection 요청을 보냄
31+
const connectToNewUser = ({ peer, socketID, stream, peerMap }) => {
32+
const mediaConnection = peer.call(socketID, stream);
33+
addAudioStream({ mediaConnection, peerMap });
34+
};
35+
36+
// 내가 다른 사람의 mediaConnection 요청을 받았을 때
37+
const setAnswerBehavior = ({ stream, peerMap, peer }) => {
38+
peer.on('call', (mediaConnection) => {
39+
// 다른 사람의 요청에 answer를 날림
40+
mediaConnection.answer(stream);
41+
addAudioStream({ mediaConnection, peerMap });
42+
});
43+
};
44+
45+
const getAudioStream = () =>
46+
navigator.mediaDevices.getUserMedia({
47+
audio: true,
48+
});
49+
50+
export { getAudioStream, setAnswerBehavior, connectToNewUser };

0 commit comments

Comments
 (0)