diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d83557933..ca423562ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog +[0.13.0] - 2025-03-24 + +* [All] feat: add getBufferedAmount for DataChannel. (#1796) +* [Windows] fix: fixed non-platform thread call error. (#1795) + +[0.12.12+hotfix.1] - 2025-03-12 + +* [Android] fix: fixed video not rendered after resume from background. + [0.12.12] - 2025-03-09 * [Android] feat: Migrate to the new Surface API. (#1726) diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java b/android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java index 60931b0116..b5c3df69ef 100644 --- a/android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java +++ b/android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java @@ -482,6 +482,12 @@ public void onMethodCall(MethodCall call, @NonNull Result notSafeResult) { createDataChannel(peerConnectionId, label, new ConstraintsMap(dataChannelDict), result); break; } + case "dataChannelGetBufferedAmount": { + String peerConnectionId = call.argument("peerConnectionId"); + String dataChannelId = call.argument("dataChannelId"); + dataChannelGetBufferedAmount(peerConnectionId, dataChannelId, result); + break; + } case "dataChannelSend": { String peerConnectionId = call.argument("peerConnectionId"); String dataChannelId = call.argument("dataChannelId"); @@ -2039,6 +2045,17 @@ public void dataChannelSend(String peerConnectionId, String dataChannelId, ByteB } } + public void dataChannelGetBufferedAmount(String peerConnectionId, String dataChannelId, Result result) { + PeerConnectionObserver pco + = mPeerConnectionObservers.get(peerConnectionId); + if (pco == null || pco.getPeerConnection() == null) { + Log.d(TAG, "dataChannelGetBufferedAmount() peerConnection is null"); + resultError("dataChannelGetBufferedAmount", "peerConnection is null", result); + } else { + pco.dataChannelGetBufferedAmount(dataChannelId, result); + } + } + public void dataChannelClose(String peerConnectionId, String dataChannelId) { // Forward to PeerConnectionObserver which deals with DataChannels // because DataChannel is owned by PeerConnection. diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/PeerConnectionObserver.java b/android/src/main/java/com/cloudwebrtc/webrtc/PeerConnectionObserver.java index a4e8736268..9c36dce354 100755 --- a/android/src/main/java/com/cloudwebrtc/webrtc/PeerConnectionObserver.java +++ b/android/src/main/java/com/cloudwebrtc/webrtc/PeerConnectionObserver.java @@ -168,6 +168,18 @@ void dataChannelSend(String dataChannelId, ByteBuffer byteBuffer, Boolean isBina } } + void dataChannelGetBufferedAmount(String dataChannelId, Result result) { + DataChannel dataChannel = dataChannels.get(dataChannelId); + if (dataChannel != null) { + ConstraintsMap params = new ConstraintsMap(); + params.putLong("bufferedAmount", dataChannel.bufferedAmount()); + result.success(params.toMap()); + } else { + Log.d(TAG, "dataChannelGetBufferedAmount() dataChannel is null"); + resultError("dataChannelGetBufferedAmount", "DataChannel is null", result); + } + } + RtpTransceiver getRtpTransceiverById(String id) { RtpTransceiver transceiver = transceivers.get(id); if (null == transceiver) { diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/SurfaceTextureRenderer.java b/android/src/main/java/com/cloudwebrtc/webrtc/SurfaceTextureRenderer.java index 60cbd6de68..c4daa8acdd 100755 --- a/android/src/main/java/com/cloudwebrtc/webrtc/SurfaceTextureRenderer.java +++ b/android/src/main/java/com/cloudwebrtc/webrtc/SurfaceTextureRenderer.java @@ -134,6 +134,7 @@ public void surfaceDestroyed() { final CountDownLatch completionLatch = new CountDownLatch(1); releaseEglSurface(completionLatch::countDown); ThreadUtils.awaitUninterruptibly(completionLatch); + surface = null; } // Update frame dimensions and report any changes to |rendererEvents|. diff --git a/common/cpp/include/flutter_common.h b/common/cpp/include/flutter_common.h index f82054d68d..50e6097bf9 100644 --- a/common/cpp/include/flutter_common.h +++ b/common/cpp/include/flutter_common.h @@ -12,8 +12,10 @@ #include #include -#include +#include #include +#include +#include typedef flutter::EncodableValue EncodableValue; typedef flutter::EncodableMap EncodableMap; @@ -27,6 +29,8 @@ typedef flutter::EventSink EventSink; typedef flutter::MethodCall MethodCall; typedef flutter::MethodResult MethodResult; +class TaskRunner; + // foo.StringValue() becomes std::get(foo) // foo.IsString() becomes std::holds_alternative(foo) @@ -90,7 +94,8 @@ inline double findDouble(const EncodableMap& map, const std::string& key) { return 0.0; } -inline std::optional maybeFindDouble(const EncodableMap& map, const std::string& key) { +inline std::optional maybeFindDouble(const EncodableMap& map, + const std::string& key) { auto it = map.find(EncodableValue(key)); if (it != map.end() && TypeIs(it->second)) return GetValue(it->second); @@ -171,6 +176,7 @@ class EventChannelProxy { public: static std::unique_ptr Create( BinaryMessenger* messenger, + TaskRunner* task_runner, const std::string& channelName); virtual ~EventChannelProxy() = default; diff --git a/common/cpp/include/flutter_data_channel.h b/common/cpp/include/flutter_data_channel.h index ccffa511cd..1e5bfd1584 100644 --- a/common/cpp/include/flutter_data_channel.h +++ b/common/cpp/include/flutter_data_channel.h @@ -10,6 +10,7 @@ class FlutterRTCDataChannelObserver : public RTCDataChannelObserver { public: FlutterRTCDataChannelObserver(scoped_refptr data_channel, BinaryMessenger* messenger, + TaskRunner* task_runner, const std::string& channel_name); virtual ~FlutterRTCDataChannelObserver(); @@ -39,6 +40,9 @@ class FlutterDataChannel { const EncodableValue& data, std::unique_ptr); + void DataChannelGetBufferedAmount(RTCDataChannel* data_channel, + std::unique_ptr result); + void DataChannelClose(RTCDataChannel* data_channel, const std::string& data_channel_uuid, std::unique_ptr); diff --git a/common/cpp/include/flutter_frame_cryptor.h b/common/cpp/include/flutter_frame_cryptor.h index 111b2f6abf..36756272f9 100644 --- a/common/cpp/include/flutter_frame_cryptor.h +++ b/common/cpp/include/flutter_frame_cryptor.h @@ -10,8 +10,8 @@ namespace flutter_webrtc_plugin { class FlutterFrameCryptorObserver : public libwebrtc::RTCFrameCryptorObserver { public: - FlutterFrameCryptorObserver(BinaryMessenger* messenger,const std::string& channelName) - : event_channel_(EventChannelProxy::Create(messenger, channelName)) {} + FlutterFrameCryptorObserver(BinaryMessenger* messenger, TaskRunner* task_runner, const std::string& channelName) + : event_channel_(EventChannelProxy::Create(messenger, task_runner, channelName)) {} void OnFrameCryptionStateChanged( const string participant_id, libwebrtc::RTCFrameCryptionState state); diff --git a/common/cpp/include/flutter_peerconnection.h b/common/cpp/include/flutter_peerconnection.h index 5efd1e5a45..699823dfdc 100644 --- a/common/cpp/include/flutter_peerconnection.h +++ b/common/cpp/include/flutter_peerconnection.h @@ -11,6 +11,7 @@ class FlutterPeerConnectionObserver : public RTCPeerConnectionObserver { FlutterPeerConnectionObserver(FlutterWebRTCBase* base, scoped_refptr peerconnection, BinaryMessenger* messenger, + TaskRunner* task_runner, const std::string& channel_name, std::string& peerConnectionId); diff --git a/common/cpp/include/flutter_video_renderer.h b/common/cpp/include/flutter_video_renderer.h index 41bec0c4de..b2454f8458 100644 --- a/common/cpp/include/flutter_video_renderer.h +++ b/common/cpp/include/flutter_video_renderer.h @@ -22,6 +22,7 @@ class FlutterVideoRenderer void initialize(TextureRegistrar* registrar, BinaryMessenger* messenger, + TaskRunner* task_runner, std::unique_ptr texture, int64_t texture_id); diff --git a/common/cpp/include/flutter_webrtc.h b/common/cpp/include/flutter_webrtc.h index 5886a67dff..573956b9aa 100644 --- a/common/cpp/include/flutter_webrtc.h +++ b/common/cpp/include/flutter_webrtc.h @@ -21,6 +21,8 @@ class FlutterWebRTCPlugin : public flutter::Plugin { virtual BinaryMessenger* messenger() = 0; virtual TextureRegistrar* textures() = 0; + + virtual TaskRunner* task_runner() = 0; }; class FlutterWebRTC : public FlutterWebRTCBase, diff --git a/common/cpp/include/flutter_webrtc_base.h b/common/cpp/include/flutter_webrtc_base.h index 9d24455df7..9edabc7680 100644 --- a/common/cpp/include/flutter_webrtc_base.h +++ b/common/cpp/include/flutter_webrtc_base.h @@ -43,7 +43,7 @@ class FlutterWebRTCBase { enum ParseConstraintType { kMandatory, kOptional }; public: - FlutterWebRTCBase(BinaryMessenger* messenger, TextureRegistrar* textures); + FlutterWebRTCBase(BinaryMessenger* messenger, TextureRegistrar* textures, TaskRunner* task_runner); ~FlutterWebRTCBase(); std::string GenerateUUID(); @@ -122,6 +122,7 @@ class FlutterWebRTCBase { protected: BinaryMessenger* messenger_; + TaskRunner *task_runner_; TextureRegistrar* textures_; std::unique_ptr event_channel_; }; diff --git a/common/cpp/include/task_runner.h b/common/cpp/include/task_runner.h new file mode 100644 index 0000000000..74c510c581 --- /dev/null +++ b/common/cpp/include/task_runner.h @@ -0,0 +1,17 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + #ifndef PACKAGES_FLUTTER_WEBRTC_TASK_RUNNER_H_ + #define PACKAGES_FLUTTER_WEBRTC_TASK_RUNNER_H_ + + #include + + using TaskClosure = std::function; + + class TaskRunner { + public: + virtual void EnqueueTask(TaskClosure task) = 0; + virtual ~TaskRunner() = default; + }; + + #endif // PACKAGES_FLUTTER_WEBRTC_TASK_RUNNER_H_ \ No newline at end of file diff --git a/common/cpp/src/flutter_common.cc b/common/cpp/src/flutter_common.cc index c967f2eb12..1daa606a17 100644 --- a/common/cpp/src/flutter_common.cc +++ b/common/cpp/src/flutter_common.cc @@ -1,4 +1,7 @@ #include "flutter_common.h" +#include "task_runner.h" + +#include class MethodCallProxyImpl : public MethodCallProxy { public: @@ -66,56 +69,75 @@ std::unique_ptr MethodResultProxy::Create( } class EventChannelProxyImpl : public EventChannelProxy { - public: - EventChannelProxyImpl(BinaryMessenger* messenger, - const std::string& channelName) - : channel_(std::make_unique( - messenger, - channelName, - &flutter::StandardMethodCodec::GetInstance())) { - auto handler = std::make_unique< - flutter::StreamHandlerFunctions>( - [&](const EncodableValue* arguments, - std::unique_ptr>&& events) - -> std::unique_ptr> { - sink_ = std::move(events); - for (auto& event : event_queue_) { - sink_->Success(event); - } - event_queue_.clear(); - on_listen_called_ = true; - return nullptr; - }, - [&](const EncodableValue* arguments) - -> std::unique_ptr> { - on_listen_called_ = false; - return nullptr; - }); - - channel_->SetStreamHandler(std::move(handler)); - } - - virtual ~EventChannelProxyImpl() {} - - void Success(const EncodableValue& event, bool cache_event = true) override { - if (on_listen_called_) { + public: + EventChannelProxyImpl(BinaryMessenger* messenger, + TaskRunner* task_runner, + const std::string& channelName) + : channel_(std::make_unique( + messenger, + channelName, + &flutter::StandardMethodCodec::GetInstance())), + task_runner_(task_runner) { + auto handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [&](const EncodableValue* arguments, + std::unique_ptr>&& events) + -> std::unique_ptr> { + sink_ = std::move(events); + std::weak_ptr weak_sink = sink_; + for (auto& event : event_queue_) { + PostEvent(event); + } + event_queue_.clear(); + on_listen_called_ = true; + return nullptr; + }, + [&](const EncodableValue* arguments) + -> std::unique_ptr> { + on_listen_called_ = false; + return nullptr; + }); + + channel_->SetStreamHandler(std::move(handler)); + } + + virtual ~EventChannelProxyImpl() {} + + void Success(const EncodableValue& event, bool cache_event = true) override { + if (on_listen_called_) { + PostEvent(event); + } else { + if (cache_event) { + event_queue_.push_back(event); + } + } + } + + void PostEvent(const EncodableValue& event) { + if(task_runner_) { + std::weak_ptr weak_sink = sink_; + task_runner_->EnqueueTask([weak_sink, event]() { + auto sink = weak_sink.lock(); + if (sink) { + sink->Success(event); + } + }); + } else { sink_->Success(event); - } else { - if (cache_event) { - event_queue_.push_back(event); - } - } - } - - private: - std::unique_ptr channel_; - std::unique_ptr sink_; - std::list event_queue_; - bool on_listen_called_ = false; -}; + } + } + + private: + std::unique_ptr channel_; + std::shared_ptr> sink_; + std::list event_queue_; + bool on_listen_called_ = false; + TaskRunner* task_runner_; + }; std::unique_ptr EventChannelProxy::Create( BinaryMessenger* messenger, + TaskRunner* task_runner, const std::string& channelName) { - return std::make_unique(messenger, channelName); -} + return std::make_unique(messenger, task_runner, channelName); +} \ No newline at end of file diff --git a/common/cpp/src/flutter_data_channel.cc b/common/cpp/src/flutter_data_channel.cc index f333d42a6f..37afd12b54 100644 --- a/common/cpp/src/flutter_data_channel.cc +++ b/common/cpp/src/flutter_data_channel.cc @@ -7,8 +7,9 @@ namespace flutter_webrtc_plugin { FlutterRTCDataChannelObserver::FlutterRTCDataChannelObserver( scoped_refptr data_channel, BinaryMessenger* messenger, + TaskRunner* task_runner, const std::string& channelName) - : event_channel_(EventChannelProxy::Create(messenger, channelName)), + : event_channel_(EventChannelProxy::Create(messenger, task_runner, channelName)), data_channel_(data_channel) { data_channel_->RegisterObserver(this); } @@ -53,7 +54,7 @@ void FlutterDataChannel::CreateDataChannel( "FlutterWebRTC/dataChannelEvent" + peerConnectionId + uuid; std::unique_ptr observer( - new FlutterRTCDataChannelObserver(data_channel, base_->messenger_, + new FlutterRTCDataChannelObserver(data_channel, base_->messenger_, base_->task_runner_, event_channel)); base_->lock(); @@ -86,6 +87,13 @@ void FlutterDataChannel::DataChannelSend( result->Success(); } +void FlutterDataChannel::DataChannelGetBufferedAmount(RTCDataChannel* data_channel, + std::unique_ptr result) { + EncodableMap params; + params[EncodableValue("bufferedAmount")] = EncodableValue((int64_t)data_channel->buffered_amount()); + result->Success(EncodableValue(params)); +} + void FlutterDataChannel::DataChannelClose( RTCDataChannel* data_channel, const std::string& data_channel_uuid, diff --git a/common/cpp/src/flutter_frame_cryptor.cc b/common/cpp/src/flutter_frame_cryptor.cc index 975f400751..a9e44e9bd6 100644 --- a/common/cpp/src/flutter_frame_cryptor.cc +++ b/common/cpp/src/flutter_frame_cryptor.cc @@ -166,7 +166,7 @@ void FlutterFrameCryptor::FrameCryptorFactoryCreateFrameCryptor( keyProvider); std::string event_channel = "FlutterWebRTC/frameCryptorEvent" + uuid; - scoped_refptr observer(new RefCountedObject(base_->messenger_, event_channel)); + scoped_refptr observer(new RefCountedObject(base_->messenger_, base_->task_runner_, event_channel)); frameCryptor->RegisterRTCFrameCryptorObserver(observer); @@ -192,7 +192,7 @@ void FlutterFrameCryptor::FrameCryptorFactoryCreateFrameCryptor( std::string event_channel = "FlutterWebRTC/frameCryptorEvent" + uuid; - scoped_refptr observer(new RefCountedObject(base_->messenger_, event_channel)); + scoped_refptr observer(new RefCountedObject(base_->messenger_, base_->task_runner_, event_channel)); frameCryptor->RegisterRTCFrameCryptorObserver(observer.get()); diff --git a/common/cpp/src/flutter_peerconnection.cc b/common/cpp/src/flutter_peerconnection.cc index d77af9c5f2..691ec29f19 100644 --- a/common/cpp/src/flutter_peerconnection.cc +++ b/common/cpp/src/flutter_peerconnection.cc @@ -338,6 +338,7 @@ void FlutterPeerConnection::CreateRTCPeerConnection( std::unique_ptr observer( new FlutterPeerConnectionObserver(base_, pc, base_->messenger_, + base_->task_runner_, event_channel, uuid)); base_->peerconnection_observers_[uuid] = std::move(observer); @@ -1118,9 +1119,10 @@ FlutterPeerConnectionObserver::FlutterPeerConnectionObserver( FlutterWebRTCBase* base, scoped_refptr peerconnection, BinaryMessenger* messenger, + TaskRunner* task_runner, const std::string& channel_name, std::string& peerConnectionId) - : event_channel_(EventChannelProxy::Create(messenger, channel_name)), + : event_channel_(EventChannelProxy::Create(messenger, task_runner, channel_name)), peerconnection_(peerconnection), base_(base), id_(peerConnectionId) { @@ -1326,6 +1328,7 @@ void FlutterPeerConnectionObserver::OnDataChannel( std::unique_ptr observer( new FlutterRTCDataChannelObserver(data_channel, base_->messenger_, + base_->task_runner_, event_channel)); base_->lock(); diff --git a/common/cpp/src/flutter_video_renderer.cc b/common/cpp/src/flutter_video_renderer.cc index e7e1774a39..77c5dd12c6 100644 --- a/common/cpp/src/flutter_video_renderer.cc +++ b/common/cpp/src/flutter_video_renderer.cc @@ -7,6 +7,7 @@ FlutterVideoRenderer::~FlutterVideoRenderer() {} void FlutterVideoRenderer::initialize( TextureRegistrar* registrar, BinaryMessenger* messenger, + TaskRunner* task_runner, std::unique_ptr texture, int64_t trxture_id) { registrar_ = registrar; @@ -14,7 +15,7 @@ void FlutterVideoRenderer::initialize( texture_id_ = trxture_id; std::string channel_name = "FlutterWebRTC/Texture" + std::to_string(texture_id_); - event_channel_ = EventChannelProxy::Create(messenger, channel_name); + event_channel_ = EventChannelProxy::Create(messenger, task_runner, channel_name); } const FlutterDesktopPixelBuffer* FlutterVideoRenderer::CopyPixelBuffer( @@ -121,7 +122,7 @@ void FlutterVideoRendererManager::CreateVideoRendererTexture( })); auto texture_id = base_->textures_->RegisterTexture(textureVariant.get()); - texture->initialize(base_->textures_, base_->messenger_, + texture->initialize(base_->textures_, base_->messenger_, base_->task_runner_, std::move(textureVariant), texture_id); renderers_[texture_id] = texture; EncodableMap params; diff --git a/common/cpp/src/flutter_webrtc.cc b/common/cpp/src/flutter_webrtc.cc index 77cddc1bd4..a402e47ed9 100644 --- a/common/cpp/src/flutter_webrtc.cc +++ b/common/cpp/src/flutter_webrtc.cc @@ -6,7 +6,8 @@ namespace flutter_webrtc_plugin { FlutterWebRTC::FlutterWebRTC(FlutterWebRTCPlugin* plugin) : FlutterWebRTCBase::FlutterWebRTCBase(plugin->messenger(), - plugin->textures()), + plugin->textures(), + plugin->task_runner()), FlutterVideoRendererManager::FlutterVideoRendererManager(this), FlutterMediaStream::FlutterMediaStream(this), FlutterPeerConnection::FlutterPeerConnection(this), @@ -351,6 +352,29 @@ void FlutterWebRTC::HandleMethodCall( return; } DataChannelSend(data_channel, type, data, std::move(result)); + } else if (method_call.method_name().compare("dataChannelGetBufferedAmount") == 0) { + if (!method_call.arguments()) { + result->Error("Bad Arguments", "Null constraints arguments received"); + return; + } + const EncodableMap params = + GetValue(*method_call.arguments()); + const std::string peerConnectionId = findString(params, "peerConnectionId"); + RTCPeerConnection* pc = PeerConnectionForId(peerConnectionId); + if (pc == nullptr) { + result->Error("dataChannelGetBufferedAmountFailed", + "dataChannelGetBufferedAmount() peerConnection is null"); + return; + } + + const std::string dataChannelId = findString(params, "dataChannelId"); + RTCDataChannel* data_channel = DataChannelForId(dataChannelId); + if (data_channel == nullptr) { + result->Error("dataChannelGetBufferedAmountFailed", + "dataChannelGetBufferedAmount() data_channel is null"); + return; + } + DataChannelGetBufferedAmount(data_channel, std::move(result)); } else if (method_call.method_name().compare("dataChannelClose") == 0) { if (!method_call.arguments()) { result->Error("Bad Arguments", "Null constraints arguments received"); diff --git a/common/cpp/src/flutter_webrtc_base.cc b/common/cpp/src/flutter_webrtc_base.cc index 901ba384eb..a8c184ba15 100644 --- a/common/cpp/src/flutter_webrtc_base.cc +++ b/common/cpp/src/flutter_webrtc_base.cc @@ -8,14 +8,15 @@ namespace flutter_webrtc_plugin { const char* kEventChannelName = "FlutterWebRTC.Event"; FlutterWebRTCBase::FlutterWebRTCBase(BinaryMessenger* messenger, - TextureRegistrar* textures) - : messenger_(messenger), textures_(textures) { + TextureRegistrar* textures, + TaskRunner *task_runner) + : messenger_(messenger), task_runner_(task_runner), textures_(textures) { LibWebRTC::Initialize(); factory_ = LibWebRTC::CreateRTCPeerConnectionFactory(); audio_device_ = factory_->GetAudioDevice(); video_device_ = factory_->GetVideoDevice(); desktop_device_ = factory_->GetDesktopDevice(); - event_channel_ = EventChannelProxy::Create(messenger_, kEventChannelName); + event_channel_ = EventChannelProxy::Create(messenger_, task_runner_, kEventChannelName); } FlutterWebRTCBase::~FlutterWebRTCBase() { diff --git a/common/darwin/Classes/FlutterRTCDataChannel.h b/common/darwin/Classes/FlutterRTCDataChannel.h index 96f4b5b86a..2b1d685274 100644 --- a/common/darwin/Classes/FlutterRTCDataChannel.h +++ b/common/darwin/Classes/FlutterRTCDataChannel.h @@ -24,4 +24,7 @@ data:(nonnull NSString*)data type:(nonnull NSString*)type; +- (void)dataChannelGetBufferedAmount:(nonnull NSString*)peerConnectionId + dataChannelId:(nonnull NSString*)dataChannelId + result:(nonnull FlutterResult)result; @end diff --git a/common/darwin/Classes/FlutterRTCDataChannel.m b/common/darwin/Classes/FlutterRTCDataChannel.m index 03003fcca5..67e1083b2f 100644 --- a/common/darwin/Classes/FlutterRTCDataChannel.m +++ b/common/darwin/Classes/FlutterRTCDataChannel.m @@ -118,6 +118,21 @@ - (void)dataChannelClose:(nonnull NSString*)peerConnectionId } } +- (void)dataChannelGetBufferedAmount:(nonnull NSString*)peerConnectionId + dataChannelId:(nonnull NSString*)dataChannelId + result:(nonnull FlutterResult)result { + RTCPeerConnection* peerConnection = self.peerConnections[peerConnectionId]; + RTCDataChannel* dataChannel = peerConnection.dataChannels[dataChannelId]; + if(dataChannel == NULL || dataChannel.readyState != RTCDataChannelStateOpen) { + result([FlutterError + errorWithCode:[NSString stringWithFormat:@"%@Failed", @"dataChannelGetBufferedAmount"] + message:[NSString stringWithFormat:@"Error: dataChannel not found or not opened!"] + details:nil]); + } else { + result(@{@"bufferedAmount": @(dataChannel.bufferedAmount)}); + } +} + - (void)dataChannelSend:(nonnull NSString*)peerConnectionId dataChannelId:(nonnull NSString*)dataChannelId data:(id)data diff --git a/common/darwin/Classes/FlutterWebRTCPlugin.m b/common/darwin/Classes/FlutterWebRTCPlugin.m index 7e4867417e..4c3cf8726f 100644 --- a/common/darwin/Classes/FlutterWebRTCPlugin.m +++ b/common/darwin/Classes/FlutterWebRTCPlugin.m @@ -570,7 +570,14 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [self dataChannelSend:peerConnectionId dataChannelId:dataChannelId data:data type:type]; result(nil); - } else if ([@"dataChannelClose" isEqualToString:call.method]) { + } else if ([@"dataChannelGetBufferedAmount" isEqualToString:call.method]) { + NSDictionary* argsMap = call.arguments; + NSString* peerConnectionId = argsMap[@"peerConnectionId"]; + NSString* dataChannelId = argsMap[@"dataChannelId"]; + + [self dataChannelGetBufferedAmount:peerConnectionId dataChannelId:dataChannelId result:result]; + } + else if ([@"dataChannelClose" isEqualToString:call.method]) { NSDictionary* argsMap = call.arguments; NSString* peerConnectionId = argsMap[@"peerConnectionId"]; NSString* dataChannelId = argsMap[@"dataChannelId"]; diff --git a/elinux/flutter_webrtc_plugin.cc b/elinux/flutter_webrtc_plugin.cc index 9c2c5fd2b6..4e8a36656c 100644 --- a/elinux/flutter_webrtc_plugin.cc +++ b/elinux/flutter_webrtc_plugin.cc @@ -37,6 +37,8 @@ class FlutterWebRTCPluginImpl : public FlutterWebRTCPlugin { TextureRegistrar* textures() { return textures_; } + TaskRunner* task_runner() { return nullptr; } + private: // Creates a plugin that communicates on the given channel. FlutterWebRTCPluginImpl(PluginRegistrar* registrar, diff --git a/example/lib/src/loopback_data_channel_sample.dart b/example/lib/src/loopback_data_channel_sample.dart index 4e94b3030f..ba02873324 100644 --- a/example/lib/src/loopback_data_channel_sample.dart +++ b/example/lib/src/loopback_data_channel_sample.dart @@ -49,12 +49,14 @@ class _DataChannelLoopBackSampleState extends State { _dataChannel2Status += '\ndataChannel2: state: ${state.toString()}'; }); }; - _dataChannel2!.onMessage = (data) { + _dataChannel2!.onMessage = (data) async { + var bufferedAmount = await _dataChannel2!.getBufferedAmount(); setState(() { _dataChannel2Status += - '\ndataChannel2: Received message: ${data.text}'; + '\ndataChannel2: Received message: ${data.text}, bufferedAmount: $bufferedAmount'; }); - _dataChannel2!.send(RTCDataChannelMessage( + + await _dataChannel2!.send(RTCDataChannelMessage( '(dataChannel2 ==> dataChannel1) Hello from dataChannel2 echo !!!')); }; }; @@ -69,10 +71,12 @@ class _DataChannelLoopBackSampleState extends State { } }; - _dataChannel1!.onMessage = (data) => setState(() { - _dataChannel1Status += - '\ndataChannel1: Received message: ${data.text}'; - }); + _dataChannel1!.onMessage = (data) async { + var bufferedAmount = await _dataChannel2!.getBufferedAmount(); + _dataChannel1Status += + '\ndataChannel1: Received message: ${data.text}, bufferedAmount: $bufferedAmount'; + setState(() {}); + }; var offer = await _peerConnection1!.createOffer({}); print('peerConnection1 offer: ${offer.sdp}'); diff --git a/lib/src/native/rtc_data_channel_impl.dart b/lib/src/native/rtc_data_channel_impl.dart index 2aca9819da..2c3c84b676 100644 --- a/lib/src/native/rtc_data_channel_impl.dart +++ b/lib/src/native/rtc_data_channel_impl.dart @@ -109,6 +109,17 @@ class RTCDataChannelNative extends RTCDataChannel { } } + @override + Future getBufferedAmount() async { + final Map response = await WebRTC.invokeMethod( + 'dataChannelGetBufferedAmount', { + 'peerConnectionId': _peerConnectionId, + 'dataChannelId': _flutterId + }); + _bufferedAmount = response['bufferedAmount']; + return _bufferedAmount; + } + @override Future send(RTCDataChannelMessage message) async { await WebRTC.invokeMethod('dataChannelSend', { diff --git a/linux/flutter_webrtc_plugin.cc b/linux/flutter_webrtc_plugin.cc index 3cd15f8c1a..79ce73d0f3 100644 --- a/linux/flutter_webrtc_plugin.cc +++ b/linux/flutter_webrtc_plugin.cc @@ -37,6 +37,8 @@ class FlutterWebRTCPluginImpl : public FlutterWebRTCPlugin { TextureRegistrar* textures() { return textures_; } + TaskRunner* task_runner() { return nullptr; } + private: // Creates a plugin that communicates on the given channel. FlutterWebRTCPluginImpl(PluginRegistrar* registrar, diff --git a/pubspec.yaml b/pubspec.yaml index 56f8f8219b..5359459f74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_webrtc description: Flutter WebRTC plugin for iOS/Android/Destkop/Web, based on GoogleWebRTC. -version: 0.12.12 +version: 0.13.0 homepage: https://github.com/cloudwebrtc/flutter-webrtc environment: sdk: '>=3.3.0 <4.0.0' @@ -8,12 +8,12 @@ environment: dependencies: collection: ^1.17.0 - dart_webrtc: ^1.5.2+hotfix.1 + dart_webrtc: ^1.5.3 flutter: sdk: flutter path_provider: ^2.0.2 web: ^1.0.0 - webrtc_interface: ^1.2.1+hotfix.1 + webrtc_interface: ^1.2.2+hotfix.1 dev_dependencies: flutter_test: @@ -38,4 +38,4 @@ flutter: linux: pluginClass: FlutterWebRTCPlugin elinux: - pluginClass: FlutterWebRTCPlugin \ No newline at end of file + pluginClass: FlutterWebRTCPlugin diff --git a/third_party/libwebrtc/include/rtc_data_channel.h b/third_party/libwebrtc/include/rtc_data_channel.h index 183d5da6df..e1351959f2 100644 --- a/third_party/libwebrtc/include/rtc_data_channel.h +++ b/third_party/libwebrtc/include/rtc_data_channel.h @@ -103,6 +103,13 @@ class RTCDataChannel : public RefCountInterface { */ virtual int id() const = 0; + /** + * Returns the amount of data buffered in the data channel. + * + * @return uint64_t + */ + virtual uint64_t buffered_amount() const = 0; + /** * Returns the state of the data channel. */ diff --git a/third_party/libwebrtc/include/rtc_ice_transport.h b/third_party/libwebrtc/include/rtc_ice_transport.h index b6c1ee5a68..645f305b2d 100644 --- a/third_party/libwebrtc/include/rtc_ice_transport.h +++ b/third_party/libwebrtc/include/rtc_ice_transport.h @@ -21,7 +21,8 @@ #include "rtc_base/ref_count.h" namespace libwebrtc { -class IceTransport : public rtc::RefCountInterface { + +class IceTransport : public RefCountInterface { public: virtual IceTransport* internal() = 0; }; diff --git a/third_party/libwebrtc/lib/linux-arm64/libwebrtc.so b/third_party/libwebrtc/lib/linux-arm64/libwebrtc.so index 2b7c1251f9..22c3bde89f 100755 Binary files a/third_party/libwebrtc/lib/linux-arm64/libwebrtc.so and b/third_party/libwebrtc/lib/linux-arm64/libwebrtc.so differ diff --git a/third_party/libwebrtc/lib/linux-x64/libwebrtc.so b/third_party/libwebrtc/lib/linux-x64/libwebrtc.so index 08ce7d1c60..ae9c4eec39 100755 Binary files a/third_party/libwebrtc/lib/linux-x64/libwebrtc.so and b/third_party/libwebrtc/lib/linux-x64/libwebrtc.so differ diff --git a/third_party/libwebrtc/lib/win64/libwebrtc.dll b/third_party/libwebrtc/lib/win64/libwebrtc.dll index 04a82c0bd8..e62437621a 100644 Binary files a/third_party/libwebrtc/lib/win64/libwebrtc.dll and b/third_party/libwebrtc/lib/win64/libwebrtc.dll differ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index ffa61d788c..39ee5943ac 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(${PLUGIN_NAME} SHARED "../common/cpp/src/flutter_webrtc_base.cc" "../third_party/uuidxx/uuidxx.cc" "flutter_webrtc_plugin.cc" + "task_runner_windows.cc" ) include_directories( diff --git a/windows/flutter_webrtc_plugin.cc b/windows/flutter_webrtc_plugin.cc index f22d06efaa..bfe08328dd 100644 --- a/windows/flutter_webrtc_plugin.cc +++ b/windows/flutter_webrtc_plugin.cc @@ -2,6 +2,7 @@ #include "flutter_common.h" #include "flutter_webrtc.h" +#include "task_runner_windows.h" const char* kChannelName = "FlutterWebRTC.Method"; @@ -35,13 +36,16 @@ class FlutterWebRTCPluginImpl : public FlutterWebRTCPlugin { TextureRegistrar* textures() { return textures_; } + TaskRunner* task_runner() { return task_runner_.get(); } + private: // Creates a plugin that communicates on the given channel. FlutterWebRTCPluginImpl(PluginRegistrar* registrar, std::unique_ptr channel) : channel_(std::move(channel)), messenger_(registrar->messenger()), - textures_(registrar->texture_registrar()) { + textures_(registrar->texture_registrar()), + task_runner_(std::make_unique()) { webrtc_ = std::make_unique(this); } @@ -59,6 +63,7 @@ class FlutterWebRTCPluginImpl : public FlutterWebRTCPlugin { std::unique_ptr webrtc_; BinaryMessenger* messenger_; TextureRegistrar* textures_; + std::unique_ptr task_runner_; }; } // namespace flutter_webrtc_plugin @@ -66,7 +71,7 @@ class FlutterWebRTCPluginImpl : public FlutterWebRTCPlugin { void FlutterWebRTCPluginRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { - static auto* plugin_registrar = new flutter::PluginRegistrar(registrar); - flutter_webrtc_plugin::FlutterWebRTCPluginImpl::RegisterWithRegistrar( - plugin_registrar); + static auto* plugin_registrar = new flutter::PluginRegistrar(registrar); + flutter_webrtc_plugin::FlutterWebRTCPluginImpl::RegisterWithRegistrar( + plugin_registrar); } \ No newline at end of file diff --git a/windows/task_runner_windows.cc b/windows/task_runner_windows.cc new file mode 100644 index 0000000000..0c47ab519e --- /dev/null +++ b/windows/task_runner_windows.cc @@ -0,0 +1,106 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file. + + #include "task_runner_windows.h" + + #include + #include + + namespace flutter_webrtc_plugin { + + TaskRunnerWindows::TaskRunnerWindows() { + WNDCLASS window_class = RegisterWindowClass(); + window_handle_ = + CreateWindowEx(0, window_class.lpszClassName, L"", 0, 0, 0, 0, 0, + HWND_MESSAGE, nullptr, window_class.hInstance, nullptr); + + if (window_handle_) { + SetWindowLongPtr(window_handle_, GWLP_USERDATA, + reinterpret_cast(this)); + } else { + auto error = GetLastError(); + LPWSTR message = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), 0, NULL); + OutputDebugString(message); + LocalFree(message); + } + } + + TaskRunnerWindows::~TaskRunnerWindows() { + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + UnregisterClass(window_class_name_.c_str(), nullptr); + } + + void TaskRunnerWindows::EnqueueTask(TaskClosure task) { + { + std::lock_guard lock(tasks_mutex_); + tasks_.push(task); + } + if (!PostMessage(window_handle_, WM_NULL, 0, 0)) { + DWORD error_code = GetLastError(); + std::cerr << "Failed to post message to main thread; error_code: " + << error_code << std::endl; + } + } + + void TaskRunnerWindows::ProcessTasks() { + // Even though it would usually be sufficient to process only a single task + // whenever we receive the message, if the message queue happens to be full, + // we might not receive a message for each individual task. + for (;;) { + std::lock_guard lock(tasks_mutex_); + if (tasks_.empty()) break; + TaskClosure task = tasks_.front(); + tasks_.pop(); + task(); + } + } + + WNDCLASS TaskRunnerWindows::RegisterWindowClass() { + window_class_name_ = L"FlutterWebRTCWindowsTaskRunnerWindow"; + + WNDCLASS window_class{}; + window_class.hCursor = nullptr; + window_class.lpszClassName = window_class_name_.c_str(); + window_class.style = 0; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = nullptr; + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = WndProc; + RegisterClass(&window_class); + return window_class; + } + + LRESULT + TaskRunnerWindows::HandleMessage(UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_NULL: + ProcessTasks(); + return 0; + } + return DefWindowProcW(window_handle_, message, wparam, lparam); + } + + LRESULT TaskRunnerWindows::WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (auto* that = reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA))) { + return that->HandleMessage(message, wparam, lparam); + } else { + return DefWindowProc(window, message, wparam, lparam); + } + } + + } // namespace flutter_webrtc_plugin \ No newline at end of file diff --git a/windows/task_runner_windows.h b/windows/task_runner_windows.h new file mode 100644 index 0000000000..f86c99d3f8 --- /dev/null +++ b/windows/task_runner_windows.h @@ -0,0 +1,55 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file. + #ifndef PACKAGES_FLUTTER_WEBRTC_WINDOWS_TASK_RUNNER_WINDOW_H_ + #define PACKAGES_FLUTTER_WEBRTC_WINDOWS_TASK_RUNNER_WINDOW_H_ + + #include + + #include + #include + #include + #include + #include + + #include "task_runner.h" + + namespace flutter_webrtc_plugin { + + // Hidden HWND responsible for processing camera tasks on main thread + // Adapted from Flutter Engine, see: + // https://github.com/flutter/flutter/issues/134346#issuecomment-2141023146 + // and: + // https://github.com/flutter/engine/blob/d7c0bcfe7a30408b0722c9d47d8b0b1e4cdb9c81/shell/platform/windows/task_runner_window.h + class TaskRunnerWindows : public TaskRunner { + public: + virtual void EnqueueTask(TaskClosure task); + + TaskRunnerWindows(); + ~TaskRunnerWindows(); + + private: + void ProcessTasks(); + + WNDCLASS RegisterWindowClass(); + + LRESULT + HandleMessage(UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept; + + static LRESULT CALLBACK WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + HWND window_handle_; + std::wstring window_class_name_; + std::mutex tasks_mutex_; + std::queue tasks_; + + // Prevent copying. + TaskRunnerWindows(TaskRunnerWindows const&) = delete; + TaskRunnerWindows& operator=(TaskRunnerWindows const&) = delete; + }; + } // namespace flutter_webrtc_plugin + + #endif // PACKAGES_FLUTTER_WEBRTC_WINDOWS_TASK_RUNNER_WINDOW_H_ \ No newline at end of file