Compare commits
16 Commits
c59687564e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d4125846ec | |||
|
|
f87198c054 | ||
|
|
58fe3c409d | ||
|
|
bdc44f36f0 | ||
|
|
145aaf8288 | ||
|
|
ddcd08aeae | ||
|
|
435d6fefa8 | ||
|
|
986cd765d8 | ||
|
|
a9c4612a72 | ||
|
|
bc0a64f450 | ||
| faaffd86d0 | |||
| b1d3416684 | |||
|
|
7eacaa6298 | ||
|
|
76a007ff42 | ||
|
|
20dd5933d9 | ||
|
|
939a4d55f4 |
@@ -1,5 +1,6 @@
|
|||||||
package im.rosetta;
|
package im.rosetta;
|
||||||
|
|
||||||
|
import im.rosetta.calls.CallManager;
|
||||||
import im.rosetta.client.ClientManager;
|
import im.rosetta.client.ClientManager;
|
||||||
import im.rosetta.client.OnlineManager;
|
import im.rosetta.client.OnlineManager;
|
||||||
import im.rosetta.event.EventManager;
|
import im.rosetta.event.EventManager;
|
||||||
@@ -82,6 +83,7 @@ public class Boot {
|
|||||||
private OnlineManager onlineManager;
|
private OnlineManager onlineManager;
|
||||||
private BufferCleanupService bufferCleanupService;
|
private BufferCleanupService bufferCleanupService;
|
||||||
private ForwardUnitService forwardUnitService;
|
private ForwardUnitService forwardUnitService;
|
||||||
|
private CallManager callManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Конструктор по умолчанию, использует порт 3000 для сервера
|
* Конструктор по умолчанию, использует порт 3000 для сервера
|
||||||
@@ -104,7 +106,8 @@ public class Boot {
|
|||||||
port,
|
port,
|
||||||
30
|
30
|
||||||
), packetManager, this.serverAdapter);
|
), packetManager, this.serverAdapter);
|
||||||
this.clientManager = new ClientManager(server);
|
this.clientManager = new ClientManager(this.server);
|
||||||
|
this.callManager = new CallManager(this.clientManager);
|
||||||
/**
|
/**
|
||||||
* Каждые сколько дней будет очищаться буфер (это влияет на синхронизацию сообщений, так
|
* Каждые сколько дней будет очищаться буфер (это влияет на синхронизацию сообщений, так
|
||||||
* как при синхронизации клиент запрашивает пакеты из буфера за последние 7 дней, если этот параметр будет меньше,
|
* как при синхронизации клиент запрашивает пакеты из буфера за последние 7 дней, если этот параметр будет меньше,
|
||||||
@@ -113,7 +116,7 @@ public class Boot {
|
|||||||
int cleanupEveryDays = System.getenv("BUFFER_CLEANUP_DAYS") != null ?
|
int cleanupEveryDays = System.getenv("BUFFER_CLEANUP_DAYS") != null ?
|
||||||
Integer.parseInt(System.getenv("BUFFER_CLEANUP_DAYS")) : 7;
|
Integer.parseInt(System.getenv("BUFFER_CLEANUP_DAYS")) : 7;
|
||||||
this.bufferCleanupService = new BufferCleanupService(cleanupEveryDays, this.logger);
|
this.bufferCleanupService = new BufferCleanupService(cleanupEveryDays, this.logger);
|
||||||
this.forwardUnitService = new ForwardUnitService(this.logger, this.clientManager);
|
this.forwardUnitService = new ForwardUnitService(this.logger, this.clientManager, this.callManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,8 +231,8 @@ public class Boot {
|
|||||||
this.packetManager.registerExecutor(22, new Executor22GroupBan());
|
this.packetManager.registerExecutor(22, new Executor22GroupBan());
|
||||||
this.packetManager.registerExecutor(24, new Executor24DeviceResolve(this.clientManager, this.eventManager, this.packetManager));
|
this.packetManager.registerExecutor(24, new Executor24DeviceResolve(this.clientManager, this.eventManager, this.packetManager));
|
||||||
this.packetManager.registerExecutor(25, new Executor25Sync(this.packetManager));
|
this.packetManager.registerExecutor(25, new Executor25Sync(this.packetManager));
|
||||||
this.packetManager.registerExecutor(26, new Executor26SignalPeer(this.clientManager, this.forwardUnitService));
|
this.packetManager.registerExecutor(26, new Executor26SignalPeer(this.clientManager, this.forwardUnitService, this.callManager));
|
||||||
this.packetManager.registerExecutor(27, new Executor27WebRTC(this.forwardUnitService));
|
this.packetManager.registerExecutor(27, new Executor27WebRTC(this.callManager));
|
||||||
this.packetManager.registerExecutor(28, new Executor28IceServers(this.forwardUnitService));
|
this.packetManager.registerExecutor(28, new Executor28IceServers(this.forwardUnitService));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ public enum Failures implements BaseFailures {
|
|||||||
/**
|
/**
|
||||||
* Слишком много подписок на онлайн статусы
|
* Слишком много подписок на онлайн статусы
|
||||||
*/
|
*/
|
||||||
TOO_MANY_ONLINE_SUBSCRIPTIONS(3010);
|
TOO_MANY_ONLINE_SUBSCRIPTIONS(3010),
|
||||||
|
/**
|
||||||
|
* Нет сессии звонка
|
||||||
|
*/
|
||||||
|
NO_CALL_SESSION(3011);
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|
||||||
|
|||||||
119
src/main/java/im/rosetta/calls/CallManager.java
Normal file
119
src/main/java/im/rosetta/calls/CallManager.java
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package im.rosetta.calls;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import im.rosetta.client.ClientManager;
|
||||||
|
import im.rosetta.packet.Packet26SignalPeer;
|
||||||
|
import im.rosetta.packet.runtime.NetworkSignalType;
|
||||||
|
import io.g365sfu.Room;
|
||||||
|
import io.orprotocol.ProtocolException;
|
||||||
|
import io.orprotocol.client.Client;
|
||||||
|
import io.orprotocol.packet.Packet;
|
||||||
|
|
||||||
|
public class CallManager {
|
||||||
|
|
||||||
|
private List<CallSession> callSessions = new ArrayList<>();
|
||||||
|
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private static final long RINGING_TIMEOUT = 30 * 1000;
|
||||||
|
private ClientManager clientManager;
|
||||||
|
|
||||||
|
|
||||||
|
public CallManager(ClientManager clientManager) {
|
||||||
|
this.clientManager = clientManager;
|
||||||
|
scheduler.scheduleAtFixedRate(this::cleanupCallSessions, 0, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanupCallSessions() {
|
||||||
|
/**
|
||||||
|
* Такая конструкция нужна для избежания ConcurrentModificationException,
|
||||||
|
* так как мы не можем удалять элементы из списка, по которому проходим в цикле,
|
||||||
|
* поэтому мы сначала собираем сессии звонков, которые нужно удалить, а потом
|
||||||
|
* удаляем их из основного списка
|
||||||
|
*/
|
||||||
|
List<CallSession> sessionsToRemove = new ArrayList<>();
|
||||||
|
for (CallSession session : this.callSessions) {
|
||||||
|
if (session.shouldRemove()) {
|
||||||
|
/**
|
||||||
|
* Отправляем всем в сессии что звонок завершился, так как он устарел, и удаляем сессию из списка активных сессий
|
||||||
|
*/
|
||||||
|
Packet26SignalPeer rtout = new Packet26SignalPeer();
|
||||||
|
rtout.setSignalType(NetworkSignalType.RINGING_TIMEOUT);
|
||||||
|
Packet26SignalPeer endCallPacket = new Packet26SignalPeer();
|
||||||
|
endCallPacket.setSignalType(NetworkSignalType.END_CALL);
|
||||||
|
endCallPacket.setJoinToken(session.getJoinToken());
|
||||||
|
endCallPacket.setCallId(session.getCallId());
|
||||||
|
|
||||||
|
try {
|
||||||
|
session.sendPacket(rtout, null);
|
||||||
|
this.sendPacketToRinging(session, endCallPacket);
|
||||||
|
} catch (ProtocolException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
sessionsToRemove.add(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (CallSession session : sessionsToRemove) {
|
||||||
|
this.callSessions.remove(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallSession createCall(String callId, String joinToken) {
|
||||||
|
CallSession session = new CallSession(callId, joinToken, RINGING_TIMEOUT);
|
||||||
|
this.callSessions.add(session);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallSession getCallSession(String callId, String joinToken) {
|
||||||
|
for (CallSession session : this.callSessions) {
|
||||||
|
if (session.getCallId().equals(callId) && session.getJoinToken().equals(joinToken)) {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallSession getCallSession(Room room) {
|
||||||
|
for (CallSession session : this.callSessions) {
|
||||||
|
if (session.getRoom() != null && session.getRoom().equals(room)) {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBusy(String publicKey) {
|
||||||
|
for (CallSession session : this.callSessions) {
|
||||||
|
if (session.clients.containsKey(publicKey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(session.ringing.containsKey(publicKey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPacketToRinging(CallSession session, Packet packet) throws ProtocolException {
|
||||||
|
for (String publicKey : session.ringing.keySet()) {
|
||||||
|
this.clientManager.sendPacketToAuthorizedPK(publicKey, packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallSession getCallSession(Client client) {
|
||||||
|
for (CallSession session : this.callSessions) {
|
||||||
|
if (session.clients.containsValue(client)) {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSession(CallSession session) {
|
||||||
|
this.callSessions.remove(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
131
src/main/java/im/rosetta/calls/CallSession.java
Normal file
131
src/main/java/im/rosetta/calls/CallSession.java
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package im.rosetta.calls;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import io.g365sfu.Room;
|
||||||
|
import io.orprotocol.ProtocolException;
|
||||||
|
import io.orprotocol.client.Client;
|
||||||
|
import io.orprotocol.packet.Packet;
|
||||||
|
|
||||||
|
public class CallSession {
|
||||||
|
|
||||||
|
public String callId;
|
||||||
|
public String joinToken;
|
||||||
|
public Long createdAt;
|
||||||
|
public Long ringingTimeout;
|
||||||
|
/**
|
||||||
|
* Клиенты которым сейчас идет дозвон (публичные ключи)
|
||||||
|
* Клиенты в этом списке не могут принимать другие звонки, так как они уже заняты дозвоном,
|
||||||
|
* но они еще не в звонке, так как не приняли звонок
|
||||||
|
*
|
||||||
|
* Клиенты удаляются из этого списка, когда они принимают звонок или отклоняют его,
|
||||||
|
* тогда они либо переходят в звонок, либо становятся свободными для других звонков
|
||||||
|
* pk -> время начала дозвона (timestamp в миллисекундах)
|
||||||
|
*/
|
||||||
|
public HashMap<String, Long> ringing;
|
||||||
|
/**
|
||||||
|
* Клиенты, которые уже приняли звонок и находятся в звонке (публичные ключи) и их сокеты
|
||||||
|
* pk -> client
|
||||||
|
*/
|
||||||
|
public HashMap<String, Client> clients;
|
||||||
|
/**
|
||||||
|
* Если звонок активен у него появляется комната, иначе комната null
|
||||||
|
*/
|
||||||
|
public Room room;
|
||||||
|
|
||||||
|
|
||||||
|
public CallSession(String callId, String joinToken, Long ringingTimeout) {
|
||||||
|
this.callId = callId;
|
||||||
|
this.joinToken = joinToken;
|
||||||
|
this.clients = new HashMap<>();
|
||||||
|
this.ringing = new HashMap<>();
|
||||||
|
this.createdAt = System.currentTimeMillis();
|
||||||
|
this.ringingTimeout = ringingTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void joinCall(String publicKey, Client client) {
|
||||||
|
if(this.ringing.containsKey(publicKey)) {
|
||||||
|
this.ringing.remove(publicKey);
|
||||||
|
}
|
||||||
|
this.clients.put(publicKey, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, может ли этот публичный ключ выполнять какие-либо действия в рамках этой сессии звонка,
|
||||||
|
* чтобы не допустить выполнение действий от посторонних публичных ключей, которые не участвуют в звонке
|
||||||
|
* @param publicKey Публичный ключ для проверки
|
||||||
|
* @return true, если этот публичный ключ может выполнять действия в рамках этой сессии звонка, false иначе
|
||||||
|
*/
|
||||||
|
public boolean isValidSource(String publicKey) {
|
||||||
|
return this.ringing.containsKey(publicKey) || this.clients.containsKey(publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получаем публичный ключ клиента по его сокету, чтобы понимать, кто отправляет сигналы в рамках звонка
|
||||||
|
* @param client Сокет клиента, для которого нужно получить публичный ключ
|
||||||
|
* @return Публичный ключ клиента, или null если клиент не найден в сессии звонка
|
||||||
|
*/
|
||||||
|
public String getPublicKey(Client client) {
|
||||||
|
for (String publicKey : this.clients.keySet()) {
|
||||||
|
if (this.clients.get(publicKey).equals(client)) {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCallId() {
|
||||||
|
return this.callId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJoinToken() {
|
||||||
|
return this.joinToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void leaveCall(String publicKey) {
|
||||||
|
if(this.clients.containsKey(publicKey)) {
|
||||||
|
this.clients.remove(publicKey);
|
||||||
|
}
|
||||||
|
if(this.ringing.containsKey(publicKey)) {
|
||||||
|
this.ringing.remove(publicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addRinging(String publicKey) {
|
||||||
|
this.ringing.put(publicKey, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoom(Room room) {
|
||||||
|
this.room = room;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Room getRoom() {
|
||||||
|
return this.room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправляем пакет всем участникам звонка, кроме исключенного клиента (обычно отправителя)
|
||||||
|
* @param packet Пакет для отправки
|
||||||
|
* @param excludeClient Клиент, которому не нужно отправлять пакет (обычно отправитель)
|
||||||
|
* @throws ProtocolException Если произошла ошибка при отправке пакета клиенту
|
||||||
|
*/
|
||||||
|
public void sendPacket(Packet packet, Client excludeClient) throws ProtocolException {
|
||||||
|
for (Client client : this.clients.values()) {
|
||||||
|
if (!client.equals(excludeClient)) {
|
||||||
|
client.send(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяем, нужно ли удалять сессию звонка.
|
||||||
|
* Сессию звонка нужно удалять, если в ней меньше 2 клиентов и при этом нет клиентов в состоянии дозвона,
|
||||||
|
* или если сессия была создана более 1 минуты назад, так как это значит, что клиенты не ответили на звонок и он устарел
|
||||||
|
* @return true, если сессию звонка нужно удалять, false иначе
|
||||||
|
*/
|
||||||
|
public boolean shouldRemove() {
|
||||||
|
return this.clients.size() <= 1 && (this.ringing.size() == 0 || System.currentTimeMillis() - this.createdAt > this.ringingTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -56,9 +56,10 @@ public class ClientManager {
|
|||||||
* Отправить пакет ВСЕМ АВТОРИЗОВАННЫМ клиентам с публичным ключом publicKey
|
* Отправить пакет ВСЕМ АВТОРИЗОВАННЫМ клиентам с публичным ключом publicKey
|
||||||
* @param publicKey публичный ключ получателя
|
* @param publicKey публичный ключ получателя
|
||||||
* @param packet пакет для отправки
|
* @param packet пакет для отправки
|
||||||
|
* @param exclude клиент, который не должен получать этот пакет, может быть null
|
||||||
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту
|
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту
|
||||||
*/
|
*/
|
||||||
public void sendPacketToAuthorizedPK(String publicKey, Packet packet) throws ProtocolException {
|
public void sendPacketToAuthorizedPK(String publicKey, Packet packet, Client exclude) throws ProtocolException {
|
||||||
Set<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey);
|
Set<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey);
|
||||||
if(clients == null){
|
if(clients == null){
|
||||||
/**
|
/**
|
||||||
@@ -77,12 +78,55 @@ public class ClientManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Отправляем пакет каждому клиенту с таким публичным ключом (то есть всем его авторизованным сессиям/устройствам)
|
* Отправляем пакет каждому клиенту с таким публичным ключом (то есть всем его авторизованным сессиям/устройствам),
|
||||||
|
* исключая клиента exclude
|
||||||
*/
|
*/
|
||||||
|
if(exclude != null && client.equals(exclude)){
|
||||||
|
/**
|
||||||
|
* Этот клиент является исключением, он не должен получать этот пакет
|
||||||
|
*/
|
||||||
|
continue;
|
||||||
|
}
|
||||||
client.send(packet);
|
client.send(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправить пакет ВСЕМ АВТОРИЗОВАННЫМ клиентам с публичным ключом publicKey
|
||||||
|
* @param publicKey публичный ключ получателя
|
||||||
|
* @param packet пакет для отправки
|
||||||
|
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту
|
||||||
|
*/
|
||||||
|
public void sendPacketToAuthorizedPK(String publicKey, Packet packet) throws ProtocolException {
|
||||||
|
this.sendPacketToAuthorizedPK(publicKey, packet, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправить пакет всем клиентам с публичными ключами из списка publicKeys
|
||||||
|
* @param publicKeys список публичных ключей получателей
|
||||||
|
* @param packet пакет для отправки
|
||||||
|
* @param exclude клиент, который не должен получать этот пакет, может быть null
|
||||||
|
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту
|
||||||
|
*/
|
||||||
|
public void sendPacketToAuthorizedPK(List<String> publicKeys, Packet packet, Client exclude) throws ProtocolException {
|
||||||
|
for(String publicKey : publicKeys){
|
||||||
|
this.sendPacketToAuthorizedPK(publicKey, packet, exclude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправить пакет всем клиентам с публичными ключами из списка publicKeys
|
||||||
|
* @param publicKeys список публичных ключей получателей
|
||||||
|
* @param packet пакет для отправки
|
||||||
|
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту
|
||||||
|
*/
|
||||||
|
public void sendPacketToAuthorizedPK(List<String> publicKeys, Packet packet) throws ProtocolException {
|
||||||
|
for(String publicKey : publicKeys){
|
||||||
|
this.sendPacketToAuthorizedPK(publicKey, packet, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Отправить пакет всем клиентам с публичными ключом как у client, кроме клиента client, который является отправителем и не должен получать этот пакет
|
* Отправить пакет всем клиентам с публичными ключом как у client, кроме клиента client, который является отправителем и не должен получать этот пакет
|
||||||
* @param client клиент
|
* @param client клиент
|
||||||
@@ -111,18 +155,6 @@ public class ClientManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Отправить пакет всем клиентам с публичными ключами из списка publicKeys
|
|
||||||
* @param publicKeys список публичных ключей получателей
|
|
||||||
* @param packet пакет для отправки
|
|
||||||
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту
|
|
||||||
*/
|
|
||||||
public void sendPacketToAuthorizedPK(List<String> publicKeys, Packet packet) throws ProtocolException {
|
|
||||||
for(String publicKey : publicKeys){
|
|
||||||
this.sendPacketToAuthorizedPK(publicKey, packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить список клиентов по публичному ключу (get PublicKey clients), могут быть неавторизованные клиенты
|
* Получить список клиентов по публичному ключу (get PublicKey clients), могут быть неавторизованные клиенты
|
||||||
* @param publicKey публичный ключ клиента
|
* @param publicKey публичный ключ клиента
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import java.util.Set;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import im.rosetta.Failures;
|
import im.rosetta.Failures;
|
||||||
|
import im.rosetta.calls.CallManager;
|
||||||
|
import im.rosetta.calls.CallSession;
|
||||||
import im.rosetta.client.ClientManager;
|
import im.rosetta.client.ClientManager;
|
||||||
import im.rosetta.client.tags.ECIAuthentificate;
|
import im.rosetta.client.tags.ECIAuthentificate;
|
||||||
import im.rosetta.packet.Packet26SignalPeer;
|
import im.rosetta.packet.Packet26SignalPeer;
|
||||||
@@ -33,16 +35,18 @@ public class Executor26SignalPeer extends PacketExecutor<Packet26SignalPeer> {
|
|||||||
*/
|
*/
|
||||||
private Set<NetworkSignalType> authentificatedTypes = new HashSet<>(){{
|
private Set<NetworkSignalType> authentificatedTypes = new HashSet<>(){{
|
||||||
add(NetworkSignalType.CALL);
|
add(NetworkSignalType.CALL);
|
||||||
/**
|
|
||||||
* Так как комнату создает звонящий, то комнату может создать только
|
|
||||||
* авторизованный пользователь
|
|
||||||
*/
|
|
||||||
add(NetworkSignalType.CREATE_ROOM);
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
public Executor26SignalPeer(ClientManager clientManager, ForwardUnitService fus) {
|
/**
|
||||||
|
* Менеджер звонков, который реализует весь необхоимый функционал для управления звонками,
|
||||||
|
* например проверку занятости пользователя, и тд
|
||||||
|
*/
|
||||||
|
private CallManager callManager;
|
||||||
|
|
||||||
|
public Executor26SignalPeer(ClientManager clientManager, ForwardUnitService fus, CallManager callManager) {
|
||||||
this.clientManager = clientManager;
|
this.clientManager = clientManager;
|
||||||
this.fus = fus;
|
this.fus = fus;
|
||||||
|
this.callManager = callManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -60,7 +64,7 @@ public class Executor26SignalPeer extends PacketExecutor<Packet26SignalPeer> {
|
|||||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!src.equals(eciAuthentificate.getPublicKey()) && authentificatedTypes.contains(type)) {
|
if(src != null && !src.equals(eciAuthentificate.getPublicKey()) && authentificatedTypes.contains(type)) {
|
||||||
/**
|
/**
|
||||||
* Если src в пакете не совпадает с авторизованным PK клиента, то это может означать, что клиент пытается
|
* Если src в пакете не совпадает с авторизованным PK клиента, то это может означать, что клиент пытается
|
||||||
* отправить сигнал от другого пользователя, отключаем его от сервера.
|
* отправить сигнал от другого пользователя, отключаем его от сервера.
|
||||||
@@ -72,42 +76,147 @@ public class Executor26SignalPeer extends PacketExecutor<Packet26SignalPeer> {
|
|||||||
/**
|
/**
|
||||||
* Инициируется звонок от src к dst, проверяем, что dst не занят другим звонком, если занят, то отправляем сигнал END_CALL_BECAUSE_BUSY обратно src
|
* Инициируется звонок от src к dst, проверяем, что dst не занят другим звонком, если занят, то отправляем сигнал END_CALL_BECAUSE_BUSY обратно src
|
||||||
*/
|
*/
|
||||||
Room room = this.fus.getRoomByParticipantId(packet.getDst());
|
if(this.callManager.isBusy(dst) || this.callManager.isBusy(src)) {
|
||||||
if(room != null) {
|
|
||||||
/**
|
/**
|
||||||
* Получатель сигнала уже находится в другой комнате, значит он занят другим звонком, отправляем сигнал END_CALL_BECAUSE_BUSY обратно src
|
* Получатель сигнала уже находится в другой комнате, значит он занят другим звонком, отправляем сигнал END_CALL_BECAUSE_BUSY обратно src
|
||||||
*/
|
*/
|
||||||
Packet26SignalPeer responsePacket = new Packet26SignalPeer();
|
Packet26SignalPeer responsePacket = new Packet26SignalPeer();
|
||||||
responsePacket.setSignalType(NetworkSignalType.END_CALL_BECAUSE_BUSY);
|
responsePacket.setSignalType(NetworkSignalType.END_CALL_BECAUSE_BUSY);
|
||||||
this.clientManager.sendPacketToAuthorizedPK(packet.getSrc(), responsePacket);
|
client.send(responsePacket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Генерируем CallID и JoinToken
|
||||||
|
*/
|
||||||
|
String callId = UUID.randomUUID().toString();
|
||||||
|
String joinToken = UUID.randomUUID().toString();
|
||||||
|
packet.setJoinToken(joinToken);
|
||||||
|
packet.setCallId(callId);
|
||||||
|
/**
|
||||||
|
* Создаем сессию звонка и добавляем в нее звонящего
|
||||||
|
*/
|
||||||
|
CallSession session = this.callManager.createCall(callId, joinToken);
|
||||||
|
session.joinCall(src, client);
|
||||||
|
/**
|
||||||
|
* Добавляем dst в ringing, чтобы пометить, что ему поступает звонок
|
||||||
|
*/
|
||||||
|
session.addRinging(dst);
|
||||||
/**
|
/**
|
||||||
* Получатель сигнала не занят, отправляем ему пуш уведомление о входящем звонке и сигнал CALL для инициализации звонка
|
* Получатель сигнала не занят, отправляем ему пуш уведомление о входящем звонке и сигнал CALL для инициализации звонка
|
||||||
*/
|
*/
|
||||||
pushNotifyDispatcher.sendPush(dst, new HashMap<>(){{
|
pushNotifyDispatcher.sendPush(dst, new HashMap<>(){{
|
||||||
put("type", PushType.CALL);
|
put("type", PushType.CALL);
|
||||||
put("dialog", src);
|
put("dialog", src);
|
||||||
put("callId", UUID.randomUUID().toString());
|
put("callId", callId);
|
||||||
|
put("joinToken", joinToken);
|
||||||
}});
|
}});
|
||||||
}
|
|
||||||
if(type == NetworkSignalType.CREATE_ROOM){
|
|
||||||
/**
|
/**
|
||||||
* Создается комната для звонка
|
* Отправляем сигнал CALL всем авторизованным устройствам вызываемого абонента
|
||||||
*/
|
*/
|
||||||
Room room = this.fus.createRoom();
|
this.clientManager.sendPacketToAuthorizedPK(dst, packet);
|
||||||
room.addParticipant(packet.getSrc());
|
|
||||||
room.addParticipant(packet.getDst());
|
|
||||||
packet.setRoomId(room.getRoomId());
|
|
||||||
/**
|
|
||||||
* Результат создания комнаты транслируем обоим участникам, чтобы они могли начать обмен WebRTC SDP, и тд
|
|
||||||
*/
|
|
||||||
this.clientManager.sendPacketToAuthorizedPK(packet.getSrc(), packet);
|
|
||||||
this.clientManager.sendPacketToAuthorizedPK(packet.getDst(), packet);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(type == NetworkSignalType.ACCEPT){
|
||||||
this.clientManager.sendPacketToAuthorizedPK(packet.getDst(), packet);
|
String callId = packet.getCallId();
|
||||||
|
String joinToken = packet.getJoinToken();
|
||||||
|
CallSession session = this.callManager.getCallSession(callId, joinToken);
|
||||||
|
if(session == null) {
|
||||||
|
/**
|
||||||
|
* Сессии звонка нет
|
||||||
|
*/
|
||||||
|
client.disconnect(Failures.NO_CALL_SESSION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!session.isValidSource(src)) {
|
||||||
|
/**
|
||||||
|
* Клиент не состоит в сессии звонка, отключаем его от сервера, так как он отправляет некорректные данные
|
||||||
|
*/
|
||||||
|
client.disconnect(Failures.DATA_MISSMATCH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Room room = this.fus.createRoom();
|
||||||
|
session.setRoom(room);
|
||||||
|
session.joinCall(src, client);
|
||||||
|
for(String participant : session.clients.keySet()) {
|
||||||
|
room.addParticipant(participant);
|
||||||
|
}
|
||||||
|
session.sendPacket(packet, client);
|
||||||
|
/**
|
||||||
|
* Сбрасываем вызов на всех остальных устройствах пользователя, который принимает звонок,
|
||||||
|
* чтобы он не смог принять или отклонить звонок с другого устройства
|
||||||
|
*/
|
||||||
|
Packet26SignalPeer endCallOtherDevices = new Packet26SignalPeer();
|
||||||
|
endCallOtherDevices.setSignalType(NetworkSignalType.END_CALL);
|
||||||
|
this.clientManager.sendPacketToAuthorizedPK(src, endCallOtherDevices, client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(type == NetworkSignalType.KEY_EXCHANGE){
|
||||||
|
/**
|
||||||
|
* Ретранслируем ключи в рамках сессии
|
||||||
|
*/
|
||||||
|
CallSession session = this.callManager.getCallSession(client);
|
||||||
|
if(session == null) {
|
||||||
|
/**
|
||||||
|
* Сессии звонка нет
|
||||||
|
*/
|
||||||
|
client.disconnect(Failures.NO_CALL_SESSION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Обмениваемся ключами в рамках сессии, ретранслируя их всем участникам сессии, кроме отправителя
|
||||||
|
*/
|
||||||
|
session.sendPacket(packet, client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(type == NetworkSignalType.END_CALL) {
|
||||||
|
/**
|
||||||
|
* Ретранслируем сигнал окончания звонка всем участникам сессии, кроме отправителя, и удаляем сессию
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Сначала получаем сессию по сокету отправителя пакета, если не находим, то пробуем найти сессию по callId и joinToken из пакета, если не находим,
|
||||||
|
* то отключаем клиента от сервера, так как он отправляет некорректные данные
|
||||||
|
*/
|
||||||
|
CallSession session = this.callManager.getCallSession(client);
|
||||||
|
if(session == null) {
|
||||||
|
String callId = packet.getCallId();
|
||||||
|
String joinToken = packet.getJoinToken();
|
||||||
|
session = this.callManager.getCallSession(callId, joinToken);
|
||||||
|
}
|
||||||
|
if(session == null) {
|
||||||
|
/**
|
||||||
|
* Сессии звонка нет, скорее всего она была удалена при обрыве RTC Peer Connection,
|
||||||
|
* при срабатывании RTCPeerConnection::close на клиенте раньше, чем клиент отправил сигнал END_CALL
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Отправляем сигнал окончания звонка всем участникам сессии, кроме отправителя
|
||||||
|
*/
|
||||||
|
session.sendPacket(packet, client);
|
||||||
|
/**
|
||||||
|
* Отправляем пакет вызываемым (ringing) пользователям (которые еще не в сессии)
|
||||||
|
*/
|
||||||
|
this.callManager.sendPacketToRinging(session, packet);
|
||||||
|
/**
|
||||||
|
* Удаляем сессию из активных сессий звонков
|
||||||
|
*/
|
||||||
|
this.callManager.removeSession(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(type == NetworkSignalType.ACTIVE) {
|
||||||
|
/**
|
||||||
|
* Клиент сообщил, что прошел стадию обмена ключами и звонок активен
|
||||||
|
*/
|
||||||
|
CallSession session = this.callManager.getCallSession(client);
|
||||||
|
if(session == null) {
|
||||||
|
/**
|
||||||
|
* Сессии звонка нет
|
||||||
|
*/
|
||||||
|
client.disconnect(Failures.NO_CALL_SESSION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
session.sendPacket(packet, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
package im.rosetta.executors;
|
package im.rosetta.executors;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
import im.rosetta.Failures;
|
import im.rosetta.Failures;
|
||||||
import im.rosetta.database.repository.DeviceRepository;
|
import im.rosetta.calls.CallManager;
|
||||||
|
import im.rosetta.calls.CallSession;
|
||||||
import im.rosetta.packet.Packet27WebRTC;
|
import im.rosetta.packet.Packet27WebRTC;
|
||||||
import im.rosetta.packet.runtime.NetworkWebRTCType;
|
import im.rosetta.packet.runtime.NetworkWebRTCType;
|
||||||
import im.rosetta.service.services.DeviceService;
|
|
||||||
import im.rosetta.service.services.ForwardUnitService;
|
|
||||||
import io.g365sfu.Room;
|
import io.g365sfu.Room;
|
||||||
import io.orprotocol.ProtocolException;
|
import io.orprotocol.ProtocolException;
|
||||||
import io.orprotocol.client.Client;
|
import io.orprotocol.client.Client;
|
||||||
@@ -15,38 +12,42 @@ import io.orprotocol.packet.PacketExecutor;
|
|||||||
|
|
||||||
public class Executor27WebRTC extends PacketExecutor<Packet27WebRTC> {
|
public class Executor27WebRTC extends PacketExecutor<Packet27WebRTC> {
|
||||||
|
|
||||||
private ForwardUnitService fus;
|
private CallManager callManager;
|
||||||
private final DeviceRepository deviceRepository = new DeviceRepository();
|
|
||||||
private final DeviceService deviceService = new DeviceService(deviceRepository);
|
|
||||||
|
|
||||||
public Executor27WebRTC(ForwardUnitService fus) {
|
public Executor27WebRTC(CallManager callManager) {
|
||||||
this.fus = fus;
|
this.callManager = callManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceived(Packet27WebRTC packet, Client client) throws Exception, ProtocolException {
|
public void onPacketReceived(Packet27WebRTC packet, Client client) throws Exception, ProtocolException {
|
||||||
String publicKey = packet.getPublicKey();
|
/**
|
||||||
String deviceId = packet.getDeviceId();
|
* Получаем, в какой сессии находится этот сокет
|
||||||
|
*/
|
||||||
HashSet<String> publicKeys = this.deviceService.getPublicKeysByDeviceId(deviceId);
|
CallSession session = this.callManager.getCallSession(client);
|
||||||
if(!publicKeys.contains(publicKey)) {
|
if(session == null) {
|
||||||
/**
|
/**
|
||||||
* Если публичный ключ, который отправил пакет, не связан с deviceId, с которого был отправлен пакет, то отключаем клиента от сервера, так как это может быть попыткой подделки пакета
|
* Если сессия не найдена, то мы не будем обрабатывать сигналы для звонка
|
||||||
*/
|
*/
|
||||||
client.disconnect(Failures.DATA_MISSMATCH);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Room room = session.getRoom();
|
||||||
/**
|
|
||||||
* Так как в комнатах Participants это публичные ключи пользователей, то мы можем
|
|
||||||
* найти комнату, в которой находится пользователь, по его публичному ключу
|
|
||||||
*/
|
|
||||||
Room room = this.fus.getRoomByParticipantId(publicKey);
|
|
||||||
|
|
||||||
if(room == null) {
|
if(room == null) {
|
||||||
/**
|
/**
|
||||||
* Если комната не найдена, то мы не будем обрабатывать сигналы для звонка
|
* Звонок еще не активен, а значит комнаты еще нет. Нельзя обменяться WebRTC сигналами пока комнаты еще нет.
|
||||||
* и просто отключим клиента от сервера.
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Получаем публичный ключ, которым представился клиент, в рамках сессии звонка.
|
||||||
|
* Мы не делаем это через ECIAuthentificate, так как в рамках звонка клиент может не быть авторизован, но при этом он уже находится в сессии звонка, и мы можем идентифицировать его по публичному ключу,
|
||||||
|
* который он указал при присоединении к звонку используя joinToken.
|
||||||
|
* Так что, несмотря на то, что клиент может быть не авторизован, мы все равно можем достоверно знать его публичный ключ
|
||||||
|
*/
|
||||||
|
String publicKey = session.getPublicKey(client);
|
||||||
|
if(publicKey == null) {
|
||||||
|
/**
|
||||||
|
* Избыточная проверка, так как если клиент находится в сессии, то он должен быть в списке клиентов сессии,
|
||||||
|
* но на всякий случай проверим это, чтобы избежать возможных ошибок
|
||||||
*/
|
*/
|
||||||
client.disconnect(Failures.DATA_MISSMATCH);
|
client.disconnect(Failures.DATA_MISSMATCH);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -29,16 +29,18 @@ public class Packet26SignalPeer extends Packet {
|
|||||||
* Тип сигнала
|
* Тип сигнала
|
||||||
*/
|
*/
|
||||||
private NetworkSignalType signalType;
|
private NetworkSignalType signalType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Идентификатор комнаты, в которой происходит звонок, заполняется если тип сигнала CREATE_ROOM, иначе null
|
* callId и joinToken нужны для того, чтобы идентифицировать сессию звонка. Так как roomId это только ID комнаты на sfu
|
||||||
*/
|
*/
|
||||||
private String roomId;
|
private String callId;
|
||||||
|
private String joinToken;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(Stream stream) {
|
public void read(Stream stream) {
|
||||||
this.signalType = NetworkSignalType.fromCode(stream.readInt8());
|
this.signalType = NetworkSignalType.fromCode(stream.readInt8());
|
||||||
if(this.signalType == NetworkSignalType.END_CALL_BECAUSE_BUSY || this.signalType == NetworkSignalType.END_CALL_BECAUSE_PEER_DISCONNECTED) {
|
if(this.signalType == NetworkSignalType.END_CALL_BECAUSE_BUSY
|
||||||
|
|| this.signalType == NetworkSignalType.END_CALL_BECAUSE_PEER_DISCONNECTED
|
||||||
|
|| this.signalType == NetworkSignalType.RINGING_TIMEOUT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.src = stream.readString();
|
this.src = stream.readString();
|
||||||
@@ -46,8 +48,9 @@ public class Packet26SignalPeer extends Packet {
|
|||||||
if (signalType == NetworkSignalType.KEY_EXCHANGE) {
|
if (signalType == NetworkSignalType.KEY_EXCHANGE) {
|
||||||
this.sharedPublic = stream.readString();
|
this.sharedPublic = stream.readString();
|
||||||
}
|
}
|
||||||
if(signalType == NetworkSignalType.CREATE_ROOM) {
|
if(signalType == NetworkSignalType.CALL || signalType == NetworkSignalType.ACCEPT || signalType == NetworkSignalType.END_CALL) {
|
||||||
this.roomId = stream.readString();
|
this.callId = stream.readString();
|
||||||
|
this.joinToken = stream.readString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +59,9 @@ public class Packet26SignalPeer extends Packet {
|
|||||||
Stream stream = new Stream();
|
Stream stream = new Stream();
|
||||||
stream.writeInt16(this.packetId);
|
stream.writeInt16(this.packetId);
|
||||||
stream.writeInt8(this.signalType.getCode());
|
stream.writeInt8(this.signalType.getCode());
|
||||||
if(this.signalType == NetworkSignalType.END_CALL_BECAUSE_BUSY || this.signalType == NetworkSignalType.END_CALL_BECAUSE_PEER_DISCONNECTED) {
|
if(this.signalType == NetworkSignalType.END_CALL_BECAUSE_BUSY
|
||||||
|
|| this.signalType == NetworkSignalType.END_CALL_BECAUSE_PEER_DISCONNECTED
|
||||||
|
|| this.signalType == NetworkSignalType.RINGING_TIMEOUT) {
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
stream.writeString(this.src);
|
stream.writeString(this.src);
|
||||||
@@ -64,8 +69,9 @@ public class Packet26SignalPeer extends Packet {
|
|||||||
if (signalType == NetworkSignalType.KEY_EXCHANGE) {
|
if (signalType == NetworkSignalType.KEY_EXCHANGE) {
|
||||||
stream.writeString(this.sharedPublic);
|
stream.writeString(this.sharedPublic);
|
||||||
}
|
}
|
||||||
if(signalType == NetworkSignalType.CREATE_ROOM) {
|
if(signalType == NetworkSignalType.CALL || signalType == NetworkSignalType.ACCEPT || signalType == NetworkSignalType.END_CALL) {
|
||||||
stream.writeString(this.roomId);
|
stream.writeString(this.callId);
|
||||||
|
stream.writeString(this.joinToken);
|
||||||
}
|
}
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
@@ -134,20 +140,35 @@ public class Packet26SignalPeer extends Packet {
|
|||||||
public void setSignalType(NetworkSignalType signalType) {
|
public void setSignalType(NetworkSignalType signalType) {
|
||||||
this.signalType = signalType;
|
this.signalType = signalType;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Получить идентификатор сессии звонка, если тип сигнала CALL или ACCEPT
|
||||||
|
* @return идентификатор сессии звонка, если тип сигнала CALL или ACCEPT, иначе null
|
||||||
|
*/
|
||||||
|
public String getCallId() {
|
||||||
|
return callId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить идентификатор созданной комнаты, если тип сигнала CREATE_ROOM
|
* Установить идентификатор сессии звонка, если тип сигнала CALL или ACCEPT
|
||||||
* @return идентификатор комнаты, если тип сигнала CREATE_ROOM, иначе null
|
* @param callId идентификатор сессии звонка, если тип сигнала CALL или ACCEPT
|
||||||
*/
|
*/
|
||||||
public String getRoomId() {
|
public void setCallId(String callId) {
|
||||||
return roomId;
|
this.callId = callId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Установить идентификатор комнаты, в которой происходит звонок, если тип сигнала CREATE_ROOM
|
* Получить токен для присоединения к сессии звонка, если тип сигнала CALL или ACCEPT
|
||||||
* @param roomId идентификатор комнаты, если тип сигнала CREATE_ROOM
|
* @return токен для присоединения к сессии звонка, если тип сигнала CALL или ACCEPT, иначе null
|
||||||
*/
|
*/
|
||||||
public void setRoomId(String roomId) {
|
public String getJoinToken() {
|
||||||
this.roomId = roomId;
|
return joinToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установить токен для присоединения к сессии звонка, если тип сигнала CALL или ACCEPT
|
||||||
|
* @param joinToken токен для присоединения к сессии звонка, если тип сигнала CALL или ACCEPT
|
||||||
|
*/
|
||||||
|
public void setJoinToken(String joinToken) {
|
||||||
|
this.joinToken = joinToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,21 +13,11 @@ public class Packet27WebRTC extends Packet {
|
|||||||
* Тип сообщения WebRTC
|
* Тип сообщения WebRTC
|
||||||
*/
|
*/
|
||||||
private NetworkWebRTCType type;
|
private NetworkWebRTCType type;
|
||||||
/**
|
|
||||||
* Публичный ключ участника комнаты, который отправил этот пакет
|
|
||||||
*/
|
|
||||||
private String publicKey;
|
|
||||||
/**
|
|
||||||
* Device ID участника комнаты, который отправил этот пакет
|
|
||||||
*/
|
|
||||||
private String deviceId;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(Stream stream) {
|
public void read(Stream stream) {
|
||||||
this.type = NetworkWebRTCType.fromCode(stream.readInt8());
|
this.type = NetworkWebRTCType.fromCode(stream.readInt8());
|
||||||
this.sdpOrCandidate = stream.readString();
|
this.sdpOrCandidate = stream.readString();
|
||||||
this.publicKey = stream.readString();
|
|
||||||
this.deviceId = stream.readString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -36,8 +26,6 @@ public class Packet27WebRTC extends Packet {
|
|||||||
steram.writeInt16(this.packetId);
|
steram.writeInt16(this.packetId);
|
||||||
steram.writeInt8(this.type.getCode());
|
steram.writeInt8(this.type.getCode());
|
||||||
steram.writeString(this.sdpOrCandidate);
|
steram.writeString(this.sdpOrCandidate);
|
||||||
steram.writeString(this.publicKey);
|
|
||||||
steram.writeString(this.deviceId);
|
|
||||||
return steram;
|
return steram;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,36 +60,4 @@ public class Packet27WebRTC extends Packet {
|
|||||||
public void setType(NetworkWebRTCType type) {
|
public void setType(NetworkWebRTCType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить публичный ключ участника комнаты, который отправил этот пакет
|
|
||||||
* @return публичный ключ участника комнаты, который отправил этот пакет
|
|
||||||
*/
|
|
||||||
public String getPublicKey() {
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Установить публичный ключ участника комнаты, который отправил этот пакет
|
|
||||||
* @param publicKey публичный ключ участника комнаты, который отправил этот пакет
|
|
||||||
*/
|
|
||||||
public void setPublicKey(String publicKey) {
|
|
||||||
this.publicKey = publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить device ID участника комнаты, который отправил этот пакет
|
|
||||||
* @return device ID участника комнаты, который отправил этот пакет
|
|
||||||
*/
|
|
||||||
public String getDeviceId() {
|
|
||||||
return deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Установить device ID участника комнаты, который отправил этот пакет
|
|
||||||
* @param deviceId device ID участника комнаты, который отправил этот пакет
|
|
||||||
*/
|
|
||||||
public void setDeviceId(String deviceId) {
|
|
||||||
this.deviceId = deviceId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ public enum AttachmentType {
|
|||||||
MESSAGES(1),
|
MESSAGES(1),
|
||||||
FILE(2),
|
FILE(2),
|
||||||
AVATAR(3),
|
AVATAR(3),
|
||||||
CALL(4);
|
CALL(4),
|
||||||
|
VOICE(5),
|
||||||
|
VIDEO_CIRCLE(6);
|
||||||
|
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ public enum NetworkSignalType {
|
|||||||
*/
|
*/
|
||||||
END_CALL(3),
|
END_CALL(3),
|
||||||
/**
|
/**
|
||||||
* Создание комнаты
|
* Активная стадия звонка, значит комната уже создана на SFU
|
||||||
*/
|
*/
|
||||||
CREATE_ROOM(4),
|
ACTIVE(4),
|
||||||
/**
|
/**
|
||||||
* Обрыв связи с пиром
|
* Обрыв связи с пиром
|
||||||
*/
|
*/
|
||||||
@@ -31,7 +31,16 @@ public enum NetworkSignalType {
|
|||||||
/**
|
/**
|
||||||
* Не удалось дозвониться - пользователь занят другим звонком
|
* Не удалось дозвониться - пользователь занят другим звонком
|
||||||
*/
|
*/
|
||||||
END_CALL_BECAUSE_BUSY(6);
|
END_CALL_BECAUSE_BUSY(6),
|
||||||
|
/**
|
||||||
|
* Принятие звонка
|
||||||
|
*/
|
||||||
|
ACCEPT(7),
|
||||||
|
/**
|
||||||
|
* Таймаут на этапе дозвона, если пользователь не ответил на звонок в течение определенного времени, то звонок считается неуспешным и вызывающей
|
||||||
|
* стороне отправляется этот сигнал, а сессия звонка удаляется, так как она уже не актуальна
|
||||||
|
*/
|
||||||
|
RINGING_TIMEOUT(8);
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ public class FCM extends Pusher {
|
|||||||
* Тихий тип уведомления для очистки отправленных уведомлений на устройстве,
|
* Тихий тип уведомления для очистки отправленных уведомлений на устройстве,
|
||||||
* не должен отображаться пользователю, поэтому не задаем звук и ставим contentAvailable для iOS и high priority для Android
|
* не должен отображаться пользователю, поэтому не задаем звук и ставим contentAvailable для iOS и high priority для Android
|
||||||
*/
|
*/
|
||||||
apnsConfig.setAps(Aps.builder().setContentAvailable(true).setSound("default").build());
|
apnsConfig.setAps(Aps.builder().setContentAvailable(true).setMutableContent(true).build());
|
||||||
androidConfig.setPriority(AndroidConfig.Priority.HIGH);
|
androidConfig.setPriority(AndroidConfig.Priority.HIGH);
|
||||||
messageBuilder.setApnsConfig(apnsConfig.build());
|
messageBuilder.setApnsConfig(apnsConfig.build());
|
||||||
|
messageBuilder.setNotification(Notification.builder().setTitle("").setBody("").build());
|
||||||
messageBuilder.setAndroidConfig(androidConfig.build());
|
messageBuilder.setAndroidConfig(androidConfig.build());
|
||||||
break;
|
break;
|
||||||
case PushType.PERSONAL_MESSAGE:
|
case PushType.PERSONAL_MESSAGE:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class VoIPApns extends Pusher {
|
|||||||
this.topic = bundleId + ".voip";
|
this.topic = bundleId + ".voip";
|
||||||
|
|
||||||
this.client = new ApnsClientBuilder()
|
this.client = new ApnsClientBuilder()
|
||||||
.setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
|
.setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST)
|
||||||
.setClientCredentials(new File(p12Path), p12Password)
|
.setClientCredentials(new File(p12Path), p12Password)
|
||||||
.build();
|
.build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -59,11 +59,13 @@ public class VoIPApns extends Pusher {
|
|||||||
"aps": { "content-available": 1 },
|
"aps": { "content-available": 1 },
|
||||||
"type": "CALL",
|
"type": "CALL",
|
||||||
"callId": "%s",
|
"callId": "%s",
|
||||||
"from": "%s"
|
"from": "%s",
|
||||||
|
"joinToken": "%s"
|
||||||
}
|
}
|
||||||
""".formatted(
|
""".formatted(
|
||||||
escape(data.getOrDefault("callId", "")),
|
escape(data.getOrDefault("callId", "")),
|
||||||
escape(data.getOrDefault("dialog", ""))
|
escape(data.getOrDefault("dialog", "")),
|
||||||
|
escape(data.getOrDefault("joinToken", ""))
|
||||||
);
|
);
|
||||||
|
|
||||||
SimpleApnsPushNotification push = new SimpleApnsPushNotification(
|
SimpleApnsPushNotification push = new SimpleApnsPushNotification(
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import im.rosetta.calls.CallManager;
|
||||||
|
import im.rosetta.calls.CallSession;
|
||||||
import im.rosetta.client.ClientManager;
|
import im.rosetta.client.ClientManager;
|
||||||
import im.rosetta.logger.Logger;
|
import im.rosetta.logger.Logger;
|
||||||
import im.rosetta.logger.enums.Color;
|
import im.rosetta.logger.enums.Color;
|
||||||
@@ -33,10 +35,12 @@ public class ForwardUnitService {
|
|||||||
private Set<SFU> sfuConnections = ConcurrentHashMap.newKeySet();
|
private Set<SFU> sfuConnections = ConcurrentHashMap.newKeySet();
|
||||||
private ClientManager clientManager;
|
private ClientManager clientManager;
|
||||||
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
private CallManager callManager;
|
||||||
|
|
||||||
public ForwardUnitService(Logger logger, ClientManager clientManager) {
|
public ForwardUnitService(Logger logger, ClientManager clientManager, CallManager callManager) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.clientManager = clientManager;
|
this.clientManager = clientManager;
|
||||||
|
this.callManager = callManager;
|
||||||
this.sfuConnectionsSheduler();
|
this.sfuConnectionsSheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,25 +139,33 @@ public class ForwardUnitService {
|
|||||||
|
|
||||||
public void onPeerDisconnected(DisconnectedPeer disconnectedPeer) throws ProtocolException {
|
public void onPeerDisconnected(DisconnectedPeer disconnectedPeer) throws ProtocolException {
|
||||||
Room room = disconnectedPeer.getRoom();
|
Room room = disconnectedPeer.getRoom();
|
||||||
if(disconnectedPeer.getReason() != DisconnectReason.FAILED){
|
CallSession callSession = this.callManager.getCallSession(room);
|
||||||
|
callSession.leaveCall(disconnectedPeer.getPeerId());
|
||||||
|
|
||||||
|
if(disconnectedPeer.getReason() == DisconnectReason.FAILED){
|
||||||
/**
|
/**
|
||||||
* Если у нас произошло штатное отключение, а не в результате обрыва связи - то не нужно отправлять
|
* Произошло нештатное отключение клиента от сервера SFU, например, из-за сбоя сети
|
||||||
* оппонентам пакеты о том, что участник отключился в результате обрыва связи.
|
|
||||||
*/
|
*/
|
||||||
return;
|
if(callSession.shouldRemove()){
|
||||||
}
|
|
||||||
for(String peerId : room.getParticipants()) {
|
|
||||||
/**
|
|
||||||
* Уведомляем все пиры, что соединение с пиром было потеряно
|
|
||||||
*/
|
|
||||||
if(room.getParticipants().size() == 1) {
|
|
||||||
/**
|
|
||||||
* Звонок был завершен, так как в комнате остался только один участник, который не может продолжать звонок в одиночку.
|
|
||||||
*/
|
|
||||||
Packet26SignalPeer packet = new Packet26SignalPeer();
|
Packet26SignalPeer packet = new Packet26SignalPeer();
|
||||||
packet.setSignalType(NetworkSignalType.END_CALL_BECAUSE_PEER_DISCONNECTED);
|
packet.setSignalType(NetworkSignalType.END_CALL_BECAUSE_PEER_DISCONNECTED);
|
||||||
this.clientManager.sendPacketToAuthorizedPK(peerId, packet);
|
callSession.sendPacket(packet, null);
|
||||||
|
this.callManager.removeSession(callSession);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(disconnectedPeer.getReason() == DisconnectReason.CLOSED){
|
||||||
|
/**
|
||||||
|
* Клиент намеренно покинул звонок, например, отключился от SFU сервера, так как завершил звонок или вышел из комнаты
|
||||||
|
* (например если клиент отрабатывает выход из звонка по кнопке END не правильно)
|
||||||
|
*/
|
||||||
|
if(callSession.shouldRemove()){
|
||||||
|
Packet26SignalPeer packet = new Packet26SignalPeer();
|
||||||
|
packet.setSignalType(NetworkSignalType.END_CALL);
|
||||||
|
callSession.sendPacket(packet, null);
|
||||||
|
this.callManager.removeSession(callSession);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,4 +157,12 @@ public class Room {
|
|||||||
buffer.flip();
|
buffer.flip();
|
||||||
this.sfu.getConnection().send(buffer);
|
this.sfu.getConnection().send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null || getClass() != obj.getClass()) return false;
|
||||||
|
Room room = (Room) obj;
|
||||||
|
return roomId.equals(room.roomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -150,6 +150,7 @@ public class Server extends WebSocketServer {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
client.disconnect(ServerFailures.BAD_PACKET);
|
client.disconnect(ServerFailures.BAD_PACKET);
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -239,17 +239,18 @@ public class Client {
|
|||||||
* @param client клиент
|
* @param client клиент
|
||||||
* @return true если это один и тот же клиент, false если нет
|
* @return true если это один и тот же клиент, false если нет
|
||||||
*/
|
*/
|
||||||
public boolean equals(Client client) {
|
@Override
|
||||||
if(client == null){
|
public boolean equals(Object obj) {
|
||||||
return false;
|
if (this == obj) return true;
|
||||||
}
|
if (obj == null || getClass() != obj.getClass()) return false;
|
||||||
if(!(client instanceof Client)){
|
|
||||||
return false;
|
Client client = (Client) obj;
|
||||||
}
|
return this.clientId != null && this.clientId.equals(client.clientId);
|
||||||
if(!client.getClientId().equals(this.clientId)){
|
}
|
||||||
return false;
|
|
||||||
}
|
@Override
|
||||||
return true;
|
public int hashCode() {
|
||||||
|
return this.clientId == null ? 0 : this.clientId.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user