From aadd59a51ba9b4c52f0e06a6a7f1ad8f741fd784 Mon Sep 17 00:00:00 2001 From: acgist <289547414@qq.com> Date: Wed, 19 Apr 2023 20:43:59 +0800 Subject: [PATCH] =?UTF-8?q?[*]=20=E9=80=BB=E8=BE=91=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/acgist/taoyao/boot/model/Message.java | 64 ++-- .../acgist/taoyao/client/MainActivity.java | 4 +- .../acgist/taoyao/client/signal/Taoyao.java | 43 ++- .../client/src/main/res/values/settings.xml | 2 + .../src/main/cpp/webrtc/RouterCallback.cpp | 4 + .../com/acgist/taoyao/media/MediaManager.java | 312 +++++++++--------- .../acgist/taoyao/media/client/Client.java | 2 + .../taoyao/media/client/PhotographClient.java | 26 +- .../taoyao/media/client/RecordClient.java | 20 +- .../com/acgist/taoyao/media/client/Room.java | 142 ++++---- .../taoyao/media/client/SessionClient.java | 196 ++++++----- .../taoyao/media/config/WebrtcProperties.java | 57 +++- .../acgist/taoyao/media/signal/ITaoyao.java | 64 ++++ .../com/acgist/taoyao/boot/model/Message.java | 79 +++-- .../boot/model/MessageCodeException.java | 26 +- .../acgist/taoyao/boot/utils/ErrorUtils.java | 10 +- .../taoyao/signal/protocol/Protocol.java | 12 +- .../signal/protocol/ProtocolAdapter.java | 12 +- .../platform/PlatformErrorProtocol.java | 6 +- 19 files changed, 661 insertions(+), 420 deletions(-) diff --git a/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/Message.java b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/Message.java index 69c70b1..d18b2ad 100644 --- a/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/Message.java +++ b/taoyao-client-android/taoyao/boot/src/main/java/com/acgist/taoyao/boot/model/Message.java @@ -19,6 +19,11 @@ public class Message implements Cloneable, Serializable { private static final long serialVersionUID = 1L; + /** + * 成功标识 + */ + public static final String CODE_0000 = "0000"; + /** * 状态编码 */ @@ -37,20 +42,21 @@ public class Message implements Cloneable, Serializable { private Object body; /** - * @param code 状态编码 + * @param messageCode 状态编码 */ - public void setCode(MessageCode code) { - this.setCode(code, null); + public void setCode(MessageCode messageCode) { + this.setCode(messageCode, null); } /** - * @param code 状态编码 - * @param message 状态描述 + * @param messageCode 状态编码 + * @param message 状态描述 + * * @return this */ - public Message setCode(MessageCode code, String message) { - this.code = code.getCode(); - this.message = StringUtils.isEmpty(message) ? code.getMessage() : message; + public Message setCode(MessageCode messageCode, String message) { + this.code = messageCode.getCode(); + this.message = StringUtils.isEmpty(message) ? messageCode.getMessage() : message; return this; } @@ -63,6 +69,7 @@ public class Message implements Cloneable, Serializable { /** * @param body 消息主体 + * * @return 成功消息 */ public static final Message success(Object body) { @@ -80,20 +87,22 @@ public class Message implements Cloneable, Serializable { } /** - * @param code 状态编码 + * @param messageCode 状态编码 + * * @return 失败消息 */ - public static final Message fail(MessageCode code) { - return fail(code, null, null); + public static final Message fail(MessageCode messageCode) { + return fail(messageCode, null, null); } /** - * @param code 状态编码 - * @param body 消息主体 + * @param messageCode 状态编码 + * @param body 消息主体 + * * @return 失败消息 */ - public static final Message fail(MessageCode code, Object body) { - return fail(code, null, body); + public static final Message fail(MessageCode messageCode, Object body) { + return fail(messageCode, null, body); } /** @@ -107,6 +116,7 @@ public class Message implements Cloneable, Serializable { /** * @param message 状态描述 * @param body 消息主体 + * * @return 失败消息 */ public static final Message fail(String message, Object body) { @@ -114,23 +124,25 @@ public class Message implements Cloneable, Serializable { } /** - * @param code 状态编码 - * @param message 状态描述 + * @param messageCode 状态编码 + * @param message 状态描述 + * * @return 失败消息 */ - public static final Message fail(MessageCode code, String message) { - return fail(code, message, null); + public static final Message fail(MessageCode messageCode, String message) { + return fail(messageCode, message, null); } /** - * @param code 状态编码 - * @param message 状态描述 - * @param body 消息主体 + * @param messageCode 状态编码 + * @param message 状态描述 + * @param body 消息主体 + * * @return 失败消息 */ - public static final Message fail(MessageCode code, String message, Object body) { + public static final Message fail(MessageCode messageCode, String message, Object body) { final Message failMessage = new Message(); - failMessage.setCode(code == null ? MessageCode.CODE_9999 : code, message); + failMessage.setCode(messageCode == null ? MessageCode.CODE_9999 : messageCode, message); failMessage.body = body; return failMessage; } @@ -171,6 +183,10 @@ public class Message implements Cloneable, Serializable { this.body = body; } + public boolean isSuccess() { + return CODE_0000.equals(this.code); + } + public String getCode() { return this.code; } diff --git a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MainActivity.java b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MainActivity.java index 7632195..da73752 100644 --- a/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MainActivity.java +++ b/taoyao-client-android/taoyao/client/src/main/java/com/acgist/taoyao/client/MainActivity.java @@ -28,6 +28,7 @@ import com.acgist.taoyao.client.databinding.ActivityMainBinding; import com.acgist.taoyao.client.signal.Taoyao; import com.acgist.taoyao.media.MediaManager; import com.acgist.taoyao.media.TransportType; +import com.acgist.taoyao.media.VideoSourceType; import com.acgist.taoyao.media.config.Config; import java.io.Serializable; @@ -127,7 +128,8 @@ public class MainActivity extends AppCompatActivity implements Serializable { resources.getInteger(R.integer.iFrameInterval), resources.getString(R.string.storagePathImage), resources.getString(R.string.storagePathVideo), - TransportType.valueOf(resources.getString(R.string.transportType)) + TransportType.valueOf(resources.getString(R.string.transportType)), + VideoSourceType.valueOf(resources.getString(R.string.videoSourceType)) ); final Display display = this.getWindow().getContext().getDisplay(); while (Display.STATE_ON != display.getState() && waitCount++ < 10) { 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 6c1d72d..e9f757b 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 @@ -13,6 +13,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.PowerManager; import android.os.Process; +import android.se.omapi.Session; import android.util.Log; import com.acgist.taoyao.boot.model.Header; @@ -405,6 +406,7 @@ public final class Taoyao implements ITaoyao { * * @return 信令响应消息 */ + @Override public Message request(Message request) { final Header header = request.getHeader(); final Long id = header.getId(); @@ -714,8 +716,8 @@ public final class Taoyao implements ITaoyao { final Room room = this.rooms.computeIfAbsent( roomId, key -> new Room( - this.name, this.clientId, - key, password, + this.name, key, + this.clientId, password, this, this.mainHandler, resources.getBoolean(R.bool.preview), resources.getBoolean(R.bool.playAudio), @@ -726,7 +728,8 @@ public final class Taoyao implements ITaoyao { resources.getBoolean(R.bool.audioProduce), resources.getBoolean(R.bool.dataProduce), resources.getBoolean(R.bool.videoProduce), - this.mediaManager.getMediaProperties() + this.mediaManager.getMediaProperties(), + this.mediaManager.getWebrtcProperties() ) ); final boolean success = room.enter(); @@ -772,6 +775,37 @@ public final class Taoyao implements ITaoyao { room.closeRemoteClient(clientId); } + private void sessionCall(String clientId) { + this.requestFuture( + this.buildMessage( + "session::call", + "clientId", clientId + ), + response -> { + final Map body = response.body(); + final String name = MapUtils.get(body, "name"); + final String sessionId = MapUtils.get(body, "sessionId"); + final Resources resources = this.context.getResources(); + final SessionClient sessionClient = new SessionClient( + sessionId, name, MapUtils.get(body, "clientId"), this, this.mainHandler, + resources.getBoolean(R.bool.preview), + resources.getBoolean(R.bool.playAudio), + resources.getBoolean(R.bool.playVideo), + resources.getBoolean(R.bool.dataConsume), + resources.getBoolean(R.bool.audioConsume), + resources.getBoolean(R.bool.videoConsume), + resources.getBoolean(R.bool.audioProduce), + resources.getBoolean(R.bool.dataProduce), + resources.getBoolean(R.bool.videoProduce), + this.mediaManager.getMediaProperties(), + this.mediaManager.getWebrtcProperties() + ); + sessionClient.init(); + sessionClient.offer(); + } + ); + } + private void sessionCall(Message message, Map body) { final String name = MapUtils.get(body, "name"); final String clientId = MapUtils.get(body, "clientId"); @@ -788,7 +822,8 @@ public final class Taoyao implements ITaoyao { resources.getBoolean(R.bool.audioProduce), resources.getBoolean(R.bool.dataProduce), resources.getBoolean(R.bool.videoProduce), - this.mediaManager.getMediaProperties() + this.mediaManager.getMediaProperties(), + this.mediaManager.getWebrtcProperties() ); this.sessionClients.put(sessionId, sessionClient); sessionClient.init(); diff --git a/taoyao-client-android/taoyao/client/src/main/res/values/settings.xml b/taoyao-client-android/taoyao/client/src/main/res/values/settings.xml index 6775e9b..c80c13a 100644 --- a/taoyao-client-android/taoyao/client/src/main/res/values/settings.xml +++ b/taoyao-client-android/taoyao/client/src/main/res/values/settings.xml @@ -21,6 +21,8 @@ /taoyao/video WEBRTC + + BACK false diff --git a/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/RouterCallback.cpp b/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/RouterCallback.cpp index b6ac323..87b44c8 100644 --- a/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/RouterCallback.cpp +++ b/taoyao-client-android/taoyao/media/src/main/cpp/webrtc/RouterCallback.cpp @@ -82,6 +82,10 @@ namespace acgist { jTransportId, jRtpParameters ); + if(jResult == nullptr) { + // TODO:处理空指针 + return ""; + } const char* result = env->GetStringUTFChars(jResult, nullptr); env->DeleteLocalRef(jResult); env->ReleaseStringUTFChars(jResult, result); 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 1fcaab8..607088d 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 @@ -43,6 +43,8 @@ import org.webrtc.VideoTrack; import org.webrtc.audio.JavaAudioDeviceModule; import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 媒体来源管理器 @@ -91,14 +93,14 @@ public final class MediaManager { * 关键帧频率 */ private int iFrameInterval; - /** - * 视频来源类型 - */ - private VideoSourceType videoSourceType; /** * 传输通道类型 */ private TransportType transportType; + /** + * 视频来源类型 + */ + private VideoSourceType videoSourceType; /** * 信令 */ @@ -132,17 +134,9 @@ public final class MediaManager { */ private EglBase eglBase; /** - * EGL共享上下文 + * EGL上下文 */ - private EglBase.Context shareEglContext; - /** - * 媒体流:声音、视频 - */ - private MediaStream mediaStream; - /** - * 音频Track - */ - private AudioTrack audioTrack; + private EglBase.Context eglContext; /** * 音频来源 */ @@ -151,18 +145,10 @@ public final class MediaManager { * 视频捕获 */ private VideoCapturer videoCapturer; - /** - * 主码流视频Track - */ - private VideoTrack mainVideoTrack; /** * 主码流视频来源 */ private VideoSource mainVideoSource; - /** - * 次码流视频Track - */ - private VideoTrack shareVideoTrack; /** * 次码流视频来源 */ @@ -181,31 +167,28 @@ public final class MediaManager { private PeerConnectionFactory peerConnectionFactory; static { - // 采样 -// WebRtcAudioUtils.setDefaultSampleRateHz(); - // 噪声消除 +// // 设置采样 +// WebRtcAudioUtils.setDefaultSampleRateHz(48000); +// // 噪声消除 // WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true); - // 回声消除 +// // 回声消除 // WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true); - // 自动增益 +// // 自动增益 // WebRtcAudioUtils.setWebRtcBasedAutomaticGainControl(true); - // OpenSL ES -// WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true); - final MediaCodecList mediaCodecList = new MediaCodecList(-1); - for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) { -// if (mediaCodecInfo.isEncoder()) { - final String[] supportedTypes = mediaCodecInfo.getSupportedTypes(); - Log.d(MediaManager.class.getSimpleName(), "编码器名称:" + mediaCodecInfo.getName()); - Log.d(MediaManager.class.getSimpleName(), "编码器类型:" + String.join(" , ", supportedTypes)); - for (String supportType : supportedTypes) { - final MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(supportType); - Log.d(MediaManager.class.getSimpleName(), "编码器支持的文件格式:" + codecCapabilities.getMimeType()); -// MediaCodecInfo.CodecCapabilities.COLOR_* -// final int[] colorFormats = codecCapabilities.colorFormats; -// Log.d(MediaManager.class.getSimpleName(), "编码器支持的色彩格式:" + IntStream.of(colorFormats).boxed().map(String::valueOf).collect(Collectors.joining(" , "))); - } -// } - } +// // 支持的编码器 +// final MediaCodecList mediaCodecList = new MediaCodecList(-1); +// for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) { +// final String[] supportedTypes = mediaCodecInfo.getSupportedTypes(); +// Log.d(MediaManager.class.getSimpleName(), "编码器名称:" + mediaCodecInfo.getName()); +// Log.d(MediaManager.class.getSimpleName(), "编码器类型:" + String.join(", ", supportedTypes)); +// for (String supportType : supportedTypes) { +// final MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(supportType); +// Log.d(MediaManager.class.getSimpleName(), "编码器支持的文件格式:" + codecCapabilities.getMimeType()); +// // MediaCodecInfo.CodecCapabilities.COLOR_* +// final int[] colorFormats = codecCapabilities.colorFormats; +// Log.d(MediaManager.class.getSimpleName(), "编码器支持的色彩格式:" + IntStream.of(colorFormats).boxed().map(String::valueOf).collect(Collectors.joining(", "))); +// } +// } } private MediaManager() { @@ -231,7 +214,7 @@ public final class MediaManager { int imageQuantity, String audioQuantity, String videoQuantity, int channelCount, int iFrameInterval, String imagePath, String videoPath, - TransportType transportType + TransportType transportType, VideoSourceType videoSourceType ) { this.mainHandler = mainHandler; this.context = context; @@ -243,6 +226,7 @@ public final class MediaManager { this.imagePath = imagePath; this.videoPath = videoPath; this.transportType = transportType; + this.videoSourceType = videoSourceType; } /** @@ -255,11 +239,9 @@ public final class MediaManager { /** * 新建终端 * - * @param videoSourceType 视频类型 - * * @return PeerConnectionFactory PeerConnectionFactory */ - public PeerConnectionFactory newClient(VideoSourceType videoSourceType) { + public PeerConnectionFactory newClient() { synchronized (this) { while(!this.available()) { Log.i(MediaManager.class.getSimpleName(), "等待配置"); @@ -273,7 +255,7 @@ public final class MediaManager { Log.i(MediaManager.class.getSimpleName(), "加载PeerConnectionFactory"); this.initPeerConnectionFactory(); this.nativeInit(); - this.initMedia(videoSourceType); + this.initMedia(); this.startVideoCapture(); } this.clientCount++; @@ -293,9 +275,6 @@ public final class MediaManager { Log.i(MediaManager.class.getSimpleName(), "释放PeerConnectionFactory"); // 注意顺序 this.stopVideoCapture(); - this.closeAudio(); - this.closeMainVideo(); - this.closeShareVideo(); this.closeMedia(); this.nativeStop(); this.stopPeerConnectionFactory(); @@ -312,6 +291,10 @@ public final class MediaManager { return this.mediaProperties; } + public WebrtcProperties getWebrtcProperties() { + return this.webrtcProperties; + } + private void initPeerConnectionFactory() { PeerConnectionFactory.initialize( PeerConnectionFactory.InitializationOptions.builder(this.context) @@ -321,24 +304,26 @@ public final class MediaManager { // .setEnableInternalTracer(true) .createInitializationOptions() ); + this.eglBase = EglBase.create(); + this.eglContext = this.eglBase.getEglBaseContext(); } private void stopPeerConnectionFactory() { + if (this.eglBase != null) { + this.eglBase.release(); + this.eglBase = null; + this.eglContext = null; + } PeerConnectionFactory.shutdownInternalTracer(); } /** * 加载媒体 - * - * @param videoSourceType 视频来源类型 */ - private void initMedia(VideoSourceType videoSourceType) { - Log.i(MediaManager.class.getSimpleName(), "加载媒体:" + videoSourceType); - this.eglBase = EglBase.create(); - this.shareEglContext = this.eglBase.getEglBaseContext(); - this.videoSourceType = videoSourceType; - final VideoDecoderFactory videoDecoderFactory = new DefaultVideoDecoderFactory(this.shareEglContext); - final VideoEncoderFactory videoEncoderFactory = new DefaultVideoEncoderFactory(this.shareEglContext, true, true); + private void initMedia() { + Log.i(MediaManager.class.getSimpleName(), "加载媒体:" + this.videoSourceType); + final VideoDecoderFactory videoDecoderFactory = new DefaultVideoDecoderFactory(this.eglContext); + final VideoEncoderFactory videoEncoderFactory = new DefaultVideoEncoderFactory(this.eglContext, true, true); final JavaAudioDeviceModule javaAudioDeviceModule = this.javaAudioDeviceModule(); this.peerConnectionFactory = PeerConnectionFactory.builder() .setVideoDecoderFactory(videoDecoderFactory) @@ -348,7 +333,6 @@ public final class MediaManager { // .setAudioEncoderFactoryFactory(new BuiltinAudioEncoderFactoryFactory()) // .setAudioDecoderFactoryFactory(new BuiltinAudioDecoderFactoryFactory()) .createPeerConnectionFactory(); - this.mediaStream = this.peerConnectionFactory.createLocalMediaStream("Taoyao"); Arrays.stream(videoEncoderFactory.getSupportedCodecs()).forEach(v -> { Log.d(MediaManager.class.getSimpleName(), "支持的视频解码器:" + v.name); }); @@ -414,27 +398,8 @@ public final class MediaManager { */ private void initAudio() { // 加载音频 - final MediaConstraints mediaConstraints = new MediaConstraints(); - // 高音过滤 - mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAudioMirroring", "false")); - // 自动增益:AGC - mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl2", "true")); - // 回声消除:AEC - mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation2", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googDAEchoCancellation", "true")); - // 噪音处理:NS - mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression2", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true")); + final MediaConstraints mediaConstraints = this.buildMediaConstraints(); this.audioSource = this.peerConnectionFactory.createAudioSource(mediaConstraints); - this.audioTrack = this.peerConnectionFactory.createAudioTrack("TaoyaoA0", this.audioSource); - this.audioTrack.setVolume(Config.DEFAULT_VOLUME); - this.audioTrack.setEnabled(true); - this.mediaStream.addTrack(this.audioTrack); - Log.i(MediaManager.class.getSimpleName(), "加载音频:" + this.audioTrack.id()); } /** @@ -464,9 +429,9 @@ public final class MediaManager { final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context); final String[] names = cameraEnumerator.getDeviceNames(); for (String name : names) { - if (this.videoSourceType == VideoSourceType.FRONT && cameraEnumerator.isFrontFacing(name)) { + if (this.videoSourceType == VideoSourceType.BACK && cameraEnumerator.isBackFacing(name)) { this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler()); - } else if (this.videoSourceType == VideoSourceType.BACK && cameraEnumerator.isBackFacing(name)) { + } else if (this.videoSourceType == VideoSourceType.FRONT && cameraEnumerator.isFrontFacing(name)) { this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler()); } else { // 忽略其他摄像头 @@ -496,22 +461,14 @@ public final class MediaManager { */ private void initVideoTrack() { // 加载视频 - this.surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", this.shareEglContext); + this.surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", this.eglContext); // this.surfaceTextureHelper.setTextureSize(); // this.surfaceTextureHelper.setFrameRotation(); // 主码流 this.mainVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast()); - this.mainVideoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoV0", this.mainVideoSource); - this.mainVideoTrack.setEnabled(true); - Log.i(MediaManager.class.getSimpleName(), "加载视频(主码流):" + this.mainVideoTrack.id()); // 次码流 this.shareVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast()); this.shareVideoSource.adaptOutputFormat(this.mediaVideoProperties.getWidth(), this.mediaVideoProperties.getHeight(), this.mediaVideoProperties.getFrameRate()); - this.shareVideoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoV1", this.shareVideoSource); - this.shareVideoTrack.setEnabled(true); - Log.i(MediaManager.class.getSimpleName(), "加载视频(次码流):" + this.shareVideoTrack.id()); - // 共享次码流 - this.mediaStream.addTrack(this.shareVideoTrack); // 视频捕获 this.videoCapturer.initialize(this.surfaceTextureHelper, this.context, new VideoCapturerObserver()); // 次码流视频处理 @@ -559,10 +516,10 @@ public final class MediaManager { if (this.videoSourceType == videoSourceType) { return; } - Log.i(MediaManager.class.getSimpleName(), "设置视频来源:" + videoSourceType); - final VideoSourceType old = this.videoSourceType; + final VideoSourceType oldVideoSourceType = this.videoSourceType; this.videoSourceType = videoSourceType; - if (old.isCamera() && videoSourceType.isCamera()) { + Log.i(MediaManager.class.getSimpleName(), "设置视频来源:" + this.videoSourceType); + if (videoSourceType.isCamera() && oldVideoSourceType.isCamera()) { // TODO:测试是否需要完全重置 final CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) this.videoCapturer; cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() { @@ -578,8 +535,57 @@ public final class MediaManager { } } - public MediaStream getMediaStream() { - return this.mediaStream; + public MediaStream buildLocalMediaStream(boolean audioProduce, boolean videoProduce) { + final long id = Thread.currentThread().getId(); + final MediaStream mediaStream = this.peerConnectionFactory.createLocalMediaStream("TaoyaoM" + id); + Log.i(MediaManager.class.getSimpleName(), "加载媒体:" + mediaStream.getId()); + if(audioProduce) { + final AudioTrack audioTrack = this.peerConnectionFactory.createAudioTrack("TaoyaoA" + id, this.audioSource); + audioTrack.setVolume(Config.DEFAULT_VOLUME); + audioTrack.setEnabled(true); + mediaStream.addTrack(audioTrack); + Log.i(MediaManager.class.getSimpleName(), "加载音频:" + audioTrack.id()); + } + if(videoProduce) { + final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoV" + id, this.shareVideoSource); + videoTrack.setEnabled(true); + Log.i(MediaManager.class.getSimpleName(), "加载视频(次码流):" + videoTrack.id()); + mediaStream.addTrack(videoTrack); + } + return mediaStream; + } + + public MediaConstraints buildMediaConstraints() { + final MediaConstraints mediaConstraints = new MediaConstraints(); + // ================ PC ================ // +// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")); +// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")); +// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true")); + // ================ Audio ================ // + // 高音过滤 + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true")); +// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAudioMirroring", "false")); + // 自动增益:AGC + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true")); +// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl2", "true")); + // 回声消除:AEC + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true")); +// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation2", "true")); +// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googDAEchoCancellation", "true")); + // 噪音处理:NS + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true")); +// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression2", "true")); +// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true")); + // ================ Video ================ // + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minWidth", this.mediaProperties.getMinWidth().toString())); + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxWidth", this.mediaProperties.getMaxWidth().toString())); + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minHeight", this.mediaProperties.getMinHeight().toString())); + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxHeight", this.mediaProperties.getMaxHeight().toString())); + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minFrameRate", this.mediaProperties.getMinFrameRate().toString())); + mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxFrameRate", this.mediaProperties.getMaxFrameRate().toString())); + // ================ SCTP ================ // +// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true")); + return mediaConstraints; } private void startVideoCapture() { @@ -595,7 +601,7 @@ public final class MediaManager { return; } try { - videoCapturer.stopCapture(); + this.videoCapturer.stopCapture(); } catch (InterruptedException e) { Log.e(MediaManager.class.getSimpleName(), "关闭视频捕获异常", e); } @@ -607,12 +613,12 @@ public final class MediaManager { final PhotographClient photographClient = new PhotographClient(this.imageQuantity, this.imagePath); if(this.clientCount <= 0) { final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity); - photographClient.photograph(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate(), VideoSourceType.BACK, this.context); + photographClient.photograph(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate(), this.videoSourceType, this.context); filepath = photographClient.waitForPhotograph(); } else { - this.mainVideoTrack.addSink(photographClient); + final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoVP", this.mainVideoSource); + photographClient.photograph(videoTrack); filepath = photographClient.waitForPhotograph(); - this.mainVideoTrack.removeSink(photographClient); } return filepath; } @@ -632,7 +638,8 @@ public final class MediaManager { this.videoPath, this.taoyao, this.mainHandler ); this.recordClient.start(); - this.mainVideoTrack.addSink(this.recordClient); + final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoVR", this.mainVideoSource); + this.recordClient.source(videoTrack); return this.recordClient; } } @@ -642,7 +649,6 @@ public final class MediaManager { if(this.recordClient == null) { return; } else { - this.mainVideoTrack.removeSink(this.recordClient); this.recordClient.close(); this.recordClient = null; } @@ -667,7 +673,7 @@ public final class MediaManager { surfaceViewRenderer.setEnableHardwareScaler(true); // surfaceViewRenderer.setFpsReduction(); // 加载OpenSL ES - surfaceViewRenderer.init(this.shareEglContext, null); + surfaceViewRenderer.init(this.eglContext, null); // 添加播放 videoTrack.addSink(surfaceViewRenderer); }); @@ -679,54 +685,18 @@ public final class MediaManager { return surfaceViewRenderer; } - /** - * 关闭声音 - */ - private void closeAudio() { -// if(this.audioTrack != null) { -// this.audioTrack.dispose(); -// this.audioTrack = null; -// } + private void closeMedia() { if(this.audioSource != null) { this.audioSource.dispose(); this.audioSource = null; } - } - - /** - * 关闭视频 - */ - private void closeShareVideo() { -// if(this.videoTrack != null) { -// this.videoTrack.dispose(); -// this.videoTrack = null; -// } - if(this.shareVideoSource != null) { - this.shareVideoSource.dispose(); - this.shareVideoSource = null; - } - } - - private void closeMainVideo() { - if(this.mainVideoTrack != null) { - this.mainVideoTrack.dispose(); - this.mainVideoTrack = null; - } if(this.mainVideoSource != null) { this.mainVideoSource.dispose(); this.mainVideoSource = null; } - } - - private void closeMedia() { - if (this.eglBase != null) { - this.eglBase.release(); - this.eglBase = null; - this.shareEglContext = null; - } - if (this.mediaStream != null) { - this.mediaStream.dispose(); - this.mediaStream = null; + if(this.shareVideoSource != null) { + this.shareVideoSource.dispose(); + this.shareVideoSource = null; } if (this.videoCapturer != null) { this.videoCapturer.dispose(); @@ -742,6 +712,11 @@ public final class MediaManager { } } + /** + * 视频捕获器观察者 + * + * @author acgist + */ private class VideoCapturerObserver implements CapturerObserver { private CapturerObserver mainObserver; @@ -754,19 +729,21 @@ public final class MediaManager { @Override public void onCapturerStarted(boolean status) { + Log.i(MediaManager.class.getSimpleName(), "结束视频捕获"); this.mainObserver.onCapturerStarted(status); this.shareObserver.onCapturerStarted(status); } @Override public void onCapturerStopped() { + Log.i(MediaManager.class.getSimpleName(), "开始视频捕获"); this.mainObserver.onCapturerStopped(); this.shareObserver.onCapturerStopped(); } @Override public void onFrameCaptured(VideoFrame videoFrame) { - // 注意:VideoFrame必须释放,如果分开线程需要调用retain和release方法,否则不要调用。 + // 注意:VideoFrame必须释放,多线程环境需要调用retain和release方法。 this.mainObserver.onFrameCaptured(videoFrame); this.shareObserver.onFrameCaptured(videoFrame); } @@ -774,42 +751,47 @@ public final class MediaManager { } /** - * 摄像头事件 + * 摄像头事件处理器 * * @author acgist */ private class MediaCameraEventsHandler implements CameraVideoCapturer.CameraEventsHandler { - @Override - public void onCameraError(String message) { - } - - @Override - public void onCameraDisconnected() { - } - - @Override - public void onCameraFreezed(String message) { - } - - @Override - public void onCameraOpening(String message) { - Log.i(MediaManager.class.getSimpleName(), "开始视频捕获"); - } - @Override public void onFirstFrameAvailable() { } + @Override + public void onCameraOpening(String message) { + Log.i(MediaManager.class.getSimpleName(), "打开摄像头"); + } + + @Override + public void onCameraFreezed(String message) { + Log.i(MediaManager.class.getSimpleName(), "释放摄像头:" + message); + } + @Override public void onCameraClosed() { - Log.i(MediaManager.class.getSimpleName(), "停止视频捕获"); + Log.i(MediaManager.class.getSimpleName(), "关闭摄像头"); + } + + @Override + public void onCameraDisconnected() { + Log.i(MediaManager.class.getSimpleName(), "断开摄像头"); + } + + @Override + public void onCameraError(String message) { + Log.e(MediaManager.class.getSimpleName(), "摄像头异常:" + message); } } /** * 屏幕录制回调 + * + * @author acgist */ private class ScreenCallback extends MediaProjection.Callback { 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 35d02ff..3f99ec8 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 @@ -77,10 +77,12 @@ public abstract class Client extends CloseableClient { super.close(); Log.i(this.getClass().getSimpleName(), "关闭终端:" + this.clientId); if(this.surfaceViewRenderer != null) { + // 移除 final Message message = new Message(); message.obj = surfaceViewRenderer; message.what = Config.WHAT_REMOVE_VIDEO; this.mainHandler.sendMessage(message); + // 销毁 this.surfaceViewRenderer.release(); this.surfaceViewRenderer = null; } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/PhotographClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/PhotographClient.java index e5a8f7f..d0ad0d7 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/PhotographClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/PhotographClient.java @@ -29,6 +29,7 @@ import com.acgist.taoyao.media.VideoSourceType; import org.webrtc.VideoFrame; import org.webrtc.VideoSink; +import org.webrtc.VideoTrack; import java.io.ByteArrayOutputStream; import java.io.File; @@ -53,6 +54,7 @@ public class PhotographClient implements VideoSink { private volatile boolean done; private volatile boolean finish; private Surface surface; + private VideoTrack videoTrack; private ImageReader imageReader; private CameraDevice cameraDevice; private HandlerThread handlerThread; @@ -84,12 +86,20 @@ public class PhotographClient implements VideoSink { } catch (InterruptedException e) { Log.e(PhotographClient.class.getSimpleName(), "拍照等待异常", e); } finally { + this.closeVideoTrack(); + this.closeCamera(); this.handlerThread.quitSafely(); } } return this.filepath; } + public void photograph(VideoTrack videoTrack) { + videoTrack.setEnabled(true); + videoTrack.addSink(this); + this.videoTrack = videoTrack; + } + @Override public void onFrame(VideoFrame videoFrame) { if(this.done) { @@ -157,8 +167,16 @@ public class PhotographClient implements VideoSink { return new YuvImage(nv21, ImageFormat.NV21, width, height, null); } + private void closeVideoTrack() { + if(this.videoTrack != null) { + this.videoTrack.removeSink(this); + this.videoTrack.dispose(); + this.videoTrack = null; + } + } + @SuppressLint("MissingPermission") - public void photograph(int width, int height, int fps, VideoSourceType type, Context context) { + public void photograph(int width, int height, int fps, VideoSourceType videoSourceType, Context context) { this.handlerThread = new HandlerThread("PhotographThread"); this.handlerThread.start(); final Handler handler = new Handler(this.handlerThread.getLooper()); @@ -171,13 +189,14 @@ public class PhotographClient implements VideoSink { final String[] cameraIdList = cameraManager.getCameraIdList(); for (String id : cameraIdList) { final CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id); - if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK && type == VideoSourceType.BACK) { + if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK && videoSourceType == VideoSourceType.BACK) { cameraId = id; break; - } else if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT && type == VideoSourceType.FRONT) { + } else if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT && videoSourceType == VideoSourceType.FRONT) { cameraId = id; break; } else { + // TODO:截屏 } } cameraManager.openCamera(cameraId, this.cameraDeviceStateCallback, null); @@ -265,7 +284,6 @@ public class PhotographClient implements VideoSink { Log.e(PhotographClient.class.getSimpleName(), "拍照异常", e); } finally { image.close(); - PhotographClient.this.closeCamera(); PhotographClient.this.notifyWait(); } } 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 8507298..c4fd242 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 @@ -1,7 +1,5 @@ package com.acgist.taoyao.media.client; -import android.media.AudioFormat; -import android.media.AudioManager; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; @@ -9,7 +7,6 @@ import android.media.MediaMuxer; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; -import android.provider.MediaStore; import android.util.Log; import com.acgist.taoyao.boot.utils.DateUtils; @@ -19,6 +16,7 @@ import com.acgist.taoyao.media.signal.ITaoyao; import org.webrtc.VideoFrame; import org.webrtc.VideoSink; +import org.webrtc.VideoTrack; import org.webrtc.YuvHelper; import org.webrtc.audio.JavaAudioDeviceModule; @@ -27,9 +25,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Paths; import java.time.LocalDateTime; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; /** * 录像机 @@ -123,6 +118,7 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo * 媒体合成器 */ private MediaMuxer mediaMuxer; + private VideoTrack videoTrack; public RecordClient( int audioBitRate, int sampleRate, int channelCount, @@ -150,7 +146,7 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo } Log.i(RecordClient.class.getSimpleName(), "录制视频文件:" + this.filepath); super.init(); - this.mediaManager.newClient(VideoSourceType.BACK); + this.mediaManager.newClient(); if ( this.audioThread == null || !this.audioThread.isAlive() || this.videoThread == null || !this.videoThread.isAlive() @@ -365,6 +361,12 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo } } + public void source(VideoTrack videoTrack) { + this.videoTrack = videoTrack; + this.videoTrack.setEnabled(true); + this.videoTrack.addSink(this); + } + @Override public void close() { synchronized (this) { @@ -373,7 +375,9 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo } super.close(); Log.i(RecordClient.class.getSimpleName(), "结束录制:" + this.filepath); - if (audioThread != null) { + this.videoTrack.removeSink(this); + this.videoTrack.dispose(); + if (this.audioThread != null) { this.audioThread.quitSafely(); } if (this.videoThread != null) { 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 4742ebe..962d6c3 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 @@ -13,6 +13,7 @@ import com.acgist.taoyao.media.config.Config; import com.acgist.taoyao.media.config.MediaAudioProperties; import com.acgist.taoyao.media.config.MediaProperties; import com.acgist.taoyao.media.config.MediaVideoProperties; +import com.acgist.taoyao.media.config.WebrtcProperties; import com.acgist.taoyao.media.signal.ITaoyao; import org.webrtc.AudioTrack; @@ -35,8 +36,8 @@ import java.util.concurrent.ConcurrentHashMap; public class Room extends CloseableClient implements RouterCallback { private final String name; - private final String clientId; private final String roomId; + private final String clientId; private final String password; private final boolean preview; private final boolean playAudio; @@ -48,6 +49,7 @@ public class Room extends CloseableClient implements RouterCallback { private final boolean audioProduce; private final boolean videoProduce; private final MediaProperties mediaProperties; + private final WebrtcProperties webrtcProperties; private final Map remoteClients; private final long nativeRoomPointer; private LocalClient localClient; @@ -58,12 +60,12 @@ public class Room extends CloseableClient implements RouterCallback { /** * - * @param name - * @param clientId - * @param roomId - * @param password - * @param taoyao - * @param mainHandler + * @param name 房间名称 + * @param roomId 房间ID + * @param clientId 当前终端ID + * @param password 房间密码 + * @param taoyao 信令 + * @param mainHandler MainHandler * @param preview 是否预览视频 * @param playAudio 是否播放音频 * @param playVideo 是否播放视频 @@ -73,20 +75,22 @@ public class Room extends CloseableClient implements RouterCallback { * @param dataProduce 是否生产数据 * @param audioProduce 是否生产音频 * @param videoProduce 是否生产视频 + * @param mediaProperties 媒体配置 + * @param webrtcProperties WebRTC配置 */ public Room( - String name, String clientId, - String roomId, String password, + String name, String roomId, + String clientId, String password, ITaoyao taoyao, Handler mainHandler, boolean preview, boolean playAudio, boolean playVideo, boolean dataConsume, boolean audioConsume, boolean videoConsume, boolean dataProduce, boolean audioProduce, boolean videoProduce, - MediaProperties mediaProperties + MediaProperties mediaProperties, WebrtcProperties webrtcProperties ) { super(taoyao, mainHandler); this.name = name; - this.clientId = clientId; this.roomId = roomId; + this.clientId = clientId; this.password = password; this.preview = preview; this.playAudio = playAudio; @@ -98,6 +102,7 @@ public class Room extends CloseableClient implements RouterCallback { this.audioProduce = audioProduce; this.videoProduce = videoProduce; this.mediaProperties = mediaProperties; + this.webrtcProperties = webrtcProperties; this.remoteClients = new ConcurrentHashMap<>(); this.nativeRoomPointer = this.nativeNewRoom(roomId, this); } @@ -108,23 +113,26 @@ public class Room extends CloseableClient implements RouterCallback { return true; } super.init(); - this.peerConnectionFactory = this.mediaManager.newClient(VideoSourceType.BACK); + this.peerConnectionFactory = this.mediaManager.newClient(); this.localClient = new LocalClient(this.name, this.clientId, this.taoyao, this.mainHandler); - this.localClient.setMediaStream(this.mediaManager.getMediaStream()); + this.localClient.setMediaStream(this.mediaManager.buildLocalMediaStream(this.audioProduce, this.videoProduce)); // 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 List iceServers = this.webrtcProperties.getIceServers(); 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.nativeEnterRoom(this.nativeRoomPointer, JSONUtils.toJSON(rtpCapabilities), this.peerConnectionFactory.getNativePeerConnectionFactory(), this.rtcConfiguration); - return true; + return this.taoyao.requestFuture( + this.taoyao.buildMessage("media::router::rtp::capabilities", "roomId", this.roomId), + response -> { + final Object rtpCapabilities = MapUtils.get(response.body(), "rtpCapabilities"); + this.nativeEnterRoom(this.nativeRoomPointer, JSONUtils.toJSON(rtpCapabilities), this.peerConnectionFactory.getNativePeerConnectionFactory(), this.rtcConfiguration); + return true; + }, + response -> { + this.close(); + return false; + } + ); } } @@ -146,35 +154,41 @@ public class Room extends CloseableClient implements RouterCallback { } private void createSendTransport() { - final Message response = this.taoyao.request(this.taoyao.buildMessage( - "media::transport::webrtc::create", - "roomId", this.roomId, - "forceTcp", false, - "producing", true, - "consuming", false, - "sctpCapabilities", this.dataProduce ? this.sctpCapabilities : null - )); - if (response == null) { - Log.w(Room.class.getSimpleName(), "创建发送通道失败"); - return; - } - this.nativeCreateSendTransport(this.nativeRoomPointer, JSONUtils.toJSON(response.body())); + this.taoyao.requestFuture( + this.taoyao.buildMessage( + "media::transport::webrtc::create", + "roomId", this.roomId, + "forceTcp", false, + "producing", true, + "consuming", false, + "sctpCapabilities", this.dataProduce ? this.sctpCapabilities : null + ), + response -> { + this.nativeCreateSendTransport(this.nativeRoomPointer, JSONUtils.toJSON(response.body())); + }, + response -> { + Log.w(Room.class.getSimpleName(), "创建发送通道失败"); + } + ); } private void createRecvTransport() { - final Message response = this.taoyao.request(this.taoyao.buildMessage( - "media::transport::webrtc::create", - "roomId", this.roomId, - "forceTcp", false, - "producing", false, - "consuming", true, - "sctpCapabilities", this.dataProduce ? this.sctpCapabilities : null - )); - if (response == null) { - Log.w(Room.class.getSimpleName(), "创建接收通道失败"); - return; - } - this.nativeCreateRecvTransport(this.nativeRoomPointer, JSONUtils.toJSON(response.body())); + this.taoyao.requestFuture( + this.taoyao.buildMessage( + "media::transport::webrtc::create", + "roomId", this.roomId, + "forceTcp", false, + "producing", false, + "consuming", true, + "sctpCapabilities", this.dataProduce ? this.sctpCapabilities : null + ), + response -> { + this.nativeCreateRecvTransport(this.nativeRoomPointer, JSONUtils.toJSON(response.body())); + }, + response -> { + Log.w(Room.class.getSimpleName(), "创建接收通道失败"); + } + ); } public void mediaConsume(Message message, Map body) { @@ -302,7 +316,7 @@ public class Room extends CloseableClient implements RouterCallback { public void enterRoomCallback(String rtpCapabilities, String sctpCapabilities) { this.rtpCapabilities = rtpCapabilities; this.sctpCapabilities = sctpCapabilities; - this.taoyao.request(this.taoyao.buildMessage( + this.taoyao.push(this.taoyao.buildMessage( "room::enter", "roomId", this.roomId, "password", this.password, @@ -318,7 +332,7 @@ public class Room extends CloseableClient implements RouterCallback { @Override public void sendTransportConnectCallback(String transportId, String dtlsParameters) { - this.taoyao.request(this.taoyao.buildMessage( + this.taoyao.push(this.taoyao.buildMessage( "media::transport::webrtc::connect", "roomId", this.roomId, "transportId", transportId, @@ -328,7 +342,7 @@ public class Room extends CloseableClient implements RouterCallback { @Override public void recvTransportConnectCallback(String transportId, String dtlsParameters) { - this.taoyao.request(this.taoyao.buildMessage( + this.taoyao.push(this.taoyao.buildMessage( "media::transport::webrtc::connect", "roomId", this.roomId, "transportId", transportId, @@ -338,15 +352,19 @@ public class Room extends CloseableClient implements RouterCallback { @Override public String sendTransportProduceCallback(String kind, String transportId, String rtpParameters) { - final Message response = this.taoyao.request(this.taoyao.buildMessage( - "media::produce", - "kind", kind, - "roomId", this.roomId, - "transportId", transportId, - "rtpParameters", JSONUtils.toMap(rtpParameters) - )); - final Map body = response.body(); - return MapUtils.get(body, "producerId"); + return this.taoyao.requestFuture( + this.taoyao.buildMessage( + "media::produce", + "kind", kind, + "roomId", this.roomId, + "transportId", transportId, + "rtpParameters", JSONUtils.toMap(rtpParameters) + ), + response -> { + final Map body = response.body(); + return MapUtils.get(body, "producerId"); + } + ); } @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 4af0a98..2a3ef98 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 @@ -9,8 +9,11 @@ import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.media.VideoSourceType; import com.acgist.taoyao.media.config.Config; import com.acgist.taoyao.media.config.MediaProperties; +import com.acgist.taoyao.media.config.WebrtcProperties; +import com.acgist.taoyao.media.config.WebrtcStunProperties; import com.acgist.taoyao.media.signal.ITaoyao; +import org.apache.commons.lang3.ArrayUtils; import org.webrtc.DataChannel; import org.webrtc.IceCandidate; import org.webrtc.MediaConstraints; @@ -23,6 +26,7 @@ import org.webrtc.SessionDescription; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; /** * P2P终端 @@ -48,18 +52,27 @@ public class SessionClient extends Client { private final boolean audioProduce; private final boolean videoProduce; private final MediaProperties mediaProperties; + private final WebrtcProperties webrtcProperties; /** * 本地媒体 */ private MediaStream mediaStream; + /** + * 数据通道 + */ + private DataChannel dataChannel; /** * 远程媒体 */ private MediaStream remoteMediaStream; /** - * SDPObserver + * OfferSdpObserver */ - private SdpObserver sdpObserver; + private SdpObserver offerSdpObserver; + /** + * AnswerSdpObserver + */ + private SdpObserver answerSdpObserver; /** * Peer连接 */ @@ -73,12 +86,31 @@ public class SessionClient extends Client { */ private PeerConnectionFactory peerConnectionFactory; + /** + * + * @param sessionId 会话ID + * @param name 远程终端名称 + * @param clientId 远程终端ID + * @param taoyao 信令 + * @param mainHandler MainHandler + * @param preview 是否预览视频 + * @param playAudio 是否播放音频 + * @param playVideo 是否播放视频 + * @param dataConsume 是否消费数据 + * @param audioConsume 是否消费音频 + * @param videoConsume 是否消费视频 + * @param dataProduce 是否生产数据 + * @param audioProduce 是否生产音频 + * @param videoProduce 是否生产视频 + * @param mediaProperties 媒体配置 + * @param webrtcProperties WebRTC配置 + */ public SessionClient( String sessionId, String name, String clientId, ITaoyao taoyao, Handler mainHandler, boolean preview, boolean playAudio, boolean playVideo, boolean dataConsume, boolean audioConsume, boolean videoConsume, boolean dataProduce, boolean audioProduce, boolean videoProduce, - MediaProperties mediaProperties + MediaProperties mediaProperties, WebrtcProperties webrtcProperties ) { super(name, clientId, taoyao, mainHandler); this.sessionId = sessionId; @@ -92,6 +124,7 @@ public class SessionClient extends Client { this.audioProduce = audioProduce; this.videoProduce = videoProduce; this.mediaProperties = mediaProperties; + this.webrtcProperties = webrtcProperties; } @Override @@ -101,19 +134,17 @@ public class SessionClient extends Client { return; } super.init(); - this.peerConnectionFactory = this.mediaManager.newClient(VideoSourceType.BACK); + this.peerConnectionFactory = this.mediaManager.newClient(); // 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 List iceServers = this.webrtcProperties.getIceServers(); 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.observer = this.observer(); + this.mediaStream = this.mediaManager.buildLocalMediaStream(this.audioProduce, this.videoProduce); + this.offerSdpObserver = this.offerSdpObserver(); + this.answerSdpObserver = this.answerSdpObserver(); + this.peerConnection = this.peerConnectionFactory.createPeerConnection(configuration, this.observer); this.peerConnection.addStream(this.mediaStream); - // TODO:连接streamId作用同步 + // 设置streamId同步 // final List streamIds = new ArrayList<>(); // ListUtils.getOnlyOne(this.mediaStream.audioTracks, audioTrack -> { // this.peerConnection.addTrack(audioTrack, streamIds); @@ -136,32 +167,12 @@ public class SessionClient extends Client { } } - public void call(String clientId) { - final MediaConstraints mediaConstraints = new MediaConstraints(); - this.peerConnection.createOffer(this.sdpObserver, mediaConstraints); - // TODO:实现主动拉取别人 - } - /** * 提供媒体服务 */ public void offer() { - final MediaConstraints mediaConstraints = new MediaConstraints(); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minWidth", Integer.toString(640))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minHeight", Integer.toString(480))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxWidth", Integer.toString(1920))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxHeight", Integer.toString(1080))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minFrameRate", Integer.toString(15))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxFrameRate", Integer.toString(30))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true")); - this.peerConnection.createOffer(this.sdpObserver, mediaConstraints); + final MediaConstraints mediaConstraints = this.mediaManager.buildMediaConstraints(); + this.peerConnection.createOffer(this.offerSdpObserver, mediaConstraints); } private void offer(Message message, Map body) { @@ -169,26 +180,13 @@ public class SessionClient extends Client { final String type = MapUtils.get(body, "type"); final SessionDescription.Type sdpType = SessionDescription.Type.valueOf(type.toUpperCase()); final SessionDescription sessionDescription = new SessionDescription(sdpType, sdp); - this.peerConnection.setRemoteDescription(this.sdpObserver, sessionDescription); - final MediaConstraints mediaConstraints = new MediaConstraints(); - // PC -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true")); - // audio -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true")); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true")); - // video -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minWidth", Integer.toString(640))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minHeight", Integer.toString(480))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxWidth", Integer.toString(1920))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxHeight", Integer.toString(1080))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minFrameRate", Integer.toString(15))); -// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxFrameRate", Integer.toString(30))); -// mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true")); - this.peerConnection.createAnswer(this.sdpObserver, mediaConstraints); + this.peerConnection.setRemoteDescription(this.offerSdpObserver, sessionDescription); + this.answer(); + } + + private void answer() { + final MediaConstraints mediaConstraints = this.mediaManager.buildMediaConstraints(); + this.peerConnection.createAnswer(this.answerSdpObserver, mediaConstraints); } private void answer(Message message, Map body) { @@ -196,7 +194,7 @@ public class SessionClient extends Client { final String type = MapUtils.get(body, "type"); final SessionDescription.Type sdpType = SessionDescription.Type.valueOf(type.toUpperCase()); final SessionDescription sessionDescription = new SessionDescription(sdpType, sdp); - this.peerConnection.setRemoteDescription(this.sdpObserver, sessionDescription); + this.peerConnection.setRemoteDescription(this.answerSdpObserver, sessionDescription); } private void candidate(Message message, Map body) { @@ -296,7 +294,15 @@ public class SessionClient extends Client { return; } super.close(); - this.remoteMediaStream.dispose(); +// if(this.mediaStream != null) { +// this.mediaStream.dispose(); +// } + if(this.remoteMediaStream != null) { + this.remoteMediaStream.dispose(); + } + if(this.peerConnection != null) { + this.peerConnection.dispose(); + } this.mediaManager.closeClient(); } } @@ -309,18 +315,21 @@ public class SessionClient extends Client { @Override public void onSignalingChange(PeerConnection.SignalingState signalingState) { - } - - @Override - public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { - } - - @Override - public void onIceConnectionReceivingChange(boolean result) { + Log.d(SessionClient.class.getSimpleName(), "SignalingState状态改变:" + signalingState); } @Override public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) { + Log.d(SessionClient.class.getSimpleName(), "IceGatheringState状态改变:" + iceGatheringState); + } + + @Override + public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { + Log.d(SessionClient.class.getSimpleName(), "IceConnectionState状态改变:" + iceConnectionState); + } + + @Override + public void onIceConnectionReceivingChange(boolean result) { } @Override @@ -342,22 +351,26 @@ public class SessionClient extends Client { public void onAddStream(MediaStream mediaStream) { Log.i(SessionClient.class.getSimpleName(), "添加远程媒体:" + SessionClient.this.clientId); if(SessionClient.this.remoteMediaStream != null) { - // TODO:验证音频视频是否合在一起 + SessionClient.this.remoteMediaStream.dispose(); } SessionClient.this.remoteMediaStream = mediaStream; - SessionClient.this.playVideo(); SessionClient.this.playAudio(); + SessionClient.this.playVideo(); } @Override public void onRemoveStream(MediaStream mediaStream) { - if(SessionClient.this.remoteMediaStream == mediaStream) { - - } + Log.i(SessionClient.class.getSimpleName(), "删除远程媒体:" + SessionClient.this.clientId); + mediaStream.dispose(); } @Override public void onDataChannel(DataChannel dataChannel) { + Log.i(SessionClient.class.getSimpleName(), "添加数据通道:" + SessionClient.this.clientId); + if(SessionClient.this.dataChannel != null) { + SessionClient.this.dataChannel.dispose(); + } + SessionClient.this.dataChannel = dataChannel; } @Override @@ -372,12 +385,12 @@ public class SessionClient extends Client { }; } - private SdpObserver sdpObserver() { + private SdpObserver offerSdpObserver() { return new SdpObserver() { @Override public void onCreateSuccess(SessionDescription sessionDescription) { - Log.d(SessionClient.class.getSimpleName(), "创建SDP成功:" + SessionClient.this.sessionId); + Log.d(SessionClient.class.getSimpleName(), "创建OfferSDP成功:" + SessionClient.this.sessionId); SessionClient.this.peerConnection.setLocalDescription(this, sessionDescription); SessionClient.this.taoyao.push(SessionClient.this.taoyao.buildMessage( "session::exchange", @@ -389,17 +402,50 @@ public class SessionClient extends Client { @Override public void onSetSuccess() { - Log.d(SessionClient.class.getSimpleName(), "设置SDP成功:" + SessionClient.this.sessionId); + Log.d(SessionClient.class.getSimpleName(), "设置OfferSDP成功:" + SessionClient.this.sessionId); } @Override public void onCreateFailure(String message) { - Log.w(SessionClient.class.getSimpleName(), "创建SDP失败:" + message); + Log.w(SessionClient.class.getSimpleName(), "创建OfferSDP失败:" + message); } @Override public void onSetFailure(String message) { - Log.w(SessionClient.class.getSimpleName(), "设置SDP失败:" + message); + Log.w(SessionClient.class.getSimpleName(), "设置OfferSDP失败:" + message); + } + + }; + } + + private SdpObserver answerSdpObserver() { + return new SdpObserver() { + + @Override + public void onCreateSuccess(SessionDescription sessionDescription) { + Log.d(SessionClient.class.getSimpleName(), "创建AnswerSDP成功:" + SessionClient.this.sessionId); + SessionClient.this.peerConnection.setLocalDescription(this, sessionDescription); + SessionClient.this.taoyao.push(SessionClient.this.taoyao.buildMessage( + "session::exchange", + "sdp", sessionDescription.description, + "type", sessionDescription.type.toString().toLowerCase(), + "sessionId", SessionClient.this.sessionId + )); + } + + @Override + public void onSetSuccess() { + Log.d(SessionClient.class.getSimpleName(), "设置AnswerSDP成功:" + SessionClient.this.sessionId); + } + + @Override + public void onCreateFailure(String message) { + Log.w(SessionClient.class.getSimpleName(), "创建AnswerSDP失败:" + message); + } + + @Override + public void onSetFailure(String message) { + Log.w(SessionClient.class.getSimpleName(), "设置AnswerSDP失败:" + message); } }; diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/config/WebrtcProperties.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/config/WebrtcProperties.java index 10ea40c..d1fb0ec 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/config/WebrtcProperties.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/config/WebrtcProperties.java @@ -1,5 +1,8 @@ package com.acgist.taoyao.media.config; +import org.apache.commons.lang3.ArrayUtils; +import org.webrtc.PeerConnection; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -17,25 +20,51 @@ public class WebrtcProperties { private WebrtcStunProperties[] stun; private WebrtcTurnProperties[] turn; - public List> getIceServers() { - final List> list = new ArrayList<>(); - if(this.stun != null) { + public List getIceServers() { + final List iceServers = new ArrayList<>(); + if(ArrayUtils.isNotEmpty(this.stun)) { for (WebrtcStunProperties stun : this.stun) { - final Map map = new HashMap<>(); - map.put("urls", stun.getAddress()); - list.add(map); + final PeerConnection.IceServer iceServer = PeerConnection.IceServer + .builder(stun.getAddress()) + .createIceServer(); + iceServers.add(iceServer); } } - if(this.turn != null) { + if(ArrayUtils.isNotEmpty(this.turn)) { for (WebrtcTurnProperties turn : this.turn) { - final Map map = new HashMap<>(); - map.put("urls", turn.getAddress()); - map.put("username", turn.getUsername()); - map.put("credential", turn.getPassword()); - list.add(map); + final PeerConnection.IceServer iceServer = PeerConnection.IceServer + .builder(turn.getAddress()) + .setUsername(turn.getUsername()) + .setPassword(turn.getPassword()) + .createIceServer(); + iceServers.add(iceServer); } } - return list; + return iceServers; } - + + public Boolean getEncrypt() { + return this.encrypt; + } + + public void setEncrypt(Boolean encrypt) { + this.encrypt = encrypt; + } + + public WebrtcStunProperties[] getStun() { + return this.stun; + } + + public void setStun(WebrtcStunProperties[] stun) { + this.stun = stun; + } + + public WebrtcTurnProperties[] getTurn() { + return this.turn; + } + + public void setTurn(WebrtcTurnProperties[] turn) { + this.turn = turn; + } + } diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/signal/ITaoyao.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/signal/ITaoyao.java index 2a0ce12..72b85b4 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/signal/ITaoyao.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/signal/ITaoyao.java @@ -1,7 +1,14 @@ package com.acgist.taoyao.media.signal; +import android.util.Log; + import com.acgist.taoyao.boot.model.Message; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; + /** * 桃夭信令 * @@ -37,4 +44,61 @@ public interface ITaoyao { */ Message request(Message request); + /** + * @param request 信令请求消息 + * @param success 成功 + * + * @return 结果 + */ + default T requestFuture(Message request, Function success) { + return this.requestFuture(request, success, null); + } + + /** + * @param request 信令请求消息 + * @param success 成功 + * @param failure 失败 + * + * @return 结果 + */ + default T requestFuture(Message request, Function success, Function failure) { +// final CompletableFuture completableFuture = new CompletableFuture<>(); + final Message response = this.request(request); + if(response != null && response.isSuccess()) { + return success.apply(response); + } else { + Log.w(ITaoyao.class.getSimpleName(), "信令响应失败:" + response); + if(failure != null) { + return failure.apply(response); + } + return null; + } + } + + /** + * @param request 信令请求消息 + * @param success 成功 + */ + default void requestFuture(Message request, Consumer success) { + this.requestFuture(request, success, null); + } + + /** + * @param request 信令请求消息 + * @param success 成功 + * @param failure 失败 + */ + default void requestFuture(Message request, Consumer success, Consumer failure) { +// final CompletableFuture completableFuture = new CompletableFuture<>(); + final Message response = this.request(request); + if(response != null && response.isSuccess()) { + success.accept(response); + } else { + Log.w(ITaoyao.class.getSimpleName(), "信令响应失败:" + response); + if(failure != null) { + failure.accept(response); + } + } + } + } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java index 39f8624..89a4022 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java @@ -9,10 +9,8 @@ import com.acgist.taoyao.boot.utils.JSONUtils; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; /** @@ -25,12 +23,15 @@ import lombok.Setter; @Setter @Schema(title = "消息", description = "消息") @Builder -@NoArgsConstructor -@AllArgsConstructor @JsonIncludeProperties(value = { "code", "message", "header", "body" }) public class Message implements Cloneable, Serializable { private static final long serialVersionUID = 1L; + + /** + * 成功标识 + */ + public static final String CODE_0000 = "0000"; /** * 状态编码 @@ -53,6 +54,20 @@ public class Message implements Cloneable, Serializable { @Schema(title = "消息主体", description = "消息主体") private Object body; + public Message() { + // 默认消息都是成功 + final MessageCode messageCode = MessageCode.CODE_0000; + this.code = messageCode.getCode(); + this.message = messageCode.getMessage(); + } + + public Message(String code, String message, Header header, Object body) { + this.code = code; + this.message = message; + this.header = header; + this.body = body; + } + /** * @param code 状态编码 */ @@ -61,21 +76,21 @@ public class Message implements Cloneable, Serializable { } /** - * @param code 状态编码 + * @param messageCode 状态编码 */ - public void setCode(MessageCode code) { - this.setCode(code, null); + public void setCode(MessageCode messageCode) { + this.setCode(messageCode, null); } /** - * @param code 状态编码 - * @param message 状态描述 + * @param messageCode 状态编码 + * @param message 状态描述 * * @return this */ - public Message setCode(MessageCode code, String message) { - this.code = code.getCode(); - this.message = StringUtils.isEmpty(message) ? code.getMessage() : message; + public Message setCode(MessageCode messageCode, String message) { + this.code = messageCode.getCode(); + this.message = StringUtils.isEmpty(message) ? messageCode.getMessage() : message; return this; } @@ -106,22 +121,22 @@ public class Message implements Cloneable, Serializable { } /** - * @param code 状态编码 + * @param messageCode 状态编码 * * @return 失败消息 */ - public static final Message fail(MessageCode code) { - return fail(code, null, null); + public static final Message fail(MessageCode messageCode) { + return fail(messageCode, null, null); } /** - * @param code 状态编码 - * @param body 消息主体 + * @param messageCode 状态编码 + * @param body 消息主体 * * @return 失败消息 */ - public static final Message fail(MessageCode code, Object body) { - return fail(code, null, body); + public static final Message fail(MessageCode messageCode, Object body) { + return fail(messageCode, null, body); } /** @@ -135,7 +150,7 @@ public class Message implements Cloneable, Serializable { /** * @param message 状态描述 - * @param body 消息主体 + * @param body 消息主体 * * @return 失败消息 */ @@ -144,25 +159,25 @@ public class Message implements Cloneable, Serializable { } /** - * @param code 状态编码 - * @param message 状态描述 + * @param messageCode 状态编码 + * @param message 状态描述 * * @return 失败消息 */ - public static final Message fail(MessageCode code, String message) { - return fail(code, message, null); + public static final Message fail(MessageCode messageCode, String message) { + return fail(messageCode, message, null); } /** - * @param code 状态编码 - * @param message 状态描述 - * @param body 消息主体 + * @param messageCode 状态编码 + * @param message 状态描述 + * @param body 消息主体 * * @return 失败消息 */ - public static final Message fail(MessageCode code, String message, Object body) { + public static final Message fail(MessageCode messageCode, String message, Object body) { final Message failMessage = new Message(); - failMessage.setCode(code == null ? MessageCode.CODE_9999 : code, message); + failMessage.setCode(messageCode == null ? MessageCode.CODE_9999 : messageCode, message); failMessage.body = body; return failMessage; } @@ -195,9 +210,13 @@ public class Message implements Cloneable, Serializable { } } + public boolean isSuccess() { + return CODE_0000.equals(this.code); + } + @Override public String toString() { return JSONUtils.toJSON(this); } - + } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java index 0368cbd..8cb4bc1 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java @@ -19,7 +19,7 @@ public class MessageCodeException extends RuntimeException { /** * 状态编码 */ - private final MessageCode code; + private final MessageCode messageCode; /** * @param message 异常消息 @@ -41,40 +41,40 @@ public class MessageCodeException extends RuntimeException { } /** - * @param code 状态编码 + * @param messageCode 状态编码 * @param message 异常消息 * * @return 状态编码异常 */ - public static final MessageCodeException of(MessageCode code, String message) { - return of(null, code, message); + public static final MessageCodeException of(MessageCode messageCode, String message) { + return of(null, messageCode, message); } /** * @param t 异常 - * @param code 状态编码 + * @param messageCode 状态编码 * @param message 异常消息 * * @return 状态编码异常 */ - public static final MessageCodeException of(Throwable t, MessageCode code, String message) { - if(code == null) { - code = MessageCode.CODE_9999; + public static final MessageCodeException of(Throwable t, MessageCode messageCode, String message) { + if(messageCode == null) { + messageCode = MessageCode.CODE_9999; } if(StringUtils.isEmpty(message)) { - message = Objects.isNull(t) ? code.getMessage() : t.getMessage(); + message = Objects.isNull(t) ? messageCode.getMessage() : t.getMessage(); } - return new MessageCodeException(t, code, message); + return new MessageCodeException(t, messageCode, message); } /** * @param t 异常 - * @param code 状态编码 + * @param messageCode 状态编码 * @param message 异常消息 */ - public MessageCodeException(Throwable t, MessageCode code, String message) { + public MessageCodeException(Throwable t, MessageCode messageCode, String message) { super(message, t); - this.code = code; + this.messageCode = messageCode; } } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java index 89d8259..3e0cb9b 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java @@ -61,13 +61,13 @@ public final class ErrorUtils { /** * 注册异常(注意继承顺序) * - * @param code 状态编码 + * @param messageCode 状态编码 * @param clazz 异常类型 */ - public static final void register(MessageCode code, Class clazz) { - log.debug("注册状态编码异常映射:{} - {}", code, clazz); + public static final void register(MessageCode messageCode, Class clazz) { + log.debug("注册状态编码异常映射:{} - {}", messageCode, clazz); synchronized (CODE_MAPPING) { - CODE_MAPPING.put(clazz, code); + CODE_MAPPING.put(clazz, messageCode); } } @@ -98,7 +98,7 @@ public final class ErrorUtils { final Object rootError = rootException(globalError); if(rootError instanceof MessageCodeException messageCodeException) { // 状态编码异常 - final MessageCode messageCode = messageCodeException.getCode(); + final MessageCode messageCode = messageCodeException.getMessageCode(); status = messageCode.getStatus(); message = Message.fail(messageCode, messageCodeException.getMessage()); } else if(rootError instanceof Throwable throwable) { diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java index c9d8fe8..771e58a 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java @@ -70,12 +70,12 @@ public interface Protocol { Message build(Object body); /** - * @param code 状态编码 + * @param messageCode 状态编码 * @param body 消息主体 * * @return 信令消息 */ - Message build(MessageCode code, Object body); + Message build(MessageCode messageCode, Object body); /** * @param message 状态描述 @@ -86,22 +86,22 @@ public interface Protocol { Message build(String message, Object body); /** - * @param code 状态编码 + * @param messageCode 状态编码 * @param message 状态描述 * @param body 消息主体 * * @return 信令消息 */ - Message build(MessageCode code, String message, Object body); + Message build(MessageCode messageCode, String message, Object body); /** * @param id 消息标识 - * @param code 状态编码 + * @param messageCode 状态编码 * @param message 状态描述 * @param body 消息主体 * * @return 信令消息 */ - Message build(Long id, MessageCode code, String message, Object body); + Message build(Long id, MessageCode messageCode, String message, Object body); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java index e278ffc..d9066bf 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java @@ -79,8 +79,8 @@ public abstract class ProtocolAdapter implements Protocol { } @Override - public Message build(MessageCode code, Object body) { - return this.build(null, code, null, body); + public Message build(MessageCode messageCode, Object body) { + return this.build(null, messageCode, null, body); } @Override @@ -89,12 +89,12 @@ public abstract class ProtocolAdapter implements Protocol { } @Override - public Message build(MessageCode code, String message, Object body) { - return this.build(null, code, message, body); + public Message build(MessageCode messageCode, String message, Object body) { + return this.build(null, messageCode, message, body); } @Override - public Message build(Long id, MessageCode code, String message, Object body) { + public Message build(Long id, MessageCode messageCode, String message, Object body) { // 消息标识 if(id == null) { id = this.idService.buildId(); @@ -107,7 +107,7 @@ public abstract class ProtocolAdapter implements Protocol { .build(); final Message build = Message.builder().build(); // 设置状态编码、状态描述:默认成功 - build.setCode(code == null ? MessageCode.CODE_0000 : code, message); + build.setCode(messageCode == null ? MessageCode.CODE_0000 : messageCode, message); // 设置消息头部 build.setHeader(header); // 设置消息主体 diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformErrorProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformErrorProtocol.java index 67cf6f6..cd46215 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformErrorProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformErrorProtocol.java @@ -32,7 +32,7 @@ public class PlatformErrorProtocol extends ProtocolClientAdapter { } @Override - public Message build(Long id, MessageCode code, String message, Object body) { + public Message build(Long id, MessageCode messageCode, String message, Object body) { final Long oldId = this.idLocal.get(); if(oldId == null) { id = this.idService.buildId(); @@ -41,7 +41,7 @@ public class PlatformErrorProtocol extends ProtocolClientAdapter { this.idLocal.remove(); } // 默认设置失败状态 - return super.build(id, code == null ? MessageCode.CODE_9999 : code, message, body); + return super.build(id, messageCode == null ? MessageCode.CODE_9999 : messageCode, message, body); } /** @@ -61,7 +61,7 @@ public class PlatformErrorProtocol extends ProtocolClientAdapter { final String exceptionMessage = e.getMessage(); if(e instanceof MessageCodeException messageCodeException) { // 自定义的异常 - message.setCode(messageCodeException.getCode(), messageCodeException.getMessage()); + message.setCode(messageCodeException.getMessageCode(), messageCodeException.getMessage()); } else if(StringUtils.isNotEmpty(exceptionMessage) && exceptionMessage.length() <= Byte.MAX_VALUE) { // 少量信息返回异常信息 message.setMessage(exceptionMessage);