working rtc connection

This commit is contained in:
Thomas
2020-07-28 20:31:15 +02:00
parent c1787cacba
commit 927fa2d1a2
11 changed files with 208 additions and 79 deletions

View File

@@ -1,5 +1,6 @@
# oozik 2 # voozik
Webapp edition Webapp edition
socket messages types: serverInfos, login, leave, userList, createRoom, connectRoom, offer, answer, candidate socket messages types: serverInfos, login, leave, userList, createRoom, connectRoom, offer, answer, candidate
data types: status, vote

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
</head> </head>

View File

@@ -32,6 +32,11 @@ const actions = {
createRoom ({ commit, dispatch }, code) { createRoom ({ commit, dispatch }, code) {
commit('CREATE_ROOM') commit('CREATE_ROOM')
dispatch('room/setRoomCode', code, { root: true }) 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) { error ({ commit }, error) {
commit('ERROR', error) commit('ERROR', error)
@@ -56,8 +61,9 @@ const mutations = {
state.serverStatus = serverStatus state.serverStatus = serverStatus
}, },
CREATE_ROOM (state) { CREATE_ROOM (state) {
state.room = true router.push({ name: 'Room' })
state.admin = true },
CONNECT_ROOM (state) {
router.push({ name: 'Room' }) router.push({ name: 'Room' })
}, },
ERROR (state, error) { ERROR (state, error) {

View File

@@ -9,8 +9,6 @@ const state = {
} }
const getters = { const getters = {
displayRoomCode: state => state.roomCode,
displayRoomName: state => state.roomName
} }
const actions = { const actions = {
@@ -22,6 +20,9 @@ const actions = {
}, },
setRoomStatus ({ commit }, roomStatus) { setRoomStatus ({ commit }, roomStatus) {
commit('SET_ROOMSTATUS', roomStatus) commit('SET_ROOMSTATUS', roomStatus)
},
setAdmin ({ commit }) {
commit('SET_ADMIN')
} }
} }
@@ -34,6 +35,12 @@ const mutations = {
}, },
SET_ROOMSTATUS (state, roomStatus) { SET_ROOMSTATUS (state, roomStatus) {
state.roomStatus = roomStatus state.roomStatus = roomStatus
},
SET_ADMIN (state) {
state.admin = true
},
BROADCAST_ROOMSTATUS (state) {
} }
} }

View File

@@ -1,4 +1,7 @@
import { send } from './signalPlugin' import { send } from './signalPlugin'
import store from './index'
var lastPeer // horrible
const configuration = { const configuration = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
@@ -6,7 +9,7 @@ const configuration = {
const state = { const state = {
name: null, name: null,
connections: new Map() peers: []
} }
const getters = { const getters = {
@@ -17,15 +20,21 @@ const actions = {
setName ({ commit }, name) { setName ({ commit }, name) {
commit('SET_NAME', name) commit('SET_NAME', name)
}, },
async offer ({ commit }, offer, name) { async makeOffer ({ commit }, targetName) {
commit('CREATE_PEER_CONNECTION', name) commit('CREATE_PEER_CONNECTION', targetName)
commit('ANSWER', name, offer) commit('OFFER', targetName)
}, },
answer ({ commit }, answer, name) { async offer ({ commit }, { offer, senderName }) {
commit('OFFER', name, answer) console.log('offer from ' + senderName)
commit('CREATE_PEER_CONNECTION', senderName)
commit('ANSWER', { target: senderName, offer: offer })
}, },
candidate ({ commit }, candidate, name) { answer ({ commit }, { answer, senderName }) {
commit('ANSWER', name, candidate) console.log('answer from ' + senderName)
commit('FINALIZE', { target: senderName, answer: answer })
},
candidate ({ commit }, { candidate, senderName }) {
commit('CANDIDATE', { target: senderName, candidate: candidate })
}, },
leave ({ commit }) { leave ({ commit }) {
commit('LEAVE') commit('LEAVE')
@@ -40,78 +49,105 @@ const mutations = {
state.name = name state.name = name
}, },
CREATE_PEER_CONNECTION (state, target) { CREATE_PEER_CONNECTION (state, target) {
console.log('CREATED PEER CONNECTION') console.log('[RTC] create peer connection with ' + target)
var connection = new RTCPeerConnection(configuration) var peer = {
state.connections[target] = connection name: target,
connection: new RTCPeerConnection(configuration),
dataChannel: null
}
connection.onicecandidate = function (event) { state.peers.push(peer)
peer.connection.onicecandidate = function (event) {
if (event.candidate) { if (event.candidate) {
send({ send({
type: 'candidate', type: 'candidate',
name: name, name: state.name,
target: target, target: target,
candidate: event.candidate data: event.candidate
}) })
} }
} }
connection.onnegotiationneeded = function () { handleNegotiationNeededEvent(target) } peer.connection.onnegotiationneeded = function () { handleNegotiationNeededEvent(target) }
connection.onsignalingstatechange = function () { handleSignalingStateChangeEvent(connection) } peer.connection.onsignalingstatechange = function () { handleSignalingStateChangeEvent(peer.connection) }
connection.oniceconnectionstatechange = function () { handleICEConnectionStateChangeEvent(connection) } peer.connection.oniceconnectionstatechange = function () { handleICEConnectionStateChangeEvent(peer.connection) }
connection.onicegatheringstatechange = function () { handleICEGatheringStateChangeEvent(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) { async ANSWER (state, { target, offer }) {
var connection = state.connections[target] 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({ send({
type: 'answer', type: 'answer',
name: name, name: state.name,
target: target, target: target,
answer: answer data: answer
}) })
}, },
OFFER (state, target, answer) { FINALIZE (state, { target, answer }) {
var connection = state.connections[target] var peer = state.peers.find(peer => peer.name === target)
connection.setRemoteDescription(new RTCSessionDescription(answer)) peer.connection.setRemoteDescription(new RTCSessionDescription(answer))
}, },
CANDIDATE (state, target, candidate) { CANDIDATE (state, { target, candidate }) {
var connection = state.connections[target] var peer = state.peers.find(peer => peer.name === target)
connection.addIceCandidate(new RTCIceCandidate(candidate)) peer.connection.addIceCandidate(new RTCIceCandidate(candidate))
}, },
LEAVE (state) { LEAVE (state) {
state.connections.forEach((connection) => { state.peers.forEach((peer) => {
connection.close() peer.connection.close()
connection = null
}) })
state.connections = new Map() state.peers = {}
}, },
BROADCAST (state, message) { BROADCAST (state, message) {
state.peers.forEach(peer => {
peer.dataChannel.send(message)
})
} }
} }
function handleICEConnectionStateChangeEvent (connection) { function handleICEConnectionStateChangeEvent (connection) {
console.log('ICE CONNECTION CHANGE ' + connection.iceConnectionState) console.log('[RTC] ice connection change to ' + connection.iceConnectionState)
} }
function handleICEGatheringStateChangeEvent (connection) { function handleICEGatheringStateChangeEvent (connection) {
console.log('ICE GATHERING CHANGE ' + connection.iceGatheringState) console.log('[RTC] ice gathering change to ' + connection.iceGatheringState)
} }
async function handleNegotiationNeededEvent (target) { async function handleNegotiationNeededEvent (target) {
console.log('NEGOTIATION NEEDED FROM ' + target) console.log('[RTC] negotiation needed from ' + target)
} }
async function handleSignalingStateChangeEvent (connection) { async function handleSignalingStateChangeEvent (connection) {
console.log('STATE CHANGED TO : ' + connection.signalingState) console.log('[RTC] state changed to ' + connection.signalingState)
switch (connection.signalingState) { switch (connection.signalingState) {
case 'closed': case 'closed':
await connection.close() 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 { export default {
namespaced: true, namespaced: true,
state, state,

View File

@@ -4,31 +4,33 @@ const connection = new WebSocket('ws://localhost:8181/socket')
export default function createSignalPlugin () { export default function createSignalPlugin () {
return store => { return store => {
connection.onopen = function () { connection.onopen = function () {
console.log('WS connected') console.log('[WS] connected')
store.dispatch('app/signalConnected') store.dispatch('app/signalConnected')
} }
connection.onerror = function (error) { connection.onerror = function (error) {
console.log('WS error ' + error) console.log('[WS] error ' + error)
store.dispatch('app/signalError', error) store.dispatch('app/signalError', error)
} }
connection.onmessage = function (message) { connection.onmessage = function (message) {
console.log('WS message', message.data) console.log('[WS] message', message.data)
var data = JSON.parse(message.data) var data = JSON.parse(message.data)
switch (data.type) { switch (data.type) {
case 'offer': 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 break
case 'answer': 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 break
case 'candidate': case 'candidate':
store.dispatch('rtc/candidate', data.candidate, data.name) store.dispatch('rtc/candidate', { candidate: data.data, senderName: data.name })
break break
case 'leave': case 'leave':
@@ -47,6 +49,10 @@ export default function createSignalPlugin () {
store.dispatch('app/createRoom', data.message) store.dispatch('app/createRoom', data.message)
break break
case 'connectRoom':
store.dispatch('app/connectRoom', data.message)
break
case 'error': case 'error':
store.dispatch('app/error', data.message) store.dispatch('app/error', data.message)
break break
@@ -59,6 +65,6 @@ export default function createSignalPlugin () {
} }
export function send (message) { export function send (message) {
console.log('WS send', message) console.log('[WS] send', message)
connection.send(JSON.stringify(message)) connection.send(JSON.stringify(message))
} }

View File

@@ -33,6 +33,7 @@ export default {
this.$buefy.dialog.prompt({ this.$buefy.dialog.prompt({
message: 'Choose a name', message: 'Choose a name',
trapFocus: true, trapFocus: true,
canCancel: false,
inputAttrs: { inputAttrs: {
placeholder: 'pedro', placeholder: 'pedro',
minlength: 3, minlength: 3,

View File

@@ -1,20 +1,21 @@
<template> <template>
<div class="room container"> <div class="room container">
<h1 class="title is-1">{{roomStatus.roomName}}</h1> <h1 class="title is-1">{{roomStatus.roomName}}</h1>
<h1 class="subtitle is-4">{{roomStatus.roomCode}}</h1>
<h2 class="subtitle">{{roomStatus.current.title}}</h2> <h2 class="subtitle">{{roomStatus.current.title}}</h2>
<b-tabs>
<b-tab-item label="Playlist">
<b-table :data="roomStatus.playlist" striped hoverable default-sort="vote"> <b-table :data="roomStatus.playlist" striped hoverable default-sort="vote">
<template slot-scope="props"> <template slot-scope="props">
<b-table-column field="title"> <b-table-column field="title" label="Title">
{{props.row.title}} {{props.row.title}}
</b-table-column> </b-table-column>
<b-table-column field="link"> <b-table-column field="link" label="Link">
{{props.row.link}} {{props.row.link}}
</b-table-column> </b-table-column>
<b-table-column field="vote"> <b-table-column field="vote" label="Votes">
{{props.row.vote}} {{props.row.vote}}
</b-table-column> </b-table-column>
@@ -24,6 +25,27 @@
</b-table-column> </b-table-column>
</template> </template>
</b-table> </b-table>
</b-tab-item>
<b-tab-item label="Admin" :visible="showAdmin">
<b-table :data="usersList" striped hoverable>
<template slot-scope="props">
<b-table-column field="name" label="Name">
{{props.row.name}}
</b-table-column>
<b-table-column>
<b-button icon-left="karate" type="is-dark"/>
</b-table-column>
</template>
</b-table>
</b-tab-item>
<b-tab-item label="Invite">
<h1 class="subtitle">{{roomStatus.roomCode}}</h1>
</b-tab-item>
</b-tabs>
</div> </div>
</template> </template>
@@ -33,7 +55,20 @@ export default {
computed: { computed: {
roomStatus () { roomStatus () {
return this.$store.state.room.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' })
} }
} }
</script> </script>

View File

@@ -2,7 +2,7 @@ package gltronic.oozik.business;
import java.io.IOException; import java.io.IOException;
import org.json.JSONObject; import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
public interface IRoomManager { public interface IRoomManager {
@@ -10,7 +10,7 @@ public interface IRoomManager {
public void leave(WebSocketSession session); public void leave(WebSocketSession session);
public void createRoom(WebSocketSession session) throws InterruptedException, IOException; public void createRoom(WebSocketSession session) throws InterruptedException, IOException;
public void connectRoom(WebSocketSession session, String roomName) 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 sendMessage(WebSocketSession session, String type, String message) throws InterruptedException, IOException;
public void sendServerInfos(WebSocketSession session) throws InterruptedException, IOException; public void sendServerInfos(WebSocketSession session) throws InterruptedException, IOException;
} }

View File

@@ -74,14 +74,17 @@ public class RoomManager implements IRoomManager{
return; return;
} }
String roomAdmin = rooms.getKey(roomName); String roomAdmin = rooms.get(roomName);
sendMessage(session, "coonectRoom", roomAdmin); 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 target = (String) jsonObject.get("target");
String type = (String) jsonObject.get("type");
String data = (String) jsonObject.get("data");
if (target == null) { if (target == null) {
sendMessage(session, "error", "no target"); sendMessage(session, "error", "no target");
@@ -96,7 +99,11 @@ public class RoomManager implements IRoomManager{
} }
System.err.println("[ROOM] Foward RTC"); 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 { public void sendMessage(WebSocketSession session, String type, String message) throws InterruptedException, IOException {

View File

@@ -43,10 +43,12 @@ public class SocketHandler extends TextWebSocketHandler {
break; break;
case "leave": case "leave":
roomManager.leave(session); roomManager.leave(session);
break;
case "offer": case "offer":
case "answer": case "answer":
case "candidate": case "candidate":
roomManager.followRTC(session, jsonObject); roomManager.followRTC(session, message);
break;
default: default:
roomManager.sendMessage(session, "error", "unknow command"); roomManager.sendMessage(session, "error", "unknow command");
} }