diff options
author | Nicolas Werner <nicolas.werner@hotmail.de> | 2021-08-28 00:38:33 +0200 |
---|---|---|
committer | Nicolas Werner <nicolas.werner@hotmail.de> | 2021-08-29 16:32:22 +0200 |
commit | 09c041c8ac40d2d3608c7224614fde69e1e4f08b (patch) | |
tree | b606a260d7f22a703684e186a0af9d8215be734d /src/ui | |
parent | Remove CC-BY as main project license (diff) | |
download | nheko-09c041c8ac40d2d3608c7224614fde69e1e4f08b.tar.xz |
Use in memory media player instead of storing unencrypted files on disk
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/MxcMediaProxy.cpp | 142 | ||||
-rw-r--r-- | src/ui/MxcMediaProxy.h | 80 |
2 files changed, 222 insertions, 0 deletions
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; +}; |