Files
client-sdk-flutter/linux/livekit_plugin.cpp

287 lines
9.7 KiB
C++

// Copyright 2024 LiveKit, Inc.
//
// 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 "include/livekit_client/live_kit_plugin.h"
#include <flutter_linux/flutter_linux.h>
#include <gtk/gtk.h>
#include <sys/utsname.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar.h>
#include <flutter/standard_method_codec.h>
#include <flutter_common.h>
#include <flutter_webrtc.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <algorithm>
#include <cstring>
#include <iomanip>
#include <map>
#include <memory>
#include <sstream>
#include "audio_visualizer.h"
#include "task_runner_linux.h"
namespace livekit_client_plugin {
/// Centers the sorted bands by placing higher values in the middle.
std::vector<float> centerBands(const std::vector<float> &sortedBands) {
std::vector<float> centeredBands(sortedBands.size(), 0);
size_t leftIndex = sortedBands.size() / 2;
size_t rightIndex = leftIndex;
for (size_t index = 0; index < sortedBands.size(); ++index) {
if (index % 2 == 0) {
// Place value to the right
centeredBands[rightIndex] = sortedBands[index];
rightIndex += 1;
} else {
// Place value to the left
leftIndex -= 1;
centeredBands[leftIndex] = sortedBands[index];
}
}
return centeredBands;
}
class VisualizerSink : public libwebrtc::AudioTrackSink {
public:
VisualizerSink(BinaryMessenger *messenger, std::string event_channel_name,
libwebrtc::scoped_refptr<libwebrtc::RTCMediaTrack> media_track,
bool is_centered = false, int bar_count = 7)
: channel_(
std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
messenger, event_channel_name,
&flutter::StandardMethodCodec::GetInstance())),
media_track_(media_track), is_centered_(is_centered),
bar_count_(bar_count) {
task_runner_ = std::make_unique<livekit_client_plugin::TaskRunnerLinux>();
auto handler = std::make_unique<
flutter::StreamHandlerFunctions<flutter::EncodableValue>>(
[&](const flutter::EncodableValue *arguments,
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>>
&&events)
-> std::unique_ptr<
flutter::StreamHandlerError<flutter::EncodableValue>> {
sink_ = std::move(events);
std::weak_ptr<flutter::EventSink<flutter::EncodableValue>> weak_sink =
sink_;
for (auto &event : event_queue_) {
PostEvent(event);
}
event_queue_.clear();
on_listen_called_ = true;
return nullptr;
},
[&](const flutter::EncodableValue *arguments)
-> std::unique_ptr<
flutter::StreamHandlerError<flutter::EncodableValue>> {
on_listen_called_ = false;
return nullptr;
});
channel_->SetStreamHandler(std::move(handler));
audio_visualizer_ =
std::make_unique<AudioVisualizer>(bar_count_, is_centered_);
((libwebrtc::RTCAudioTrack *)media_track_.get())->AddSink(this);
}
~VisualizerSink() override {}
public:
void OnData(const void *audio_data, int bits_per_sample, int sample_rate,
size_t number_of_channels, size_t number_of_frames) override {
if (!on_listen_called_) {
return;
}
std::vector<float> bands;
if (audio_visualizer_->Process((const int16_t *)audio_data,
(unsigned int)number_of_frames,
float(sample_rate), bands)) {
// Post the processed data to the event sink
EncodableList bands_list = EncodableList(bands.begin(), bands.end());
Success(EncodableValue(bands_list));
}
}
void Success(const flutter::EncodableValue &event, bool cache_event = true) {
if (on_listen_called_) {
PostEvent(event);
} else {
if (cache_event) {
event_queue_.push_back(event);
}
}
}
void PostEvent(const flutter::EncodableValue &event) {
if (task_runner_) {
std::weak_ptr<flutter::EventSink<EncodableValue>> weak_sink = sink_;
task_runner_->EnqueueTask([weak_sink, event]() {
auto sink = weak_sink.lock();
if (sink) {
sink->Success(event);
}
});
} else {
sink_->Success(event);
}
}
void RemoveSink() {
((libwebrtc::RTCAudioTrack *)media_track_.get())->RemoveSink(this);
}
private:
std::unique_ptr<AudioVisualizer> audio_visualizer_;
std::unique_ptr<livekit_client_plugin::TaskRunnerLinux> task_runner_;
std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>> channel_;
std::shared_ptr<flutter::EventSink<flutter::EncodableValue>> sink_;
std::list<flutter::EncodableValue> event_queue_;
bool on_listen_called_ = false;
libwebrtc::scoped_refptr<libwebrtc::RTCMediaTrack> media_track_;
bool is_centered_ = false;
int bar_count_ = 7;
};
class LiveKitPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrar *registrar);
LiveKitPlugin(BinaryMessenger *messenger);
virtual ~LiveKitPlugin();
private:
// Called when a method is called on this plugin's channel from Dart.
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
private:
flutter_webrtc_plugin::FlutterWebRTC *webrtc_instance_ = nullptr;
std::unordered_map<std::string, std::unique_ptr<VisualizerSink>> visualizers_;
BinaryMessenger *messenger_ = nullptr;
mutable std::mutex mutex_;
};
// static
void LiveKitPlugin::RegisterWithRegistrar(flutter::PluginRegistrar *registrar) {
auto channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "livekit_client",
&flutter::StandardMethodCodec::GetInstance());
auto plugin = std::make_unique<LiveKitPlugin>(registrar->messenger());
channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto &call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});
registrar->AddPlugin(std::move(plugin));
}
LiveKitPlugin::LiveKitPlugin(BinaryMessenger *messenger)
: messenger_(messenger) {
webrtc_instance_ = flutter_webrtc_plugin_get_shared_instance();
}
LiveKitPlugin::~LiveKitPlugin() {}
void LiveKitPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (method_call.method_name().compare("startVisualizer") == 0) {
if (!method_call.arguments()) {
result->Error("Bad Arguments", "Null arguments received");
return;
}
flutter::EncodableMap params =
GetValue<flutter::EncodableMap>(*method_call.arguments());
std::string trackId = findString(params, "trackId");
std::string visualizerId = findString(params, "visualizerId");
int barCount = findInt(params, "barCount");
bool isCentered = findBoolean(params, "isCentered");
if (trackId.empty() || visualizerId.empty()) {
result->Error("Invalid Arguments",
"trackId and visualizerId are required");
return;
}
libwebrtc::scoped_refptr<libwebrtc::RTCMediaTrack> media_track =
webrtc_instance_->MediaTrackForId(trackId);
if (!media_track) {
result->Error("Track Not Found", "No media track found for the given ID");
return;
}
std::ostringstream oss;
oss << "io.livekit.audio.visualizer/eventChannel-" << trackId << "-"
<< visualizerId;
mutex_.lock();
visualizers_[visualizerId] = std::make_unique<VisualizerSink>(
messenger_, oss.str(), media_track, isCentered, barCount);
mutex_.unlock();
result->Success(flutter::EncodableValue(true));
} else if (method_call.method_name().compare("stopVisualizer") == 0) {
if (!method_call.arguments()) {
result->Error("Bad Arguments", "Null arguments received");
return;
}
flutter::EncodableMap args =
GetValue<flutter::EncodableMap>(*method_call.arguments());
std::string trackId = findString(args, "trackId");
std::string visualizerId = findString(args, "visualizerId");
if (trackId.empty() || visualizerId.empty()) {
result->Error("Invalid Arguments",
"trackId and visualizerId are required");
return;
}
libwebrtc::scoped_refptr<libwebrtc::RTCMediaTrack> media_track =
webrtc_instance_->MediaTrackForId(trackId);
if (!media_track) {
result->Error("Track Not Found", "No media track found for the given ID");
return;
}
mutex_.lock();
auto it = visualizers_.find(visualizerId);
if (it != visualizers_.end()) {
it->second->RemoveSink();
visualizers_.erase(it);
mutex_.unlock();
} else {
mutex_.unlock();
result->Error("Visualizer Not Found",
"No visualizer found for the given visualizerId");
return;
}
result->Success();
} else {
result->NotImplemented();
}
}
} // namespace livekit_client_plugin
void live_kit_plugin_register_with_registrar(FlPluginRegistrar *registrar) {
static auto *plugin_registrar = new flutter::PluginRegistrar(registrar);
livekit_client_plugin::LiveKitPlugin::RegisterWithRegistrar(plugin_registrar);
}