diff --git a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/signal/Taoyao.java b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/signal/Taoyao.java index 8354c3e..f905c39 100644 --- a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/signal/Taoyao.java +++ b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/signal/Taoyao.java @@ -505,24 +505,26 @@ public final class Taoyao implements ITaoyao { case "client::shutdown" -> this.clientShutdown(message, body); case "media::consume" -> this.mediaConsume(message, body); // case "media::audio::volume" -> this.mediaAudioVolume(message, body); -// case "media::consumer::close" -> this.mediaConsumerClose(message, body); -// case "media::consumer::pause" -> this.mediaConsumerPause(message, body); + case "media::consumer::close" -> this.mediaConsumerClose(message, body); + case "media::consumer::pause" -> this.mediaConsumerPause(message, body); // case "media::consumer::request::key::frame" -> this.mediaConsumerRequestKeyFrame(message, body); -// case "media::consumer::resume" -> this.mediaConsumerResume(message, body); + case "media::consumer::resume" -> this.mediaConsumerResume(message, body); // case "media::consumer::set::preferred::layers" -> this.mediaConsumerSetPreferredLayers(message, body); // case "media::consumer::status" -> this.mediaConsumerStatus(message, body); -// case "media::producer::close" -> this.mediaProducerClose(message, body); -// case "media::producer::pause" -> this.mediaProducerPause(message, body); -// case "media::producer::resume" -> this.mediaProducerResume(message, body); + case "media::producer::close" -> this.mediaProducerClose(message, body); + case "media::producer::pause" -> this.mediaProducerPause(message, body); + case "media::producer::resume" -> this.mediaProducerResume(message, body); // case "media::producer::video::orientation:change" -> this.mediaVideoOrientationChange(message, body); case "room::close" -> this.roomClose(message, body); case "room::enter" -> this.roomEnter(message, body); -// case "room::expel" -> this.roomExpel(message, body); + case "room::expel" -> this.roomExpel(message, body); case "room::invite" -> this.roomInivte(message, body); -// case "room::leave" -> this.roomLeave(message, body); + case "room::leave" -> this.roomLeave(message, body); case "session::call" -> this.sessionCall(message, body); case "session::close" -> this.sessionClose(message, body); case "session::exchange" -> this.sessionExchange(message, body); + case "session::pause" -> this.sessionPause(message, body); + case "session::resume" -> this.sessionResume(message, body); default -> Log.d(Taoyao.class.getSimpleName(), "没有适配信令:" + content); } } @@ -590,6 +592,60 @@ public final class Taoyao implements ITaoyao { room.mediaConsume(message, body); } + private void mediaConsumerClose(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final Room room = this.rooms.get(roomId); + if(room == null) { + return; + } + room.mediaConsumerClose(body); + } + + private void mediaConsumerPause(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final Room room = this.rooms.get(roomId); + if(room == null) { + return; + } + room.mediaConsumerPause(body); + } + + private void mediaConsumerResume(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final Room room = this.rooms.get(roomId); + if(room == null) { + return; + } + room.mediaConsumerResume(body); + } + + private void mediaProducerClose(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final Room room = this.rooms.get(roomId); + if(room == null) { + return; + } + room.mediaProducerClose(body); + } + + private void mediaProducerPause(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final Room room = this.rooms.get(roomId); + if(room == null) { + return; + } + room.mediaProducerPause(body); + } + + private void mediaProducerResume(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final Room room = this.rooms.get(roomId); + if(room == null) { + return; + } + room.mediaProducerResume(body); + } + private void roomClose(Message message, Map body) { final String roomId = MapUtils.get(body, "roomId"); final Room room = this.rooms.remove(roomId); @@ -608,12 +664,6 @@ public final class Taoyao implements ITaoyao { room.newRemoteClient(body); } - private void roomInivte(Message message, Map body) { - final String roomId = MapUtils.get(body, "roomId"); - final String password = MapUtils.get(body, "password"); - this.roomEnter(roomId, password); - } - public Room roomEnter(String roomId, String password) { final Resources resources = this.context.getResources(); final Room room = this.rooms.computeIfAbsent( @@ -621,7 +671,7 @@ public final class Taoyao implements ITaoyao { key -> new Room( this.name, this.clientId, key, password, - this.mainHandler, this, + this, this.mainHandler, resources.getBoolean(R.bool.dataConsume), resources.getBoolean(R.bool.audioConsume), resources.getBoolean(R.bool.videoConsume), @@ -630,22 +680,66 @@ public final class Taoyao implements ITaoyao { resources.getBoolean(R.bool.videoProduce) ) ); - room.enter(); - room.mediaProduce(); - return room; + final boolean success = room.enter(); + if(success) { + room.mediaProduce(); + return room; + } else { + this.rooms.remove(roomId); + return null; + } + } + + private void roomExpel(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + this.roomLeave(roomId); + } + + private void roomInivte(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final String password = MapUtils.get(body, "password"); + this.roomEnter(roomId, password); + } + + public void roomLeave(String roomId) { + final Room room = this.rooms.remove(roomId); + if(room == null) { + return; + } + this.push(this.buildMessage( + "room::leave", + "roomId", roomId + )); + room.close(); + } + + private void roomLeave(Message message, Map body) { + final String roomId = MapUtils.get(body, "roomId"); + final String clientId = MapUtils.get(body, "clientId"); + final Room room = this.rooms.get(roomId); + if(room == null) { + return; + } + room.closeRemoteClient(clientId); } private void sessionCall(Message message, Map body) { final String name = MapUtils.get(body, "name"); final String clientId = MapUtils.get(body, "clientId"); final String sessionId = MapUtils.get(body, "sessionId"); - final SessionClient sessionClient = new SessionClient(sessionId, name, clientId, this.mainHandler, this); + final SessionClient sessionClient = new SessionClient(sessionId, name, clientId, this, this.mainHandler); this.sessionClients.put(sessionId, sessionClient); sessionClient.init(); sessionClient.offer(); } private void sessionClose(Message message, Map body) { + final String sessionId = MapUtils.get(body, "sessionId"); + final SessionClient sessionClient = this.sessionClients.remove(sessionId); + if(sessionClient == null) { + return; + } + sessionClient.close(); } private void sessionExchange(Message message, Map body) { @@ -658,6 +752,24 @@ public final class Taoyao implements ITaoyao { sessionClient.exchange(message, body); } + private void sessionPause(Message message, Map body) { + final String sessionId = MapUtils.get(body, "sessionId"); + final SessionClient sessionClient = this.sessionClients.get(sessionId); + if(sessionClient == null) { + return; + } + sessionClient.pause(); + } + + private void sessionResume(Message message, Map body) { + final String sessionId = MapUtils.get(body, "sessionId"); + final SessionClient sessionClient = this.sessionClients.get(sessionId); + if(sessionClient == null) { + return; + } + sessionClient.resume(); + } + /** * 心跳 */ diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java index cce0a7e..625c779 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java @@ -278,6 +278,7 @@ public final class MediaManager { /** * 关闭一个终端 * 最后一个终端关闭时,释放所有资源。 + * 注意:所有本地媒体关闭调用,不要直接关闭本地媒体流。 * * @return 剩余终端数量 */ @@ -298,7 +299,7 @@ public final class MediaManager { public RecordClient startRecord(String path, String filename) { synchronized (this) { - this.recordClient = new RecordClient(path, filename, this.handler, this.taoyao); + this.recordClient = new RecordClient(path, filename, this.taoyao, this.handler); this.recordClient.start(); return this.recordClient; } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/Client.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/Client.java index 40bc64a..b261956 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/Client.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/Client.java @@ -20,7 +20,7 @@ import java.io.Closeable; * * @author acgist */ -public abstract class Client implements Closeable { +public abstract class Client extends CloseableClient { /** * 终端名称 @@ -30,29 +30,15 @@ public abstract class Client implements Closeable { * 终端ID */ protected final String clientId; - /** - * Handler - */ - protected final Handler handler; - /** - * 信令通道 - */ - protected final ITaoyao taoyao; - /** - * 媒体服务 - */ - protected final MediaManager mediaManager; /** * 视频预览 */ protected SurfaceViewRenderer surfaceViewRenderer; - public Client(String name, String clientId, Handler handler, ITaoyao taoyao) { + public Client(String name, String clientId, ITaoyao taoyao, Handler handler) { + super(taoyao, handler); this.name = name; this.clientId = clientId; - this.taoyao = taoyao; - this.handler = handler; - this.mediaManager = MediaManager.getInstance(); } /** @@ -86,8 +72,15 @@ public abstract class Client implements Closeable { } } + public void pause() { + } + + public void resume() { + } + @Override public void close() { + super.close(); Log.i(this.getClass().getSimpleName(), "关闭终端:" + this.clientId); if(this.surfaceViewRenderer != null) { this.surfaceViewRenderer.release(); diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/CloseableClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/CloseableClient.java new file mode 100644 index 0000000..733411a --- /dev/null +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/CloseableClient.java @@ -0,0 +1,61 @@ +package com.acgist.taoyao.media.client; + +import android.os.Handler; + +import com.acgist.taoyao.media.MediaManager; +import com.acgist.taoyao.media.signal.ITaoyao; + +import org.webrtc.PeerConnectionFactory; + +import java.io.Closeable; + +/** + * 需要关闭终端 + * + * @author acgist + */ +public abstract class CloseableClient implements Closeable { + + /** + * 是否加载 + * 防止重复加载 + */ + protected volatile boolean init; + /** + * 是否关闭 + */ + protected volatile boolean close; + /** + * 信令通道 + */ + protected final ITaoyao taoyao; + /** + * Handler + */ + protected final Handler handler; + /** + * 媒体服务 + */ + protected final MediaManager mediaManager; + + public CloseableClient(ITaoyao taoyao, Handler handler) { + this.init = false; + this.close = false; + this.taoyao = taoyao; + this.handler = handler; + this.mediaManager = MediaManager.getInstance(); + } + + /** + * 加载 + */ + protected void init() { + this.init = true; + } + + + @Override + public void close() { + this.close = true; + } +} diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/LocalClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/LocalClient.java index 1daf1be..f5e7499 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/LocalClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/LocalClient.java @@ -20,8 +20,8 @@ public class LocalClient extends RoomClient { */ protected MediaStream mediaStream; - public LocalClient(String name, String clientId, Handler handler, ITaoyao taoyao) { - super(name, clientId, handler, taoyao); + public LocalClient(String name, String clientId, ITaoyao taoyao, Handler handler) { + super(name, clientId, taoyao, handler); } public MediaStream getMediaStream() { @@ -58,9 +58,6 @@ public class LocalClient extends RoomClient { @Override public void close() { super.close(); - if(this.mediaStream != null) { - this.mediaStream.dispose(); - } } } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RecordClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RecordClient.java index 3b809c9..b1a6155 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RecordClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RecordClient.java @@ -35,10 +35,6 @@ import java.util.concurrent.Executors; */ public class RecordClient extends Client { - /** - * 是否正在录像 - */ - private volatile boolean active; /** * 音频准备录制 */ @@ -92,8 +88,8 @@ public class RecordClient extends Client { */ private final ExecutorService executorService; - public RecordClient(String path, String filename, Handler handler, ITaoyao taoyao) { - super("本地录像", "LocalRecordClient", handler, taoyao); + public RecordClient(String path, String filename, ITaoyao taoyao, Handler handler) { + super("本地录像", "LocalRecordClient", taoyao, handler); this.filename = filename; final Path filePath = Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, filename); final File parentFile = filePath.getParent().toFile(); @@ -105,19 +101,13 @@ public class RecordClient extends Client { this.executorService = Executors.newFixedThreadPool(2); } - /** - * @return 是否正在录像 - */ - public boolean isActive() { - return this.active; - } - public void start() { synchronized (this) { - if(this.active) { + if(this.init) { return; } - this.active = true; + super.init(); + this.mediaManager.newClient(MediaManager.Type.BACK); this.record(null, null, 1, 1); } } @@ -161,7 +151,7 @@ public class RecordClient extends Client { int outputIndex; this.audioCodec.start(); this.audioActive = true; - while (this.active) { + while (!this.close) { outputIndex = this.audioCodec.dequeueOutputBuffer(info, 1000L * 1000); if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { @@ -173,7 +163,7 @@ public class RecordClient extends Client { this.pts = System.currentTimeMillis(); this.mediaMuxer.start(); this.notifyAll(); - } else if (this.active) { + } else if (!this.close) { try { this.wait(); } catch (InterruptedException e) { @@ -208,12 +198,12 @@ public class RecordClient extends Client { } public void putAudio(JavaAudioDeviceModule.AudioSamples audioSamples) { - if(this.active && this.audioActive) { + if(!this.close && this.audioActive) { } } public void putVideo(VideoFrame videoFrame) { - if (this.active && this.videoActive) { + if (!this.close && this.videoActive) { this.executorService.submit(() -> { // TextureBufferImpl // videoFrame.retain(); @@ -281,7 +271,7 @@ public class RecordClient extends Client { int outputIndex; this.videoCodec.start(); this.videoActive = true; - while (this.active) { + while (!this.close) { outputIndex = this.videoCodec.dequeueOutputBuffer(info, 1000L * 1000); if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { @@ -293,7 +283,7 @@ public class RecordClient extends Client { this.pts = System.currentTimeMillis(); this.mediaMuxer.start(); this.notifyAll(); - } else if (this.active) { + } else if (!this.close) { try { this.wait(); } catch (InterruptedException e) { @@ -346,28 +336,25 @@ public class RecordClient extends Client { } } - private void stop() { - Log.i(RecordClient.class.getSimpleName(), "结束录制:" + this.filepath); - this.active = false; - if (audioThread != null) { - this.audioThread.quitSafely(); - } - if (this.videoThread != null) { - this.videoThread.quitSafely(); - } - if(this.executorService != null) { - this.executorService.shutdown(); - } - synchronized (this) { - this.notifyAll(); - } - } - @Override public void close() { synchronized (this) { + if(this.close) { + return; + } super.close(); - this.stop(); + Log.i(RecordClient.class.getSimpleName(), "结束录制:" + this.filepath); + if (audioThread != null) { + this.audioThread.quitSafely(); + } + if (this.videoThread != null) { + this.videoThread.quitSafely(); + } + if(this.executorService != null) { + this.executorService.shutdown(); + } + this.notifyAll(); + this.mediaManager.closeClient(); } } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RemoteClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RemoteClient.java index 54b1e5a..281b62e 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RemoteClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RemoteClient.java @@ -25,8 +25,8 @@ public class RemoteClient extends RoomClient { */ protected final Map tracks; - public RemoteClient(String name, String clientId, Handler handler, ITaoyao taoyao) { - super(name, clientId, handler, taoyao); + public RemoteClient(String name, String clientId, ITaoyao taoyao, Handler handler) { + super(name, clientId, taoyao, handler); this.tracks = new ConcurrentHashMap<>(); } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/Room.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/Room.java index c1d4b14..9140076 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/Room.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/Room.java @@ -30,14 +30,12 @@ import java.util.concurrent.ConcurrentHashMap; * * @author acgist */ -public class Room implements Closeable, RouterCallback { +public class Room extends CloseableClient implements RouterCallback { private final String name; private final String clientId; private final String roomId; private final String password; - private final Handler handler; - private final ITaoyao taoyao; private final boolean dataConsume; private final boolean audioConsume; private final boolean videoConsume; @@ -45,8 +43,6 @@ public class Room implements Closeable, RouterCallback { private final boolean audioProduce; private final boolean videoProduce; private final long nativeRoomPointer; - private final MediaManager mediaManager; - private volatile boolean enter; private LocalClient localClient; private Map remoteClients; private PeerConnection.RTCConfiguration rtcConfiguration; @@ -56,16 +52,15 @@ public class Room implements Closeable, RouterCallback { public Room( String name, String clientId, String roomId, String password, - Handler handler, ITaoyao taoyao, + ITaoyao taoyao, Handler handler, boolean dataConsume, boolean audioConsume, boolean videoConsume, boolean dataProduce, boolean audioProduce, boolean videoProduce ) { + super(taoyao, handler); this.name = name; this.clientId = clientId; this.roomId = roomId; this.password = password; - this.handler = handler; - this.taoyao = taoyao; this.dataConsume = dataConsume; this.audioConsume = audioConsume; this.videoConsume = videoConsume; @@ -73,31 +68,33 @@ public class Room implements Closeable, RouterCallback { this.audioProduce = audioProduce; this.videoProduce = videoProduce; this.nativeRoomPointer = this.nativeNewRoom(roomId, this); - this.mediaManager = MediaManager.getInstance(); this.remoteClients = new ConcurrentHashMap<>(); - this.enter = false; } - public synchronized void enter() { - if (this.enter) { - return; + public boolean enter() { + synchronized (this) { + if (this.init) { + return true; + } + super.init(); + this.peerConnectionFactory = this.mediaManager.newClient(MediaManager.Type.BACK); + this.localClient = new LocalClient(this.name, this.clientId, this.taoyao, this.handler); + this.localClient.setMediaStream(this.mediaManager.getMediaStream()); + // STUN | TURN + final List iceServers = new ArrayList<>(); + // TODO:读取配置 + final PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer(); + iceServers.add(iceServer); + this.rtcConfiguration = new PeerConnection.RTCConfiguration(iceServers); + final Message response = this.taoyao.request(this.taoyao.buildMessage("media::router::rtp::capabilities", "roomId", this.roomId)); + if(response == null) { + this.close(); + return false; + } + final Object rtpCapabilities = MapUtils.get(response.body(), "rtpCapabilities"); + this.nativeEnter(this.nativeRoomPointer, JSONUtils.toJSON(rtpCapabilities), this.peerConnectionFactory.getNativePeerConnectionFactory(), this.rtcConfiguration); + return true; } - final Message response = this.taoyao.request(this.taoyao.buildMessage("media::router::rtp::capabilities", "roomId", this.roomId)); - if (response == null) { - Log.w(Room.class.getSimpleName(), "获取通道能力失败"); - return; - } - this.localClient = new LocalClient(this.name, this.clientId, this.handler, this.taoyao); - this.localClient.setMediaStream(this.mediaManager.getMediaStream()); - // STUN | TURN - final List iceServers = new ArrayList<>(); - // TODO:读取配置 - final PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer(); - iceServers.add(iceServer); - this.rtcConfiguration = new PeerConnection.RTCConfiguration(iceServers); - this.peerConnectionFactory = this.mediaManager.newClient(MediaManager.Type.BACK); - final Object rtpCapabilities = MapUtils.get(response.body(), "rtpCapabilities"); - this.nativeEnter(this.nativeRoomPointer, JSONUtils.toJSON(rtpCapabilities), this.peerConnectionFactory.getNativePeerConnectionFactory(), this.rtcConfiguration); } public void mediaProduce() { @@ -162,7 +159,7 @@ public class Room implements Closeable, RouterCallback { final String clientId = MapUtils.get(body, "clientId"); final Map status = MapUtils.get(body, "status"); final String name = MapUtils.get(status, "name"); - final RemoteClient remoteClient = new RemoteClient(name, clientId, this.handler, this.taoyao); + final RemoteClient remoteClient = new RemoteClient(name, clientId, this.taoyao, this.handler); final RemoteClient old = this.remoteClients.put(clientId, remoteClient); if(old != null) { // 关闭旧的资源 @@ -183,11 +180,18 @@ public class Room implements Closeable, RouterCallback { @Override public void close() { - Log.i(Room.class.getSimpleName(), "关闭房间:" + this.roomId); - this.localClient.close(); - this.remoteClients.values().forEach(v -> this.closeRemoteClient(v.clientId)); - this.mediaManager.closeClient(); - this.nativeCloseRoom(this.nativeRoomPointer); + synchronized (this) { + if(this.close) { + return; + } + Log.i(Room.class.getSimpleName(), "关闭房间:" + this.roomId); + super.close(); + this.nativeCloseRoom(this.nativeRoomPointer); + this.remoteClients.values().forEach(v -> this.closeRemoteClient(v.clientId)); + this.remoteClients.clear(); + this.localClient.close(); + this.mediaManager.closeClient(); + } } public void mediaConsumerClose(String consumerId) { @@ -198,6 +202,10 @@ public class Room implements Closeable, RouterCallback { )); } + public void mediaConsumerClose(Map body) { + + } + public void mediaConsumerPause(String consumerId) { this.taoyao.push(this.taoyao.buildMessage( "media::consumer::pause", @@ -206,6 +214,10 @@ public class Room implements Closeable, RouterCallback { )); } + public void mediaConsumerPause(Map body) { + + } + public void mediaConsumerResume(String consumerId) { this.taoyao.push(this.taoyao.buildMessage( "media::consumer::resume", @@ -214,6 +226,10 @@ public class Room implements Closeable, RouterCallback { )); } + public void mediaConsumerResume(Map body) { + + } + public void mediaProducerClose(String producerId) { this.taoyao.push(this.taoyao.buildMessage( "media::producer::close", @@ -222,6 +238,10 @@ public class Room implements Closeable, RouterCallback { )); } + public void mediaProducerClose(Map body) { + + } + public void mediaProducerPause(String producerId) { this.taoyao.push(this.taoyao.buildMessage( "media::producer::pause", @@ -230,6 +250,10 @@ public class Room implements Closeable, RouterCallback { )); } + public void mediaProducerPause(Map body) { + + } + public void mediaProducerResume(String producerId) { this.taoyao.push(this.taoyao.buildMessage( "media::producer::resume", @@ -238,6 +262,10 @@ public class Room implements Closeable, RouterCallback { )); } + public void mediaProducerResume(Map body) { + + } + @Override public void enterCallback(String rtpCapabilities, String sctpCapabilities) { this.taoyao.request(this.taoyao.buildMessage( @@ -247,7 +275,6 @@ public class Room implements Closeable, RouterCallback { "rtpCapabilities", rtpCapabilities, "sctpCapabilities", sctpCapabilities )); - this.enter = true; } @Override diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RoomClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RoomClient.java index deb4fc8..6b4a97d 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RoomClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/RoomClient.java @@ -14,8 +14,8 @@ import org.webrtc.MediaStreamTrack; */ public class RoomClient extends Client { - public RoomClient(String name, String clientId, Handler handler, ITaoyao taoyao) { - super(name, clientId, handler, taoyao); + public RoomClient(String name, String clientId, ITaoyao taoyao, Handler handler) { + super(name, clientId, taoyao, handler); } @Override diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/SessionClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/SessionClient.java index 22def61..109565f 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/SessionClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/SessionClient.java @@ -1,7 +1,6 @@ package com.acgist.taoyao.media.client; import android.os.Handler; -import android.se.omapi.Session; import android.util.Log; import com.acgist.taoyao.boot.model.Message; @@ -23,7 +22,6 @@ import org.webrtc.VideoTrack; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; /** * P2P终端 @@ -64,24 +62,31 @@ public class SessionClient extends Client { */ private PeerConnectionFactory peerConnectionFactory; - public SessionClient(String sessionId, String name, String clientId, Handler handler, ITaoyao taoyao) { - super(name, clientId, handler, taoyao); + public SessionClient(String sessionId, String name, String clientId, ITaoyao taoyao, Handler handler) { + super(name, clientId, taoyao, handler); this.sessionId = sessionId; } + @Override public void init() { - this.peerConnectionFactory = this.mediaManager.newClient(MediaManager.Type.BACK); - // STUN | TURN - final List iceServers = new ArrayList<>(); - // TODO:读取配置 - final PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer(); - iceServers.add(iceServer); - final PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers); - this.observer = this.observer(); - this.mediaStream = this.mediaManager.getMediaStream(); - this.sdpObserver = this.sdpObserver(); - this.peerConnection = this.peerConnectionFactory.createPeerConnection(configuration, this.observer); - this.peerConnection.addStream(this.mediaStream); + synchronized (this) { + if(this.init) { + return; + } + super.init(); + this.peerConnectionFactory = this.mediaManager.newClient(MediaManager.Type.BACK); + // STUN | TURN + final List iceServers = new ArrayList<>(); + // TODO:读取配置 + final PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer(); + iceServers.add(iceServer); + final PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers); + this.observer = this.observer(); + this.mediaStream = this.mediaManager.getMediaStream(); + this.sdpObserver = this.sdpObserver(); + this.peerConnection = this.peerConnectionFactory.createPeerConnection(configuration, this.observer); + this.peerConnection.addStream(this.mediaStream); + } } public void exchange(Message message, Map body) { @@ -153,6 +158,27 @@ public class SessionClient extends Client { } } + @Override + public void playAudio() { + super.playAudio(); + if(this.remoteMediaStream == null) { + return; + } + this.remoteMediaStream.audioTracks.forEach(v -> v.setEnabled(true)); + } + + @Override + public void pauseAudio() { + super.pauseAudio(); + this.remoteMediaStream.audioTracks.forEach(v -> v.setEnabled(false)); + } + + @Override + public void resumeAudio() { + super.resumeAudio(); + this.remoteMediaStream.audioTracks.forEach(v -> v.setEnabled(true)); + } + @Override public void playVideo() { super.playVideo(); @@ -168,21 +194,41 @@ public class SessionClient extends Client { } @Override - public void playAudio() { - super.playAudio(); - if(this.remoteMediaStream == null) { - return; - } - this.remoteMediaStream.audioTracks.forEach(v -> v.setEnabled(true)); + public void pauseVideo() { + super.pauseVideo(); + this.mediaStream.videoTracks.forEach(v -> v.setEnabled(false)); + } + + @Override + public void resumeVideo() { + super.resumeVideo(); + this.mediaStream.videoTracks.forEach(v -> v.setEnabled(true)); + } + + @Override + public void pause() { + super.pause(); + this.pauseAudio(); + this.pauseVideo(); + } + + @Override + public void resume() { + super.resume(); + this.resumeAudio(); + this.resumeVideo(); } @Override public void close() { - super.close(); - // 本地资源释放:不要直接关闭MediaStream(共享资源) - this.mediaManager.closeClient(); - // 远程资源释放 - this.remoteMediaStream.dispose(); + synchronized (this) { + if(this.close) { + return; + } + super.close(); + this.remoteMediaStream.dispose(); + this.mediaManager.closeClient(); + } } /** diff --git a/taoyao-client-web/src/components/Taoyao.js b/taoyao-client-web/src/components/Taoyao.js index 4dc7398..e879c1b 100644 --- a/taoyao-client-web/src/components/Taoyao.js +++ b/taoyao-client-web/src/components/Taoyao.js @@ -237,6 +237,8 @@ class Session { name; // 远程终端ID clientId; + // 本地媒体流 + localStream; // 本地音频 localAudioTrack; // 本地视频 @@ -258,6 +260,27 @@ class Session { this.clientId = clientId; } + async pause() { + this.localAudioTrack.enabled = false; + this.localVideoTrack.enabled = false; + this.localStream.active = false; + } + + async resume() { + this.localAudioTrack.enabled = true; + this.localVideoTrack.enabled = true; + this.localStream.active = true; + } + + async close() { + this.localStream.active = false; + this.localAudioTrack.stop(); + this.localVideoTrack.stop(); + this.remoteAudioTrack.stop(); + this.remoteVideoTrack.stop(); + this.peerConnection.close(); + } + } /** @@ -599,12 +622,18 @@ class Taoyao extends RemoteClient { case "session::call": me.defaultSessionCall(message); break; - case "session::close": - me.defaultSessionClose(message); - break; + case "session::close": + me.defaultSessionClose(message); + break; case "session::exchange": me.defaultSessionExchange(message); break; + case "session::pause": + me.defaultSessionPause(message); + break; + case "session::resume": + me.defaultSessionResume(message); + break; case "room::client::list": me.defaultRoomClientList(message); break; @@ -2122,6 +2151,7 @@ class Taoyao extends RemoteClient { this.sessionClients.set(sessionId, session); session.peerConnection = await me.buildPeerConnection(session, sessionId); const localStream = await me.getStream(); + session.localStream = localStream; session.localAudioTrack = localStream.getAudioTracks()[0]; session.localVideoTrack = localStream.getVideoTracks()[0]; // 相同Stream音视频同步 @@ -2139,10 +2169,24 @@ class Taoyao extends RemoteClient { }); } - async sessionClose() { + async sessionClose(sessionId) { + const me = this; + me.push(protocol.buildMessage("session::close", { + sessionId + })); } async defaultSessionClose(message) { + const me = this; + const { sessionId } = message.body; + const session = me.sessionClients.get(sessionId); + if(session) { + console.debug("会话关闭", sessionId); + session.close(); + me.sessionClients.delete(sessionId); + } else { + console.debug("关闭会话无效", sessionId); + } } async defaultSessionExchange(message) { @@ -2169,6 +2213,24 @@ class Taoyao extends RemoteClient { } } } + + async defaultSessionPause(message) { + const me = this; + const { sessionId } = message.body; + const session = me.sessionClients.get(sessionId); + if(session) { + session.pause(); + } + } + + async defaultSessionResume(message) { + const me = this; + const { sessionId } = message.body; + const session = me.sessionClients.get(sessionId); + if(session) { + session.resume(); + } + } async buildPeerConnection(session, sessionId) { const me = this; diff --git a/taoyao-client-web/vite.config.js b/taoyao-client-web/vite.config.js index 186a916..f88bd79 100644 --- a/taoyao-client-web/vite.config.js +++ b/taoyao-client-web/vite.config.js @@ -1,6 +1,6 @@ import fs from "node:fs"; -import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; +import { defineConfig } from "vite"; import { fileURLToPath, URL } from "node:url"; export default defineConfig({ diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/session/Session.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/session/Session.java index c6f9eaa..568fa94 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/session/Session.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/session/Session.java @@ -27,11 +27,26 @@ public class Session implements Closeable { * 接收者 */ private final Client target; + /** + * P2P会话管理器 + */ + private final SessionManager sessionManager; - public Session(String id, Client source, Client target) { + public Session(String id, Client source, Client target, SessionManager sessionManager) { this.id = id; this.source = source; this.target = target; + this.sessionManager = sessionManager; + } + + /** + * 推送消息 + * + * @param message 消息 + */ + public void push(Message message) { + this.source.push(message); + this.target.push(message); } /** @@ -40,7 +55,7 @@ public class Session implements Closeable { * @param clientId 当前终端ID * @param message 消息 */ - public void pushRemote(String clientId, Message message) { + public void pushOther(String clientId, Message message) { if(this.source.clientId().equals(clientId)) { this.target.push(message); } else { @@ -50,7 +65,7 @@ public class Session implements Closeable { @Override public void close() { - + this.sessionManager.remove(this.id); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/session/SessionManager.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/session/SessionManager.java index b0ba260..1e42059 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/session/SessionManager.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/party/session/SessionManager.java @@ -30,7 +30,7 @@ public class SessionManager { * @return 会话 */ public Session call(Client source, Client target) { - final Session session = new Session(this.idService.buildUuid(), source, target); + final Session session = new Session(this.idService.buildUuid(), source, target, this); this.sessions.put(session.getId(), session); return session; } @@ -44,4 +44,13 @@ public class SessionManager { return this.sessions.get(sessionId); } + /** + * @param sessionId 会话ID + * + * @return 会话 + */ + public Session remove(String sessionId) { + return this.sessions.remove(sessionId); + } + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java index fac12f8..59d1208 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java @@ -45,7 +45,10 @@ public class RoomLeaveProtocol extends ProtocolRoomAdapter implements Applicatio public void onApplicationEvent(RoomLeaveEvent event) { final Room room = event.getRoom(); final Client client = event.getClient(); - final Map body = Map.of(Constant.CLIENT_ID, client.clientId()); + final Map body = Map.of( + Constant.ROOM_ID, room.getRoomId(), + Constant.CLIENT_ID, client.clientId() + ); room.broadcast(client, this.build(body)); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCloseProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCloseProtocol.java index 27bdcb5..bc782b5 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCloseProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCloseProtocol.java @@ -21,7 +21,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolSessionAdapter; { } """, - flow = "终端->信令服务->终端" + flow = "终端->信令服务+)终端" ) public class SessionCloseProtocol extends ProtocolSessionAdapter { @@ -33,6 +33,8 @@ public class SessionCloseProtocol extends ProtocolSessionAdapter { @Override public void execute(String clientId, ClientType clientType, Session session, Client client, Message message, Map body) { + session.push(message); + session.close(); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionExchangeProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionExchangeProtocol.java index 5e2266b..f0c31e4 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionExchangeProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionExchangeProtocol.java @@ -34,7 +34,7 @@ public class SessionExchangeProtocol extends ProtocolSessionAdapter { @Override public void execute(String clientId, ClientType clientType, Session session, Client client, Message message, Map body) { - session.pushRemote(clientId, message); + session.pushOther(clientId, message); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionPauseProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionPauseProtocol.java index 5550cdc..6efcfca 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionPauseProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionPauseProtocol.java @@ -35,7 +35,7 @@ public class SessionPauseProtocol extends ProtocolSessionAdapter { @Override public void execute(String clientId, ClientType clientType, Session session, Client client, Message message, Map body) { - session.pushRemote(clientId, message); + session.pushOther(clientId, message); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionResumeProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionResumeProtocol.java index e434e06..558bd21 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionResumeProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionResumeProtocol.java @@ -35,7 +35,7 @@ public class SessionResumeProtocol extends ProtocolSessionAdapter { @Override public void execute(String clientId, ClientType clientType, Session session, Client client, Message message, Map body) { - session.pushRemote(clientId, message); + session.pushOther(clientId, message); } }