From 927fa2d1a2982f98b199c73077d79e9f6c09c601 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 28 Jul 2020 20:31:15 +0200 Subject: [PATCH] working rtc connection --- README.md | 3 +- client/public/index.html | 1 + client/src/store/appModule.js | 10 +- client/src/store/roomModule.js | 11 +- client/src/store/rtcModule.js | 143 +++++++++++++----- client/src/store/signalPlugin.js | 20 ++- client/src/views/Home.vue | 1 + client/src/views/Room.vue | 71 ++++++--- .../gltronic/oozik/business/IRoomManager.java | 4 +- .../gltronic/oozik/business/RoomManager.java | 19 ++- .../gltronic/oozik/web/SocketHandler.java | 4 +- 11 files changed, 208 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 571e1d6..ef3687f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -# oozik 2 +# voozik Webapp edition socket messages types: serverInfos, login, leave, userList, createRoom, connectRoom, offer, answer, candidate +data types: status, vote diff --git a/client/public/index.html b/client/public/index.html index 4123528..e9bb139 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -4,6 +4,7 @@ + <%= htmlWebpackPlugin.options.title %> diff --git a/client/src/store/appModule.js b/client/src/store/appModule.js index 7478e05..09fff12 100644 --- a/client/src/store/appModule.js +++ b/client/src/store/appModule.js @@ -32,6 +32,11 @@ const actions = { createRoom ({ commit, dispatch }, code) { commit('CREATE_ROOM') dispatch('room/setRoomCode', code, { root: true }) + dispatch('room/setAdmin', null, { root: true }) + }, + connectRoom ({ commit, dispatch }, name) { + commit('CONNECT_ROOM') + dispatch('rtc/makeOffer', name, { root: true }) }, error ({ commit }, error) { commit('ERROR', error) @@ -56,8 +61,9 @@ const mutations = { state.serverStatus = serverStatus }, CREATE_ROOM (state) { - state.room = true - state.admin = true + router.push({ name: 'Room' }) + }, + CONNECT_ROOM (state) { router.push({ name: 'Room' }) }, ERROR (state, error) { diff --git a/client/src/store/roomModule.js b/client/src/store/roomModule.js index 83c9709..7a0f27c 100644 --- a/client/src/store/roomModule.js +++ b/client/src/store/roomModule.js @@ -9,8 +9,6 @@ const state = { } const getters = { - displayRoomCode: state => state.roomCode, - displayRoomName: state => state.roomName } const actions = { @@ -22,6 +20,9 @@ const actions = { }, setRoomStatus ({ commit }, roomStatus) { commit('SET_ROOMSTATUS', roomStatus) + }, + setAdmin ({ commit }) { + commit('SET_ADMIN') } } @@ -34,6 +35,12 @@ const mutations = { }, SET_ROOMSTATUS (state, roomStatus) { state.roomStatus = roomStatus + }, + SET_ADMIN (state) { + state.admin = true + }, + BROADCAST_ROOMSTATUS (state) { + } } diff --git a/client/src/store/rtcModule.js b/client/src/store/rtcModule.js index 4a522a4..d08dadf 100644 --- a/client/src/store/rtcModule.js +++ b/client/src/store/rtcModule.js @@ -1,4 +1,7 @@ import { send } from './signalPlugin' +import store from './index' + +var lastPeer // horrible const configuration = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] @@ -6,7 +9,7 @@ const configuration = { const state = { name: null, - connections: new Map() + peers: [] } const getters = { @@ -17,15 +20,21 @@ const actions = { setName ({ commit }, name) { commit('SET_NAME', name) }, - async offer ({ commit }, offer, name) { - commit('CREATE_PEER_CONNECTION', name) - commit('ANSWER', name, offer) + async makeOffer ({ commit }, targetName) { + commit('CREATE_PEER_CONNECTION', targetName) + commit('OFFER', targetName) }, - answer ({ commit }, answer, name) { - commit('OFFER', name, answer) + async offer ({ commit }, { offer, senderName }) { + console.log('offer from ' + senderName) + commit('CREATE_PEER_CONNECTION', senderName) + commit('ANSWER', { target: senderName, offer: offer }) }, - candidate ({ commit }, candidate, name) { - commit('ANSWER', name, candidate) + answer ({ commit }, { answer, senderName }) { + console.log('answer from ' + senderName) + commit('FINALIZE', { target: senderName, answer: answer }) + }, + candidate ({ commit }, { candidate, senderName }) { + commit('CANDIDATE', { target: senderName, candidate: candidate }) }, leave ({ commit }) { commit('LEAVE') @@ -40,78 +49,105 @@ const mutations = { state.name = name }, CREATE_PEER_CONNECTION (state, target) { - console.log('CREATED PEER CONNECTION') - var connection = new RTCPeerConnection(configuration) - state.connections[target] = connection + console.log('[RTC] create peer connection with ' + target) + var peer = { + name: target, + connection: new RTCPeerConnection(configuration), + dataChannel: null + } - connection.onicecandidate = function (event) { + state.peers.push(peer) + + peer.connection.onicecandidate = function (event) { if (event.candidate) { send({ type: 'candidate', - name: name, + name: state.name, target: target, - candidate: event.candidate + data: event.candidate }) } } - connection.onnegotiationneeded = function () { handleNegotiationNeededEvent(target) } - connection.onsignalingstatechange = function () { handleSignalingStateChangeEvent(connection) } - connection.oniceconnectionstatechange = function () { handleICEConnectionStateChangeEvent(connection) } - connection.onicegatheringstatechange = function () { handleICEGatheringStateChangeEvent(connection) } + peer.connection.onnegotiationneeded = function () { handleNegotiationNeededEvent(target) } + peer.connection.onsignalingstatechange = function () { handleSignalingStateChangeEvent(peer.connection) } + peer.connection.oniceconnectionstatechange = function () { handleICEConnectionStateChangeEvent(peer.connection) } + peer.connection.onicegatheringstatechange = function () { handleICEGatheringStateChangeEvent(peer.connection) } }, - CREATE_DATA_CHANNEL (state) { + async OFFER (state, target) { + console.log('[RTC] make offer to ' + target) + var peer = state.peers.find(peer => peer.name === target) + peer.dataChannel = peer.connection.createDataChannel('dataChannel') + peer.dataChannel.onopen = handleDataChannelStateChangeEvent + peer.dataChannel.onclose = handleDataChannelStateChangeEvent + + var offer = await peer.connection.createOffer() + send({ + type: 'offer', + name: state.name, + target: target, + data: offer + }) + await peer.connection.setLocalDescription(offer) }, - ANSWER (state, target, offer) { - var connection = state.connections[target] + async ANSWER (state, { target, offer }) { + console.log('[RTC] answer to ' + target) + var peer = state.peers.find(peer => peer.name === target) - connection.setRemoteDescription(new RTCSessionDescription(offer)) + // Permet d'associer le datachannel de la callback au bon peer + lastPeer = peer - var answer = connection.createAnswer() + peer.connection.ondatachannel = handleDataChannelCallback - connection.setLocalDescription(answer) + await peer.connection.setRemoteDescription(new RTCSessionDescription(offer)) + + var answer = await peer.connection.createAnswer() + + await peer.connection.setLocalDescription(answer) send({ type: 'answer', - name: name, + name: state.name, target: target, - answer: answer + data: answer }) }, - OFFER (state, target, answer) { - var connection = state.connections[target] - connection.setRemoteDescription(new RTCSessionDescription(answer)) + FINALIZE (state, { target, answer }) { + var peer = state.peers.find(peer => peer.name === target) + peer.connection.setRemoteDescription(new RTCSessionDescription(answer)) }, - CANDIDATE (state, target, candidate) { - var connection = state.connections[target] - connection.addIceCandidate(new RTCIceCandidate(candidate)) + CANDIDATE (state, { target, candidate }) { + var peer = state.peers.find(peer => peer.name === target) + peer.connection.addIceCandidate(new RTCIceCandidate(candidate)) }, LEAVE (state) { - state.connections.forEach((connection) => { - connection.close() - connection = null + state.peers.forEach((peer) => { + peer.connection.close() }) - state.connections = new Map() + state.peers = {} }, BROADCAST (state, message) { + state.peers.forEach(peer => { + peer.dataChannel.send(message) + }) } } function handleICEConnectionStateChangeEvent (connection) { - console.log('ICE CONNECTION CHANGE ' + connection.iceConnectionState) + console.log('[RTC] ice connection change to ' + connection.iceConnectionState) } function handleICEGatheringStateChangeEvent (connection) { - console.log('ICE GATHERING CHANGE ' + connection.iceGatheringState) + console.log('[RTC] ice gathering change to ' + connection.iceGatheringState) } async function handleNegotiationNeededEvent (target) { - console.log('NEGOTIATION NEEDED FROM ' + target) + console.log('[RTC] negotiation needed from ' + target) } async function handleSignalingStateChangeEvent (connection) { - console.log('STATE CHANGED TO : ' + connection.signalingState) + console.log('[RTC] state changed to ' + connection.signalingState) switch (connection.signalingState) { case 'closed': await connection.close() @@ -119,6 +155,33 @@ async function handleSignalingStateChangeEvent (connection) { } } +function handleDataChannelStateChangeEvent () { + console.log('[RTC] data channel state change') +} + +function handleDataChannelCallback (event) { + console.log('[RTC] data channel callback ' + event) + var peer = lastPeer + peer.dataChannel = event.channel + peer.dataChannel.onmessage = handleDataChannelMessage + peer.dataChannel.onopen = handleDataChannelStateChangeEvent + peer.dataChannel.onclose = handleDataChannelStateChangeEvent + + store.dispatch('rtc/broadcast', store.state.room.roomStatus) +} + +function handleDataChannelMessage (event) { + console.log('[RTC] data channel message ' + event.data) + var data = event.data + switch (data.type) { + case 'status': + store.dispatch('room/setRoomStatus', data.message) + break + case 'vote': + break + } +} + export default { namespaced: true, state, diff --git a/client/src/store/signalPlugin.js b/client/src/store/signalPlugin.js index f827bb6..7b1a51b 100644 --- a/client/src/store/signalPlugin.js +++ b/client/src/store/signalPlugin.js @@ -4,31 +4,33 @@ const connection = new WebSocket('ws://localhost:8181/socket') export default function createSignalPlugin () { return store => { connection.onopen = function () { - console.log('WS connected') + console.log('[WS] connected') store.dispatch('app/signalConnected') } connection.onerror = function (error) { - console.log('WS error ' + error) + console.log('[WS] error ' + error) store.dispatch('app/signalError', error) } connection.onmessage = function (message) { - console.log('WS message', message.data) + console.log('[WS] message', message.data) var data = JSON.parse(message.data) switch (data.type) { case 'offer': - store.dispatch('rtc/offer', data.offer, data.name) + console.log('offer from ' + data.name) + store.dispatch('rtc/offer', { offer: data.data, senderName: data.name }) break case 'answer': - store.dispatch('rtc/answer', data.answer, data.name) + console.log('answer from ' + data.name) + store.dispatch('rtc/answer', { answer: data.data, senderName: data.name }) break case 'candidate': - store.dispatch('rtc/candidate', data.candidate, data.name) + store.dispatch('rtc/candidate', { candidate: data.data, senderName: data.name }) break case 'leave': @@ -47,6 +49,10 @@ export default function createSignalPlugin () { store.dispatch('app/createRoom', data.message) break + case 'connectRoom': + store.dispatch('app/connectRoom', data.message) + break + case 'error': store.dispatch('app/error', data.message) break @@ -59,6 +65,6 @@ export default function createSignalPlugin () { } export function send (message) { - console.log('WS send', message) + console.log('[WS] send', message) connection.send(JSON.stringify(message)) } diff --git a/client/src/views/Home.vue b/client/src/views/Home.vue index de5471b..b5cd71b 100644 --- a/client/src/views/Home.vue +++ b/client/src/views/Home.vue @@ -33,6 +33,7 @@ export default { this.$buefy.dialog.prompt({ message: 'Choose a name', trapFocus: true, + canCancel: false, inputAttrs: { placeholder: 'pedro', minlength: 3, diff --git a/client/src/views/Room.vue b/client/src/views/Room.vue index 242cac7..7b5a712 100644 --- a/client/src/views/Room.vue +++ b/client/src/views/Room.vue @@ -1,29 +1,51 @@ @@ -33,7 +55,20 @@ export default { computed: { roomStatus () { return this.$store.state.room.roomStatus + }, + showAdmin () { + return this.$store.state.room.admin + }, + usersList () { + console.log('USER LIST ' + this.$store.state.rtc.peers) + return this.$store.state.rtc.peers + }, + isLoggedIn () { + return this.$store.state.app.loginSuccess } + }, + mounted () { + if (!this.isLoggedIn) this.$router.push({ name: 'Home' }) } } diff --git a/server/src/main/java/gltronic/oozik/business/IRoomManager.java b/server/src/main/java/gltronic/oozik/business/IRoomManager.java index c0a1027..9555b75 100644 --- a/server/src/main/java/gltronic/oozik/business/IRoomManager.java +++ b/server/src/main/java/gltronic/oozik/business/IRoomManager.java @@ -2,7 +2,7 @@ package gltronic.oozik.business; import java.io.IOException; -import org.json.JSONObject; +import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; public interface IRoomManager { @@ -10,7 +10,7 @@ public interface IRoomManager { public void leave(WebSocketSession session); public void createRoom(WebSocketSession session) throws InterruptedException, IOException; public void connectRoom(WebSocketSession session, String roomName) throws InterruptedException, IOException; - public void followRTC(WebSocketSession session, JSONObject jsonObject) throws InterruptedException, IOException; + public void followRTC(WebSocketSession session, TextMessage message) throws InterruptedException, IOException; public void sendMessage(WebSocketSession session, String type, String message) throws InterruptedException, IOException; public void sendServerInfos(WebSocketSession session) throws InterruptedException, IOException; } \ No newline at end of file diff --git a/server/src/main/java/gltronic/oozik/business/RoomManager.java b/server/src/main/java/gltronic/oozik/business/RoomManager.java index d6000c3..4ff6bce 100644 --- a/server/src/main/java/gltronic/oozik/business/RoomManager.java +++ b/server/src/main/java/gltronic/oozik/business/RoomManager.java @@ -74,14 +74,17 @@ public class RoomManager implements IRoomManager{ return; } - String roomAdmin = rooms.getKey(roomName); - sendMessage(session, "coonectRoom", roomAdmin); + String roomAdmin = rooms.get(roomName); + sendMessage(session, "connectRoom", roomAdmin); + + String userName = users.getKey(session); + System.err.println("[ROOM] Connection to room "+roomName+" ("+roomAdmin+") by "+userName); } - public void followRTC(WebSocketSession session, JSONObject jsonObject) throws InterruptedException, IOException { + public void followRTC(WebSocketSession session, TextMessage message) throws InterruptedException, IOException { + String payload = message.getPayload(); + JSONObject jsonObject = new JSONObject(payload); String target = (String) jsonObject.get("target"); - String type = (String) jsonObject.get("type"); - String data = (String) jsonObject.get("data"); if (target == null) { sendMessage(session, "error", "no target"); @@ -96,7 +99,11 @@ public class RoomManager implements IRoomManager{ } System.err.println("[ROOM] Foward RTC"); - sendMessage(targetSession, type, data); + followMessage(targetSession, message); + } + + public void followMessage (WebSocketSession session, TextMessage message) throws IOException { + session.sendMessage(message); } public void sendMessage(WebSocketSession session, String type, String message) throws InterruptedException, IOException { diff --git a/server/src/main/java/gltronic/oozik/web/SocketHandler.java b/server/src/main/java/gltronic/oozik/web/SocketHandler.java index a481581..306f95f 100644 --- a/server/src/main/java/gltronic/oozik/web/SocketHandler.java +++ b/server/src/main/java/gltronic/oozik/web/SocketHandler.java @@ -43,10 +43,12 @@ public class SocketHandler extends TextWebSocketHandler { break; case "leave": roomManager.leave(session); + break; case "offer": case "answer": case "candidate": - roomManager.followRTC(session, jsonObject); + roomManager.followRTC(session, message); + break; default: roomManager.sendMessage(session, "error", "unknow command"); }