From 1eddba2181ccc3f6d98c188b7c31d1f8d2ca077b Mon Sep 17 00:00:00 2001 From: Alex Andres Date: Wed, 23 Jul 2025 22:12:06 +0200 Subject: [PATCH] feat: add RTCDtmfSender implementation for sending DTMF tones #115 --- README.md | 8 +- docs/_sidebar.md | 1 + docs/guide/dtmf_sender.md | 166 +++++++++++++++++ docs/guide/overview.md | 1 + .../src/main/cpp/include/JNI_RTCDtmfSender.h | 69 +++++++ .../src/main/cpp/include/JNI_RTCRtpSender.h | 8 + .../src/main/cpp/include/api/RTCDtmfSender.h | 44 +++++ .../cpp/include/api/RTCDtmfSenderObserver.h | 55 ++++++ .../src/main/cpp/src/JNI_RTCDtmfSender.cpp | 87 +++++++++ .../src/main/cpp/src/JNI_RTCRtpSender.cpp | 21 ++- .../src/main/cpp/src/api/RTCDtmfSender.cpp | 43 +++++ .../cpp/src/api/RTCDtmfSenderObserver.cpp | 49 +++++ .../dev/onvoid/webrtc/RTCDataChannel.java | 2 +- .../java/dev/onvoid/webrtc/RTCDtmfSender.java | 145 +++++++++++++++ .../onvoid/webrtc/RTCDtmfSenderObserver.java | 42 +++++ .../java/dev/onvoid/webrtc/RTCRtpSender.java | 10 ++ .../onvoid/webrtc/RTCDataChannelTests.java | 100 ++++++++++- .../dev/onvoid/webrtc/RTCDtmfSenderTests.java | 170 ++++++++++++++++++ .../dev/onvoid/webrtc/TestPeerConnection.java | 84 +-------- 19 files changed, 1013 insertions(+), 92 deletions(-) create mode 100644 docs/guide/dtmf_sender.md create mode 100644 webrtc-jni/src/main/cpp/include/JNI_RTCDtmfSender.h create mode 100644 webrtc-jni/src/main/cpp/include/api/RTCDtmfSender.h create mode 100644 webrtc-jni/src/main/cpp/include/api/RTCDtmfSenderObserver.h create mode 100644 webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp create mode 100644 webrtc-jni/src/main/cpp/src/api/RTCDtmfSender.cpp create mode 100644 webrtc-jni/src/main/cpp/src/api/RTCDtmfSenderObserver.cpp create mode 100644 webrtc/src/main/java/dev/onvoid/webrtc/RTCDtmfSender.java create mode 100644 webrtc/src/main/java/dev/onvoid/webrtc/RTCDtmfSenderObserver.java create mode 100644 webrtc/src/test/java/dev/onvoid/webrtc/RTCDtmfSenderTests.java diff --git a/README.md b/README.md index 1a95458c..d8ecafd2 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ The library provides a comprehensive set of Java classes that map to the WebRTC For more detailed information, check out the documentation: -- **[Quickstart](https://jrtc.dev/#/quickstart)** - Get up and running quickly with webrtc-java -- **[Guides](https://jrtc.dev/#/guide/overview)** - Comprehensive documentation on using the library -- **[Examples](https://jrtc.dev/#/examples)** - Sample code demonstrating various features -- **[Build Notes](https://jrtc.dev/#/build)** - Instructions for building the library from source +- [Quickstart](https://jrtc.dev/#/quickstart) - Get up and running quickly with webrtc-java +- [Guides](https://jrtc.dev/#/guide/overview) - Comprehensive documentation on using the library +- [Examples](https://jrtc.dev/#/examples) - Sample code demonstrating various features +- [Build Notes](https://jrtc.dev/#/build) - Instructions for building the library from source ## License diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 37a8c238..1336f6b4 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -11,6 +11,7 @@ - [Bitrate and Framerate Constraints](guide/constraints.md) - [Desktop Capture](guide/desktop_capture.md) - [Data Channels](guide/data_channels.md) + - [DTMF Sender](guide/dtmf_sender.md) - [RTC Stats](guide/rtc_stats.md) - [Logging](guide/logging.md) diff --git a/docs/guide/dtmf_sender.md b/docs/guide/dtmf_sender.md new file mode 100644 index 00000000..c08426b8 --- /dev/null +++ b/docs/guide/dtmf_sender.md @@ -0,0 +1,166 @@ +# DTMF Sender + +This guide explains how to use the DTMF (Dual-Tone Multi-Frequency) sender functionality with the webrtc-java library. DTMF senders allow you to send DTMF tones over WebRTC audio connections, which is useful for interactive voice response (IVR) systems and telephony applications. + +## Overview + +WebRTC DTMF senders allow you to: +- Send DTMF tones over an established audio connection +- Configure tone duration and inter-tone gap +- Monitor tone transmission events +- Check if DTMF tones can be inserted + +DTMF tones are the audible tones generated when pressing keys on a telephone keypad. The supported DTMF tones are: 0-9, A-D, *, and #. In addition, the special character ',' (comma) can be used to insert a 2-second delay between tones. + +## Getting a DTMF Sender + +To use DTMF functionality, you need an established `RTCPeerConnection` with an audio track. You can then get the DTMF sender from the RTP sender associated with the audio track: + +```java +import dev.onvoid.webrtc.RTCPeerConnection; +import dev.onvoid.webrtc.RTCRtpSender; +import dev.onvoid.webrtc.RTCDtmfSender; +import dev.onvoid.webrtc.media.audio.AudioTrack; + +// Assuming you already have a PeerConnectionFactory and RTCConfiguration +RTCPeerConnection peerConnection = factory.createPeerConnection(config, peerConnectionObserver); + +// Create and add an audio track +AudioTrackSource audioSource = factory.createAudioSource(new AudioOptions()); +AudioTrack audioTrack = factory.createAudioTrack("audioTrack", audioSource); + +// Add the track to the peer connection +List streamIds = new ArrayList<>(); +streamIds.add("stream1"); +RTCRtpSender sender = peerConnection.addTrack(audioTrack, streamIds); + +// Get the DTMF sender +RTCDtmfSender dtmfSender = sender.getDtmfSender(); +``` + +## Checking DTMF Capability + +Before attempting to send DTMF tones, you should check if the DTMF sender is capable of sending tones: + +```java +if (dtmfSender != null && dtmfSender.canInsertDtmf()) { + // DTMF is supported and can be used + System.out.println("DTMF is supported"); +} else { + // DTMF is not supported + System.out.println("DTMF is not supported"); +} +``` + +The `canInsertDtmf()` method returns true if and only if: +- The associated RTCRtpSender's track is non-null and is of kind "audio" +- The RTCDtmfSender is able to send packets +- A "telephone-event" codec has been negotiated + +## Sending DTMF Tones + +To send DTMF tones, use the `insertDtmf` method: + +```java +// Send DTMF tones with custom duration (100ms) and inter-tone gap (70ms) +boolean success = dtmfSender.insertDtmf("123", 100, 70); +``` + +The `insertDtmf` method takes the following parameters: +- `tones`: A string containing the DTMF tones to send +- `duration`: The duration in milliseconds for each tone (default: 100ms) +- `interToneGap`: The gap between tones in milliseconds (default: 50ms) + +The method returns `true` if the tones were successfully queued for transmission, or `false` if the operation failed. + +### Valid Tones + +The following characters are valid in the `tones` parameter: +- Digits: 0-9 +- Letters: A-D (or a-d, case-insensitive) +- Symbols: * (asterisk), # (pound/hash) +- Special: , (comma) - inserts a 2-second delay + +Unrecognized characters are ignored. + +### Duration and Inter-Tone Gap Constraints + +The duration and inter-tone gap parameters have the following constraints: +- Duration must be between 70ms and 6000ms (default: 100ms) +- Inter-tone gap must be at least 50ms (default: 50ms) + +If these constraints are not met, the `insertDtmf` method will return `false`. + +## Monitoring DTMF Events + +To receive notifications about DTMF tone events, implement the `RTCDtmfSenderObserver` interface and register it with the DTMF sender: + +```java +import dev.onvoid.webrtc.RTCDtmfSenderObserver; + +dtmfSender.registerObserver(new RTCDtmfSenderObserver() { + @Override + public void onToneChange(String tone, String toneBuffer) { + if (tone == null || tone.isEmpty()) { + System.out.println("All tones have been played"); + } else { + System.out.println("Playing tone: " + tone); + System.out.println("Remaining tones: " + toneBuffer); + } + } +}); +``` + +The `onToneChange` method is called: +- When a new tone starts playing, with the tone character and the remaining tones buffer +- When all tones have finished playing, with an empty string for both parameters + +## Getting DTMF Properties + +You can query various properties of the DTMF sender: + +```java +// Get the tones currently in the queue +String remainingTones = dtmfSender.tones(); + +// Get the current duration setting +int duration = dtmfSender.duration(); + +// Get the current inter-tone gap setting +int interToneGap = dtmfSender.interToneGap(); +``` + +## Cleanup + +When you're done with the DTMF sender, you should unregister any observers: + +```java +// Unregister the observer +dtmfSender.unregisterObserver(); +``` + +Note that you don't need to explicitly dispose of the DTMF sender, as it will be cleaned up when the associated RTP sender is disposed. + +## Best Practices + +1. **Check Capability**: Always check if DTMF is supported using `canInsertDtmf()` before attempting to send tones. + +2. **Error Handling**: Check the return value of `insertDtmf()` to ensure the tones were successfully queued. + +3. **Observer Cleanup**: Always unregister observers when you're done to prevent memory leaks. + +4. **Tone Duration**: Use appropriate tone durations based on your application needs: + - For standard telephony applications, the default 100ms is usually sufficient + - For IVR systems that might need more processing time, consider longer durations + +5. **Tone Buffering**: Be aware that tones are queued and played sequentially. If you need to cancel queued tones, you can call `insertDtmf("")` to clear the queue. + +--- + +## Conclusion + +The RTCDtmfSender provides a standard way to send DTMF tones over WebRTC audio connections. This functionality is particularly useful for applications that need to interact with traditional telephony systems, IVR systems, or any service that uses DTMF for signaling. + +By following the guidelines in this document, you can effectively integrate DTMF functionality into your WebRTC applications, enabling users to interact with automated systems or trigger actions using their device's keypad. + +For more information on other WebRTC features, refer to the additional guides in the documentation. \ No newline at end of file diff --git a/docs/guide/overview.md b/docs/guide/overview.md index 9c9b9bf9..15816907 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -9,6 +9,7 @@ This section provides detailed guides for various features of the webrtc-java li - [Audio Processing](guide/audio_processing.md) - Voice processing components - [Bitrate and Framerate Constraints](guide/constraints.md) - Controlling media quality - [Desktop Capture](guide/desktop_capture.md) - Capturing and sharing screens and windows +- [DTMF Sender](guide/dtmf_sender.md) - Sending DTMF tones in a call ## Data Communication diff --git a/webrtc-jni/src/main/cpp/include/JNI_RTCDtmfSender.h b/webrtc-jni/src/main/cpp/include/JNI_RTCDtmfSender.h new file mode 100644 index 00000000..6a25bb55 --- /dev/null +++ b/webrtc-jni/src/main/cpp/include/JNI_RTCDtmfSender.h @@ -0,0 +1,69 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class dev_onvoid_webrtc_RTCDtmfSender */ + +#ifndef _Included_dev_onvoid_webrtc_RTCDtmfSender +#define _Included_dev_onvoid_webrtc_RTCDtmfSender +#ifdef __cplusplus +extern "C" { +#endif + /* + * Class: dev_onvoid_webrtc_RTCDtmfSender + * Method: canInsertDtmf + * Signature: ()Z + */ + JNIEXPORT jboolean JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_canInsertDtmf + (JNIEnv *, jobject); + + /* + * Class: dev_onvoid_webrtc_RTCDtmfSender + * Method: insertDtmf + * Signature: (Ljava/lang/String;II)Z + */ + JNIEXPORT jboolean JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_insertDtmf + (JNIEnv *, jobject, jstring, jint, jint); + + /* + * Class: dev_onvoid_webrtc_RTCDtmfSender + * Method: tones + * Signature: ()Ljava/lang/String; + */ + JNIEXPORT jstring JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_tones + (JNIEnv *, jobject); + + /* + * Class: dev_onvoid_webrtc_RTCDtmfSender + * Method: duration + * Signature: ()I + */ + JNIEXPORT jint JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_duration + (JNIEnv *, jobject); + + /* + * Class: dev_onvoid_webrtc_RTCDtmfSender + * Method: interToneGap + * Signature: ()I + */ + JNIEXPORT jint JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_interToneGap + (JNIEnv *, jobject); + + /* + * Class: dev_onvoid_webrtc_RTCDtmfSender + * Method: registerObserver + * Signature: (Ldev/onvoid/webrtc/RTCDtmfSenderObserver;)V + */ + JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_registerObserver + (JNIEnv *, jobject, jobject); + + /* + * Class: dev_onvoid_webrtc_RTCDtmfSender + * Method: unregisterObserver + * Signature: ()V + */ + JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_unregisterObserver + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h b/webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h index ce01361b..83f23ffc 100644 --- a/webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h +++ b/webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h @@ -55,6 +55,14 @@ extern "C" { JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_setStreams (JNIEnv *, jobject, jobject); + /* + * Class: dev_onvoid_webrtc_RTCRtpSender + * Method: getDtmfSender + * Signature: ()Ldev/onvoid/webrtc/RTCDtmfSender; + */ + JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getDtmfSender + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/webrtc-jni/src/main/cpp/include/api/RTCDtmfSender.h b/webrtc-jni/src/main/cpp/include/api/RTCDtmfSender.h new file mode 100644 index 00000000..98cf6a22 --- /dev/null +++ b/webrtc-jni/src/main/cpp/include/api/RTCDtmfSender.h @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JNI_WEBRTC_API_RTC_DTMF_SENDER_H_ +#define JNI_WEBRTC_API_RTC_DTMF_SENDER_H_ + +#include "JavaClass.h" +#include "JavaRef.h" + +#include "api/dtmf_sender_interface.h" + +#include + +namespace jni +{ + namespace RTCDtmfSender + { + class JavaRTCDtmfSenderClass : public JavaClass + { + public: + explicit JavaRTCDtmfSenderClass(JNIEnv * env); + + jclass cls; + jmethodID ctor; + }; + + JavaLocalRef toJava(JNIEnv * env); + }; +} + +#endif \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/include/api/RTCDtmfSenderObserver.h b/webrtc-jni/src/main/cpp/include/api/RTCDtmfSenderObserver.h new file mode 100644 index 00000000..994ea047 --- /dev/null +++ b/webrtc-jni/src/main/cpp/include/api/RTCDtmfSenderObserver.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JNI_WEBRTC_API_RTC_DTMF_SENDER_OBSERVER_H_ +#define JNI_WEBRTC_API_RTC_DTMF_SENDER_OBSERVER_H_ + +#include "JavaClass.h" +#include "JavaRef.h" + +#include "api/dtmf_sender_interface.h" + +#include +#include + +namespace jni +{ + class RTCDtmfSenderObserver : public webrtc::DtmfSenderObserverInterface + { + public: + explicit RTCDtmfSenderObserver(JNIEnv * env, const JavaGlobalRef & observer); + ~RTCDtmfSenderObserver() = default; + + // DtmfSenderObserverInterface implementation. + void OnToneChange(const std::string & tone, const std::string & tone_buffer) override; + + private: + class JavaRTCDtmfSenderObserverClass : public JavaClass + { + public: + explicit JavaRTCDtmfSenderObserverClass(JNIEnv * env); + + jmethodID onToneChange; + }; + + private: + JavaGlobalRef observer; + + const std::shared_ptr javaClass; + }; +} + +#endif \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp new file mode 100644 index 00000000..e6dd5c7b --- /dev/null +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "JNI_RTCDtmfSender.h" +#include "api/RTCDtmfSenderObserver.h" +#include "JavaString.h" +#include "JavaUtils.h" + +#include "api/dtmf_sender_interface.h" + +JNIEXPORT jboolean JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_canInsertDtmf +(JNIEnv * env, jobject caller) +{ + webrtc::DtmfSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLEV(sender, false); + + return static_cast(sender->CanInsertDtmf()); +} + +JNIEXPORT jboolean JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_insertDtmf +(JNIEnv * env, jobject caller, jstring jTones, jint duration, jint interToneGap) +{ + webrtc::DtmfSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLEV(sender, false); + + std::string tones = jni::JavaString::toNative(env, jni::JavaLocalRef(env, jTones)); + + return static_cast(sender->InsertDtmf(tones, static_cast(duration), static_cast(interToneGap))); +} + +JNIEXPORT jstring JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_tones +(JNIEnv * env, jobject caller) +{ + webrtc::DtmfSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLEV(sender, nullptr); + + return jni::JavaString::toJava(env, sender->tones()).release(); +} + +JNIEXPORT jint JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_duration +(JNIEnv * env, jobject caller) +{ + webrtc::DtmfSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLEV(sender, 0); + + return static_cast(sender->duration()); +} + +JNIEXPORT jint JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_interToneGap +(JNIEnv * env, jobject caller) +{ + webrtc::DtmfSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLEV(sender, 0); + + return static_cast(sender->inter_tone_gap()); +} + +JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_registerObserver +(JNIEnv * env, jobject caller, jobject jObserver) +{ + webrtc::DtmfSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLE(sender); + + sender->RegisterObserver(new jni::RTCDtmfSenderObserver(env, jni::JavaGlobalRef(env, jObserver))); +} + +JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_unregisterObserver +(JNIEnv * env, jobject caller) +{ + webrtc::DtmfSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLE(sender); + + sender->UnregisterObserver(); +} \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp index e63b577e..2899121e 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp @@ -16,13 +16,14 @@ #include "JNI_RTCRtpSender.h" #include "api/RTCRtpSendParameters.h" -#include +#include "api/WebRTCUtils.h" #include "JavaFactories.h" #include "JavaList.h" #include "JavaRef.h" #include "JavaRuntimeException.h" #include "JavaUtils.h" +#include "api/RTCDtmfSender.h" #include "api/rtp_sender_interface.h" JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getTrack @@ -101,4 +102,22 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_setStreams std::vector streamIDs = jni::JavaList::toStringVector(env, jni::JavaLocalRef(env, streamIdList)); sender->SetStreams(streamIDs); +} + +JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getDtmfSender +(JNIEnv * env, jobject caller) +{ + webrtc::RtpSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLEV(sender, nullptr); + + auto dtmfSender = sender->GetDtmfSender(); + if (!dtmfSender) { + return nullptr; + } + + auto jDtmfSender = jni::RTCDtmfSender::toJava(env); + + SetHandle(env, jDtmfSender, dtmfSender.get()); + + return jDtmfSender.release(); } \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/api/RTCDtmfSender.cpp b/webrtc-jni/src/main/cpp/src/api/RTCDtmfSender.cpp new file mode 100644 index 00000000..8f275436 --- /dev/null +++ b/webrtc-jni/src/main/cpp/src/api/RTCDtmfSender.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "api/RTCDtmfSender.h" +#include "JavaClasses.h" +#include "JavaObject.h" +#include "JavaUtils.h" +#include "JNI_WebRTC.h" + +namespace jni +{ + namespace RTCDtmfSender + { + JavaLocalRef toJava(JNIEnv * env) + { + const auto javaClass = JavaClasses::get(env); + + jobject jDtmfSender = env->NewObject(javaClass->cls, javaClass->ctor); + + return JavaLocalRef(env, jDtmfSender); + } + + JavaRTCDtmfSenderClass::JavaRTCDtmfSenderClass(JNIEnv * env) + { + cls = FindClass(env, PKG"RTCDtmfSender"); + + ctor = GetMethod(env, cls, "", "()V"); + } + } +} diff --git a/webrtc-jni/src/main/cpp/src/api/RTCDtmfSenderObserver.cpp b/webrtc-jni/src/main/cpp/src/api/RTCDtmfSenderObserver.cpp new file mode 100644 index 00000000..92cc9986 --- /dev/null +++ b/webrtc-jni/src/main/cpp/src/api/RTCDtmfSenderObserver.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "api/RTCDtmfSenderObserver.h" +#include "JavaClasses.h" +#include "JavaString.h" +#include "JavaUtils.h" +#include "JNI_WebRTC.h" + +namespace jni +{ + RTCDtmfSenderObserver::RTCDtmfSenderObserver(JNIEnv * env, const JavaGlobalRef & observer) : + observer(observer), + javaClass(JavaClasses::get(env)) + { + } + + void RTCDtmfSenderObserver::OnToneChange(const std::string & tone, const std::string & tone_buffer) + { + JNIEnv * env = AttachCurrentThread(); + + JavaLocalRef jTone = JavaString::toJava(env, tone); + JavaLocalRef jToneBuffer = JavaString::toJava(env, tone_buffer); + + env->CallVoidMethod(observer, javaClass->onToneChange, jTone.get(), jToneBuffer.get()); + + ExceptionCheck(env); + } + + RTCDtmfSenderObserver::JavaRTCDtmfSenderObserverClass::JavaRTCDtmfSenderObserverClass(JNIEnv * env) + { + jclass cls = FindClass(env, PKG"RTCDtmfSenderObserver"); + + onToneChange = GetMethod(env, cls, "onToneChange", "(Ljava/lang/String;Ljava/lang/String;)V"); + } +} \ No newline at end of file diff --git a/webrtc/src/main/java/dev/onvoid/webrtc/RTCDataChannel.java b/webrtc/src/main/java/dev/onvoid/webrtc/RTCDataChannel.java index 1e218917..87c3ec6b 100644 --- a/webrtc/src/main/java/dev/onvoid/webrtc/RTCDataChannel.java +++ b/webrtc/src/main/java/dev/onvoid/webrtc/RTCDataChannel.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; /** - * Represents a bi-directional data channel between two peers. An RTCDataChannel + * Represents a bidirectional data channel between two peers. An RTCDataChannel * is created via a factory method on an {@link RTCPeerConnection}. * * @author Alex Andres diff --git a/webrtc/src/main/java/dev/onvoid/webrtc/RTCDtmfSender.java b/webrtc/src/main/java/dev/onvoid/webrtc/RTCDtmfSender.java new file mode 100644 index 00000000..dc9412f1 --- /dev/null +++ b/webrtc/src/main/java/dev/onvoid/webrtc/RTCDtmfSender.java @@ -0,0 +1,145 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.onvoid.webrtc; + +import dev.onvoid.webrtc.internal.NativeObject; + +/** + * Represents a DTMF (Dual-Tone Multi-Frequency) sender for WebRTC. + * This class allows sending DTMF tones over a WebRTC connection. + *

+ * The RTCDtmfSender interface enables the generation of DTMF tones on an audio track. + * This is typically used in Voice over IP (VoIP) applications to send DTMF tones to + * a remote telephony system or interactive voice response (IVR) system. + *

+ * DTMF tones are the audible tones generated when pressing keys on a telephone keypad. + * The supported DTMF tones are: 0-9, A-D, *, and #. In addition, the special characters + * ',' (comma) can be used to insert a 2-second delay between tones. + * + * @author Alex Andres + * + * @see W3C WebRTC 1.0: RTCDtmfSender + */ +public class RTCDtmfSender extends NativeObject { + + RTCDtmfSender() { + // Default constructor for native object instantiation. + } + + /** + * Checks whether this RTCDtmfSender is capable of sending DTMF tones. + *

+ * This method returns true if and only if the associated RTCRtpSender's + * track is non-null and is of kind "audio". + * To be able to send DTMF, the associated RTCDtmfSender must be able to + * send packets, and a "telephone-event" codec must be negotiated. + *

+ * When this method returns false, calling {@link #insertDtmf} will have no effect. + * + * @return true if DTMF tones can be inserted. + */ + public native boolean canInsertDtmf(); + + /** + * Inserts DTMF tones to be transmitted on the audio track. + *

+ * The tones parameter is treated as a series of characters. Characters + * 0 through 9, A through D, #, and * generate the associated DTMF tones. + * The characters a through d are equivalent to A through D. The character ',' (comma) + * indicates a delay of 2 seconds before processing the next character in the tones parameter. + *

+ * Unrecognized characters in the tones parameter are ignored. + *

+ * If {@link #canInsertDtmf} is false, this method will have no effect and return false. + *

+ * The duration parameter indicates the duration in milliseconds to use for each + * character passed in the tones parameter. The duration cannot be more than 6000 + * milliseconds or less than 70 milliseconds. The default duration is 100 milliseconds. + *

+ * The interToneGap parameter indicates the gap between tones in milliseconds. + * The interToneGap cannot be less than 50 milliseconds. The default value is 50 milliseconds. + * + * @param tones String containing the DTMF tones to be transmitted. + * Valid characters are 0-9, A-D, #, *, and , (comma). + * @param duration Duration in milliseconds for each tone. Must be between 40 and 6000 ms. + * @param interToneGap Gap between tones in milliseconds. Must be at least 30 ms. + * + * @return true if the tones were successfully queued for transmission. + */ + public native boolean insertDtmf(String tones, int duration, int interToneGap); + + /** + * Gets the tones remaining in the transmission queue. + *

+ * This method returns the list of DTMF tones that have been queued for transmission + * but not yet sent. This includes any tones currently being transmitted. + *

+ * If the value is an empty string, it indicates that there are no tones + * currently queued for transmission. + * + * @return String containing the remaining DTMF tones in the queue, or an empty string if none. + */ + public native String tones(); + + /** + * Gets the current tone duration setting. + *

+ * This method returns the current value of the duration parameter that will be used for + * future calls to {@link #insertDtmf}. The duration is the length of time, in milliseconds, + * that each DTMF tone is played. + *

+ * The default value is 100 milliseconds. Valid values are between 70 and 6000 milliseconds. + * + * @return The duration in milliseconds for each tone. + */ + public native int duration(); + + /** + * Gets the current gap between tones setting. + *

+ * This method returns the current value of the interToneGap parameter that will be used for + * future calls to {@link #insertDtmf}. The interToneGap is the length of time, in milliseconds, + * between the end of one DTMF tone and the start of the next. + *

+ * The default value is 50 milliseconds. Valid values must be at least 50 milliseconds but should + * be as short as possible. + * + * @return The gap between tones in milliseconds. + */ + public native int interToneGap(); + + /** + * Registers an observer to receive notifications about DTMF-related events. + *

+ * Only one observer can be registered at a time. Registering a new observer + * when one is already registered will replace the existing observer. + * + * @param observer The DTMF sender observer to register. + */ + public native void registerObserver(RTCDtmfSenderObserver observer); + + /** + * Unregisters the previously registered DTMF sender observer. + *

+ * After calling this method, no observer will receive notifications about + * DTMF-related events until a new observer is registered. + *

+ * If no observer is currently registered, this method has no effect. + */ + public native void unregisterObserver(); + +} diff --git a/webrtc/src/main/java/dev/onvoid/webrtc/RTCDtmfSenderObserver.java b/webrtc/src/main/java/dev/onvoid/webrtc/RTCDtmfSenderObserver.java new file mode 100644 index 00000000..b08e2c46 --- /dev/null +++ b/webrtc/src/main/java/dev/onvoid/webrtc/RTCDtmfSenderObserver.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.onvoid.webrtc; + +/** + * Observer interface for DTMF (Dual-Tone Multi-Frequency) tone events. + *

+ * This interface allows applications to receive notifications when DTMF tones + * are sent by an {@link RTCDtmfSender}. It corresponds to the + * "tonechange" event in the W3C WebRTC specification. + * + * @author Alex Andres + * + * @see RTCDtmfSender + */ +public interface RTCDtmfSenderObserver { + + /** + * Called when a DTMF tone sent by an {@link RTCDtmfSender}. + * + * @param tone The tone being played (one of "0123456789ABCD#*") or an + * empty string if the previous tone has finished playing. + * @param toneBuffer The remaining tones in the queue, including the current tone. + * Empty string if no tones remain. + */ + void onToneChange(String tone, String toneBuffer); + +} diff --git a/webrtc/src/main/java/dev/onvoid/webrtc/RTCRtpSender.java b/webrtc/src/main/java/dev/onvoid/webrtc/RTCRtpSender.java index 37c24690..5c053a02 100644 --- a/webrtc/src/main/java/dev/onvoid/webrtc/RTCRtpSender.java +++ b/webrtc/src/main/java/dev/onvoid/webrtc/RTCRtpSender.java @@ -102,4 +102,14 @@ private RTCRtpSender() { */ public native void setStreams(List streamIds); + /** + * Returns the RTCDtmfSender associated with this RTCRtpSender. The RTCDtmfSender + * enables the transmission of DTMF (Dual-Tone Multi-Frequency) tones over the + * RTCPeerConnection. + * + * @return The DTMF sender object associated with this RTP sender, or null if DTMF + * is not supported for the media type of the associated track. + */ + public native RTCDtmfSender getDtmfSender(); + } diff --git a/webrtc/src/test/java/dev/onvoid/webrtc/RTCDataChannelTests.java b/webrtc/src/test/java/dev/onvoid/webrtc/RTCDataChannelTests.java index eea8ffd7..60a6e167 100644 --- a/webrtc/src/test/java/dev/onvoid/webrtc/RTCDataChannelTests.java +++ b/webrtc/src/test/java/dev/onvoid/webrtc/RTCDataChannelTests.java @@ -16,18 +16,24 @@ package dev.onvoid.webrtc; +import static java.util.Objects.nonNull; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.Arrays; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class RTCDataChannelTests extends TestBase { @Test void textMessage() throws Exception { - TestPeerConnection caller = new TestPeerConnection(factory); - TestPeerConnection callee = new TestPeerConnection(factory); + DataPeerConnection caller = new DataPeerConnection(factory); + DataPeerConnection callee = new DataPeerConnection(factory); caller.setRemotePeerConnection(callee); callee.setRemotePeerConnection(caller); @@ -45,11 +51,95 @@ void textMessage() throws Exception { Thread.sleep(500); - assertEquals(Arrays.asList("Hello world"), callee.getReceivedTexts()); - assertEquals(Arrays.asList("Hi :)"), caller.getReceivedTexts()); + assertEquals(Collections.singletonList("Hello world"), callee.getReceivedTexts()); + assertEquals(Collections.singletonList("Hi :)"), caller.getReceivedTexts()); caller.close(); callee.close(); } + + + private static class DataPeerConnection extends TestPeerConnection { + + private final List receivedTexts = new ArrayList<>(); + + private final RTCDataChannel localDataChannel; + + private RTCDataChannel remoteDataChannel; + + + DataPeerConnection(PeerConnectionFactory factory) { + super(factory); + + localDataChannel = getPeerConnection().createDataChannel("dc", new RTCDataChannelInit()); + } + + @Override + public void onDataChannel(RTCDataChannel dataChannel) { + remoteDataChannel = dataChannel; + remoteDataChannel.registerObserver(new RTCDataChannelObserver() { + + @Override + public void onBufferedAmountChange(long previousAmount) { } + + @Override + public void onStateChange() { } + + @Override + public void onMessage(RTCDataChannelBuffer buffer) { + try { + decodeMessage(buffer); + } + catch (Exception e) { + Assertions.fail(e); + } + } + }); + } + + List getReceivedTexts() { + return receivedTexts; + } + + void sendTextMessage(String message) throws Exception { + ByteBuffer data = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)); + RTCDataChannelBuffer buffer = new RTCDataChannelBuffer(data, false); + + localDataChannel.send(buffer); + } + + private void decodeMessage(RTCDataChannelBuffer buffer) { + ByteBuffer byteBuffer = buffer.data; + byte[] payload; + + if (byteBuffer.hasArray()) { + payload = byteBuffer.array(); + } + else { + payload = new byte[byteBuffer.limit()]; + + byteBuffer.get(payload); + } + + String text = new String(payload, StandardCharsets.UTF_8); + + receivedTexts.add(text); + } + + void close() { + if (nonNull(localDataChannel)) { + localDataChannel.unregisterObserver(); + localDataChannel.close(); + localDataChannel.dispose(); + } + if (nonNull(remoteDataChannel)) { + remoteDataChannel.unregisterObserver(); + remoteDataChannel.close(); + remoteDataChannel.dispose(); + } + + super.close(); + } + } } diff --git a/webrtc/src/test/java/dev/onvoid/webrtc/RTCDtmfSenderTests.java b/webrtc/src/test/java/dev/onvoid/webrtc/RTCDtmfSenderTests.java new file mode 100644 index 00000000..8c2743e9 --- /dev/null +++ b/webrtc/src/test/java/dev/onvoid/webrtc/RTCDtmfSenderTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.onvoid.webrtc; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import dev.onvoid.webrtc.media.audio.AudioOptions; +import dev.onvoid.webrtc.media.audio.AudioTrack; +import dev.onvoid.webrtc.media.audio.AudioTrackSource; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the {@link RTCDtmfSender} class. + */ +class RTCDtmfSenderTests extends TestBase { + + /** + * Test implementation of RTCDtmfSenderObserver that records tone change events. + */ + private static class TestDtmfSenderObserver implements RTCDtmfSenderObserver { + + private final List tones = new ArrayList<>(); + private final List toneBuffers = new ArrayList<>(); + private final CountDownLatch completedLatch = new CountDownLatch(1); + + + @Override + public void onToneChange(String tone, String toneBuffer) { + tones.add(tone); + toneBuffers.add(toneBuffer); + + if (tone == null || tone.isEmpty()) { + completedLatch.countDown(); + } + } + + List getTones() { + return tones; + } + + List getToneBuffers() { + return toneBuffers; + } + + void waitUntilCompleted() throws InterruptedException { + completedLatch.await(); + } + } + + TestPeerConnection caller; + TestPeerConnection callee; + + RTCDtmfSender dtmfSender; + + + @BeforeEach + void init() throws Exception { + caller = new TestPeerConnection(factory); + callee = new TestPeerConnection(factory); + + RTCPeerConnection peerConnection = caller.getPeerConnection(); + + AudioOptions audioOptions = new AudioOptions(); + AudioTrackSource audioSource = factory.createAudioSource(audioOptions); + AudioTrack audioTrack = factory.createAudioTrack("audioTrack", audioSource); + + List streamIds = new ArrayList<>(); + streamIds.add("stream1"); + + RTCRtpSender sender = peerConnection.addTrack(audioTrack, streamIds); + dtmfSender = sender.getDtmfSender(); + + caller.setRemotePeerConnection(callee); + callee.setRemotePeerConnection(caller); + + callee.setRemoteDescription(caller.createOffer()); + caller.setRemoteDescription(callee.createAnswer()); + + caller.waitUntilConnected(); + callee.waitUntilConnected(); + } + + @AfterEach + void dispose() { + caller.close(); + callee.close(); + } + + @Test + void canInsertDtmf() { + assertNotNull(dtmfSender); + assertTrue(dtmfSender.canInsertDtmf(), "DTMF sender should be able to insert DTMF tones."); + } + + @Test + void insertDtmf() { + assertTrue(dtmfSender.insertDtmf("123", 100, 70)); + assertTrue(dtmfSender.insertDtmf("ABC", 100, 70)); + assertTrue(dtmfSender.insertDtmf("#*,", 100, 70)); + assertFalse(dtmfSender.insertDtmf("123", 30, 70)); // Duration too short + assertFalse(dtmfSender.insertDtmf("123", 100, 20)); // InterToneGap too short + } + + @Test + void getTones() { + dtmfSender.insertDtmf("123", 100, 70); + assertEquals("123", dtmfSender.tones()); + } + + @Test + void getDuration() { + assertEquals(100, dtmfSender.duration()); // Default value + + assertTrue(dtmfSender.insertDtmf("123", 120, 70)); + assertEquals(120, dtmfSender.duration()); + assertTrue(dtmfSender.insertDtmf("456", 170, 70)); + assertEquals(170, dtmfSender.duration()); + } + + @Test + void getInterToneGap() { + assertEquals(50, dtmfSender.interToneGap()); // Default value + + assertTrue(dtmfSender.insertDtmf("123", 100, 70)); + assertEquals(70, dtmfSender.interToneGap()); + assertTrue(dtmfSender.insertDtmf("456", 100, 60)); + assertEquals(60, dtmfSender.interToneGap()); + } + + @Test + void registerAndUnregisterObserver() throws InterruptedException { + TestDtmfSenderObserver observer = new TestDtmfSenderObserver(); + + dtmfSender.registerObserver(observer); + dtmfSender.insertDtmf("123", 100, 70); + + observer.waitUntilCompleted(); + + assertEquals(Arrays.asList("1", "2", "3", null), observer.getTones()); + + dtmfSender.unregisterObserver(); + dtmfSender.insertDtmf("456", 100, 70); + + Thread.sleep(500); + + assertEquals(Arrays.asList("1", "2", "3", null), observer.getTones()); // No new events + } +} \ No newline at end of file diff --git a/webrtc/src/test/java/dev/onvoid/webrtc/TestPeerConnection.java b/webrtc/src/test/java/dev/onvoid/webrtc/TestPeerConnection.java index a8b2e777..fe134f8b 100644 --- a/webrtc/src/test/java/dev/onvoid/webrtc/TestPeerConnection.java +++ b/webrtc/src/test/java/dev/onvoid/webrtc/TestPeerConnection.java @@ -18,42 +18,29 @@ import static java.util.Objects.nonNull; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CountDownLatch; -import org.junit.jupiter.api.Assertions; - /** * Convenience test class implementing the basic {@link RTCPeerConnection} - * functionality to establish a connection to a remote peer and send text - * messages via the {@link RTCDataChannel}. + * functionality to establish a connection to a remote peer. * * @author Alex Andres */ class TestPeerConnection implements PeerConnectionObserver { - private final List receivedTexts; - private final CountDownLatch connectedLatch; private RTCPeerConnection localPeerConnection; private RTCPeerConnection remotePeerConnection; - private RTCDataChannel localDataChannel; - - private RTCDataChannel remoteDataChannel; - TestPeerConnection(PeerConnectionFactory factory) { RTCConfiguration config = new RTCConfiguration(); localPeerConnection = factory.createPeerConnection(config, this); - localDataChannel = localPeerConnection.createDataChannel("dc", new RTCDataChannelInit()); - receivedTexts = new ArrayList<>(); + localPeerConnection.createDataChannel("dummy", new RTCDataChannelInit()); + connectedLatch = new CountDownLatch(1); } @@ -62,29 +49,6 @@ public void onIceCandidate(RTCIceCandidate candidate) { remotePeerConnection.addIceCandidate(candidate); } - @Override - public void onDataChannel(RTCDataChannel dataChannel) { - remoteDataChannel = dataChannel; - remoteDataChannel.registerObserver(new RTCDataChannelObserver() { - - @Override - public void onBufferedAmountChange(long previousAmount) { } - - @Override - public void onStateChange() { } - - @Override - public void onMessage(RTCDataChannelBuffer buffer) { - try { - decodeMessage(buffer); - } - catch (Exception e) { - Assertions.fail(e); - } - } - }); - } - @Override public void onConnectionChange(RTCPeerConnectionState state) { if (state == RTCPeerConnectionState.CONNECTED) { @@ -135,56 +99,14 @@ void setRemoteDescription(RTCSessionDescription description) throws Exception { setObserver.get(); } - void sendTextMessage(String message) throws Exception { - ByteBuffer data = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)); - RTCDataChannelBuffer buffer = new RTCDataChannelBuffer(data, false); - - localDataChannel.send(buffer); - } - RTCPeerConnection getPeerConnection() { return localPeerConnection; } - List getReceivedTexts() { - return receivedTexts; - } - void close() { - if (nonNull(localDataChannel)) { - localDataChannel.unregisterObserver(); - localDataChannel.close(); - localDataChannel.dispose(); - localDataChannel = null; - } - if (nonNull(remoteDataChannel)) { - remoteDataChannel.unregisterObserver(); - remoteDataChannel.close(); - remoteDataChannel.dispose(); - remoteDataChannel = null; - } if (nonNull(localPeerConnection)) { localPeerConnection.close(); localPeerConnection = null; } } - - private void decodeMessage(RTCDataChannelBuffer buffer) { - ByteBuffer byteBuffer = buffer.data; - byte[] payload; - - if (byteBuffer.hasArray()) { - payload = byteBuffer.array(); - } - else { - payload = new byte[byteBuffer.limit()]; - - byteBuffer.get(payload); - } - - String text = new String(payload, StandardCharsets.UTF_8); - - receivedTexts.add(text); - } - }