Skip to content

Commit f1e80c5

Browse files
Add group call
1 parent 715d502 commit f1e80c5

File tree

11 files changed

+1405
-33
lines changed

11 files changed

+1405
-33
lines changed

src/main/kotlin/io/openfuture/openmessenger/kurento/KurentoWebsocketConfigurer.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.openfuture.openmessenger.kurento
22

3+
import io.openfuture.openmessenger.kurento.groupcall.CallHandler
4+
import io.openfuture.openmessenger.kurento.groupcall.RoomManager
5+
import io.openfuture.openmessenger.kurento.groupcall.UserRegistry
36
import org.kurento.client.KurentoClient
47
import org.springframework.beans.factory.annotation.Value
58
import org.springframework.context.annotation.Bean
@@ -33,7 +36,23 @@ class KurentoWebsocketConfigurer(
3336
}
3437

3538
override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
39+
registry.addHandler(groupCallHandler(), "/groupcall").setAllowedOriginPatterns("*")
3640
registry.addHandler(handler(), "/helloworld").setAllowedOriginPatterns("*")
3741
}
42+
@Bean
43+
fun registry(): UserRegistry {
44+
return UserRegistry()
45+
}
46+
47+
@Bean
48+
fun roomManager(): RoomManager {
49+
return RoomManager()
50+
}
51+
52+
@Bean
53+
fun groupCallHandler(): CallHandler {
54+
return CallHandler()
55+
}
56+
3857
}
3958

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package io.openfuture.openmessenger.kurento.groupcall
2+
3+
import com.google.gson.Gson
4+
import com.google.gson.GsonBuilder
5+
import com.google.gson.JsonObject
6+
import org.kurento.client.IceCandidate
7+
import org.slf4j.Logger
8+
import org.slf4j.LoggerFactory
9+
import org.springframework.beans.factory.annotation.Autowired
10+
import org.springframework.web.socket.CloseStatus
11+
import org.springframework.web.socket.TextMessage
12+
import org.springframework.web.socket.WebSocketSession
13+
import org.springframework.web.socket.handler.TextWebSocketHandler
14+
import java.io.IOException
15+
16+
class CallHandler : TextWebSocketHandler() {
17+
@Autowired
18+
private val roomManager: RoomManager? = null
19+
20+
@Autowired
21+
private val registry: UserRegistry? = null
22+
23+
@Throws(Exception::class)
24+
public override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
25+
val jsonMessage = gson.fromJson(message.payload, JsonObject::class.java)
26+
27+
val user = registry!!.getBySession(session)
28+
29+
if (user != null) {
30+
log.debug("Incoming message from user '{}': {}", user.name, jsonMessage)
31+
} else {
32+
log.debug("Incoming message from new user: {}", jsonMessage)
33+
}
34+
35+
when (jsonMessage["id"].asString) {
36+
"joinRoom" -> joinRoom(jsonMessage, session)
37+
"receiveVideoFrom" -> {
38+
val senderName = jsonMessage["sender"].asString
39+
val sender = registry.getByName(senderName)
40+
val sdpOffer = jsonMessage["sdpOffer"].asString
41+
user!!.receiveVideoFrom(sender!!, sdpOffer)
42+
}
43+
44+
"leaveRoom" -> leaveRoom(user!!)
45+
"onIceCandidate" -> {
46+
val candidate = jsonMessage["candidate"].asJsonObject
47+
48+
if (user != null) {
49+
val cand = IceCandidate(
50+
candidate["candidate"].asString,
51+
candidate["sdpMid"].asString, candidate["sdpMLineIndex"].asInt
52+
)
53+
user.addCandidate(cand, jsonMessage["name"].asString)
54+
}
55+
}
56+
57+
else -> {}
58+
}
59+
}
60+
61+
@Throws(Exception::class)
62+
override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) {
63+
val user = registry!!.removeBySession(session)
64+
roomManager!!.getRoom(user.roomName).leave(user)
65+
}
66+
67+
@Throws(IOException::class)
68+
private fun joinRoom(params: JsonObject, session: WebSocketSession) {
69+
val roomName = params["room"].asString
70+
val name = params["name"].asString
71+
log.info("PARTICIPANT {}: trying to join room {}", name, roomName)
72+
73+
val room = roomManager!!.getRoom(roomName)
74+
val user = room.join(name, session)
75+
registry!!.register(user)
76+
}
77+
78+
@Throws(IOException::class)
79+
private fun leaveRoom(user: UserSession) {
80+
val room = roomManager!!.getRoom(user.roomName)
81+
room.leave(user)
82+
if (room.participants.isEmpty()) {
83+
roomManager.removeRoom(room)
84+
}
85+
}
86+
87+
companion object {
88+
private val log: Logger = LoggerFactory.getLogger(CallHandler::class.java)
89+
90+
private val gson: Gson = GsonBuilder().create()
91+
}
92+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package io.openfuture.openmessenger.kurento.groupcall
2+
3+
import com.google.gson.*
4+
import org.kurento.client.*
5+
import org.slf4j.Logger
6+
import org.slf4j.LoggerFactory
7+
import org.springframework.web.socket.WebSocketSession
8+
import java.io.Closeable
9+
import java.io.IOException
10+
import java.util.concurrent.ConcurrentHashMap
11+
import java.util.concurrent.ConcurrentMap
12+
import javax.annotation.PreDestroy
13+
14+
class Room(val name: String?, private val pipeline: MediaPipeline) : Closeable {
15+
private val log: Logger = LoggerFactory.getLogger(Room::class.java)
16+
17+
val participants: ConcurrentMap<String?, UserSession> = ConcurrentHashMap()
18+
19+
init {
20+
log.info("ROOM {} has been created", name)
21+
}
22+
23+
@PreDestroy
24+
private fun shutdown() {
25+
this.close()
26+
}
27+
28+
@Throws(IOException::class)
29+
fun join(userName: String, session: WebSocketSession): UserSession {
30+
log.info("ROOM {}: adding participant {}", this.name, userName)
31+
val participant = UserSession(userName, this.name, session, this.pipeline)
32+
joinRoom(participant)
33+
participants[participant.name] = participant
34+
sendParticipantNames(participant)
35+
return participant
36+
}
37+
38+
@Throws(IOException::class)
39+
fun leave(user: UserSession) {
40+
log.debug("PARTICIPANT {}: Leaving room {}", user.name, this.name)
41+
this.removeParticipant(user.name)
42+
user.close()
43+
}
44+
45+
@Throws(IOException::class)
46+
private fun joinRoom(newParticipant: UserSession): Collection<String?> {
47+
val newParticipantMsg = JsonObject()
48+
newParticipantMsg.addProperty("id", "newParticipantArrived")
49+
newParticipantMsg.addProperty("name", newParticipant.name)
50+
51+
val participantsList: MutableList<String?> = ArrayList(participants.values.size)
52+
log.debug(
53+
"ROOM {}: notifying other participants of new participant {}", name,
54+
newParticipant.name
55+
)
56+
57+
for (participant in participants.values) {
58+
try {
59+
participant.sendMessage(newParticipantMsg)
60+
} catch (e: IOException) {
61+
log.debug("ROOM {}: participant {} could not be notified", name, participant.name, e)
62+
}
63+
participantsList.add(participant.name)
64+
}
65+
66+
return participantsList
67+
}
68+
69+
@Throws(IOException::class)
70+
private fun removeParticipant(name: String?) {
71+
participants.remove(name)
72+
73+
log.debug("ROOM {}: notifying all users that {} is leaving the room", this.name, name)
74+
75+
val unnotifiedParticipants: MutableList<String?> = ArrayList()
76+
val participantLeftJson = JsonObject()
77+
participantLeftJson.addProperty("id", "participantLeft")
78+
participantLeftJson.addProperty("name", name)
79+
for (participant in participants.values) {
80+
try {
81+
participant.cancelVideoFrom(name)
82+
participant.sendMessage(participantLeftJson)
83+
} catch (e: IOException) {
84+
unnotifiedParticipants.add(participant.name)
85+
}
86+
}
87+
88+
if (!unnotifiedParticipants.isEmpty()) {
89+
log.debug(
90+
"ROOM {}: The users {} could not be notified that {} left the room", this.name,
91+
unnotifiedParticipants, name
92+
)
93+
}
94+
}
95+
96+
@Throws(IOException::class)
97+
fun sendParticipantNames(user: UserSession) {
98+
val participantsArray = JsonArray()
99+
for (participant in this.getParticipants()) {
100+
if (participant != user) {
101+
val participantName: JsonElement = JsonPrimitive(participant.name)
102+
participantsArray.add(participantName)
103+
}
104+
}
105+
106+
val existingParticipantsMsg = JsonObject()
107+
existingParticipantsMsg.addProperty("id", "existingParticipants")
108+
existingParticipantsMsg.add("data", participantsArray)
109+
log.debug(
110+
"PARTICIPANT {}: sending a list of {} participants", user.name,
111+
participantsArray.size()
112+
)
113+
user.sendMessage(existingParticipantsMsg)
114+
}
115+
116+
fun getParticipants(): Collection<UserSession> {
117+
return participants.values
118+
}
119+
120+
fun getParticipant(name: String?): UserSession? {
121+
return participants[name]
122+
}
123+
124+
override fun close() {
125+
for (user in participants.values) {
126+
try {
127+
user.close()
128+
} catch (e: IOException) {
129+
log.debug(
130+
"ROOM {}: Could not invoke close on participant {}", this.name, user.name,
131+
e
132+
)
133+
}
134+
}
135+
136+
participants.clear()
137+
138+
pipeline.release(object : Continuation<Void?> {
139+
@Throws(Exception::class)
140+
override fun onSuccess(result: Void?) {
141+
log.trace("ROOM {}: Released Pipeline", this@Room.name)
142+
}
143+
144+
@Throws(Exception::class)
145+
override fun onError(cause: Throwable) {
146+
log.warn("PARTICIPANT {}: Could not release Pipeline", this@Room.name)
147+
}
148+
})
149+
150+
log.debug("Room {} closed", this.name)
151+
}
152+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* (C) Copyright 2014 Kurento (http://kurento.org/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package io.openfuture.openmessenger.kurento.groupcall
18+
19+
import org.kurento.client.KurentoClient
20+
import org.slf4j.Logger
21+
import org.slf4j.LoggerFactory
22+
import org.springframework.beans.factory.annotation.Autowired
23+
import java.util.concurrent.ConcurrentHashMap
24+
import java.util.concurrent.ConcurrentMap
25+
26+
class RoomManager {
27+
private val log: Logger = LoggerFactory.getLogger(RoomManager::class.java)
28+
29+
@Autowired
30+
private val kurento: KurentoClient? = null
31+
32+
private val rooms: ConcurrentMap<String?, Room> = ConcurrentHashMap()
33+
34+
fun getRoom(roomName: String?): Room {
35+
log.debug("Searching for room {}", roomName)
36+
var room = rooms[roomName]
37+
38+
if (room == null) {
39+
log.debug("Room {} not existent. Will create now!", roomName)
40+
room = Room(roomName, kurento!!.createMediaPipeline())
41+
rooms[roomName] = room
42+
}
43+
log.debug("Room {} found!", roomName)
44+
return room
45+
}
46+
47+
fun removeRoom(room: Room) {
48+
rooms.remove(room.name)
49+
room.close()
50+
log.info("Room {} removed and closed", room.name)
51+
}
52+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.openfuture.openmessenger.kurento.groupcall
2+
3+
import org.springframework.web.socket.WebSocketSession
4+
import java.util.concurrent.ConcurrentHashMap
5+
6+
class UserRegistry {
7+
private val usersByName = ConcurrentHashMap<String?, UserSession>()
8+
private val usersBySessionId = ConcurrentHashMap<String, UserSession>()
9+
10+
fun register(user: UserSession) {
11+
usersByName[user.name] = user
12+
usersBySessionId[user.session.id] = user
13+
}
14+
15+
fun getByName(name: String?): UserSession? {
16+
return usersByName[name]
17+
}
18+
19+
fun getBySession(session: WebSocketSession): UserSession? {
20+
return usersBySessionId[session.id]
21+
}
22+
23+
fun exists(name: String?): Boolean {
24+
return usersByName.keys.contains(name)
25+
}
26+
27+
fun removeBySession(session: WebSocketSession): UserSession {
28+
val user = getBySession(session)!!
29+
usersByName.remove(user.name)
30+
usersBySessionId.remove(session.id)
31+
return user
32+
}
33+
}

0 commit comments

Comments
 (0)