[*] 视频会话管理逻辑
This commit is contained in:
@@ -244,8 +244,8 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
|||||||
private void previewVideo(Message message) {
|
private void previewVideo(Message message) {
|
||||||
final GridLayout video = this.binding.video;
|
final GridLayout video = this.binding.video;
|
||||||
final int count = video.getChildCount();
|
final int count = video.getChildCount();
|
||||||
final GridLayout.Spec rowSpec = GridLayout.spec(count / 2);
|
final GridLayout.Spec rowSpec = GridLayout.spec(count / 2, 1, 0);
|
||||||
final GridLayout.Spec columnSpec = GridLayout.spec(count % 2);
|
final GridLayout.Spec columnSpec = GridLayout.spec(count % 2, 1, 0);
|
||||||
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(rowSpec, columnSpec);
|
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(rowSpec, columnSpec);
|
||||||
layoutParams.width = 0;
|
layoutParams.width = 0;
|
||||||
layoutParams.height = 0;
|
layoutParams.height = 0;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.acgist.taoyao.media;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.media.AudioRecord;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.media.MediaCodecList;
|
import android.media.MediaCodecList;
|
||||||
import android.media.projection.MediaProjection;
|
import android.media.projection.MediaProjection;
|
||||||
@@ -41,6 +42,9 @@ import org.webrtc.VideoFrame;
|
|||||||
import org.webrtc.VideoSource;
|
import org.webrtc.VideoSource;
|
||||||
import org.webrtc.VideoTrack;
|
import org.webrtc.VideoTrack;
|
||||||
import org.webrtc.audio.JavaAudioDeviceModule;
|
import org.webrtc.audio.JavaAudioDeviceModule;
|
||||||
|
import org.webrtc.voiceengine.WebRtcAudioManager;
|
||||||
|
import org.webrtc.voiceengine.WebRtcAudioRecord;
|
||||||
|
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@@ -361,6 +365,11 @@ public final class MediaManager {
|
|||||||
// .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
// .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
||||||
// .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
// .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||||
// .build();
|
// .build();
|
||||||
|
// WebRtcAudioRecord.setOnAudioSamplesReady(audioSamples -> {
|
||||||
|
// if(this.recordClient != null) {
|
||||||
|
// this.recordClient.onWebRtcAudioRecordSamplesReady(audioSamples);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
final JavaAudioDeviceModule javaAudioDeviceModule = JavaAudioDeviceModule.builder(this.context)
|
final JavaAudioDeviceModule javaAudioDeviceModule = JavaAudioDeviceModule.builder(this.context)
|
||||||
// .setSampleRate()
|
// .setSampleRate()
|
||||||
// .setAudioSource(MediaRecorder.AudioSource.MIC)
|
// .setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ export default {
|
|||||||
this.taoyao.sessionClose(this.client.id);
|
this.taoyao.sessionClose(this.client.id);
|
||||||
},
|
},
|
||||||
media(track) {
|
media(track) {
|
||||||
console.log(track);
|
|
||||||
if(track.kind === 'audio') {
|
if(track.kind === 'audio') {
|
||||||
if (this.audioStream) {
|
if (this.audioStream) {
|
||||||
// TODO:资源释放
|
// TODO:资源释放
|
||||||
|
|||||||
@@ -12,3 +12,7 @@
|
|||||||
## 信令格式
|
## 信令格式
|
||||||
|
|
||||||
[信令格式](https://localhost:8888/protocol/list)
|
[信令格式](https://localhost:8888/protocol/list)
|
||||||
|
|
||||||
|
## STUN/TURN
|
||||||
|
|
||||||
|
视频房间不用`STUN/TURN`服务,视频会话需要自己搭建`coturn`服务。
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import lombok.Setter;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* WebRTC配置
|
* WebRTC配置
|
||||||
* P2P视频监控会用,正常会议不会使用,需要自己搭建`coturn`服务。
|
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.acgist.taoyao.signal.event;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
|
import com.acgist.taoyao.signal.party.session.Session;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频会话事件适配器
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public abstract class SessionEventAdapter extends ApplicationEventAdapter {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频会话
|
||||||
|
*/
|
||||||
|
private final Session session;
|
||||||
|
/**
|
||||||
|
* 视频会话ID
|
||||||
|
*/
|
||||||
|
private final String sessionId;
|
||||||
|
|
||||||
|
public SessionEventAdapter(Session session) {
|
||||||
|
this(session, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionEventAdapter(Session session, Message message) {
|
||||||
|
this(session, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionEventAdapter(Session session, Message message, Map<String, Object> body) {
|
||||||
|
super(session, message, body);
|
||||||
|
this.session = session;
|
||||||
|
this.sessionId = session.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.acgist.taoyao.signal.event.session;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.signal.event.SessionEventAdapter;
|
||||||
|
import com.acgist.taoyao.signal.party.session.Session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭视频会话事件
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
public class SessionCloseEvent extends SessionEventAdapter {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public SessionCloseEvent(Session session) {
|
||||||
|
super(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -139,6 +139,7 @@ public class Room extends OperatorAdapter {
|
|||||||
synchronized (this.clients) {
|
synchronized (this.clients) {
|
||||||
final ClientWrapper wrapper = this.clients.remove(client);
|
final ClientWrapper wrapper = this.clients.remove(client);
|
||||||
if(wrapper != null) {
|
if(wrapper != null) {
|
||||||
|
log.info("终端离开房间:{} - {}", this.roomId, client.clientId());
|
||||||
try {
|
try {
|
||||||
wrapper.close();
|
wrapper.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import java.io.Closeable;
|
|||||||
|
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
import com.acgist.taoyao.signal.client.Client;
|
import com.acgist.taoyao.signal.client.Client;
|
||||||
|
import com.acgist.taoyao.signal.event.EventPublisher;
|
||||||
|
import com.acgist.taoyao.signal.event.session.SessionCloseEvent;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P2P会话
|
* 视频会话
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
@@ -27,16 +29,11 @@ public class Session implements Closeable {
|
|||||||
* 接收者
|
* 接收者
|
||||||
*/
|
*/
|
||||||
private final Client target;
|
private final Client target;
|
||||||
/**
|
|
||||||
* P2P会话管理器
|
|
||||||
*/
|
|
||||||
private final SessionManager sessionManager;
|
|
||||||
|
|
||||||
public Session(String id, Client source, Client target, SessionManager sessionManager) {
|
public Session(String id, Client source, Client target) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.sessionManager = sessionManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,10 +60,18 @@ public class Session implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void close() {
|
* @param client 终端
|
||||||
this.sessionManager.remove(this.id);
|
*
|
||||||
|
* @return 是否含有终端
|
||||||
|
*/
|
||||||
|
public boolean hasClient(Client client) {
|
||||||
|
return this.source == client || this.target == client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
EventPublisher.publishEvent(new SessionCloseEvent(this));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import com.acgist.taoyao.boot.annotation.Manager;
|
|||||||
import com.acgist.taoyao.boot.service.IdService;
|
import com.acgist.taoyao.boot.service.IdService;
|
||||||
import com.acgist.taoyao.signal.client.Client;
|
import com.acgist.taoyao.signal.client.Client;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P2P会话管理器
|
* 视频会话管理器
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Manager
|
@Manager
|
||||||
public class SessionManager {
|
public class SessionManager {
|
||||||
|
|
||||||
@@ -30,8 +33,9 @@ public class SessionManager {
|
|||||||
* @return 会话
|
* @return 会话
|
||||||
*/
|
*/
|
||||||
public Session call(Client source, Client target) {
|
public Session call(Client source, Client target) {
|
||||||
final Session session = new Session(this.idService.buildUuid(), source, target, this);
|
final Session session = new Session(this.idService.buildUuid(), source, target);
|
||||||
this.sessions.put(session.getId(), session);
|
this.sessions.put(session.getId(), session);
|
||||||
|
log.info("创建视频会话:{} - {} - {}", session.getId(), session.getSource().clientId(), session.getTarget().clientId());
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +54,22 @@ public class SessionManager {
|
|||||||
* @return 会话
|
* @return 会话
|
||||||
*/
|
*/
|
||||||
public Session remove(String sessionId) {
|
public Session remove(String sessionId) {
|
||||||
return this.sessions.remove(sessionId);
|
final Session session = this.sessions.remove(sessionId);
|
||||||
|
if(session != null) {
|
||||||
|
log.info("视频会话关闭:{} - {} - {}", sessionId, session.getSource().clientId(), session.getTarget().clientId());
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭所有资源
|
||||||
|
*
|
||||||
|
* @param client 终端
|
||||||
|
*/
|
||||||
|
public void close(Client client) {
|
||||||
|
this.sessions.values().stream()
|
||||||
|
.filter(v -> v.hasClient(client))
|
||||||
|
.forEach(Session::close);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import com.acgist.taoyao.boot.service.IdService;
|
|||||||
import com.acgist.taoyao.signal.client.ClientManager;
|
import com.acgist.taoyao.signal.client.ClientManager;
|
||||||
import com.acgist.taoyao.signal.event.ApplicationEventAdapter;
|
import com.acgist.taoyao.signal.event.ApplicationEventAdapter;
|
||||||
import com.acgist.taoyao.signal.party.media.RoomManager;
|
import com.acgist.taoyao.signal.party.media.RoomManager;
|
||||||
|
import com.acgist.taoyao.signal.party.session.SessionManager;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@@ -32,6 +33,8 @@ public abstract class ProtocolAdapter implements Protocol {
|
|||||||
@Autowired
|
@Autowired
|
||||||
protected ClientManager clientManager;
|
protected ClientManager clientManager;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected SessionManager sessionManager;
|
||||||
|
@Autowired
|
||||||
protected TaoyaoProperties taoyaoProperties;
|
protected TaoyaoProperties taoyaoProperties;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ApplicationContext applicationContext;
|
protected ApplicationContext applicationContext;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package com.acgist.taoyao.signal.protocol;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.config.Constant;
|
import com.acgist.taoyao.boot.config.Constant;
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
import com.acgist.taoyao.boot.model.MessageCodeException;
|
import com.acgist.taoyao.boot.model.MessageCodeException;
|
||||||
@@ -11,7 +9,6 @@ import com.acgist.taoyao.boot.utils.MapUtils;
|
|||||||
import com.acgist.taoyao.signal.client.Client;
|
import com.acgist.taoyao.signal.client.Client;
|
||||||
import com.acgist.taoyao.signal.client.ClientType;
|
import com.acgist.taoyao.signal.client.ClientType;
|
||||||
import com.acgist.taoyao.signal.party.session.Session;
|
import com.acgist.taoyao.signal.party.session.Session;
|
||||||
import com.acgist.taoyao.signal.party.session.SessionManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会话信令适配器
|
* 会话信令适配器
|
||||||
@@ -20,9 +17,6 @@ import com.acgist.taoyao.signal.party.session.SessionManager;
|
|||||||
*/
|
*/
|
||||||
public abstract class ProtocolSessionAdapter extends ProtocolClientAdapter {
|
public abstract class ProtocolSessionAdapter extends ProtocolClientAdapter {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected SessionManager sessionManager;
|
|
||||||
|
|
||||||
protected ProtocolSessionAdapter(String name, String signal) {
|
protected ProtocolSessionAdapter(String name, String signal) {
|
||||||
super(name, signal);
|
super(name, signal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ public class ClientCloseProtocol extends ProtocolClientAdapter implements Applic
|
|||||||
log.info("关闭终端:{}", clientId);
|
log.info("关闭终端:{}", clientId);
|
||||||
// 释放房间终端
|
// 释放房间终端
|
||||||
this.roomManager.leave(client);
|
this.roomManager.leave(client);
|
||||||
|
// 释放会话终端
|
||||||
|
this.sessionManager.close(client);
|
||||||
// 终端下线事件
|
// 终端下线事件
|
||||||
this.publishEvent(new ClientOfflineEvent(client));
|
this.publishEvent(new ClientOfflineEvent(client));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,14 @@ import com.acgist.taoyao.signal.client.ClientType;
|
|||||||
import com.acgist.taoyao.signal.party.session.Session;
|
import com.acgist.taoyao.signal.party.session.Session;
|
||||||
import com.acgist.taoyao.signal.protocol.ProtocolSessionAdapter;
|
import com.acgist.taoyao.signal.protocol.ProtocolSessionAdapter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发起会话信令
|
* 发起会话信令
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Protocol
|
@Protocol
|
||||||
@Description(
|
@Description(
|
||||||
body = """
|
body = """
|
||||||
@@ -43,6 +46,10 @@ public class SessionCallProtocol extends ProtocolSessionAdapter {
|
|||||||
public void execute(String clientId, ClientType clientType, Client client, Message message, Map<String, Object> body) {
|
public void execute(String clientId, ClientType clientType, Client client, Message message, Map<String, Object> body) {
|
||||||
final String targetId = MapUtils.get(body, Constant.CLIENT_ID);
|
final String targetId = MapUtils.get(body, Constant.CLIENT_ID);
|
||||||
final Client target = this.clientManager.clients(targetId);
|
final Client target = this.clientManager.clients(targetId);
|
||||||
|
if(target == null) {
|
||||||
|
log.warn("邀请对象无效:{}", clientId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final Session session = this.sessionManager.call(client, target);
|
final Session session = this.sessionManager.call(client, target);
|
||||||
message.setBody(Map.of(
|
message.setBody(Map.of(
|
||||||
Constant.NAME, target.status().getName(),
|
Constant.NAME, target.status().getName(),
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ package com.acgist.taoyao.signal.protocol.session;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.annotation.Description;
|
import com.acgist.taoyao.boot.annotation.Description;
|
||||||
import com.acgist.taoyao.boot.annotation.Protocol;
|
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||||
|
import com.acgist.taoyao.boot.config.Constant;
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
import com.acgist.taoyao.signal.client.Client;
|
import com.acgist.taoyao.signal.client.Client;
|
||||||
import com.acgist.taoyao.signal.client.ClientType;
|
import com.acgist.taoyao.signal.client.ClientType;
|
||||||
|
import com.acgist.taoyao.signal.event.session.SessionCloseEvent;
|
||||||
import com.acgist.taoyao.signal.party.session.Session;
|
import com.acgist.taoyao.signal.party.session.Session;
|
||||||
import com.acgist.taoyao.signal.protocol.ProtocolSessionAdapter;
|
import com.acgist.taoyao.signal.protocol.ProtocolSessionAdapter;
|
||||||
|
|
||||||
@@ -23,7 +27,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolSessionAdapter;
|
|||||||
""",
|
""",
|
||||||
flow = "终端->信令服务+)终端"
|
flow = "终端->信令服务+)终端"
|
||||||
)
|
)
|
||||||
public class SessionCloseProtocol extends ProtocolSessionAdapter {
|
public class SessionCloseProtocol extends ProtocolSessionAdapter implements ApplicationListener<SessionCloseEvent> {
|
||||||
|
|
||||||
public static final String SIGNAL = "session::close";
|
public static final String SIGNAL = "session::close";
|
||||||
|
|
||||||
@@ -31,6 +35,14 @@ public class SessionCloseProtocol extends ProtocolSessionAdapter {
|
|||||||
super("关闭媒体信令", SIGNAL);
|
super("关闭媒体信令", SIGNAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(SessionCloseEvent event) {
|
||||||
|
final Session session = event.getSession();
|
||||||
|
final Map<String, String> body = Map.of(Constant.SESSION_ID, event.getSessionId());
|
||||||
|
session.push(this.build(body));
|
||||||
|
this.sessionManager.remove(session.getId());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(String clientId, ClientType clientType, Session session, Client client, Message message, Map<String, Object> body) {
|
public void execute(String clientId, ClientType clientType, Session session, Client client, Message message, Map<String, Object> body) {
|
||||||
session.push(message);
|
session.push(message);
|
||||||
|
|||||||
Reference in New Issue
Block a user