From 809600018b350a6cd20f43d05e98fb9531c90f86 Mon Sep 17 00:00:00 2001
From: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
Date: Wed, 27 Sep 2023 12:16:34 -0300
Subject: [PATCH] filesharing: use mediaplayer

GitLab: #485
Change-Id: Ie3f129cd0cee14a97764eb22ee2b5f530a3f3023
---
 CMakeLists.txt                            |   2 +-
 bin/dbus/cx.ring.Ring.VideoManager.xml    | 109 ++++++++++++++++++++++
 bin/dbus/dbusvideomanager.hpp             |  51 ++++++++++
 configure.ac                              |   2 +-
 meson.build                               |   2 +-
 src/client/videomanager.cpp               |  61 ++++++++----
 src/client/videomanager.h                 |   6 +-
 src/jami/videomanager_interface.h         |  10 +-
 src/media/audio/audio_input.h             |   2 +-
 src/media/media_decoder.cpp               |   1 +
 src/media/media_decoder.h                 |   1 +
 src/media/media_player.cpp                |  38 +++++---
 src/media/media_player.h                  |   6 +-
 src/media/media_recorder.cpp              |  41 +++++---
 src/media/media_stream.h                  |  10 ++
 src/media/video/video_input.cpp           |  50 ++++++----
 src/media/video/video_input.h             |  11 +--
 src/media/video/video_rtp_session.cpp     |  13 +--
 src/sip/sipcall.cpp                       |  32 +++++++
 src/sip/sipcall.h                         |   2 +
 test/unitTest/call/recorder.cpp           |  23 +++--
 test/unitTest/media/test_media_player.cpp |  44 +++++----
 22 files changed, 406 insertions(+), 111 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 759b5b587d..6fef36dccc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.16)
 
 project(jami
-    VERSION 13.9.0
+    VERSION 13.10.0
     LANGUAGES C CXX)
 set(PACKAGE_NAME "Jami Daemon")
 set (CMAKE_CXX_STANDARD 17)
diff --git a/bin/dbus/cx.ring.Ring.VideoManager.xml b/bin/dbus/cx.ring.Ring.VideoManager.xml
index fac32e447c..8e27e54db1 100644
--- a/bin/dbus/cx.ring.Ring.VideoManager.xml
+++ b/bin/dbus/cx.ring.Ring.VideoManager.xml
@@ -160,6 +160,103 @@
             </arg>
         </method>
 
+        <method name="createMediaPlayer" tp:name-for-bindings="createMediaPlayer">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Create a media player</tp:docstring>
+            <arg type="s" name="path" direction="in">
+                <tp:docstring>File path</tp:docstring>
+            </arg>
+            <arg type="s" name="id" direction="out">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+        </method>
+
+        <method name="closeMediaPlayer" tp:name-for-bindings="closeMediaPlayer">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Close media player</tp:docstring>
+            <arg type="s" name="id" direction="in">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+            <arg type="b" name="state" direction="out">
+                <tp:docstring>true if successfully closed</tp:docstring>
+            </arg>
+        </method>
+
+        <method name="pausePlayer" tp:name-for-bindings="pausePlayer">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Pause media player</tp:docstring>
+            <arg type="s" name="id" direction="in">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+            <arg type="b" name="pause" direction="in">
+                <tp:docstring>true if wishes to pause player</tp:docstring>
+            </arg>
+            <arg type="b" name="state" direction="out">
+                <tp:docstring>true if successfully paused</tp:docstring>
+            </arg>
+        </method>
+
+        <method name="mutePlayerAudio" tp:name-for-bindings="mutePlayerAudio">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Mute  media player audio</tp:docstring>
+            <arg type="s" name="id" direction="in">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+            <arg type="b" name="mute" direction="in">
+                <tp:docstring>true if wishes to pause player</tp:docstring>
+            </arg>
+            <arg type="b" name="state" direction="out">
+                <tp:docstring>true if successfully muted</tp:docstring>
+            </arg>
+        </method>
+
+        <method name="playerSeekToTime" tp:name-for-bindings="playerSeekToTime">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Seek media player position</tp:docstring>
+            <arg type="s" name="id" direction="in">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+            <arg type="i" name="time" direction="in">
+                <tp:docstring>timestamp</tp:docstring>
+            </arg>
+            <arg type="b" name="state" direction="out">
+                <tp:docstring>true if successfully seeked</tp:docstring>
+            </arg>
+        </method>
+
+        <method name="getPlayerPosition" tp:name-for-bindings="getPlayerPosition">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Get media player position</tp:docstring>
+            <arg type="s" name="id" direction="in">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+            <arg type="x" name="position" direction="out">
+                <tp:docstring>position in timestamp</tp:docstring>
+            </arg>
+        </method>
+
+        <method name="getPlayerDuration" tp:name-for-bindings="getPlayerDuration">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Get media player position</tp:docstring>
+            <arg type="s" name="id" direction="in">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+            <arg type="x" name="duration" direction="out">
+                <tp:docstring>duration in timestamp</tp:docstring>
+            </arg>
+        </method>
+
+        <method name="setAutoRestart" tp:name-for-bindings="setAutoRestart">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Restar media player after it reaches the end</tp:docstring>
+            <arg type="s" name="id" direction="in">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+            <arg type="b" name="restart" direction="in">
+                <tp:docstring>true if media player is to restart automaticaly</tp:docstring>
+            </arg>
+        </method>
+
         <signal name="deviceEvent" tp:name-for-bindings="deviceEvent">
            <tp:docstring>Signal triggered by changes in the detected v4l2 devices, e.g. a camera being unplugged.</tp:docstring>
         </signal>
@@ -195,5 +292,17 @@
               <tp:docstring>Whether or not this texture belongs to a video mixer or is a single texture</tp:docstring>
             </arg>
         </signal>
+
+        <signal name="fileOpened" tp:name-for-bindings="fileOpened">
+            <tp:added version="13.10.0"/>
+            <tp:docstring>Notify clients of a new media player infos</tp:docstring>
+            <arg type="s" name="playerId">
+                <tp:docstring>Media player id</tp:docstring>
+            </arg>
+            <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="MapStringString"/>
+            <arg type="a{ss}" name="info">
+                <tp:docstring>Info from the media player</tp:docstring>
+            </arg>
+        </signal>
     </interface>
 </node>
diff --git a/bin/dbus/dbusvideomanager.hpp b/bin/dbus/dbusvideomanager.hpp
index ab3cd647aa..b4e2d5d2ab 100644
--- a/bin/dbus/dbusvideomanager.hpp
+++ b/bin/dbus/dbusvideomanager.hpp
@@ -151,6 +151,54 @@ public:
         libjami::stopLocalRecorder(filepath);
     }
 
+    std::string
+    createMediaPlayer(const std::string& path)
+    {
+        return libjami::createMediaPlayer(path);
+    }
+
+    bool
+    pausePlayer(const std::string& id, const bool& pause)
+    {
+        return libjami::pausePlayer(id, pause);
+    }
+
+    bool
+    closeMediaPlayer(const std::string& id)
+    {
+        return libjami::closeMediaPlayer(id);
+    }
+
+    bool
+    mutePlayerAudio(const std::string& id, const bool& mute)
+    {
+        return libjami::mutePlayerAudio(id, mute);
+    }
+
+    bool
+    playerSeekToTime(const std::string& id, const int& time)
+    {
+        return libjami::playerSeekToTime(id, time);
+    }
+
+    int64_t
+    getPlayerPosition(const std::string& id)
+    {
+        return libjami::getPlayerPosition(id);
+    }
+
+    int64_t
+    getPlayerDuration(const std::string& id)
+    {
+        return libjami::getPlayerDuration(id);
+    }
+
+    void
+    setAutoRestart(const std::string& id, const bool& restart)
+    {
+        libjami::setAutoRestart(id, restart);
+    }
+
 private:
 
     void
@@ -160,6 +208,7 @@ private:
 
         using libjami::exportable_serialized_callback;
         using libjami::VideoSignal;
+        using libjami::MediaPlayerSignal;
         using SharedCallback = std::shared_ptr<libjami::CallbackWrapperBase>;
 
         const std::map<std::string, SharedCallback> videoEvHandlers = {
@@ -169,6 +218,8 @@ private:
                 std::bind(&DBusVideoManager::emitDecodingStarted, this, _1, _2, _3, _4, _5)),
             exportable_serialized_callback<VideoSignal::DecodingStopped>(
                 std::bind(&DBusVideoManager::emitDecodingStopped, this, _1, _2, _3)),
+            exportable_serialized_callback<MediaPlayerSignal::FileOpened>(
+                std::bind(&DBusVideoManager::emitFileOpened, this, _1, _2)),
         };
 
         libjami::registerSignalHandlers(videoEvHandlers);
diff --git a/configure.ac b/configure.ac
index a693c7e305..236fc40429 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Jami - configure.ac
 
 dnl Process this file with autoconf to produce a configure script.
 AC_PREREQ([2.69])
-AC_INIT([Jami Daemon],[13.9.0],[jami@gnu.org],[jami])
+AC_INIT([Jami Daemon],[13.10.0],[jami@gnu.org],[jami])
 
 dnl Clear the implicit flags that default to '-g -O2', otherwise they
 dnl take precedence over the values we set via the
diff --git a/meson.build b/meson.build
index 5ac983f5dd..e61b933f12 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('jami-daemon', ['c', 'cpp'],
-        version: '13.9.0',
+        version: '13.10.0',
         license: 'GPL3+',
         default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'],
         meson_version:'>= 0.56'
diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp
index 224cc7525c..d7a56c5670 100644
--- a/src/client/videomanager.cpp
+++ b/src/client/videomanager.cpp
@@ -571,25 +571,25 @@ createMediaPlayer(const std::string& path)
 }
 
 bool
-pausePlayer(const std::string& id, bool pause)
+closeMediaPlayer(const std::string& id)
 {
-    return jami::pausePlayer(id, pause);
+    return jami::closeMediaPlayer(id);
 }
 
 bool
-closeMediaPlayer(const std::string& id)
+pausePlayer(const std::string& id, const bool& pause)
 {
-    return jami::closeMediaPlayer(id);
+    return jami::pausePlayer(id, pause);
 }
 
 bool
-mutePlayerAudio(const std::string& id, bool mute)
+mutePlayerAudio(const std::string& id, const bool& mute)
 {
     return jami::mutePlayerAudio(id, mute);
 }
 
 bool
-playerSeekToTime(const std::string& id, int time)
+playerSeekToTime(const std::string& id, const int& time)
 {
     return jami::playerSeekToTime(id, time);
 }
@@ -600,6 +600,18 @@ getPlayerPosition(const std::string& id)
     return jami::getPlayerPosition(id);
 }
 
+int64_t
+getPlayerDuration(const std::string& id)
+{
+    return jami::getPlayerDuration(id);
+}
+
+void
+setAutoRestart(const std::string& id, const bool& restart)
+{
+    jami::setAutoRestart(id, restart);
+}
+
 bool
 getDecodingAccelerated()
 {
@@ -678,19 +690,20 @@ getVideoDeviceMonitor()
 }
 
 std::shared_ptr<video::VideoInput>
-getVideoInput(const std::string& id, video::VideoInputMode inputMode)
+getVideoInput(const std::string& id, video::VideoInputMode inputMode, const std::string& sink)
 {
+    auto sinkId = sink.empty() ? id : sink;
     auto& vmgr = Manager::instance().getVideoManager();
     std::lock_guard<std::mutex> lk(vmgr.videoMutex);
-    auto it = vmgr.videoInputs.find(id);
+    auto it = vmgr.videoInputs.find(sinkId);
     if (it != vmgr.videoInputs.end()) {
         if (auto input = it->second.lock()) {
             return input;
         }
     }
 
-    auto input = std::make_shared<video::VideoInput>(inputMode, id);
-    vmgr.videoInputs[id] = input;
+    auto input = std::make_shared<video::VideoInput>(inputMode, id, sinkId);
+    vmgr.videoInputs[sinkId] = input;
     return input;
 }
 
@@ -748,13 +761,14 @@ getMediaPlayer(const std::string& id)
 std::string
 createMediaPlayer(const std::string& path)
 {
-    auto player = std::make_shared<MediaPlayer>(path);
-    if (!player->isInputValid()) {
-        return "";
+    auto player = getMediaPlayer(path);
+    if (!player) {
+        player = std::make_shared<MediaPlayer>(path);
+    } else {
+        return path;
     }
-    auto playerId = player.get()->getId();
-    Manager::instance().getVideoManager().mediaPlayers[playerId] = player;
-    return playerId;
+    Manager::instance().getVideoManager().mediaPlayers[path] = player;
+    return path;
 }
 
 bool
@@ -799,4 +813,19 @@ getPlayerPosition(const std::string& id)
     return -1;
 }
 
+int64_t
+getPlayerDuration(const std::string& id)
+{
+    if (auto player = getMediaPlayer(id))
+        return player->getPlayerDuration();
+    return -1;
+}
+
+void
+setAutoRestart(const std::string& id, bool restart)
+{
+    if (auto player = getMediaPlayer(id))
+        player->setAutoRestart(restart);
+}
+
 } // namespace jami
diff --git a/src/client/videomanager.h b/src/client/videomanager.h
index bde76f42f7..81b5a72652 100644
--- a/src/client/videomanager.h
+++ b/src/client/videomanager.h
@@ -71,7 +71,9 @@ public:
 #ifdef ENABLE_VIDEO
 video::VideoDeviceMonitor& getVideoDeviceMonitor();
 std::shared_ptr<video::VideoInput> getVideoInput(
-    const std::string& id, video::VideoInputMode inputMode = video::VideoInputMode::Undefined);
+    const std::string& id,
+    video::VideoInputMode inputMode = video::VideoInputMode::Undefined,
+    const std::string& sink = "");
 #endif
 std::shared_ptr<AudioInput> getAudioInput(const std::string& id);
 std::string createMediaPlayer(const std::string& path);
@@ -81,5 +83,7 @@ bool closeMediaPlayer(const std::string& id);
 bool mutePlayerAudio(const std::string& id, bool mute);
 bool playerSeekToTime(const std::string& id, int time);
 int64_t getPlayerPosition(const std::string& id);
+int64_t getPlayerDuration(const std::string& id);
+void setAutoRestart(const std::string& id, bool restart);
 
 } // namespace jami
diff --git a/src/jami/videomanager_interface.h b/src/jami/videomanager_interface.h
index 0e5bd77b6c..a1d822a156 100644
--- a/src/jami/videomanager_interface.h
+++ b/src/jami/videomanager_interface.h
@@ -199,10 +199,12 @@ LIBJAMI_PUBLIC bool closeVideoInput(const std::string& id);
 
 LIBJAMI_PUBLIC std::string createMediaPlayer(const std::string& path);
 LIBJAMI_PUBLIC bool closeMediaPlayer(const std::string& id);
-LIBJAMI_PUBLIC bool pausePlayer(const std::string& id, bool pause);
-LIBJAMI_PUBLIC bool mutePlayerAudio(const std::string& id, bool mute);
-LIBJAMI_PUBLIC bool playerSeekToTime(const std::string& id, int time);
-int64_t getPlayerPosition(const std::string& id);
+LIBJAMI_PUBLIC bool pausePlayer(const std::string& id, const bool& pause);
+LIBJAMI_PUBLIC bool mutePlayerAudio(const std::string& id, const bool& mute);
+LIBJAMI_PUBLIC bool playerSeekToTime(const std::string& id, const int& time);
+LIBJAMI_PUBLIC int64_t getPlayerPosition(const std::string& id);
+LIBJAMI_PUBLIC int64_t getPlayerDuration(const std::string& id);
+LIBJAMI_PUBLIC void setAutoRestart(const std::string& id, const bool& restart);
 
 LIBJAMI_PUBLIC bool registerSinkTarget(const std::string& sinkId, SinkTarget target);
 #ifdef ENABLE_SHM
diff --git a/src/media/audio/audio_input.h b/src/media/audio/audio_input.h
index e9d1d81265..d17d3bcbdd 100644
--- a/src/media/audio/audio_input.h
+++ b/src/media/audio/audio_input.h
@@ -83,7 +83,7 @@ private:
     void frameResized(std::shared_ptr<AudioFrame>&& ptr);
 
     std::string id_;
-    bool muteState_ = false;
+    bool muteState_ {false};
     uint64_t sent_samples = 0;
     mutable std::mutex fmtMutex_ {};
     AudioFormat format_;
diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp
index 224ad352cf..6dcaa471ae 100644
--- a/src/media/media_decoder.cpp
+++ b/src/media/media_decoder.cpp
@@ -24,6 +24,7 @@
 #include "media_decoder.h"
 #include "media_device.h"
 #include "media_buffer.h"
+#include "media_const.h"
 #include "media_io_handle.h"
 #include "audio/ringbuffer.h"
 #include "audio/resampler.h"
diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h
index dd8e5418b9..340c2e32d7 100644
--- a/src/media/media_decoder.h
+++ b/src/media/media_decoder.h
@@ -41,6 +41,7 @@
 #include "media_device.h"
 #include "media_stream.h"
 #include "media_buffer.h"
+#include "media_attribute.h"
 #include "noncopyable.h"
 
 #include <map>
diff --git a/src/media/media_player.cpp b/src/media/media_player.cpp
index f4250c5727..fd0c971886 100644
--- a/src/media/media_player.cpp
+++ b/src/media/media_player.cpp
@@ -28,26 +28,29 @@ namespace jami {
 
 static constexpr auto MS_PER_PACKET = std::chrono::milliseconds(20);
 
-MediaPlayer::MediaPlayer(const std::string& path)
+MediaPlayer::MediaPlayer(const std::string& resource)
     : loop_(std::bind(&MediaPlayer::configureMediaInputs, this),
             std::bind(&MediaPlayer::process, this),
             [] {})
 {
+    auto suffix = resource;
     static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
-    const auto pos = path.find(sep);
-    const auto suffix = path.substr(pos + sep.size());
+    const auto pos = resource.find(sep);
+    if (pos != std::string::npos) {
+        suffix = resource.substr(pos + sep.size());
+    }
+
+    path_ = suffix;
 
     if (access(suffix.c_str(), R_OK) != 0) {
-        JAMI_ERR() << "File '" << path << "' not available";
+        JAMI_ERR() << "File '" << path_ << "' not available";
         return;
     }
 
-    path_ = path;
-    id_ = std::to_string(rand());
-    audioInput_ = jami::getAudioInput(id_);
+    audioInput_ = jami::getAudioInput(path_);
     audioInput_->setPaused(paused_);
 #ifdef ENABLE_VIDEO
-    videoInput_ = jami::getVideoInput(id_, video::VideoInputMode::ManagedByDaemon);
+    videoInput_ = jami::getVideoInput(path_, video::VideoInputMode::ManagedByDaemon, resource);
     videoInput_->setPaused(paused_);
 #endif
 
@@ -57,7 +60,10 @@ MediaPlayer::MediaPlayer(const std::string& path)
 
 MediaPlayer::~MediaPlayer()
 {
+    pause(true);
     loop_.join();
+    audioInput_.reset();
+    videoInput_.reset();
 }
 
 bool
@@ -92,10 +98,8 @@ MediaPlayer::configureMediaInputs()
     try {
         videoStream_ = demuxer_->selectStream(AVMEDIA_TYPE_VIDEO);
         if (hasVideo()) {
-            videoInput_->setSink(id_);
             videoInput_->configureFilePlayback(path_, demuxer_, videoStream_);
             videoInput_->updateStartTime(startTime_);
-            muteAudio(true);
         }
     } catch (const std::exception& e) {
         videoInput_ = nullptr;
@@ -165,13 +169,13 @@ MediaPlayer::emitInfo()
     std::map<std::string, std::string> info {{"duration", std::to_string(fileDuration_)},
                                              {"audio_stream", std::to_string(audioStream_)},
                                              {"video_stream", std::to_string(videoStream_)}};
-    emitSignal<libjami::MediaPlayerSignal::FileOpened>(id_, info);
+    emitSignal<libjami::MediaPlayerSignal::FileOpened>(path_, info);
 }
 
 bool
 MediaPlayer::isInputValid()
 {
-    return !id_.empty();
+    return !path_.empty();
 }
 
 void
@@ -262,6 +266,8 @@ MediaPlayer::playFileFromBeginning()
         videoInput_->updateStartTime(startTime_);
     }
 #endif
+    if (autoRestart_)
+        pause(false);
 }
 
 void
@@ -281,7 +287,7 @@ MediaPlayer::flushMediaBuffers()
 const std::string&
 MediaPlayer::getId() const
 {
-    return id_;
+    return path_;
 }
 
 int64_t
@@ -293,6 +299,12 @@ MediaPlayer::getPlayerPosition() const
     return av_gettime() - startTime_ - pauseInterval_;
 }
 
+int64_t
+MediaPlayer::getPlayerDuration() const
+{
+    return fileDuration_;
+}
+
 bool
 MediaPlayer::isPaused() const
 {
diff --git a/src/media/media_player.h b/src/media/media_player.h
index 1af887bbbd..d6f141b23a 100644
--- a/src/media/media_player.h
+++ b/src/media/media_player.h
@@ -31,7 +31,7 @@ namespace jami {
 class MediaPlayer
 {
 public:
-    MediaPlayer(const std::string& path);
+    MediaPlayer(const std::string& resource);
     ~MediaPlayer();
 
     void pause(bool pause);
@@ -40,11 +40,13 @@ public:
     void muteAudio(bool mute);
     bool seekToTime(int64_t time);
     int64_t getPlayerPosition() const;
+    int64_t getPlayerDuration() const;
     bool isPaused() const;
+    void setAutoRestart(bool state) { autoRestart_ = state; }
 
 private:
     std::string path_;
-    std::string id_;
+    bool autoRestart_ {false};
 
     // media inputs
 #ifdef ENABLE_VIDEO
diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp
index 2a4c9101d3..69a10ba967 100644
--- a/src/media/media_recorder.cpp
+++ b/src/media/media_recorder.cpp
@@ -276,7 +276,16 @@ MediaRecorder::addStream(const MediaStream& ms)
         it = streams_.insert(std::make_pair(ms.name, std::move(streamPtr))).first;
         JAMI_LOG("[Recorder: {:p}] Recorder input #{}: {:s}", fmt::ptr(this), streams_.size(), ms.name);
     } else {
-        JAMI_LOG("[Recorder: {:p}] Recorder already has '{:s}' as input", fmt::ptr(this), ms.name);
+        if (ms == it->second->info)
+            JAMI_LOG("[Recorder: {:p}] Recorder already has '{:s}' as input", fmt::ptr(this), ms.name);
+        else {
+            it->second
+                = std::make_unique<StreamObserver>(ms,
+                                                   [this,
+                                                    ms](const std::shared_ptr<MediaFrame>& frame) {
+                                                       onFrame(ms.name, frame);
+                                                   });
+        }
     }
 
     if (ms.isVideo)
@@ -354,15 +363,17 @@ MediaRecorder::onFrame(const std::string& name, const std::shared_ptr<MediaFrame
                                              ms.timeBase,
                                              static_cast<AVRounding>(AV_ROUND_NEAR_INF
                                                                      | AV_ROUND_PASS_MINMAX));
-    std::unique_ptr<MediaFrame> filteredFrame;
+    std::vector<std::unique_ptr<MediaFrame>> filteredFrames;
 #ifdef ENABLE_VIDEO
     if (ms.isVideo && videoFilter_ && outputVideoFilter_) {
         std::lock_guard<std::mutex> lk(mutexFilterVideo_);
-        videoFilter_->feedInput(clone->pointer(), name);
+                videoFilter_->feedInput(clone->pointer(), name);
         auto videoFilterOutput = videoFilter_->readOutput();
         if (videoFilterOutput) {
             outputVideoFilter_->feedInput(videoFilterOutput->pointer(), "input");
-            filteredFrame = outputVideoFilter_->readOutput();
+            while (auto fFrame = outputVideoFilter_->readOutput()) {
+                filteredFrames.emplace_back(std::move(fFrame));
+            }
         }
     } else if (audioFilter_ && outputAudioFilter_) {
 #endif // ENABLE_VIDEO
@@ -371,15 +382,15 @@ MediaRecorder::onFrame(const std::string& name, const std::shared_ptr<MediaFrame
         auto audioFilterOutput = audioFilter_->readOutput();
         if (audioFilterOutput) {
             outputAudioFilter_->feedInput(audioFilterOutput->pointer(), "input");
-            filteredFrame = outputAudioFilter_->readOutput();
+            filteredFrames.emplace_back(outputAudioFilter_->readOutput());
         }
 #ifdef ENABLE_VIDEO
     }
 #endif // ENABLE_VIDEO
 
-    if (filteredFrame) {
+    for (auto& fFrame : filteredFrames) {
         std::lock_guard<std::mutex> lk(mutexFrameBuff_);
-        frameBuff_.emplace_back(std::move(filteredFrame));
+        frameBuff_.emplace_back(std::move(fFrame));
         cv_.notify_one();
     }
 }
@@ -543,9 +554,11 @@ MediaRecorder::setupVideoOutput()
     if (scaledHeight > 720)
         scaleFilter += ",scale=-2:720";
 
-    ret = outputVideoFilter_->initialize(
-        "[input]" + scaleFilter + ",pad=1280:720:(ow-iw)/2:(oh-ih)/2,format=pix_fmts=yuv420p,fps=30",
-        {secondaryFilter});
+    std::ostringstream f;
+    f << "[input]" << scaleFilter
+      << ",pad=1280:720:(ow-iw)/2:(oh-ih)/2,format=pix_fmts=yuv420p,fps=30";
+
+    ret = outputVideoFilter_->initialize(f.str(), {secondaryFilter});
 
     if (ret < 0) {
         JAMI_ERR() << "Failed to initialize output video filter";
@@ -569,17 +582,17 @@ MediaRecorder::buildVideoFilter(const std::vector<MediaStream>& peers,
     case 1: {
         auto p = peers[0];
         const constexpr int minHeight = 720;
-        const auto newFps = std::max(p.frameRate, local.frameRate);
         const bool needScale = (p.height < minHeight);
         const int newHeight = (needScale ? minHeight : p.height);
 
         // NOTE -2 means preserve aspect ratio and have the new number be even
         if (needScale)
-            v << "[" << p.name << "] fps=" << newFps << ", scale=-2:" << newHeight << " [v:m]; ";
+            v << "[" << p.name << "] fps=30, scale=-2:" << newHeight
+              << " [v:m]; ";
         else
-            v << "[" << p.name << "] fps=" << newFps << " [v:m]; ";
+            v << "[" << p.name << "] fps=30 [v:m]; ";
 
-        v << "[" << local.name << "] fps=" << newFps << ", scale=-2:" << newHeight / 5
+        v << "[" << local.name << "] fps=30, scale=-2:" << newHeight / 5
           << " [v:o]; ";
 
         v << "[v:m] [v:o] overlay=main_w-overlay_w:main_h-overlay_h"
diff --git a/src/media/media_stream.h b/src/media/media_stream.h
index 8162554b52..b2bb35a593 100644
--- a/src/media/media_stream.h
+++ b/src/media/media_stream.h
@@ -150,6 +150,16 @@ struct MediaStream
                 frameSize = f->nb_samples;
         }
     }
+
+    friend bool operator==(const MediaStream& ms1, const MediaStream& ms2)
+    {
+        return ms1.bitrate == ms2.bitrate and ms1.firstTimestamp == ms2.firstTimestamp
+               and ms1.format == ms2.format and ms1.frameRate == ms2.frameRate
+               and ms1.frameSize == ms2.frameSize and ms1.height == ms2.height
+               and ms1.isVideo == ms2.isVideo and ms1.name == ms2.name
+               and ms1.nbChannels == ms2.nbChannels and ms1.sampleRate == ms2.sampleRate
+               and ms1.timeBase == ms2.timeBase and ms1.width == ms2.width;
+    }
 };
 
 inline std::ostream&
diff --git a/src/media/video/video_input.cpp b/src/media/video/video_input.cpp
index 8670ca4889..fe4235bd60 100644
--- a/src/media/video/video_input.cpp
+++ b/src/media/video/video_input.cpp
@@ -55,7 +55,7 @@ namespace video {
 static constexpr unsigned default_grab_width = 640;
 static constexpr unsigned default_grab_height = 480;
 
-VideoInput::VideoInput(VideoInputMode inputMode, const std::string& id_)
+VideoInput::VideoInput(VideoInputMode inputMode, const std::string& resource, const std::string& sink)
     : VideoGenerator::VideoGenerator()
     , loop_(std::bind(&VideoInput::setup, this),
             std::bind(&VideoInput::process, this),
@@ -69,8 +69,8 @@ VideoInput::VideoInput(VideoInputMode inputMode, const std::string& id_)
         inputMode_ = VideoInputMode::ManagedByDaemon;
 #endif
     }
-    sink_ = Manager::instance().createSinkClient(id_);
-    switchInput(id_);
+    sink_ = Manager::instance().createSinkClient(sink.empty() ? resource : sink);
+    switchInput(resource);
 }
 
 VideoInput::~VideoInput()
@@ -249,10 +249,29 @@ VideoInput::configureFilePlayback(const std::string&,
 
     decoder_ = std::move(decoder);
     playingFile_ = true;
-    loop_.start();
 
+    // For DBUS it is imperative that we start the sink before setting the frame size
+    sink_->start();
     /* Signal the client about readable sink */
     sink_->setFrameSize(decoder_->getWidth(), decoder_->getHeight());
+
+    loop_.start();
+
+    decOpts_.width = ((decoder_->getWidth() >> 3) << 3);
+    decOpts_.height = ((decoder_->getHeight() >> 3) << 3);
+    decOpts_.framerate = decoder_->getFps();
+    AVPixelFormat fmt = decoder_->getPixelFormat();
+    if (fmt != AV_PIX_FMT_NONE) {
+        decOpts_.pixel_format = av_get_pix_fmt_name(fmt);
+    } else {
+        JAMI_WARN("Could not determine pixel format, using default");
+        decOpts_.pixel_format = av_get_pix_fmt_name(AV_PIX_FMT_YUV420P);
+    }
+
+    if (onSuccessfulSetup_)
+        onSuccessfulSetup_(MEDIA_VIDEO, 0);
+    foundDecOpts(decOpts_);
+    futureDecOpts_ = foundDecOpts_.get_future().share();
 }
 
 void
@@ -579,7 +598,7 @@ void
 VideoInput::restart()
 {
     if (loop_.isStopping())
-        switchInput(currentResource_);
+        switchInput(resource_);
 }
 
 std::shared_future<DeviceParams>
@@ -592,14 +611,14 @@ VideoInput::switchInput(const std::string& resource)
         return {};
     }
 
-    currentResource_ = resource;
+    resource_ = resource;
     decOptsFound_ = false;
 
     std::promise<DeviceParams> p;
     foundDecOpts_.swap(p);
 
     // Switch off video input?
-    if (resource.empty()) {
+    if (resource_.empty()) {
         clearOptions();
         futureDecOpts_ = foundDecOpts_.get_future();
         startLoop();
@@ -609,15 +628,15 @@ VideoInput::switchInput(const std::string& resource)
     // Supported MRL schemes
     static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
 
-    const auto pos = resource.find(sep);
+    const auto pos = resource_.find(sep);
     if (pos == std::string::npos)
         return {};
 
-    const auto prefix = resource.substr(0, pos);
-    if ((pos + sep.size()) >= resource.size())
+    const auto prefix = resource_.substr(0, pos);
+    if ((pos + sep.size()) >= resource_.size())
         return {};
 
-    const auto suffix = resource.substr(pos + sep.size());
+    const auto suffix = resource_.substr(pos + sep.size());
 
     bool ready = false;
 
@@ -680,18 +699,13 @@ VideoInput::setSink(const std::string& sinkId)
 }
 
 void
-VideoInput::setFrameSize(const int width, const int height)
+VideoInput::setupSink(const int width, const int height)
 {
+    setup();
     /* Signal the client about readable sink */
     sink_->setFrameSize(width, height);
 }
 
-void
-VideoInput::setupSink()
-{
-    setup();
-}
-
 void
 VideoInput::stopSink()
 {
diff --git a/src/media/video/video_input.h b/src/media/video/video_input.h
index 658c16f8d0..bf6126e326 100644
--- a/src/media/video/video_input.h
+++ b/src/media/video/video_input.h
@@ -57,11 +57,12 @@ class VideoInput : public VideoGenerator
 {
 public:
     VideoInput(VideoInputMode inputMode = VideoInputMode::Undefined,
-               const std::string& id_ = "local");
+               const std::string& resource = "local",
+               const std::string& sink = "");
     ~VideoInput();
 
     // as VideoGenerator
-    const std::string& getName() const { return currentResource_; }
+    const std::string& getName() const { return resource_; }
     int getWidth() const;
     int getHeight() const;
     AVPixelFormat getPixelFormat() const;
@@ -83,8 +84,7 @@ public:
     void flushBuffers();
     void setPaused(bool paused) { paused_ = paused; }
     void setSeekTime(int64_t time);
-    void setFrameSize(const int width, const int height);
-    void setupSink();
+    void setupSink(const int width, const int height);
     void stopSink();
 
     void setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb);
@@ -117,8 +117,7 @@ private:
 
     std::shared_future<DeviceParams> switchInput(const std::string& resource);
 
-    std::string id_;
-    std::string currentResource_;
+    std::string resource_;
     std::atomic<bool> switchPending_ = {false};
     std::atomic_bool isStopped_ = {false};
 
diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp
index b016c4b37f..3c2e125e32 100644
--- a/src/media/video/video_rtp_session.cpp
+++ b/src/media/video/video_rtp_session.cpp
@@ -28,6 +28,7 @@
 #include "socket_pair.h"
 #include "sip/sipvoiplink.h" // for enqueueKeyframeRequest
 #include "manager.h"
+#include "media_const.h"
 #ifdef ENABLE_PLUGIN
 #include "plugin/streamdata.h"
 #include "plugin/jamipluginmanager.h"
@@ -137,9 +138,8 @@ VideoRtpSession::startSender()
         }
 
         if (not conference_) {
-            auto input = getVideoInput(input_);
-            videoLocal_ = input;
-            if (input) {
+            videoLocal_ = getVideoInput(input_);
+            if (videoLocal_) {
                 videoLocal_->setRecorderCallback(
                     [w=weak_from_this()](const MediaStream& ms) {
                         Manager::instance().ioContext()->post([w=std::move(w), ms]() {
@@ -147,7 +147,7 @@ VideoRtpSession::startSender()
                                 shared->attachLocalRecorder(ms);
                         });
                     });
-                auto newParams = input->getParams();
+                auto newParams = videoLocal_->getParams();
                 try {
                     if (newParams.valid()
                         && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) {
@@ -166,10 +166,7 @@ VideoRtpSession::startSender()
             }
 
 #if (defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS))
-            if (auto input1 = std::static_pointer_cast<VideoInput>(videoLocal_)) {
-                input1->setupSink();
-                input1->setFrameSize(localVideoParams_.width, localVideoParams_.height);
-            }
+            videoLocal_->setupSink(localVideoParams_.width, localVideoParams_.height);
 #endif
         }
 
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index 46a7b0f882..05658b9143 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -71,6 +71,8 @@
 
 #include "tracepoint.h"
 
+#include "media/media_decoder.h"
+
 namespace jami {
 
 using sip_utils::CONST_PJ_STR;
@@ -160,6 +162,8 @@ SIPCall::~SIPCall()
 
     setSipTransport({});
     setInviteSession(); // prevents callback usage
+
+    closeMediaPlayer(mediaPlayerId_);
 }
 
 int
@@ -2502,6 +2506,34 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
 {
     std::lock_guard<std::recursive_mutex> lk {callMutex_};
     auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
+    bool hasFileSharing {false};
+
+    for (const auto& media : mediaAttrList) {
+        if (!media.enabled_ || media.sourceUri_.empty())
+            continue;
+
+        // Supported MRL schemes
+        static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
+
+        const auto pos = media.sourceUri_.find(sep);
+        if (pos == std::string::npos)
+            continue;
+
+        const auto prefix = media.sourceUri_.substr(0, pos);
+        if ((pos + sep.size()) >= media.sourceUri_.size())
+            continue;
+
+        if (prefix == libjami::Media::VideoProtocolPrefix::FILE) {
+            hasFileSharing = true;
+            mediaPlayerId_ = media.sourceUri_;
+            createMediaPlayer(mediaPlayerId_);
+        }
+    }
+
+    if (!hasFileSharing) {
+        closeMediaPlayer(mediaPlayerId_);
+        mediaPlayerId_ = "";
+    }
 
     // Disable video if disabled in the account.
     auto account = getSIPAccount();
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index 74c7436d79..8dee71e1b6 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -526,6 +526,8 @@ private:
 #ifdef ENABLE_VIDEO
     int rotation_ {0};
 #endif
+
+    std::string mediaPlayerId_ {};
 };
 
 // Helpers
diff --git a/test/unitTest/call/recorder.cpp b/test/unitTest/call/recorder.cpp
index 05042c5e27..7d91495043 100644
--- a/test/unitTest/call/recorder.cpp
+++ b/test/unitTest/call/recorder.cpp
@@ -75,17 +75,19 @@ public:
     void setUp();
     void tearDown();
 
-    std::string aliceId;
-    std::string bobId;
-    std::string recordDir;
-    std::string recordedFile;
+    std::string aliceId {};
+    std::string bobId {};
+    std::string recordDir {};
+    std::string recordedFile {};
+    std::string playerId {};
+    std::shared_ptr<MediaPlayer> player {};
     CallData bobCall {};
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
 
-    std::string videoPath = std::filesystem::absolute("media/test_video_file.mp4").string();
+    std::string videoPath = "file://" + std::filesystem::absolute("media/test_video_file.mp4").string();
 
 private:
     void registerSignalHandlers();
@@ -118,6 +120,10 @@ RecorderTest::setUp()
     aliceId = actors["alice"];
     bobId = actors["bob"];
     bobCall.reset();
+    playerId = jami::createMediaPlayer(videoPath);
+    player = jami::getMediaPlayer(playerId);
+    player->setAutoRestart(true);
+    player->pause(false);
 
     libjami::setRecordPath(recordDir);
 }
@@ -127,6 +133,7 @@ RecorderTest::tearDown()
 {
     libjami::setIsAlwaysRecording(false);
     dhtnet::fileutils::removeAll(recordDir);
+    player.reset();
 
     wait_for_removal_of({aliceId, bobId});
 }
@@ -210,7 +217,7 @@ RecorderTest::testRecordCall()
         {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
         {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
         {libjami::Media::MediaAttributeKey::LABEL, "video_0"},
-        {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}};
+        {libjami::Media::MediaAttributeKey::SOURCE, videoPath}};
     mediaList.emplace_back(mediaAttributeA);
     auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
@@ -406,7 +413,7 @@ RecorderTest::testStopCallWhileRecording()
         {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
         {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
         {libjami::Media::MediaAttributeKey::LABEL, "video_0"},
-        {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}};
+        {libjami::Media::MediaAttributeKey::SOURCE, videoPath}};
     mediaList.emplace_back(mediaAttributeA);
     mediaList.emplace_back(mediaAttributeV);
     auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
@@ -458,7 +465,7 @@ RecorderTest::testDaemonPreference()
         {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
         {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
         {libjami::Media::MediaAttributeKey::LABEL, "video_0"},
-        {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}};
+        {libjami::Media::MediaAttributeKey::SOURCE, videoPath}};
     mediaList.emplace_back(mediaAttributeA);
     mediaList.emplace_back(mediaAttributeV);
     auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
diff --git a/test/unitTest/media/test_media_player.cpp b/test/unitTest/media/test_media_player.cpp
index 10d4e8e1f1..5dc719aad4 100644
--- a/test/unitTest/media/test_media_player.cpp
+++ b/test/unitTest/media/test_media_player.cpp
@@ -56,9 +56,9 @@ private:
 
     std::string playerId1_ {};
     std::string playerId2_ {};
-    std::string duration_ {};
-    std::string audio_stream_ {};
-    std::string video_stream_ {};
+    int64_t duration_ {};
+    int audio_stream_ {};
+    int video_stream_ {};
     std::shared_ptr<MediaPlayer> mediaPlayer {};
 
     std::mutex mtx;
@@ -79,45 +79,53 @@ MediaPlayerTest::setUp()
     handler.insert(libjami::exportable_callback<libjami::MediaPlayerSignal::FileOpened>(
         [=](const std::string& playerId,
             const std::map<std::string, std::string>& info) {
-            duration_ = info.at("duration");
-            audio_stream_ = info.at("audio_stream");
-            video_stream_ = info.at("video_stream");
+            duration_ = std::stol(info.at("duration"));
+            audio_stream_ = std::stoi(info.at("audio_stream"));
+            video_stream_ = std::stoi(info.at("video_stream"));
             playerId2_ = playerId;
             cv.notify_all();
         }));
 
     libjami::registerSignalHandlers(handler);
 
-    playerId1_ = libjami::createMediaPlayer(filePath);
-    mediaPlayer = Manager::instance().getVideoManager().mediaPlayers[playerId1_];
+    playerId1_ = jami::createMediaPlayer(filePath);
+    mediaPlayer = jami::getMediaPlayer(playerId1_);
     cv.wait_for(lk, 5s);
 }
 
 void
 MediaPlayerTest::tearDown()
 {
+    jami::closeMediaPlayer(playerId1_);
+    mediaPlayer.reset();
+    playerId1_ = {};
+    playerId2_ = {};
     libjami::fini();
 }
 
 void
 MediaPlayerTest::testCreate()
 {
+    JAMI_INFO() << "Start testCreate";
     CPPUNIT_ASSERT(playerId1_ == playerId2_);
     CPPUNIT_ASSERT(mediaPlayer->getId() == playerId1_);
     CPPUNIT_ASSERT(mediaPlayer->isInputValid());
-    CPPUNIT_ASSERT(audio_stream_ != "-1");
-    CPPUNIT_ASSERT(video_stream_ != "-1");
+    CPPUNIT_ASSERT(audio_stream_ != -1);
+    CPPUNIT_ASSERT(video_stream_ != -1);
     CPPUNIT_ASSERT(mediaPlayer->isPaused());
     CPPUNIT_ASSERT(mediaPlayer->getPlayerPosition() == 0);
+    JAMI_INFO() << "End testCreate";
 }
 
 void
 MediaPlayerTest::testPause()
 {
-    mediaPlayer->pause(true);
+    JAMI_INFO() << "Start testPause";
+    // should start paused
     CPPUNIT_ASSERT(mediaPlayer->isPaused());
     mediaPlayer->pause(false);
     CPPUNIT_ASSERT(!mediaPlayer->isPaused());
+    JAMI_INFO() << "End testPause";
 }
 
 bool
@@ -129,10 +137,9 @@ MediaPlayerTest::isWithinUsec(int64_t currentTime, int64_t seekTime, int64_t mar
 void
 MediaPlayerTest::testSeekWhilePaused()
 {
-    mediaPlayer->pause(true);
+    JAMI_INFO() << "Start testSeekWhilePaused";
 
     int64_t startTime = mediaPlayer->getPlayerPosition();
-    int64_t duration = std::stoi(duration_);
 
     CPPUNIT_ASSERT(mediaPlayer->seekToTime(startTime+100));
     CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), startTime+100, 1));
@@ -143,18 +150,20 @@ MediaPlayerTest::testSeekWhilePaused()
     CPPUNIT_ASSERT(mediaPlayer->seekToTime(startTime+500));
     CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), startTime+500, 1));
 
-    CPPUNIT_ASSERT(mediaPlayer->seekToTime(duration-1));
-    CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), duration-1, 1));
+    CPPUNIT_ASSERT(mediaPlayer->seekToTime(duration_-1));
+    CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), duration_-1, 1));
 
     CPPUNIT_ASSERT(mediaPlayer->seekToTime(0));
     CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), 0, 1));
 
-    CPPUNIT_ASSERT(!(mediaPlayer->seekToTime(duration+1)));
+    CPPUNIT_ASSERT(!(mediaPlayer->seekToTime(duration_+1)));
+    JAMI_INFO() << "End testSeekWhilePaused";
 }
 
 void
 MediaPlayerTest::testSeekWhilePlaying()
 {
+    JAMI_INFO() << "Start testSeekWhilePlaying";
     mediaPlayer->pause(false);
 
     int64_t startTime = mediaPlayer->getPlayerPosition();
@@ -172,7 +181,8 @@ MediaPlayerTest::testSeekWhilePlaying()
     CPPUNIT_ASSERT(mediaPlayer->seekToTime(0));
     CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), 0, 50));
 
-    CPPUNIT_ASSERT(!(mediaPlayer->seekToTime(std::stoi(duration_)+1)));
+    CPPUNIT_ASSERT(!(mediaPlayer->seekToTime(duration_+1)));
+    JAMI_INFO() << "End testSeekWhilePlaying";
 }
 
 }} // namespace jami::test
-- 
GitLab