summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/timeline/TimelineModel.h9
-rw-r--r--src/timeline/TimelineViewManager.cpp2
-rw-r--r--src/ui/MxcMediaProxy.cpp142
-rw-r--r--src/ui/MxcMediaProxy.h80
4 files changed, 233 insertions, 0 deletions
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h

index e3ca8811..417fbb7f 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h
@@ -293,6 +293,15 @@ public: crypto::Trust trustlevel() const; int roomMemberCount() const; + std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id) + { + auto e = events.get(id.toStdString(), ""); + if (e) + return *e; + else + return std::nullopt; + } + public slots: void setCurrentIndex(int index); int currentIndex() const { return idToIndex(currentId); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 97b60b0c..f42ec02f 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp
@@ -35,6 +35,7 @@ #include "dialogs/ImageOverlay.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" +#include "ui/MxcMediaProxy.h" #include "ui/NhekoCursorShape.h" #include "ui/NhekoDropArea.h" #include "ui/NhekoGlobalObject.h" @@ -176,6 +177,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea"); qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape"); + qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia"); qmlRegisterUncreatableType<DeviceVerificationFlow>( "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!"); qmlRegisterUncreatableType<UserProfile>( diff --git a/src/ui/MxcMediaProxy.cpp b/src/ui/MxcMediaProxy.cpp new file mode 100644
index 00000000..c1de2c31 --- /dev/null +++ b/src/ui/MxcMediaProxy.cpp
@@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MxcMediaProxy.h" + +#include <QDir> +#include <QFile> +#include <QFileInfo> +#include <QMediaObject> +#include <QMediaPlayer> +#include <QMimeDatabase> +#include <QStandardPaths> +#include <QUrl> + +#include "EventAccessors.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "timeline/TimelineModel.h" + +void +MxcMediaProxy::setVideoSurface(QAbstractVideoSurface *surface) +{ + qDebug() << "Changing surface"; + m_surface = surface; + setVideoOutput(m_surface); +} + +QAbstractVideoSurface * +MxcMediaProxy::getVideoSurface() +{ + return m_surface; +} + +void +MxcMediaProxy::startDownload() +{ + if (!room_) + return; + if (eventId_.isEmpty()) + return; + + auto event = room_->eventById(eventId_); + if (!event) { + nhlog::ui()->error("Failed to load media for event {}, event not found.", + eventId_.toStdString()); + return; + } + + QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); + QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); + QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)); + + auto encryptionInfo = mtx::accessors::file(*event); + + // If the message is a link to a non mxcUrl, don't download it + if (!mxcUrl.startsWith("mxc://")) { + return; + } + + QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); + + const auto url = mxcUrl.toStdString(); + const auto name = QString(mxcUrl).remove("mxc://"); + QFileInfo filename(QString("%1/media_cache/media/%2.%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(name) + .arg(suffix)); + if (QDir::cleanPath(name) != name) { + nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); + return; + } + + QDir().mkpath(filename.path()); + + QPointer<MxcMediaProxy> self = this; + + auto processBuffer = [this, encryptionInfo, filename, self](QIODevice &device) { + if (!self) + return; + + if (encryptionInfo) { + QByteArray ba = device.readAll(); + std::string temp(ba.constData(), ba.size()); + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + buffer.setData(temp.data(), temp.size()); + } else { + buffer.setData(device.readAll()); + } + buffer.open(QIODevice::ReadOnly); + buffer.reset(); + + QTimer::singleShot(0, this, [this, self, filename] { + nhlog::ui()->info("Playing buffer with size: {}, {}", + buffer.bytesAvailable(), + buffer.isOpen()); + self->setMedia(QMediaContent(filename.fileName()), &buffer); + emit loadedChanged(); + }); + }; + + if (filename.isReadable()) { + QFile f(filename.filePath()); + if (f.open(QIODevice::ReadOnly)) { + processBuffer(f); + return; + } + } + + http::client()->download( + url, + [filename, url, processBuffer](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve media {}: {} {}", + url, + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } + + try { + QFile file(filename.filePath()); + + if (!file.open(QIODevice::WriteOnly)) + return; + + QByteArray ba(data.data(), (int)data.size()); + file.write(ba); + file.close(); + + QBuffer buf(&ba); + buf.open(QBuffer::ReadOnly); + processBuffer(buf); + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + }); +} diff --git a/src/ui/MxcMediaProxy.h b/src/ui/MxcMediaProxy.h new file mode 100644
index 00000000..14541815 --- /dev/null +++ b/src/ui/MxcMediaProxy.h
@@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <QAbstractVideoSurface> +#include <QBuffer> +#include <QMediaContent> +#include <QMediaPlayer> +#include <QObject> +#include <QPointer> +#include <QString> + +#include "Logging.h" + +class TimelineModel; + +// I failed to get my own buffer into the MediaPlayer in qml, so just make our own. For that we just +// need the videoSurface property, so that part is really easy! +class MxcMediaProxy : public QMediaPlayer +{ + Q_OBJECT + Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED) + Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged) + Q_PROPERTY(QAbstractVideoSurface *videoSurface READ getVideoSurface WRITE setVideoSurface) + Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged) +public: + MxcMediaProxy(QObject *parent = nullptr) + : QMediaPlayer(parent) + { + connect(this, &MxcMediaProxy::eventIdChanged, &MxcMediaProxy::startDownload); + connect(this, &MxcMediaProxy::roomChanged, &MxcMediaProxy::startDownload); + connect(this, + qOverload<QMediaPlayer::Error>(&MxcMediaProxy::error), + [this](QMediaPlayer::Error error) { + nhlog::ui()->info("Media player error {} and errorStr {}", + error, + this->errorString().toStdString()); + }); + connect(this, + &MxcMediaProxy::mediaStatusChanged, + [this](QMediaPlayer::MediaStatus status) { + nhlog::ui()->info( + "Media player status {} and error {}", status, this->error()); + }); + } + + bool loaded() const { return buffer.size() > 0; } + QString eventId() const { return eventId_; } + TimelineModel *room() const { return room_; } + void setEventId(QString newEventId) + { + eventId_ = newEventId; + emit eventIdChanged(); + } + void setRoom(TimelineModel *room) + { + room_ = room; + emit roomChanged(); + } + void setVideoSurface(QAbstractVideoSurface *surface); + QAbstractVideoSurface *getVideoSurface(); + +signals: + void roomChanged(); + void eventIdChanged(); + void loadedChanged(); + void newBuffer(QMediaContent, QIODevice *buf); + +private slots: + void startDownload(); + +private: + TimelineModel *room_ = nullptr; + QString eventId_; + QString filename_; + QBuffer buffer; + QAbstractVideoSurface *m_surface = nullptr; +};