mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-05 23:40:02 +01:00
New InfoVisitor: get string to describe sources
Unified code in ImGui visitor and Player
This commit is contained in:
@@ -319,6 +319,7 @@ set(VMIX_SRCS
|
||||
SearchVisitor.cpp
|
||||
ImGuiToolkit.cpp
|
||||
ImGuiVisitor.cpp
|
||||
InfoVisitor.cpp
|
||||
GstToolkit.cpp
|
||||
GlmToolkit.cpp
|
||||
SystemToolkit.cpp
|
||||
|
||||
@@ -554,10 +554,9 @@ void ImGuiVisitor::visit (MediaSource& s)
|
||||
ImGui::Text("Video File");
|
||||
|
||||
// Media info
|
||||
MediaPlayer *mp = s.mediaplayer();
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
ImGui::Text("%s\n%d x %d px, %s", SystemToolkit::filename(s.path()).c_str(),
|
||||
mp->width(), mp->height(), mp->media().codec_name.c_str());
|
||||
s.accept(info);
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// icon (>) to open player
|
||||
@@ -591,8 +590,8 @@ void ImGuiVisitor::visit (SessionFileSource& s)
|
||||
|
||||
// info
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
ImGui::Text("%d sources in %s\n%d x %d px, RGB", s.session()->numSource(), SystemToolkit::filename(s.path()).c_str(),
|
||||
s.session()->frame()->width(), s.session()->frame()->height() );
|
||||
s.accept(info);
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// icon (>) to open player
|
||||
@@ -646,7 +645,8 @@ void ImGuiVisitor::visit (SessionGroupSource& s)
|
||||
|
||||
// info
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
ImGui::Text("%d sources in group\n%d x %d px, RGB", s.session()->numSource(), s.session()->frame()->width(), s.session()->frame()->height() );
|
||||
s.accept(info);
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// icon (>) to open player
|
||||
@@ -691,9 +691,9 @@ void ImGuiVisitor::visit (PatternSource& s)
|
||||
ImGui::Text("Pattern");
|
||||
|
||||
// stream info
|
||||
Pattern *mp = s.pattern();
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
ImGui::Text("%d x %d px, RGB", mp->width(), mp->height() );
|
||||
s.accept(info);
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// icon (>) to open player
|
||||
@@ -729,15 +729,10 @@ void ImGuiVisitor::visit (DeviceSource& s)
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Device");
|
||||
|
||||
// device info
|
||||
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device().c_str()));
|
||||
if ( !confs.empty()) {
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
ImGui::Text("%d x %d px @%.1ffps, %s %s ", best.width, best.height, fps, best.stream.c_str(), best.format.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
}
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
s.accept(info);
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// icon (>) to open player
|
||||
if ( s.playable() ) {
|
||||
@@ -772,14 +767,14 @@ void ImGuiVisitor::visit (NetworkSource& s)
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Network stream");
|
||||
|
||||
// network info
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
|
||||
ImGui::Text("%s", s.connection().c_str());
|
||||
ImGui::PopStyleColor(1);
|
||||
NetworkStream *ns = s.networkStream();
|
||||
ImGui::Text("%d x %d px, %s\nServer address %s", ns->resolution().x, ns->resolution().y,
|
||||
NetworkToolkit::protocol_name[ns->protocol()], ns->serverAddress().c_str());
|
||||
|
||||
// network info
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
s.accept(info);
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// icon (>) to open player
|
||||
@@ -809,8 +804,8 @@ void ImGuiVisitor::visit (MultiFileSource& s)
|
||||
|
||||
// information text
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
||||
ImGui::Text("%d numbered images [%d %d]\n%d x %d px, %s", s.sequence().max - s.sequence().min + 1,
|
||||
s.sequence().min, s.sequence().max, s.sequence().width, s.sequence().height, s.sequence().codec.c_str());
|
||||
s.accept(info);
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// icon (>) to open player
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
#define IMGUIVISITOR_H
|
||||
|
||||
#include "Visitor.h"
|
||||
#include "InfoVisitor.h"
|
||||
|
||||
class ImGuiVisitor: public Visitor
|
||||
{
|
||||
InfoVisitor info;
|
||||
|
||||
public:
|
||||
ImGuiVisitor();
|
||||
|
||||
|
||||
268
InfoVisitor.cpp
Normal file
268
InfoVisitor.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
#include "InfoVisitor.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Scene.h"
|
||||
#include "Primitives.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "MediaSource.h"
|
||||
#include "SessionSource.h"
|
||||
#include "PatternSource.h"
|
||||
#include "DeviceSource.h"
|
||||
#include "NetworkSource.h"
|
||||
#include "MultiFileSource.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "Settings.h"
|
||||
#include "Mixer.h"
|
||||
#include "ActionManager.h"
|
||||
|
||||
#include "BaseToolkit.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
|
||||
InfoVisitor::InfoVisitor(bool brief) : brief_(brief), current_id_(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit(Node &n)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Group &n)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Switch &n)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Scene &n)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Primitive &n)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
void InfoVisitor::visit(MediaPlayer &mp)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << SystemToolkit::filename(mp.filename()) << std::endl;
|
||||
oss << mp.width() << " x " << mp.height() << ", ";
|
||||
oss << mp.media().codec_name.substr(0, mp.media().codec_name.find_first_of(','));
|
||||
if (!mp.isImage())
|
||||
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
|
||||
}
|
||||
else {
|
||||
oss << mp.filename() << std::endl;
|
||||
oss << mp.media().codec_name << std::endl;
|
||||
oss << mp.width() << " x " << mp.height() ;
|
||||
if (!mp.isImage())
|
||||
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
|
||||
}
|
||||
information_ = oss.str();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Stream &n)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
}
|
||||
information_ = oss.str();
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit (MediaSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
s.mediaplayer()->accept(*this);
|
||||
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (SessionFileSource& s)
|
||||
{
|
||||
if (current_id_ == s.id() || s.session() == nullptr)
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << SystemToolkit::filename(s.path()) << " (";
|
||||
oss << s.session()->numSource() << " sources)" << std::endl;
|
||||
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
|
||||
oss << "RGB";
|
||||
}
|
||||
else {
|
||||
oss << s.path() << std::endl;
|
||||
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
|
||||
oss << "RGB";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (SessionGroupSource& s)
|
||||
{
|
||||
if (current_id_ == s.id() || s.session() == nullptr)
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << s.session()->numSource() << " sources in group" << std::endl;
|
||||
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
|
||||
oss << "RGB";
|
||||
}
|
||||
else {
|
||||
oss << s.session()->numSource() << " sources in group" << std::endl;
|
||||
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
|
||||
oss << "RGB";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (RenderSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
information_ = "Rendering Output";
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (CloneSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
information_ = "Clone of " + s.origin()->name();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (PatternSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << s.pattern()->width() << " x " << s.pattern()->height();
|
||||
oss << ", RGB";
|
||||
}
|
||||
else {
|
||||
std::string desc = s.stream()->description();
|
||||
oss << desc.substr(0, desc.find_first_of('!')) << std::endl;
|
||||
oss << s.pattern()->width() << " x " << s.pattern()->height();
|
||||
oss << ", RGB";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (DeviceSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device().c_str()));
|
||||
if ( !confs.empty()) {
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
|
||||
|
||||
if (brief_) {
|
||||
oss << best.width << " x " << best.height << ", ";
|
||||
oss << best.stream << " " << best.format << ", ";
|
||||
oss << std::fixed << std::setprecision(1) << fps << " fps";
|
||||
}
|
||||
else {
|
||||
oss << s.device() << std::endl;
|
||||
oss << best.width << " x " << best.height << ", ";
|
||||
oss << best.stream << " " << best.format << ", ";
|
||||
oss << std::fixed << std::setprecision(1) << fps << " fps";
|
||||
}
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (NetworkSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
NetworkStream *ns = s.networkStream();
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << ns->resolution().x << " x " << ns->resolution().y << ", ";
|
||||
oss << NetworkToolkit::protocol_name[ns->protocol()] << std::endl;
|
||||
oss << "IP " << ns->serverAddress();
|
||||
}
|
||||
else {
|
||||
oss << s.connection() << " (IP " << ns->serverAddress() << ")" << std::endl;
|
||||
oss << ns->resolution().x << " x " << ns->resolution().y << ", ";
|
||||
oss << NetworkToolkit::protocol_name[ns->protocol()];
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit (MultiFileSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << s.sequence().width << " x " << s.sequence().height << ", ";
|
||||
oss << s.sequence().codec << std::endl;
|
||||
oss << s.sequence().max - s.sequence().min + 1 << " images [";
|
||||
oss << s.sequence().min << " - " << s.sequence().max << "]";
|
||||
}
|
||||
else {
|
||||
oss << SystemToolkit::path_filename(s.sequence().location) ;
|
||||
oss << " " << s.sequence().max - s.sequence().min + 1 << " images [";
|
||||
oss << s.sequence().min << " - " << s.sequence().max << "]" << std::endl;
|
||||
oss << s.sequence().width << " x " << s.sequence().height << ", ";
|
||||
oss << s.sequence().codec;
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
37
InfoVisitor.h
Normal file
37
InfoVisitor.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef INFOVISITOR_H
|
||||
#define INFOVISITOR_H
|
||||
|
||||
#include "Visitor.h"
|
||||
|
||||
class InfoVisitor : public Visitor
|
||||
{
|
||||
std::string information_;
|
||||
bool brief_;
|
||||
uint64_t current_id_;
|
||||
|
||||
public:
|
||||
InfoVisitor(bool brief = true);
|
||||
inline std::string str() const { return information_; }
|
||||
|
||||
// Elements of Scene
|
||||
void visit (Scene& n) override;
|
||||
void visit (Node& n) override;
|
||||
void visit (Group& n) override;
|
||||
void visit (Switch& n) override;
|
||||
void visit (Primitive& n) override;
|
||||
|
||||
// Elements with attributes
|
||||
void visit (Stream& n) override;
|
||||
void visit (MediaPlayer& n) override;
|
||||
void visit (MediaSource& s) override;
|
||||
void visit (SessionFileSource& s) override;
|
||||
void visit (SessionGroupSource& s) override;
|
||||
void visit (RenderSource& s) override;
|
||||
void visit (CloneSource& s) override;
|
||||
void visit (PatternSource& s) override;
|
||||
void visit (DeviceSource& s) override;
|
||||
void visit (NetworkSource& s) override;
|
||||
void visit (MultiFileSource& s) override;
|
||||
};
|
||||
|
||||
#endif // INFOVISITOR_H
|
||||
@@ -186,7 +186,7 @@ MediaInfo MediaPlayer::UriDiscoverer(const std::string &uri)
|
||||
if ( tags ) {
|
||||
gchar *container = NULL;
|
||||
if ( gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &container) )
|
||||
video_stream_info.codec_name += " " + std::string(container);
|
||||
video_stream_info.codec_name += ", " + std::string(container);
|
||||
if (container)
|
||||
g_free(container);
|
||||
}
|
||||
|
||||
@@ -2404,26 +2404,17 @@ void SourceController::RenderSingleSource(Source *s)
|
||||
ImGui::Text("%s %s", ICON_FA_SNOWFLAKE, GstToolkit::time_to_string(s->playtime()).c_str() );
|
||||
ImGui::PopFont();
|
||||
|
||||
StreamSource *sts = dynamic_cast<StreamSource*>(s);
|
||||
if (sts !=nullptr){
|
||||
ImGui::SetCursorScreenPos(top + ImVec2(framesize.x - ImGui::GetTextLineHeightWithSpacing(), _v_space));
|
||||
ImGui::Text(ICON_FA_INFO_CIRCLE);
|
||||
if (ImGui::IsItemHovered()){
|
||||
std::ostringstream tooltip;
|
||||
std::string desc = sts->stream()->description();
|
||||
desc = desc.substr(0, desc.find_first_of('!'));
|
||||
tooltip << desc << "\n ";
|
||||
tooltip << sts->stream()->width() << " x " << sts->stream()->height() << " px, ";
|
||||
tooltip << std::fixed << std::setprecision(2) << sts->stream()->updateFrameRate() << " fps ";
|
||||
ImGui::SetCursorScreenPos(top + ImVec2(framesize.x - ImGui::GetTextLineHeightWithSpacing(), _v_space));
|
||||
ImGui::Text(ICON_FA_INFO_CIRCLE);
|
||||
if (ImGui::IsItemHovered()){
|
||||
static InfoVisitor info(false);
|
||||
s->accept(info);
|
||||
|
||||
float tooltip_height = 2.f * ImGui::GetTextLineHeightWithSpacing();
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddRectFilled(top, top + ImVec2(framesize.x, tooltip_height), IMGUI_COLOR_OVERLAY);
|
||||
|
||||
ImGui::SetCursorScreenPos(top + ImVec2(0, _v_space));
|
||||
ImGui::Text(" %s", tooltip.str().c_str());
|
||||
}
|
||||
float tooltip_height = 2.f * ImGui::GetTextLineHeightWithSpacing();
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddRectFilled(top, top + ImVec2(framesize.x, tooltip_height), IMGUI_COLOR_OVERLAY);
|
||||
ImGui::SetCursorScreenPos(top + ImVec2(_h_space, _v_space));
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
}
|
||||
|
||||
///
|
||||
@@ -2474,23 +2465,14 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp)
|
||||
ImGui::SetCursorScreenPos(top + ImVec2(framesize.x - ImGui::GetTextLineHeightWithSpacing(), _v_space));
|
||||
ImGui::Text(ICON_FA_INFO_CIRCLE);
|
||||
if (ImGui::IsItemHovered()){
|
||||
|
||||
std::ostringstream tooltip;
|
||||
tooltip << mp->filename() << "\n ";
|
||||
tooltip << mp->media().codec_name;
|
||||
if (Settings::application.render.gpu_decoding && !mp->hardwareDecoderName().empty() )
|
||||
tooltip << " (" << mp->hardwareDecoderName() << " hardware decoder)";
|
||||
tooltip << " \n " << mp->width() << " x " << mp->height() << " px, ";
|
||||
if ( mp->frameRate() > 1.f )
|
||||
tooltip << std::fixed << std::setprecision(2) << mp->updateFrameRate() << " / " << mp->frameRate() << " fps ";
|
||||
static InfoVisitor info(false);
|
||||
mp->accept(info);
|
||||
|
||||
float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing();
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddRectFilled(top, top + ImVec2(framesize.x, tooltip_height), IMGUI_COLOR_OVERLAY);
|
||||
|
||||
ImGui::SetCursorScreenPos(top + ImVec2(0, _v_space));
|
||||
ImGui::Text(" %s", tooltip.str().c_str());
|
||||
ImGui::SetCursorScreenPos(top + ImVec2(_h_space, _v_space));
|
||||
ImGui::Text("%s", info.str().c_str());
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@@ -63,14 +63,14 @@ public:
|
||||
virtual void visit (Handles&) {}
|
||||
virtual void visit (Symbol&) {}
|
||||
virtual void visit (Disk&) {}
|
||||
virtual void visit (Stream&) {}
|
||||
virtual void visit (MediaPlayer&) {}
|
||||
virtual void visit (Shader&) {}
|
||||
virtual void visit (ImageShader&) {}
|
||||
virtual void visit (MaskShader&) {}
|
||||
virtual void visit (ImageProcessingShader&) {}
|
||||
|
||||
// utility
|
||||
virtual void visit (Stream&) {}
|
||||
virtual void visit (MediaPlayer&) {}
|
||||
virtual void visit (MixingGroup&) {}
|
||||
virtual void visit (Source&) {}
|
||||
virtual void visit (MediaSource&) {}
|
||||
|
||||
Reference in New Issue
Block a user