diff --git a/CMakeLists.txt b/CMakeLists.txt
index 679c861f1b12db09d3a304eab0120fb9e45cd4e9..a7de6d5a64151eb42367a2b4a8b55e0430d32cc7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,6 +46,7 @@ set(QT_MODULES
     WebEngineQuick
     WebChannel
     WebEngineWidgets
+    Multimedia
 )
 find_package(Qt6 COMPONENTS ${QT_MODULES} REQUIRED)
 foreach(MODULE ${QT_MODULES})
@@ -101,14 +102,11 @@ set(COMMON_SOURCES
     ${SRC_DIR}/main.cpp
     ${SRC_DIR}/smartlistmodel.cpp
     ${SRC_DIR}/utils.cpp
-    ${SRC_DIR}/rendermanager.cpp
     ${SRC_DIR}/mainapplication.cpp
     ${SRC_DIR}/messagesadapter.cpp
     ${SRC_DIR}/accountadapter.cpp
     ${SRC_DIR}/calladapter.cpp
     ${SRC_DIR}/conversationsadapter.cpp
-    ${SRC_DIR}/distantrenderer.cpp
-    ${SRC_DIR}/previewrenderer.cpp
     ${SRC_DIR}/avadapter.cpp
     ${SRC_DIR}/contactadapter.cpp
     ${SRC_DIR}/pluginadapter.cpp
@@ -140,6 +138,7 @@ set(COMMON_SOURCES
     ${SRC_DIR}/currentaccount.cpp
     ${SRC_DIR}/videodevices.cpp
     ${SRC_DIR}/previewengine.cpp
+    ${SRC_DIR}/videoprovider.cpp
 )
 
 set(COMMON_HEADERS
@@ -152,7 +151,6 @@ set(COMMON_HEADERS
     ${SRC_DIR}/version.h
     ${SRC_DIR}/accountlistmodel.h
     ${SRC_DIR}/instancemanager.h
-    ${SRC_DIR}/rendermanager.h
     ${SRC_DIR}/connectivitymonitor.h
     ${SRC_DIR}/jamiavatartheme.h
     ${SRC_DIR}/mainapplication.h
@@ -161,8 +159,6 @@ set(COMMON_HEADERS
     ${SRC_DIR}/accountadapter.h
     ${SRC_DIR}/calladapter.h
     ${SRC_DIR}/conversationsadapter.h
-    ${SRC_DIR}/distantrenderer.h
-    ${SRC_DIR}/previewrenderer.h
     ${SRC_DIR}/qmladapterbase.h
     ${SRC_DIR}/avadapter.h
     ${SRC_DIR}/contactadapter.h
@@ -198,8 +194,20 @@ set(COMMON_HEADERS
     ${SRC_DIR}/currentaccount.h
     ${SRC_DIR}/videodevices.h
     ${SRC_DIR}/previewengine.h
+    ${SRC_DIR}/videoprovider.h
 )
 
+# For avframe dependency on Windows/macOS.
+if(NOT DEFINED LIBAV_INCLUDE_PATH)
+   set(LIBJAMI_CONTRIB_DIR "${PROJECT_SOURCE_DIR}/../daemon/contrib/")
+   if(WIN32)
+      set(LIBAV_INCLUDE_PATH ${LIBJAMI_CONTRIB_DIR}/build/ffmpeg/Build/win32/x64/include/)
+   else()
+      set(LIBAV_INCLUDE_PATH ${LIBJAMI_CONTRIB_DIR}/native/ffmpeg)
+   endif()
+endif()
+include_directories(${LIBAV_INCLUDE_PATH})
+
 if(MSVC)
     set(WINDOWS_SYS_LIBS
         Shell32.lib
diff --git a/qml.qrc b/qml.qrc
index ce5c7ae71d07eb42f55ec3d0849e2751b89bed99..9dca240a6e4954c3a9cbfcf83364d66a35004950 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -5,6 +5,8 @@
         <file>src/constant/JamiQmlUtils.qml</file>
         <file>src/constant/JamiStrings.qml</file>
         <file>src/constant/JamiTheme.qml</file>
+        <file>src/commoncomponents/VideoView.qml</file>
+        <file>src/commoncomponents/LocalVideo.qml</file>
         <file>src/commoncomponents/SettingParaCombobox.qml</file>
         <file>src/commoncomponents/PreferenceItemDelegate.qml</file>
         <file>src/commoncomponents/PasswordDialog.qml</file>
diff --git a/src/avadapter.cpp b/src/avadapter.cpp
index 94c73db1e5cbfa2ce9db9bb50e05f6ac19c70b47..c46836e9fbd19088eb2c5d3056e7a145d8ee26f5 100644
--- a/src/avadapter.cpp
+++ b/src/avadapter.cpp
@@ -40,11 +40,10 @@ AvAdapter::AvAdapter(LRCInstance* instance, QObject* parent)
             &lrc::api::AVModel::audioDeviceEvent,
             this,
             &AvAdapter::onAudioDeviceEvent);
-    connect(&lrcInstance_->avModel(), &lrc::api::AVModel::rendererStarted, [this](const QString&) {
-        auto callId = lrcInstance_->getCurrentCallId();
-        set_currentRenderingDeviceType(
-            lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type);
-    });
+    connect(&lrcInstance_->avModel(),
+            &lrc::api::AVModel::rendererStarted,
+            this,
+            &AvAdapter::onRendererStarted);
 }
 
 // The top left corner of primary screen is (0, 0).
@@ -101,8 +100,6 @@ AvAdapter::shareEntireScreen(int screenNumber)
                              resource,
                              lrc::api::NewCallModel::MediaRequestType::SCREENSHARING,
                              false);
-    set_currentRenderingDeviceType(
-        lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type);
 }
 
 void
@@ -122,14 +119,12 @@ AvAdapter::shareAllScreens()
                              resource,
                              lrc::api::NewCallModel::MediaRequestType::SCREENSHARING,
                              false);
-    set_currentRenderingDeviceType(
-        lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type);
 }
 
 void
 AvAdapter::captureScreen(int screenNumber)
 {
-    auto futureResult = QtConcurrent::run([this, screenNumber]() {
+    std::ignore = QtConcurrent::run([this, screenNumber]() {
         QScreen* screen = QGuiApplication::screens().at(screenNumber);
         if (!screen)
             return;
@@ -149,7 +144,7 @@ AvAdapter::captureScreen(int screenNumber)
 void
 AvAdapter::captureAllScreens()
 {
-    auto futureResult = QtConcurrent::run([this]() {
+    std::ignore = QtConcurrent::run([this]() {
         auto screens = QGuiApplication::screens();
 
         QList<QPixmap> scrs;
@@ -191,8 +186,6 @@ AvAdapter::shareFile(const QString& filePath)
                                  filePath,
                                  lrc::api::NewCallModel::MediaRequestType::FILESHARING,
                                  false);
-        set_currentRenderingDeviceType(
-            lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type);
     }
 }
 
@@ -218,8 +211,6 @@ AvAdapter::shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned heig
                                  resource,
                                  lrc::api::NewCallModel::MediaRequestType::SCREENSHARING,
                                  false);
-        set_currentRenderingDeviceType(
-            lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type);
     });
 #else
     auto resource = lrcInstance_->getCurrentCallModel()->getDisplay(getScreenNumber(),
@@ -234,8 +225,6 @@ AvAdapter::shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned heig
                              resource,
                              lrc::api::NewCallModel::MediaRequestType::SCREENSHARING,
                              false);
-    set_currentRenderingDeviceType(
-        lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type);
 #endif
 }
 
@@ -250,8 +239,6 @@ AvAdapter::shareWindow(const QString& windowId)
                              resource,
                              lrc::api::NewCallModel::MediaRequestType::SCREENSHARING,
                              false);
-    set_currentRenderingDeviceType(
-        lrcInstance_->getCurrentCallModel()->getCurrentRenderedDevice(callId).type);
 }
 
 QString
@@ -304,7 +291,6 @@ AvAdapter::stopSharing()
                                  lrcInstance_->avModel().getCurrentVideoCaptureDevice(),
                                  lrc::api::NewCallModel::MediaRequestType::CAMERA,
                                  muteCamera_);
-        set_currentRenderingDeviceType(lrc::api::video::DeviceType::CAMERA);
     }
 }
 
@@ -329,6 +315,16 @@ AvAdapter::onAudioDeviceEvent()
     Q_EMIT audioDeviceListChanged(inputs, outputs);
 }
 
+void
+AvAdapter::onRendererStarted(const QString& id)
+{
+    auto callId = lrcInstance_->getCurrentCallId();
+    auto callModel = lrcInstance_->getCurrentCallModel();
+    auto renderDevice = callModel->getCurrentRenderedDevice(callId);
+    set_currentRenderingDeviceId(id);
+    set_currentRenderingDeviceType(renderDevice.type);
+}
+
 int
 AvAdapter::getScreenNumber() const
 {
diff --git a/src/avadapter.h b/src/avadapter.h
index c10e725e590f79fbb27c4c8cda1e471a2cbc89c6..a21dfad3fe0ff8a5505ea09e57161059012f79d9 100644
--- a/src/avadapter.h
+++ b/src/avadapter.h
@@ -29,7 +29,10 @@
 class AvAdapter final : public QmlAdapterBase
 {
     Q_OBJECT
+    // TODO: currentRenderingDeviceType is only used in QML to check if
+    // we're sharing or not, so it should maybe just be a boolean.
     QML_RO_PROPERTY(lrc::api::video::DeviceType, currentRenderingDeviceType)
+    QML_RO_PROPERTY(QString, currentRenderingDeviceId)
     QML_PROPERTY(bool, muteCamera)
     QML_RO_PROPERTY(QStringList, windowsNames)
     QML_RO_PROPERTY(QList<QVariant>, windowsIds)
@@ -90,6 +93,7 @@ protected:
 
 private Q_SLOTS:
     void onAudioDeviceEvent();
+    void onRendererStarted(const QString& id);
 
 private:
     // Get screens arrangement rect relative to primary screen.
diff --git a/src/calladapter.cpp b/src/calladapter.cpp
index 27c23404bad729fe9041915ad724b19b3c3aedbd..3f4c1773b8ffcb1139dff25be84b595418b03ab2 100644
--- a/src/calladapter.cpp
+++ b/src/calladapter.cpp
@@ -185,7 +185,6 @@ CallAdapter::onCallStatusChanged(const QString& callId, int code)
         case lrc::api::call::Status::PEER_BUSY:
         case lrc::api::call::Status::TIMEOUT:
         case lrc::api::call::Status::TERMINATING: {
-            lrcInstance_->renderer()->removeDistantRenderer(callId);
             const auto& convInfo = lrcInstance_->getConversationFromCallId(callId);
             if (convInfo.uid.isEmpty()) {
                 return;
@@ -285,7 +284,7 @@ void
 CallAdapter::onCallAddedToConference(const QString& callId, const QString& confId)
 {
     Q_UNUSED(callId)
-    lrcInstance_->renderer()->addDistantRenderer(confId);
+    Q_UNUSED(confId)
     saveConferenceSubcalls();
 }
 
@@ -450,9 +449,9 @@ CallAdapter::updateCall(const QString& convUid, const QString& accountId, bool f
 
     if (convInfo.uid == lrcInstance_->get_selectedConvUid()) {
         auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_);
-        if (accInfo.profileInfo.type != lrc::api::profile::Type::SIP)
+        if (accInfo.profileInfo.type != lrc::api::profile::Type::SIP) {
             accInfo.callModel->setCurrentCall(call->id);
-        lrcInstance_->renderer()->addDistantRenderer(call->id);
+        }
     }
 
     updateCallOverlay(convInfo);
diff --git a/src/commoncomponents/LocalVideo.qml b/src/commoncomponents/LocalVideo.qml
new file mode 100644
index 0000000000000000000000000000000000000000..caca6c081562fd034c7fc0fc1466fd6f0d2a75de
--- /dev/null
+++ b/src/commoncomponents/LocalVideo.qml
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick
+import QtMultimedia
+import Qt5Compat.GraphicalEffects
+
+import net.jami.Adapters 1.1
+
+VideoView {
+    id: root
+
+    crop: true
+
+    function startWithId(id, force = false) {
+        if (id.length === 0) {
+            VideoDevices.stopDevice(rendererId)
+            rendererId = id
+        } else {
+            rendererId = VideoDevices.startDevice(id, force)
+        }
+    }
+
+    onVisibleChanged: {
+        const id = visible ? VideoDevices.getDefaultDevice() : ""
+        startWithId(id)
+    }
+}
+
+
diff --git a/src/commoncomponents/PhotoboothView.qml b/src/commoncomponents/PhotoboothView.qml
index c9b7137bfa4862257502d23107a592312dfd54e1..828ab91faa25fec3e4ca41437eed4940d4892f18 100644
--- a/src/commoncomponents/PhotoboothView.qml
+++ b/src/commoncomponents/PhotoboothView.qml
@@ -39,8 +39,7 @@ Item {
     height: boothLayout.height
 
     function startBooth() {
-        preview.deviceId = VideoDevices.getDefaultDevice()
-        preview.rendererId = VideoDevices.startDevice(preview.deviceId)
+        preview.startWithId(VideoDevices.getDefaultDevice())
         isPreviewing = true
     }
 
@@ -130,17 +129,19 @@ Item {
                 showPresenceIndicator: false
             }
 
-            PhotoboothPreviewRender {
+            LocalVideo {
                 id: preview
 
                 anchors.fill: parent
                 anchors.margins: 1
 
-                property string deviceId: VideoDevices.getDefaultDevice()
-                rendererId: ""
-
                 visible: isPreviewing
-                lrcInstance: LRCInstance
+
+                rendererId: VideoDevices.getDefaultDevice()
+
+                function takePhoto() {
+                    return videoProvider.captureVideoFrame(videoSink)
+                }
 
                 layer.enabled: true
                 layer.effect: OpacityMask {
@@ -150,11 +151,6 @@ Item {
                         radius: avatarSize / 2
                     }
                 }
-
-                onRenderingStopped: {
-                    if (root.visible)
-                        stopBooth()
-                }
             }
 
             Rectangle {
diff --git a/src/commoncomponents/VideoView.qml b/src/commoncomponents/VideoView.qml
new file mode 100644
index 0000000000000000000000000000000000000000..ecfb5c3d8adfb864e3b4c024882118392a1d8868
--- /dev/null
+++ b/src/commoncomponents/VideoView.qml
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick
+import QtMultimedia
+import Qt5Compat.GraphicalEffects
+
+Item {
+    id: root
+
+    property string rendererId
+    property alias videoSink: videoOutput.videoSink
+    property alias underlayItems: rootUnderlayItem.children
+    property real invAspectRatio: (videoOutput.sourceRect.height
+                                   / videoOutput.sourceRect.width) ||
+                                  0.5625 // 16:9 default
+    property bool crop: false
+
+    // This rect describes the actual rendered content rectangle
+    // as the VideoOutput component may use PreserveAspectFit
+    // (pillarbox/letterbox).
+    property rect contentRect: videoOutput.contentRect
+    property real xScale: contentRect.width / videoOutput.sourceRect.width
+    property real yScale: contentRect.height / videoOutput.sourceRect.height
+
+    onRendererIdChanged: {
+        videoProvider.unregisterSink(videoSink)
+        if (rendererId.length !== 0) {
+            videoProvider.registerSink(rendererId, videoSink)
+        }
+    }
+
+    Rectangle {
+        id: bgRect
+        anchors.fill: parent
+        color: "black"
+    }
+
+    Item {
+        id: rootUnderlayItem
+        anchors.fill: parent
+    }
+
+    VideoOutput {
+        id: videoOutput
+
+        antialiasing: true
+        anchors.fill: parent
+        opacity: videoProvider.activeRenderers[rendererId] !== undefined
+        visible: opacity
+
+        fillMode: crop ?
+                      VideoOutput.PreserveAspectCrop :
+                      VideoOutput.PreserveAspectFit
+
+        Behavior on opacity { NumberAnimation { duration: 150 } }
+
+        Component.onDestruction: videoProvider.unregisterSink(videoSink)
+
+        layer.enabled: opacity
+        layer.effect: FastBlur {
+            source: videoOutput
+            anchors.fill: root
+            radius: (1. - opacity) * 100
+        }
+    }
+}
diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml
index 38b7a41df687cdb4559fa76fe063eec60a4073a6..722f829ae09cf238b1c1814ae151b52c4823a73b 100644
--- a/src/constant/JamiStrings.qml
+++ b/src/constant/JamiStrings.qml
@@ -184,6 +184,7 @@ Item {
     property string previewUnavailable: qsTr("Preview unavailable")
     property string screenSharing: qsTr("Screen Sharing")
     property string selectScreenSharingFPS: qsTr("Select screen sharing frame rate (frames per second)")
+    property string noVideo: qsTr("no video")
 
     // BackupKeyPage
     property string  backupAccountInfos: qsTr("Your account only exists on this device. " +
diff --git a/src/distantrenderer.cpp b/src/distantrenderer.cpp
deleted file mode 100644
index f98163061dfa0c0926bbd0204b59d84d6a6cb615..0000000000000000000000000000000000000000
--- a/src/distantrenderer.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2019-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
- * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "distantrenderer.h"
-
-#include "lrcinstance.h"
-
-DistantRenderer::DistantRenderer(QQuickItem* parent)
-    : QQuickPaintedItem(parent)
-{
-    setAntialiasing(true);
-    setFillColor(Qt::black);
-    setRenderTarget(QQuickPaintedItem::FramebufferObject);
-    setPerformanceHint(QQuickPaintedItem::FastFBOResizing);
-
-    connect(this, &DistantRenderer::lrcInstanceChanged, [this] {
-        if (lrcInstance_) {
-            connect(lrcInstance_->renderer(),
-                    &RenderManager::distantFrameUpdated,
-                    [this](const QString& id) {
-                        if (rendererId_ == id)
-                            update(QRect(0, 0, width(), height()));
-                    });
-
-            connect(lrcInstance_->renderer(),
-                    &RenderManager::distantRenderingStopped,
-                    [this](const QString& id) {
-                        if (rendererId_ == id)
-                            update(QRect(0, 0, width(), height()));
-                    });
-        }
-    });
-}
-
-DistantRenderer::~DistantRenderer() {}
-
-void
-DistantRenderer::setRendererId(const QString& id)
-{
-    rendererId_ = id;
-    // Note: Force a paint to update frame as we change the renderer
-    update(QRect(0, 0, width(), height()));
-}
-
-QString
-DistantRenderer::rendererId()
-{
-    return rendererId_;
-}
-
-QString
-DistantRenderer::takePhoto(int size)
-{
-    if (auto previewImage = lrcInstance_->renderer()->getPreviewFrame(rendererId_)) {
-        return Utils::byteArrayToBase64String(Utils::QImageToByteArray(previewImage->copy()));
-    }
-    return {};
-}
-
-int
-DistantRenderer::getXOffset() const
-{
-    return xOffset_;
-}
-
-int
-DistantRenderer::getYOffset() const
-{
-    return yOffset_;
-}
-
-double
-DistantRenderer::getScaledWidth() const
-{
-    return scaledWidth_;
-}
-
-double
-DistantRenderer::getScaledHeight() const
-{
-    return scaledHeight_;
-}
-
-void
-DistantRenderer::paint(QPainter* painter)
-{
-    lrcInstance_->renderer()->drawFrame(rendererId_, [this, painter](QImage* distantImage) {
-        if (distantImage) {
-            painter->setRenderHint(QPainter::Antialiasing);
-            painter->setRenderHint(QPainter::SmoothPixmapTransform);
-#if defined(Q_OS_MACOS)
-
-            auto scaledDistant = distantImage
-                                     ->scaled(size().toSize(),
-                                              Qt::KeepAspectRatio,
-                                              Qt::SmoothTransformation)
-                                     .rgbSwapped();
-#else
-            auto scaledDistant = distantImage->scaled(size().toSize(),
-                                                      Qt::KeepAspectRatio,
-                                                      Qt::SmoothTransformation);
-#endif
-            auto tempScaledWidth = static_cast<int>(scaledWidth_ * 1000);
-            auto tempScaledHeight = static_cast<int>(scaledHeight_ * 1000);
-            auto tempXOffset = xOffset_;
-            auto tempYOffset = yOffset_;
-            scaledWidth_ = static_cast<double>(scaledDistant.width())
-                           / static_cast<double>(distantImage->width());
-            scaledHeight_ = static_cast<double>(scaledDistant.height())
-                            / static_cast<double>(distantImage->height());
-            xOffset_ = (width() - scaledDistant.width()) / 2;
-            yOffset_ = (height() - scaledDistant.height()) / 2;
-            if (tempXOffset != xOffset_ or tempYOffset != yOffset_
-                or static_cast<int>(scaledWidth_ * 1000) != tempScaledWidth
-                or static_cast<int>(scaledHeight_ * 1000) != tempScaledHeight) {
-                Q_EMIT offsetChanged();
-            }
-            painter->drawImage(QRect(xOffset_,
-                                     yOffset_,
-                                     scaledDistant.width(),
-                                     scaledDistant.height()),
-                               scaledDistant);
-        }
-    });
-}
diff --git a/src/distantrenderer.h b/src/distantrenderer.h
deleted file mode 100644
index 7474600218955b94698a652f814be4de5f9db83c..0000000000000000000000000000000000000000
--- a/src/distantrenderer.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2020-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
- * Author: Mingrui Zhang   <mingrui.zhang@savoirfairelinux.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QtQuick>
-
-class LRCInstance;
-
-/*
- * Use QQuickPaintedItem so that QPainter apis can be used.
- * Note: Old video pipeline.
- */
-class DistantRenderer : public QQuickPaintedItem
-{
-    Q_OBJECT
-    Q_PROPERTY(LRCInstance* lrcInstance MEMBER lrcInstance_ NOTIFY lrcInstanceChanged)
-    Q_PROPERTY(QString rendererId READ rendererId WRITE setRendererId)
-
-public:
-    explicit DistantRenderer(QQuickItem* parent = nullptr);
-    ~DistantRenderer();
-
-    Q_INVOKABLE void setRendererId(const QString& id);
-    Q_INVOKABLE QString rendererId();
-    Q_INVOKABLE int getXOffset() const;
-    Q_INVOKABLE int getYOffset() const;
-    Q_INVOKABLE double getScaledWidth() const;
-    Q_INVOKABLE double getScaledHeight() const;
-    Q_INVOKABLE QString takePhoto(int size);
-
-Q_SIGNALS:
-    void offsetChanged();
-    void lrcInstanceChanged();
-
-protected:
-    // LRCInstance pointer (set in qml)
-    LRCInstance* lrcInstance_ {nullptr};
-
-private:
-    void paint(QPainter* painter);
-
-    /*
-     * Unique DistantRenderId for each call.
-     */
-    QString rendererId_;
-    int xOffset_ {0};
-    int yOffset_ {0};
-    double scaledWidth_ {0};
-    double scaledHeight_ {0};
-};
diff --git a/src/lrcinstance.cpp b/src/lrcinstance.cpp
index 2318d37cf7bee15abe8cdc31d51cb195e5c47ec7..619f506a83b9ba66fde1eb2e9370b26da06f67d8 100644
--- a/src/lrcinstance.cpp
+++ b/src/lrcinstance.cpp
@@ -33,7 +33,6 @@ LRCInstance::LRCInstance(migrateCallback willMigrateCb,
                          ConnectivityMonitor* connectivityMonitor,
                          bool muteDring)
     : lrc_(std::make_unique<Lrc>(willMigrateCb, didMigrateCb, muteDring))
-    , renderer_(std::make_unique<RenderManager>(lrc_->getAVModel()))
     , updateManager_(std::make_unique<UpdateManager>(updateUrl, connectivityMonitor, this))
     , threadPool_(new QThreadPool(this))
 {
@@ -68,12 +67,6 @@ LRCInstance::LRCInstance(migrateCallback willMigrateCb,
     }
 };
 
-RenderManager*
-LRCInstance::renderer()
-{
-    return renderer_.get();
-}
-
 UpdateManager*
 LRCInstance::getUpdateManager()
 {
@@ -400,7 +393,6 @@ LRCInstance::makeConversationPermanent(const QString& convId, const QString& acc
 void
 LRCInstance::finish()
 {
-    renderer_.reset();
     lrc_.reset();
 }
 
diff --git a/src/lrcinstance.h b/src/lrcinstance.h
index 915a3dc36f582860aa7059624c8ff3da9c3f169b..1421a2a3415a0ac7b4b8c3cb6a59f46ff8bc9813 100644
--- a/src/lrcinstance.h
+++ b/src/lrcinstance.h
@@ -25,7 +25,6 @@
 #endif
 
 #include "updatemanager.h"
-#include "rendermanager.h"
 #include "qtutils.h"
 #include "utils.h"
 
@@ -71,7 +70,6 @@ public:
 
     void finish();
 
-    RenderManager* renderer();
     UpdateManager* getUpdateManager();
 
     NewAccountModel& accountModel();
@@ -138,7 +136,6 @@ Q_SIGNALS:
 
 private:
     std::unique_ptr<Lrc> lrc_;
-    std::unique_ptr<RenderManager> renderer_;
     std::unique_ptr<UpdateManager> updateManager_;
 
     QString selectedConvUid_;
diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp
index 3c526b93b59902a823d3dbcb2c8dedb5defc90b7..8865f834c37187abff6acc136141c280f578295d 100644
--- a/src/mainapplication.cpp
+++ b/src/mainapplication.cpp
@@ -26,6 +26,7 @@
 #include "connectivitymonitor.h"
 #include "systemtray.h"
 #include "previewengine.h"
+#include "videoprovider.h"
 
 #include <QAction>
 #include <QCommandLineParser>
@@ -386,6 +387,9 @@ MainApplication::initQmlLayer()
                          &screenInfo_,
                          this);
 
+    auto videoProvider = new VideoProvider(lrcInstance_->avModel(), this);
+    engine_->rootContext()->setContextProperty("videoProvider", videoProvider);
+
     engine_->load(QUrl(QStringLiteral("qrc:/src/MainApplicationWindow.qml")));
 }
 
diff --git a/src/mainview/components/OngoingCallPage.qml b/src/mainview/components/OngoingCallPage.qml
index 13f6ded2f3adbecfb2a910d8e958e3cee18bdb3a..0a391aff2454a8b1ff124545f5aab21048d527e9 100644
--- a/src/mainview/components/OngoingCallPage.qml
+++ b/src/mainview/components/OngoingCallPage.qml
@@ -44,6 +44,13 @@ Rectangle {
     property alias callId: distantRenderer.rendererId
     property var linkedWebview: null
     property string callPreviewId: ""
+    property bool sharingActive: AvAdapter.currentRenderingDeviceType === Video.DeviceType.DISPLAY
+                                 || AvAdapter.currentRenderingDeviceType === Video.DeviceType.FILE
+
+    onSharingActiveChanged: {
+        const deviceId = AvAdapter.currentRenderingDeviceId
+        previewRenderer.startWithId(deviceId, true)
+    }
 
     color: "black"
 
@@ -166,36 +173,35 @@ Rectangle {
                         callOverlay.openCallViewContextMenuInPos(mouse.x, mouse.y)
                 }
 
-                DistantRenderer {
+                VideoView {
                     id: distantRenderer
 
                     anchors.centerIn: parent
                     anchors.fill: parent
                     z: -1
 
-                    lrcInstance: LRCInstance
                     visible: !root.isAudioOnly
 
-                    onOffsetChanged: {
+                    // Update overlays if the internal or visual geometry changes.
+                    // Use Qt.callLater to combine the events in the queue since these
+                    // signals can be emitted together.
+                    property real area: width * height
+                    function updateParticipantsLayer() {
                         callOverlay.participantsLayer.update(CallAdapter.getConferencesInfos())
                     }
+                    onAreaChanged: Qt.callLater(updateParticipantsLayer)
+                    onContentRectChanged: Qt.callLater(updateParticipantsLayer)
                 }
 
-                VideoCallPreviewRenderer {
+                LocalVideo {
                     id: previewRenderer
 
-                    lrcInstance: LRCInstance
                     visible: !callOverlay.isAudioOnly && !callOverlay.isConferenceCall && !callOverlay.isVideoMuted && !callOverlay.isPaused &&
                              ((VideoDevices.listSize !== 0 && AvAdapter.currentRenderingDeviceType === Video.DeviceType.CAMERA) || AvAdapter.currentRenderingDeviceType !== Video.DeviceType.CAMERA )
 
                     rendererId: root.callPreviewId
 
-                    onVisibleChanged: {
-                        if (!visible && !AccountAdapter.hasVideoCall()) {
-                            VideoDevices.stopDevice(rendererId, true)
-                        }
-                    }
-
+                    height: width * invAspectRatio
                     width: Math.max(callPageMainRect.width / 5, JamiTheme.minimumPreviewWidth)
                     x: callPageMainRect.width - previewRenderer.width - previewMargin
                     y: previewMarginYTop
@@ -262,13 +268,6 @@ Rectangle {
                             radius: JamiTheme.primaryRadius
                         }
                     }
-
-                    onWidthChanged: {
-                        previewRenderer.height = previewRenderer.width * previewImageScalingFactor
-                    }
-                    onPreviewImageScalingFactorChanged: {
-                        previewRenderer.height = previewRenderer.width * previewImageScalingFactor
-                    }
                 }
 
                 CallOverlay {
diff --git a/src/mainview/components/ParticipantsLayer.qml b/src/mainview/components/ParticipantsLayer.qml
index 10aa2b1165f0c70117979d16ff4fde1371de383c..301652434fd60f30e76b8cac120f4dfd73587936 100644
--- a/src/mainview/components/ParticipantsLayer.qml
+++ b/src/mainview/components/ParticipantsLayer.qml
@@ -27,10 +27,10 @@ Item {
     // returns true if participant is not fully maximized
     function showMaximize(pX, pY, pW, pH) {
         // Hack: -1 offset added to avoid problems with odd sizes
-        return (pX - distantRenderer.getXOffset() !== 0
-                || pY - distantRenderer.getYOffset() !== 0
-                || pW < (distantRenderer.width - distantRenderer.getXOffset() * 2 - 1)
-                || pH < (distantRenderer.height - distantRenderer.getYOffset() * 2 - 1))
+        return (pX - distantRenderer.contentRect.x !== 0
+                || pY - distantRenderer.contentRect.y !== 0
+                || pW < (distantRenderer.width - distantRenderer.contentRect.x * 2 -  1)
+                || pH < (distantRenderer.height - distantRenderer.contentRect.y * 2 - 1))
     }
 
     function update(infos) {
@@ -48,25 +48,13 @@ Item {
                 var participant = infos.find(e => e.uri === participantOverlays[p].uri);
                 if (participant) {
                     // Update participant's information
-                    var newX = Math.trunc(distantRenderer.getXOffset()
-                                          + participant.x * distantRenderer.getScaledWidth())
-                    var newY = Math.trunc(distantRenderer.getYOffset()
-                                          + participant.y * distantRenderer.getScaledHeight())
-
-                    var newWidth = Math.ceil(participant.w * distantRenderer.getScaledWidth())
-                    var newHeight = Math.ceil(participant.h * distantRenderer.getScaledHeight())
-
-                    var newVisible = participant.w !== 0 && participant.h !== 0
-                    if (participantOverlays[p].x !== newX)
-                        participantOverlays[p].x = newX
-                    if (participantOverlays[p].y !== newY)
-                        participantOverlays[p].y = newY
-                    if (participantOverlays[p].width !== newWidth)
-                        participantOverlays[p].width = newWidth
-                    if (participantOverlays[p].height !== newHeight)
-                        participantOverlays[p].height = newHeight
-                    if (participantOverlays[p].visible !== newVisible)
-                        participantOverlays[p].visible = newVisible
+                    participantOverlays[p].x = Math.trunc(distantRenderer.contentRect.x
+                                                          + participant.x * distantRenderer.xScale)
+                    participantOverlays[p].y = Math.trunc(distantRenderer.contentRect.y
+                                                          + participant.y * distantRenderer.yScale)
+                    participantOverlays[p].width = Math.ceil(participant.w * distantRenderer.xScale)
+                    participantOverlays[p].height = Math.ceil(participant.h * distantRenderer.yScale)
+                    participantOverlays[p].visible = participant.w !== 0 && participant.h !== 0
 
                     showMax = showMaximize(participantOverlays[p].x,
                                            participantOverlays[p].y,
@@ -100,13 +88,16 @@ Item {
             for (var infoVariant in infos) {
                 // Only create overlay for new participants
                 if (!currentUris.includes(infos[infoVariant].uri)) {
-                    var hover = participantComponent.createObject(root, {
-                                                                      x: Math.trunc(distantRenderer.getXOffset() + infos[infoVariant].x * distantRenderer.getScaledWidth()),
-                                                                      y: Math.trunc(distantRenderer.getYOffset() + infos[infoVariant].y * distantRenderer.getScaledHeight()),
-                                                                      width: Math.ceil(infos[infoVariant].w * distantRenderer.getScaledWidth()),
-                                                                      height: Math.ceil(infos[infoVariant].h * distantRenderer.getScaledHeight()),
-                                                                      visible: infos[infoVariant].w !== 0 && infos[infoVariant].h !== 0
-                                                                  })
+                    const infoObj = {
+                        x: Math.trunc(distantRenderer.contentRect.x
+                                      + infos[infoVariant].x * distantRenderer.xScale),
+                        y: Math.trunc(distantRenderer.contentRect.y
+                                      + infos[infoVariant].y * distantRenderer.yScale),
+                        width: Math.ceil(infos[infoVariant].w * distantRenderer.xScale),
+                        height: Math.ceil(infos[infoVariant].h * distantRenderer.yScale),
+                        visible: infos[infoVariant].w !== 0 && infos[infoVariant].h !== 0
+                    }
+                    var hover = participantComponent.createObject(root, infoObj)
                     if (!hover) {
                         console.log("Error when creating the hover")
                         return
diff --git a/src/mainview/components/RecordBox.qml b/src/mainview/components/RecordBox.qml
index 9b160957c7eb8f6028506566b6e28c44bb5c06ca..75679e3c64cb756d4d4c586369a74ed327a502ed 100644
--- a/src/mainview/components/RecordBox.qml
+++ b/src/mainview/components/RecordBox.qml
@@ -58,8 +58,7 @@ Rectangle {
         updateState(RecordBox.States.INIT)
 
         if (isVideo) {
-            previewWidget.deviceId = VideoDevices.getDefaultDevice()
-            previewWidget.rendererId = VideoDevices.startDevice(previewWidget.deviceId)
+            previewWidget.startWithId(VideoDevices.getDefaultDevice())
         }
     }
 
@@ -245,29 +244,19 @@ Rectangle {
         color: JamiTheme.blackColor
         radius: 5
 
-        PreviewRenderer {
+        LocalVideo {
             id: previewWidget
 
             anchors.fill: rectBox
-            anchors.centerIn: rectBox
-            property string deviceId: VideoDevices.getDefaultDevice()
-            rendererId: VideoDevices.getDefaultDevice()
+            anchors.margins: 1
 
-            lrcInstance: LRCInstance
+            rendererId: VideoDevices.getDefaultDevice()
 
             layer.enabled: true
             layer.effect: OpacityMask {
                 maskSource: rectBox
             }
         }
-
-        onVisibleChanged: {
-            if (visible) {
-                previewWidget.deviceId = VideoDevices.getDefaultDevice()
-                previewWidget.rendererId = VideoDevices.startDevice(previewWidget.deviceId)
-            } else
-                VideoDevices.stopDevice(previewWidget.deviceId)
-        }
     }
 
     Label {
diff --git a/src/mainview/components/SelectScreen.qml b/src/mainview/components/SelectScreen.qml
index f6e362e7df7f07f461b2f589424a5d94f51b6224..517b47eb4a2f50dee7c0a3eb72c5ac29e29579a9 100644
--- a/src/mainview/components/SelectScreen.qml
+++ b/src/mainview/components/SelectScreen.qml
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2020-2022 Savoir-faire Linux Inc.
  * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
- *         Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
+ * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -50,11 +50,12 @@ Window {
     // How many rows the ScrollView should have.
     function calculateRepeaterModel() {
         screens = []
-        for (var idx in Qt.application.screens) {
+        var idx
+        for (idx in Qt.application.screens) {
             screens.push(qsTr("Screen") + " " + idx)
         }
         AvAdapter.getListWindows()
-        for (var idx in AvAdapter.windowsNames) {
+        for (idx in AvAdapter.windowsNames) {
             screens.push(AvAdapter.windowsNames[idx])
         }
 
@@ -153,7 +154,7 @@ Window {
                             color: JamiTheme.textColor
                         }
 
-                        PreviewRenderer {
+                        VideoView {
                             id: screenPreview
 
                             anchors.top: screenName.bottom
@@ -162,17 +163,17 @@ Window {
                             height: screenItem.height - 50
                             width: screenItem.width - 50
 
-                            lrcInstance: LRCInstance
-
                             Component.onDestruction: {
-                                if (screenPreview.rendererId !== "" && screenPreview.rendererId !== currentPreview)
-                                    VideoDevices.stopDevice(screenPreview.rendererId, true)
+                                if (rendererId !== "" && rendererId !== currentPreview) {
+                                    VideoDevices.stopDevice(rendererId, true)
+                                }
                             }
                             Component.onCompleted: {
                                 if (visible) {
-                                    var rendId = AvAdapter.getSharingResource(index, "")
-                                    if (rendId !== "")
-                                        screenPreview.rendererId = VideoDevices.startDevice(rendId, true)
+                                    const rId = AvAdapter.getSharingResource(index, "")
+                                    if (rId !== "") {
+                                        rendererId = VideoDevices.startDevice(rId, true)
+                                    }
                                 }
                             }
                         }
@@ -226,7 +227,7 @@ Window {
                         color: JamiTheme.textColor
                     }
 
-                    PreviewRenderer {
+                    VideoView {
                         id: screenShotAll
 
                         anchors.top: screenNameAll.bottom
@@ -235,17 +236,17 @@ Window {
                         height: screenSelectionRectAll.height - 50
                         width: screenSelectionRectAll.width - 50
 
-                        lrcInstance: LRCInstance
-
                         Component.onDestruction: {
-                            if (screenShotAll.rendererId !== "" && screenShotAll.rendererId !== currentPreview)
-                                VideoDevices.stopDevice(screenShotAll.rendererId, true)
+                            if (rendererId !== "" && rendererId !== currentPreview) {
+                                VideoDevices.stopDevice(rendererId, true)
+                            }
                         }
                         Component.onCompleted: {
                             if (visible) {
-                                var rendId = AvAdapter.getSharingResource(-1, "")
-                                if (rendId !== "")
-                                    screenShotAll.rendererId = VideoDevices.startDevice(rendId, true)
+                                const rId = AvAdapter.getSharingResource(-1, "")
+                                if (rId !== "") {
+                                    rendererId = VideoDevices.startDevice(rId, true)
+                                }
                             }
                         }
                     }
@@ -305,7 +306,7 @@ Window {
                             color: JamiTheme.textColor
                         }
 
-                        PreviewRenderer {
+                        VideoView {
                             id: screenPreview2
 
                             anchors.top: screenName2.bottom
@@ -316,17 +317,17 @@ Window {
                             height: screenItem2.height - 60
                             width: screenItem2.width - 50
 
-                            lrcInstance: LRCInstance
-
                             Component.onDestruction: {
-                                if (screenPreview2.rendererId !== "" && screenPreview2.rendererId !== currentPreview)
-                                    VideoDevices.stopDevice(screenPreview2.rendererId, true)
+                                if (rendererId !== "" && rendererId !== currentPreview) {
+                                    VideoDevices.stopDevice(rendererId, true)
+                                }
                             }
                             Component.onCompleted: {
                                 if (visible) {
-                                    var rendId = AvAdapter.getSharingResource(-2, AvAdapter.windowsIds[index - Qt.application.screens.length])
-                                    if (rendId !== "")
-                                        screenPreview2.rendererId = VideoDevices.startDevice(rendId, true)
+                                    const rId = AvAdapter.getSharingResource(-2, AvAdapter.windowsIds[index - Qt.application.screens.length])
+                                    if (rId !== "") {
+                                        rendererId = VideoDevices.startDevice(rId, true)
+                                    }
                                 }
                             }
                         }
diff --git a/src/previewrenderer.cpp b/src/previewrenderer.cpp
deleted file mode 100644
index 7c419fa1f536dc90c92eda49f7ea915a9710822a..0000000000000000000000000000000000000000
--- a/src/previewrenderer.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2020-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
- * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "previewrenderer.h"
-
-PreviewRenderer::PreviewRenderer(QQuickItem* parent)
-    : QQuickPaintedItem(parent)
-{
-    setAntialiasing(true);
-    setFillColor(Qt::black);
-    setRenderTarget(QQuickPaintedItem::FramebufferObject);
-    setPerformanceHint(QQuickPaintedItem::FastFBOResizing);
-
-    connect(this, &PreviewRenderer::lrcInstanceChanged, [this] {
-        if (lrcInstance_)
-            previewFrameUpdatedConnection_ = connect(lrcInstance_->renderer(),
-                                                     &RenderManager::distantFrameUpdated,
-                                                     [this](const QString& id) {
-                                                         if (rendererId_ == id && isVisible())
-                                                             update(QRect(0, 0, width(), height()));
-                                                     });
-    });
-}
-
-PreviewRenderer::~PreviewRenderer()
-{
-    disconnect(previewFrameUpdatedConnection_);
-}
-
-void
-PreviewRenderer::paint(QPainter* painter)
-{
-    lrcInstance_->renderer()->drawFrame(rendererId_, [this, painter](QImage* previewImage) {
-        if (previewImage) {
-            painter->setRenderHint(QPainter::Antialiasing);
-            painter->setRenderHint(QPainter::SmoothPixmapTransform);
-
-            auto aspectRatio = static_cast<qreal>(previewImage->width())
-                               / static_cast<qreal>(previewImage->height());
-            auto previewHeight = aspectRatio < 1 ? height() : width() / aspectRatio;
-            auto previewWidth = aspectRatio < 1 ? previewHeight * aspectRatio : width();
-
-            /* Instead of setting fixed size, we could get an x offset for the preview
-             * but this would render the horizontal spacers in the parent widget useless.
-             * e.g.
-             * auto parent = qobject_cast<QWidget*>(this->parent());
-             * auto xPos = (parent->width() - previewWidth) / 2;
-             * setGeometry(QRect(QPoint(xPos, this->pos().y()),
-             *             QSize(previewWidth, previewHeight)));
-             */
-            setWidth(previewWidth);
-            setHeight(previewHeight);
-
-            // If the given size is empty, this function returns a null image.
-            QImage scaledPreview;
-#if defined(Q_OS_MACOS)
-
-            scaledPreview = previewImage
-                                ->scaled(size().toSize(),
-                                         Qt::KeepAspectRatio,
-                                         Qt::SmoothTransformation)
-                                .rgbSwapped();
-#else
-                scaledPreview = previewImage->scaled(size().toSize(),
-                                                     Qt::KeepAspectRatio,
-                                                     Qt::SmoothTransformation);
-#endif
-            painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()),
-                               scaledPreview);
-        } else {
-            paintBackground(painter);
-        }
-    });
-}
-
-void
-PreviewRenderer::paintBackground(QPainter* painter)
-{
-    QBrush brush(Qt::black);
-    QPainterPath path;
-    path.addRect(QRect(0, 0, width(), height()));
-    painter->fillPath(path, brush);
-}
-
-VideoCallPreviewRenderer::VideoCallPreviewRenderer(QQuickItem* parent)
-    : PreviewRenderer(parent)
-{
-    setProperty("previewImageScalingFactor", 1.0);
-}
-
-VideoCallPreviewRenderer::~VideoCallPreviewRenderer() {}
-
-void
-VideoCallPreviewRenderer::paint(QPainter* painter)
-{
-    lrcInstance_->renderer()->drawFrame(get_rendererId(), [this, painter](QImage* previewImage) {
-        if (previewImage) {
-            auto scalingFactor = static_cast<qreal>(previewImage->height())
-                                 / static_cast<qreal>(previewImage->width());
-            setProperty("previewImageScalingFactor", scalingFactor);
-            QImage scaledPreview;
-#if defined(Q_OS_MACOS)
-            scaledPreview = previewImage->scaled(size().toSize(), Qt::KeepAspectRatio).rgbSwapped();
-#else
-                scaledPreview = previewImage->scaled(size().toSize(), Qt::KeepAspectRatio);
-#endif
-            painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()),
-                               scaledPreview);
-        }
-    });
-}
-
-PhotoboothPreviewRender::PhotoboothPreviewRender(QQuickItem* parent)
-    : PreviewRenderer(parent)
-{
-    connect(this, &PreviewRenderer::lrcInstanceChanged, [this] {
-        if (lrcInstance_)
-            connect(lrcInstance_->renderer(),
-                    &RenderManager::distantRenderingStopped,
-                    this,
-                    &PhotoboothPreviewRender::renderingStopped,
-                    Qt::UniqueConnection);
-    });
-}
-
-QString
-PhotoboothPreviewRender::takePhoto(int size)
-{
-    if (auto previewImage = lrcInstance_->renderer()->getPreviewFrame(get_rendererId())) {
-#if defined(Q_OS_MACOS)
-        return Utils::byteArrayToBase64String(
-            Utils::QImageToByteArray(previewImage->copy().rgbSwapped()));
-#else
-        return Utils::byteArrayToBase64String(Utils::QImageToByteArray(previewImage->copy()));
-#endif
-    }
-    return {};
-}
-
-void
-PhotoboothPreviewRender::paint(QPainter* painter)
-{
-    painter->setRenderHint(QPainter::Antialiasing, true);
-
-    lrcInstance_->renderer()->drawFrame(get_rendererId(), [this, painter](QImage* previewImage) {
-        if (previewImage) {
-            QImage scaledPreview;
-#if defined(Q_OS_MACOS)
-
-            scaledPreview = Utils::getCirclePhoto(*previewImage,
-                                                  height() <= width() ? height() : width())
-                                .rgbSwapped();
-#else
-                scaledPreview = Utils::getCirclePhoto(*previewImage,
-                                                      height() <= width() ? height() : width());
-#endif
-            painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()),
-                               scaledPreview);
-        }
-    });
-}
diff --git a/src/previewrenderer.h b/src/previewrenderer.h
deleted file mode 100644
index 1e78fe087e5bba58abeb81f2ce8d98eb034e4957..0000000000000000000000000000000000000000
--- a/src/previewrenderer.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2020-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
- * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QtQuick>
-
-#include "lrcinstance.h"
-
-/*
- * Use QQuickPaintedItem so that QPainter apis can be used.
- * Note: Old video pipeline.
- */
-class PreviewRenderer : public QQuickPaintedItem
-{
-    Q_OBJECT
-    Q_PROPERTY(LRCInstance* lrcInstance MEMBER lrcInstance_ NOTIFY lrcInstanceChanged)
-    QML_PROPERTY(QString, rendererId);
-
-public:
-    explicit PreviewRenderer(QQuickItem* parent = nullptr);
-    virtual ~PreviewRenderer();
-
-Q_SIGNALS:
-    void lrcInstanceChanged();
-
-protected:
-    void paint(QPainter* painter) override;
-    void paintBackground(QPainter* painter);
-
-    // LRCInstance pointer (set in qml)
-    LRCInstance* lrcInstance_ {nullptr};
-
-private:
-    QMetaObject::Connection previewFrameUpdatedConnection_;
-};
-
-class VideoCallPreviewRenderer : public PreviewRenderer
-{
-    Q_OBJECT
-    Q_PROPERTY(qreal previewImageScalingFactor MEMBER previewImageScalingFactor_ NOTIFY
-                   previewImageScalingFactorChanged)
-public:
-    explicit VideoCallPreviewRenderer(QQuickItem* parent = nullptr);
-    ~VideoCallPreviewRenderer();
-
-Q_SIGNALS:
-    void previewImageScalingFactorChanged();
-
-private:
-    void paint(QPainter* painter) override final;
-
-    qreal previewImageScalingFactor_;
-};
-
-class PhotoboothPreviewRender : public PreviewRenderer
-{
-    Q_OBJECT
-public:
-    explicit PhotoboothPreviewRender(QQuickItem* parent = nullptr);
-    ~PhotoboothPreviewRender() = default;
-
-    Q_INVOKABLE QString takePhoto(int size);
-
-Q_SIGNALS:
-    void renderingStopped(const QString id);
-
-private:
-    void paint(QPainter* painter) override final;
-};
diff --git a/src/qmlregister.cpp b/src/qmlregister.cpp
index fd4dab1223076cf8ba53796e76693f1393b2248d..527d7ccd2a66b32d9088769ae08b574949613a33 100644
--- a/src/qmlregister.cpp
+++ b/src/qmlregister.cpp
@@ -48,11 +48,9 @@
 #include "avatarregistry.h"
 #include "appsettingsmanager.h"
 #include "mainapplication.h"
-#include "distantrenderer.h"
 #include "namedirectory.h"
 #include "updatemanager.h"
 #include "pluginlistpreferencemodel.h"
-#include "previewrenderer.h"
 #include "version.h"
 #include "wizardviewstepmodel.h"
 
@@ -167,12 +165,6 @@ registerTypes(QQmlEngine* engine,
     QML_REGISTERNAMESPACE(NS_MODELS, FilesToSend::staticMetaObject, "FilesToSend");
     QML_REGISTERNAMESPACE(NS_MODELS, MessageList::staticMetaObject, "MessageList");
 
-    // QQuickItems
-    QML_REGISTERTYPE(NS_MODELS, PreviewRenderer);
-    QML_REGISTERTYPE(NS_MODELS, VideoCallPreviewRenderer);
-    QML_REGISTERTYPE(NS_MODELS, DistantRenderer);
-    QML_REGISTERTYPE(NS_MODELS, PhotoboothPreviewRender)
-
     // Qml singleton components
     QML_REGISTERSINGLETONTYPE_URL(NS_CONSTANTS, "qrc:/src/constant/JamiTheme.qml", JamiTheme);
     QML_REGISTERSINGLETONTYPE_URL(NS_MODELS, "qrc:/src/constant/JamiQmlUtils.qml", JamiQmlUtils);
diff --git a/src/rendermanager.cpp b/src/rendermanager.cpp
deleted file mode 100644
index cea11bc522490d1700a88f8f1d5f2321fa48f6d5..0000000000000000000000000000000000000000
--- a/src/rendermanager.cpp
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2019-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
- * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "rendermanager.h"
-
-#include <stdexcept>
-
-using namespace lrc::api;
-
-FrameWrapper::FrameWrapper(AVModel& avModel, const QString& id)
-    : avModel_(avModel)
-    , id_(id)
-    , isRendering_(false)
-{}
-
-FrameWrapper::~FrameWrapper()
-{
-    avModel_.stopPreview(id_);
-}
-
-void
-FrameWrapper::connectStartRendering()
-{
-    QObject::disconnect(renderConnections_.started);
-    renderConnections_.started = QObject::connect(&avModel_,
-                                                  &AVModel::rendererStarted,
-                                                  this,
-                                                  &FrameWrapper::slotRenderingStarted);
-}
-
-bool
-FrameWrapper::startRendering()
-{
-    if (isRendering())
-        return true;
-
-    try {
-        renderer_ = const_cast<video::Renderer*>(&avModel_.getRenderer(id_));
-    } catch (std::out_of_range& e) {
-        qWarning() << e.what();
-        return false;
-    }
-
-    QObject::disconnect(renderConnections_.updated);
-    QObject::disconnect(renderConnections_.stopped);
-
-    renderConnections_.updated = QObject::connect(&avModel_,
-                                                  &AVModel::frameUpdated,
-                                                  this,
-                                                  &FrameWrapper::slotFrameUpdated);
-
-    renderConnections_.stopped = QObject::connect(&avModel_,
-                                                  &AVModel::rendererStopped,
-                                                  this,
-                                                  &FrameWrapper::slotRenderingStopped,
-                                                  Qt::DirectConnection);
-
-    return true;
-}
-
-void
-FrameWrapper::stopRendering()
-{
-    isRendering_ = false;
-}
-
-QImage*
-FrameWrapper::getFrame()
-{
-    if (image_.get()) {
-        return isRendering_ ? (image_.get()->isNull() ? nullptr : image_.get()) : nullptr;
-    }
-    return nullptr;
-}
-
-bool
-FrameWrapper::isRendering()
-{
-    return isRendering_;
-}
-
-bool
-FrameWrapper::frameMutexTryLock()
-{
-    return mutex_.tryLock();
-}
-
-void
-FrameWrapper::frameMutexUnlock()
-{
-    mutex_.unlock();
-}
-
-void
-FrameWrapper::slotRenderingStarted(const QString& id)
-{
-    if (id != id_) {
-        return;
-    }
-
-    if (!startRendering()) {
-        qWarning() << "Couldn't start rendering for id: " << id_;
-        return;
-    }
-
-    isRendering_ = true;
-
-    Q_EMIT renderingStarted(id);
-}
-
-void
-FrameWrapper::slotFrameUpdated(const QString& id)
-{
-    if (id != id_) {
-        return;
-    }
-
-    if (!renderer_ || !renderer_->isRendering()) {
-        return;
-    }
-
-    {
-        QMutexLocker lock(&mutex_);
-
-        frame_ = renderer_->currentFrame();
-
-        unsigned int width = renderer_->size().width();
-        unsigned int height = renderer_->size().height();
-        unsigned int size;
-        QImage::Format imageFormat;
-        if (renderer_->useDirectRenderer()) {
-            size = frame_.storage.size();
-            imageFormat = QImage::Format_ARGB32_Premultiplied;
-        } else {
-            size = frame_.size;
-            imageFormat = QImage::Format_ARGB32;
-        }
-        /*
-         * If the frame is empty or not the expected size,
-         * do nothing and keep the last rendered QImage.
-         */
-        if (size != 0 && size == width * height * 4) {
-            if (renderer_->useDirectRenderer()) {
-                buffer_ = std::move(frame_.storage);
-            } else {
-                // TODO remove this path. storage should work everywhere
-                // https://git.jami.net/savoirfairelinux/jami-libclient/-/issues/492
-                buffer_.resize(size);
-                std::move(frame_.ptr, frame_.ptr + size, buffer_.begin());
-            }
-            image_.reset(new QImage((uchar*) buffer_.data(), width, height, imageFormat));
-        }
-    }
-    Q_EMIT frameUpdated(id);
-}
-
-void
-FrameWrapper::slotRenderingStopped(const QString& id)
-{
-    if (id != id_) {
-        return;
-    }
-    isRendering_ = false;
-
-    QObject::disconnect(renderConnections_.updated);
-
-    renderer_ = nullptr;
-
-    {
-        QMutexLocker lock(&mutex_);
-        image_.reset();
-    }
-
-    Q_EMIT renderingStopped(id);
-}
-
-RenderManager::RenderManager(AVModel& avModel)
-    : avModel_(avModel)
-{}
-
-RenderManager::~RenderManager()
-{
-    for (auto& dfw : distantFrameWrapperMap_) {
-        dfw.second.reset();
-    }
-}
-
-void
-RenderManager::stopPreviewing(const QString& id)
-{
-    auto dfwIt = distantFrameWrapperMap_.find(id);
-    if (dfwIt != distantFrameWrapperMap_.end()) {
-        dfwIt->second->stopRendering();
-        avModel_.stopPreview(id);
-    }
-}
-
-const QString
-RenderManager::startPreviewing(const QString& id, bool force)
-{
-    auto dfwIt = distantFrameWrapperMap_.find(id);
-    if (dfwIt != distantFrameWrapperMap_.end()) {
-        if (dfwIt->second->isRendering() && !force) {
-            return dfwIt->second->getId();
-        }
-
-        if (dfwIt->second->isRendering()) {
-            avModel_.stopPreview(id);
-        }
-        return avModel_.startPreview(id);
-    }
-    return "";
-}
-
-void
-RenderManager::addDistantRenderer(const QString& id)
-{
-    /*
-     * Check if a FrameWrapper with this id exists.
-     */
-    auto dfwIt = distantFrameWrapperMap_.find(id);
-    if (dfwIt != distantFrameWrapperMap_.end()) {
-        if (!dfwIt->second->startRendering()) {
-            qWarning() << "Couldn't start rendering for id: " << id;
-        }
-    } else {
-        auto dfw = std::make_unique<FrameWrapper>(avModel_, id);
-
-        /*
-         * Connect this to the FrameWrapper.
-         */
-        distantConnectionMap_[id].updated = QObject::connect(dfw.get(),
-                                                             &FrameWrapper::frameUpdated,
-                                                             [this](const QString& id) {
-                                                                 Q_EMIT distantFrameUpdated(id);
-                                                             });
-        distantConnectionMap_[id].stopped = QObject::connect(dfw.get(),
-                                                             &FrameWrapper::renderingStopped,
-                                                             [this](const QString& id) {
-                                                                 Q_EMIT distantRenderingStopped(id);
-                                                             });
-
-        /*
-         * Connect FrameWrapper to avmodel.
-         */
-        dfw->connectStartRendering();
-        try {
-            /*
-             * If the renderer has already started, then start the slot.
-             */
-            if (avModel_.getRenderer(id).isRendering())
-                dfw->slotRenderingStarted(id);
-        } catch (...) {
-        }
-
-        /*
-         * Add to map.
-         */
-        distantFrameWrapperMap_.insert(std::make_pair(id, std::move(dfw)));
-    }
-}
-
-void
-RenderManager::removeDistantRenderer(const QString& id)
-{
-    auto dfwIt = distantFrameWrapperMap_.find(id);
-    if (dfwIt != distantFrameWrapperMap_.end()) {
-        /*
-         * Disconnect FrameWrapper from this.
-         */
-        auto dcIt = distantConnectionMap_.find(id);
-        if (dcIt != distantConnectionMap_.end()) {
-            QObject::disconnect(dcIt->second.started);
-            QObject::disconnect(dcIt->second.updated);
-            QObject::disconnect(dcIt->second.stopped);
-        }
-
-        /*
-         * Erase.
-         */
-        distantFrameWrapperMap_.erase(dfwIt);
-    }
-}
-
-void
-RenderManager::drawFrame(const QString& id, DrawFrameCallback cb)
-{
-    auto dfwIt = distantFrameWrapperMap_.find(id);
-    if (dfwIt != distantFrameWrapperMap_.end()) {
-        if (dfwIt->second->frameMutexTryLock()) {
-            cb(dfwIt->second->getFrame());
-            dfwIt->second->frameMutexUnlock();
-        }
-    }
-}
-
-QImage*
-RenderManager::getPreviewFrame(const QString& id)
-{
-    auto dfwIt = distantFrameWrapperMap_.find(id);
-    if (dfwIt != distantFrameWrapperMap_.end()) {
-        return dfwIt->second->getFrame();
-    }
-    return {};
-}
diff --git a/src/rendermanager.h b/src/rendermanager.h
deleted file mode 100644
index a5e577668fa5b40a9d6a5b8db54aea18ca64bbe8..0000000000000000000000000000000000000000
--- a/src/rendermanager.h
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2019-2022 Savoir-faire Linux Inc.
- * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
- * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include "api/avmodel.h"
-#include "api/lrc.h"
-
-#include <QImage>
-#include <QMutex>
-#include <QObject>
-
-using namespace lrc::api;
-
-/*
- * This class acts as a QImage rendering sink and manages
- * signal/slot connections to it's underlying (AVModel) renderer
- * corresponding to the object's renderer id.
- * A QImage pointer is provisioned and updated once rendering
- * starts.
- */
-
-struct RenderConnections
-{
-    QMetaObject::Connection started, stopped, updated;
-};
-
-class FrameWrapper final : public QObject
-{
-    Q_OBJECT;
-
-public:
-    FrameWrapper(AVModel& avModel, const QString& id);
-    ~FrameWrapper();
-
-    /*
-     * Reconnect the started rendering connection for this object.
-     */
-    void connectStartRendering();
-
-    /*
-     * Get a pointer to the renderer and reconnect the update/stopped
-     * rendering connections for this object.
-     * @return whether the start succeeded or not
-     */
-    bool startRendering();
-
-    /*
-     * Locally disable frame access to this FrameWrapper
-     */
-    void stopRendering();
-
-    /*
-     * Get the most recently rendered frame as a QImage.
-     * @return the rendered image of this object's id
-     */
-    QImage* getFrame();
-
-    /*
-     * Check if the object is updating actively.
-     */
-    bool isRendering();
-
-    bool frameMutexTryLock();
-
-    void frameMutexUnlock();
-
-    const QString getId()
-    {
-        return id_;
-    }
-
-Q_SIGNALS:
-    /*
-     * Emitted once in slotRenderingStarted.
-     * @param id of the renderer
-     */
-    void renderingStarted(const QString& id);
-    /*
-     * Emitted each time a frame is ready to be displayed.
-     * @param id of the renderer
-     */
-    void frameUpdated(const QString& id);
-    /*
-     * Emitted once in slotRenderingStopped.
-     * @param id of the renderer
-     */
-    void renderingStopped(const QString& id);
-
-public Q_SLOTS:
-    /*
-     * Used to listen to AVModel::rendererStarted.
-     * @param id of the renderer
-     */
-    void slotRenderingStarted(const QString& id);
-    /*
-     * Used to listen to AVModel::frameUpdated.
-     * @param id of the renderer
-     */
-    void slotFrameUpdated(const QString& id);
-    /*
-     * Used to listen to AVModel::renderingStopped.
-     * @param id of the renderer
-     */
-    void slotRenderingStopped(const QString& id);
-
-private:
-    /*
-     * The id of the renderer.
-     */
-    QString id_;
-
-    /*
-     * A pointer to the lrc renderer object.
-     */
-    video::Renderer* renderer_;
-
-    /*
-     * A local copy of the renderer's current frame.
-     */
-    video::Frame frame_;
-
-    /*
-     * A the frame's storage data used to set the image.
-     */
-    std::vector<uint8_t> buffer_;
-
-    /*
-     * The frame's paint ready QImage.
-     */
-    std::unique_ptr<QImage> image_;
-
-    /*
-     * Used to protect the buffer during QImage creation routine.
-     */
-    QMutex mutex_;
-
-    /*
-     * True if the object is rendering
-     */
-    std::atomic_bool isRendering_;
-
-    /*
-     * Convenience ref to avmodel
-     */
-    AVModel& avModel_;
-
-    /*
-     * Connections to the underlying renderer signals in avmodel
-     */
-    RenderConnections renderConnections_;
-};
-
-/**
- * RenderManager filters signals and ecapsulates preview and distant
- * frame wrappers, providing access to QImages for each and simplified
- * start/stop mechanisms for renderers. It should contain as much
- * renderer control logic as possible and prevent ui widgets from directly
- * interfacing the rendering logic.
- */
-class RenderManager final : public QObject
-{
-    Q_OBJECT;
-
-public:
-    explicit RenderManager(AVModel& avModel);
-    ~RenderManager();
-
-    using DrawFrameCallback = std::function<void(QImage*)>;
-
-    /*
-     * Start capturing and rendering preview frames.
-     * @param force if the capture device should be started
-     */
-    const QString startPreviewing(const QString& id, bool force = false);
-    /*
-     * Stop capturing.
-     */
-    void stopPreviewing(const QString& id);
-    /*
-     * Add and connect a distant renderer for a given id
-     * to a FrameWrapper object
-     * @param id
-     */
-    void addDistantRenderer(const QString& id);
-    /*
-     * Disconnect and remove a FrameWrapper object connected to a
-     * distant renderer for a given id
-     * @param id
-     */
-    void removeDistantRenderer(const QString& id);
-    /*
-     * Frame will be provided in the callback thread safely
-     * @param id
-     * @param cb
-     */
-    void drawFrame(const QString& id, DrawFrameCallback cb);
-
-    /*
-     * Get the most recently rendered preview frame as a QImage (none thread safe).
-     * @return the rendered preview image
-     */
-    QImage* getPreviewFrame(const QString& id = "");
-
-Q_SIGNALS:
-    /*
-     * Emitted when a distant renderer has a new frame ready for a given id.
-     */
-    void distantFrameUpdated(const QString& id);
-
-    /*
-     * Emitted when a distant renderer is stopped for a given id.
-     */
-    void distantRenderingStopped(const QString& id);
-
-private:
-    /*
-     * Distant for each call/conf/conversation.
-     */
-    std::map<QString, std::unique_ptr<FrameWrapper>> distantFrameWrapperMap_;
-    std::map<QString, RenderConnections> distantConnectionMap_;
-
-    /*
-     * Convenience ref to avmodel.
-     */
-    AVModel& avModel_;
-};
diff --git a/src/settingsview/components/VideoSettings.qml b/src/settingsview/components/VideoSettings.qml
index 5545843dd75b1804e4111d06c53afd37223ed801..f03cfb257b50f0271e675e5f673edd53234ea558 100644
--- a/src/settingsview/components/VideoSettings.qml
+++ b/src/settingsview/components/VideoSettings.qml
@@ -35,30 +35,16 @@ ColumnLayout {
     property int itemWidth
 
     function startPreviewing(force = false) {
-        if (root.visible) {
-            previewWidget.deviceId = VideoDevices.getDefaultDevice()
-            previewWidget.rendererId = VideoDevices.startDevice(previewWidget.deviceId, force)
+        if (!visible) {
+            return
         }
-    }
-
-    function updatePreviewRatio() {
-        var resolution = VideoDevices.defaultRes
-        if (resolution.length !== 0) {
-            var resVec = resolution.split("x")
-            var ratio = resVec[1] / resVec[0]
-            if (ratio) {
-                aspectRatio = ratio
-            } else {
-                console.error("Could not scale recording video preview")
-            }
-        }
-
+        const deviceId = VideoDevices.getDefaultDevice()
+        previewWidget.startWithId(deviceId, force)
     }
 
     onVisibleChanged: {
         if (visible) {
             hardwareAccelControl.checked = AvAdapter.getHardwareAcceleration()
-            updatePreviewRatio()
             if (previewWidget.visible)
                 startPreviewing(true)
         } else {
@@ -70,14 +56,11 @@ ColumnLayout {
         target: VideoDevices
 
         function onDefaultResChanged() {
-            updatePreviewRatio()
-            if (previewWidget.visible)
-                startPreviewing(true)
+            startPreviewing(true)
         }
 
         function onDefaultFpsChanged() {
-            if (previewWidget.visible)
-                startPreviewing(true)
+            startPreviewing(true)
         }
 
         function onDeviceAvailable() {
@@ -207,12 +190,10 @@ ColumnLayout {
 
     // video Preview
     Rectangle {
-        id: rectBox
-
         visible: VideoDevices.listSize !== 0
 
         Layout.alignment: Qt.AlignHCenter
-        Layout.preferredHeight: width * aspectRatio
+        Layout.preferredHeight: width * previewWidget.invAspectRatio
 
         Layout.minimumWidth: 200
         Layout.maximumWidth: 400
@@ -221,28 +202,19 @@ ColumnLayout {
 
         color: JamiTheme.primaryForegroundColor
 
-        DistantRenderer {
+        LocalVideo {
             id: previewWidget
 
-            anchors.fill: rectBox
-            property string deviceId: VideoDevices.getDefaultDevice()
-            rendererId: VideoDevices.getDefaultDevice()
+            anchors.fill: parent
 
-            lrcInstance: LRCInstance
-
-            layer.enabled: true
-            layer.effect: OpacityMask {
-                maskSource: rectBox
+            underlayItems: Text {
+                anchors.centerIn: parent
+                font.pointSize: 18
+                font.capitalization: Font.AllUppercase
+                color: "white"
+                text: JamiStrings.noVideo
             }
         }
-
-        onVisibleChanged: {
-            if (visible) {
-                VideoDevices.stopDevice(previewWidget.deviceId)
-                startPreviewing(true)
-            } else
-                VideoDevices.stopDevice(previewWidget.deviceId)
-        }
     }
 
     Label {
diff --git a/src/videodevices.cpp b/src/videodevices.cpp
index 5cf90e76d9bc90343e301ce82c0a5d2232004c36..ff962588e4a4fbd1ebccf67ab94d8b18c120602c 100644
--- a/src/videodevices.cpp
+++ b/src/videodevices.cpp
@@ -174,10 +174,8 @@ VideoFormatFpsModel::roleNames() const
 int
 VideoFormatFpsModel::getCurrentIndex() const
 {
-    QString currentDeviceId = videoDevices_->get_defaultId();
     float currentFps = videoDevices_->get_defaultFps();
     auto resultList = match(index(0, 0), FPS, QVariant(currentFps));
-
     return resultList.size() > 0 ? resultList[0].row() : 0;
 }
 
@@ -291,21 +289,26 @@ VideoDevices::getDefaultDevice()
 }
 
 QString
-VideoDevices::startDevice(const QString& deviceId, bool force)
+VideoDevices::startDevice(const QString& id, bool force)
 {
-    if (deviceId.isEmpty())
+    if (id.isEmpty())
         return {};
-    lrcInstance_->renderer()->addDistantRenderer(deviceId);
+    auto& avModel = lrcInstance_->avModel();
+    if (avModel.hasRenderer(id)) {
+        if (!force) {
+            return id;
+        }
+        avModel.stopPreview(id);
+    }
     deviceOpen_ = true;
-    return lrcInstance_->renderer()->startPreviewing(deviceId, force);
+    return avModel.startPreview(id);
 }
 
 void
-VideoDevices::stopDevice(const QString& deviceId, bool force)
+VideoDevices::stopDevice(const QString& id, bool force)
 {
-    if (!deviceId.isEmpty() && (!lrcInstance_->hasActiveCall(true) || force)) {
-        lrcInstance_->renderer()->stopPreviewing(deviceId);
-        lrcInstance_->renderer()->removeDistantRenderer(deviceId);
+    if (!id.isEmpty() && (!lrcInstance_->hasActiveCall(true) || force)) {
+        lrcInstance_->avModel().stopPreview(id);
         deviceOpen_ = false;
     }
 }
@@ -481,17 +484,6 @@ VideoDevices::onVideoDeviceEvent()
         Q_EMIT deviceListChanged(currentDeviceListSize);
     } else if (deviceOpen_) {
         updateData();
-
-        // Use QueuedConnection to make sure that it happens at the event loop of current device
-        Utils::oneShotConnect(
-            lrcInstance_->renderer(),
-            &RenderManager::distantRenderingStopped,
-            this,
-            [this, cb](const QString& id) {
-                if (this->getDefaultDevice() == id)
-                    cb();
-            },
-            Qt::QueuedConnection);
     } else {
         cb();
     }
diff --git a/src/videoprovider.cpp b/src/videoprovider.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c3d4f0e0e82642a32826703fbffa0d03032976a5
--- /dev/null
+++ b/src/videoprovider.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "videoprovider.h"
+
+using namespace lrc::api;
+
+static bool
+mapVideoFrame(QVideoFrame* videoFrame)
+{
+    if (!videoFrame || !videoFrame->isValid()
+        || (!videoFrame->isMapped() && !videoFrame->map(QVideoFrame::WriteOnly))) {
+        return false;
+    }
+    return true;
+}
+
+VideoProvider::VideoProvider(AVModel& avModel, QObject* parent)
+    : QObject(parent)
+    , avModel_(avModel)
+{
+    connect(&avModel_, &AVModel::rendererStarted, this, &VideoProvider::onRendererStarted);
+    connect(&avModel_, &AVModel::frameBufferRequested, this, &VideoProvider::onFrameBufferRequested);
+    connect(&avModel_, &AVModel::frameUpdated, this, &VideoProvider::onFrameUpdated);
+    connect(&avModel_, &AVModel::rendererStopped, this, &VideoProvider::onRendererStopped);
+}
+
+void
+VideoProvider::registerSink(const QString& id, QVideoSink* obj)
+{
+    QMutexLocker lk(&framesObjsMutex_);
+    auto it = framesObjects_.find(id);
+    if (it == framesObjects_.end()) {
+        auto fo = std::make_unique<FrameObject>();
+        fo->subscribers.insert(obj);
+        framesObjects_.emplace(id, std::move(fo));
+        return;
+    }
+    it->second->subscribers.insert(obj);
+}
+
+void
+VideoProvider::unregisterSink(QVideoSink* obj)
+{
+    QMutexLocker lk(&framesObjsMutex_);
+    for (auto& frameObjIt : qAsConst(framesObjects_)) {
+        auto& subs = frameObjIt.second->subscribers;
+        auto it = subs.constFind(obj);
+        if (it != subs.cend()) {
+            subs.erase(it);
+        }
+    }
+}
+
+QString
+VideoProvider::captureVideoFrame(QVideoSink* obj)
+{
+    QImage img;
+    QVideoFrame currentFrame = obj->videoFrame();
+    FrameObject* frameObj {nullptr};
+    QMutexLocker lk(&framesObjsMutex_);
+    for (auto& frameObjIt : qAsConst(framesObjects_)) {
+        auto& subs = frameObjIt.second->subscribers;
+        auto it = subs.constFind(obj);
+        if (it != subs.cend()) {
+            frameObj = frameObjIt.second.get();
+        }
+    }
+    if (frameObj) {
+        QMutexLocker lk(&frameObj->mutex);
+        auto imageFormat = QVideoFrameFormat::imageFormatFromPixelFormat(
+            QVideoFrameFormat::Format_RGBA8888);
+        img = QImage(currentFrame.bits(0),
+                     currentFrame.width(),
+                     currentFrame.height(),
+                     currentFrame.bytesPerLine(0),
+                     imageFormat);
+    }
+    return Utils::byteArrayToBase64String(Utils::QImageToByteArray(img));
+}
+
+void
+VideoProvider::onRendererStarted(const QString& id)
+{
+    auto size = avModel_.getRendererSize(id);
+    // This slot is queued, the renderer may have been destroyed.
+    if (size.width() == 0 || size.height() == 0) {
+        return;
+    }
+    auto pixelFormat = avModel_.useDirectRenderer() ? QVideoFrameFormat::Format_RGBA8888
+                                                    : QVideoFrameFormat::Format_BGRA8888;
+    auto frameFormat = QVideoFrameFormat(size, pixelFormat);
+    {
+        QMutexLocker lk(&framesObjsMutex_);
+        auto it = framesObjects_.find(id);
+        if (it == framesObjects_.end()) {
+            auto fo = std::make_unique<FrameObject>();
+            fo->videoFrame = std::make_unique<QVideoFrame>(frameFormat);
+            framesObjects_.emplace(id, std::move(fo));
+        } else {
+            it->second->videoFrame.reset(new QVideoFrame(frameFormat));
+        }
+    }
+
+    activeRenderers_[id] = size;
+    Q_EMIT activeRenderersChanged();
+}
+
+void
+VideoProvider::onFrameBufferRequested(const QString& id, AVFrame* avframe)
+{
+    QMutexLocker framesLk(&framesObjsMutex_);
+    auto it = framesObjects_.find(id);
+    if (it == framesObjects_.end()) {
+        return;
+    }
+    if (it->second->subscribers.empty()) {
+        return;
+    }
+    QMutexLocker lk(&it->second->mutex);
+    auto videoFrame = it->second->videoFrame.get();
+    if (!mapVideoFrame(videoFrame)) {
+        qWarning() << "QVideoFrame can't be mapped" << id;
+        return;
+    }
+    // The ownership of avframe structure remains the subscriber(jamid), and
+    // the videoFrame instance is owned by the VideoProvider(client). The
+    // avframe structure contains only a description of the QVideoFrame
+    // underlying buffer.
+    // TODO: ideally, the colorspace format should likely come from jamid and
+    // be the decoded format.
+    avframe->format = AV_PIX_FMT_RGBA;
+    avframe->width = videoFrame->width();
+    avframe->height = videoFrame->height();
+    avframe->data[0] = (uint8_t*) videoFrame->bits(0);
+    avframe->linesize[0] = videoFrame->bytesPerLine(0);
+}
+
+void
+VideoProvider::onFrameUpdated(const QString& id)
+{
+    QMutexLocker framesLk(&framesObjsMutex_);
+    auto it = framesObjects_.find(id);
+    if (it == framesObjects_.end()) {
+        return;
+    }
+    if (it->second->subscribers.empty()) {
+        return;
+    }
+    QMutexLocker lk(&it->second->mutex);
+    auto videoFrame = it->second->videoFrame.get();
+    if (videoFrame == nullptr) {
+        qWarning() << "QVideoFrame has not been initialized.";
+        return;
+    }
+    if (!avModel_.useDirectRenderer()) {
+        // Shared memory renderering.
+        if (!mapVideoFrame(videoFrame)) {
+            qWarning() << "QVideoFrame can't be mapped" << id;
+            return;
+        }
+        auto frame = avModel_.getRendererFrame(id);
+        std::memcpy(videoFrame->bits(0), frame.ptr, frame.size);
+    }
+    if (videoFrame->isMapped()) {
+        videoFrame->unmap();
+        for (const auto& sink : qAsConst(it->second->subscribers)) {
+            sink->setVideoFrame(*videoFrame);
+            Q_EMIT sink->videoFrameChanged(*videoFrame);
+        }
+    }
+}
+
+void
+VideoProvider::onRendererStopped(const QString& id)
+{
+    QMutexLocker framesLk(&framesObjsMutex_);
+    auto it = framesObjects_.find(id);
+    if (it == framesObjects_.end()) {
+        return;
+    }
+
+    activeRenderers_.remove(id);
+    Q_EMIT activeRenderersChanged();
+
+    QMutexLocker lk(&it->second->mutex);
+    if (it->second->subscribers.empty()) {
+        lk.unlock();
+        framesObjects_.erase(it);
+        return;
+    }
+    it->second->videoFrame.reset();
+}
diff --git a/src/videoprovider.h b/src/videoprovider.h
new file mode 100644
index 0000000000000000000000000000000000000000..02d5a9b9b0422b55a6fb8382da5d2aeb1a36a0e8
--- /dev/null
+++ b/src/videoprovider.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "utils.h"
+#include "qtutils.h"
+
+#include "api/avmodel.h"
+
+extern "C" {
+#include "libavutil/frame.h"
+}
+
+#include <QVideoSink>
+#include <QVideoFrame>
+#include <QQmlEngine>
+#include <QMutex>
+
+using namespace lrc::api;
+
+class VideoProvider final : public QObject
+{
+    Q_OBJECT
+    QML_ELEMENT
+    QML_PROPERTY(QVariantMap, activeRenderers)
+public:
+    explicit VideoProvider(AVModel& avModel, QObject* parent = nullptr);
+    ~VideoProvider() = default;
+
+    Q_INVOKABLE void registerSink(const QString& id, QVideoSink* obj);
+    Q_INVOKABLE void unregisterSink(QVideoSink* obj);
+    Q_INVOKABLE QString captureVideoFrame(QVideoSink* obj);
+
+private Q_SLOTS:
+    void onRendererStarted(const QString& id);
+    void onFrameBufferRequested(const QString& id, AVFrame* avframe);
+    void onFrameUpdated(const QString& id);
+    void onRendererStopped(const QString& id);
+
+private:
+    AVModel& avModel_;
+
+    struct FrameObject
+    {
+        std::unique_ptr<QVideoFrame> videoFrame;
+        QMutex mutex;
+        QSet<QVideoSink*> subscribers;
+    };
+    std::map<QString, std::unique_ptr<FrameObject>> framesObjects_;
+    QMutex framesObjsMutex_;
+};