Skip to content

Commit

Permalink
Refactor properties. Refactor singleton.
Browse files Browse the repository at this point in the history
  • Loading branch information
rectalogic committed Oct 19, 2023
1 parent 8c9955b commit 778279c
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 75 deletions.
48 changes: 30 additions & 18 deletions mediafx/clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
// found in the LICENSE file.

#include "clip.h"
#include "mediafx.h"
#include "session.h"
#include <QDebug>
#include <QMediaTimeRange>
#include <QMessageLogContext>
#include <QQmlEngine>
#include <QUrl>

void Clip::setSource(const QUrl& url)
{
if (!m_source.isEmpty()) {
qWarning() << "Clip source is a write-once property and cannot be changed";
qmlWarning(this) << "Clip source is a write-once property and cannot be changed";
return;
}
m_source = url;
Expand All @@ -20,19 +23,21 @@ void Clip::setSource(const QUrl& url)
void Clip::setClipStart(qint64 clipStart)
{
if (m_clipStart != -1) {
qWarning() << "Clip clipStart is a write-once property and cannot be changed";
qmlWarning(this) << "Clip clipStart is a write-once property and cannot be changed";
return;
}
m_clipStart = clipStart;
emit clipStartChanged();
}

void Clip::setClipEnd(qint64 clipEnd)
{
if (m_clipEnd != -1) {
qWarning() << "Clip clipEnd is a write-once property and cannot be changed";
qmlWarning(this) << "Clip clipEnd is a write-once property and cannot be changed";
return;
}
m_clipEnd = clipEnd;
emit clipEndChanged();
}

bool Clip::render(const QMediaTimeRange::Interval& globalTime)
Expand All @@ -41,14 +46,11 @@ bool Clip::render(const QMediaTimeRange::Interval& globalTime)
if (currentGlobalTime() == globalTime)
return true;

qint64 duration = globalTime.end() - globalTime.start();
if (nextClipTime().end() == -1) {
setNextClipTime(QMediaTimeRange::Interval(clipStart(), clipStart() + duration));
}
qint64 frameDuration = globalTime.end() - globalTime.start();
if (active() && clipSegment().contains(nextClipTime().start())) {
if (renderClip(globalTime)) {
setCurrentGlobalTime(globalTime);
setNextClipTime(nextClipTime().translated(duration));
setNextClipTime(nextClipTime().translated(frameDuration));
return true;
} else {
return false;
Expand All @@ -64,7 +66,19 @@ bool Clip::render(const QMediaTimeRange::Interval& globalTime)

void Clip::stop()
{
setNextClipTime(QMediaTimeRange::Interval(clipStart(), -1));
setNextClipTime(QMediaTimeRange::Interval(
clipStart(),
qmlEngine(this)->singletonInstance<MediaFX*>(MediaFX::typeId)->session()->frameDuration()));
}

void Clip::setActive(bool active)
{
auto mediaFX = qmlEngine(this)->singletonInstance<MediaFX*>(MediaFX::typeId);
if (active) {
mediaFX->registerClip(this);
} else {
mediaFX->unregisterClip(this);
}
}

void Clip::classBegin()
Expand All @@ -74,17 +88,15 @@ void Clip::classBegin()
void Clip::componentComplete()
{
m_componentComplete = true;
// Clip can return clipEnd() if no intrinsic duration, ensure it is set
if (duration() == -1) {
qCritical() << "Clip has no intrinsic duration, set clipEnd property";
QCoreApplication::exit();
if (clipEnd() == -1) {
qmlWarning(this) << "Clip has no intrinsic duration, set clipEnd property";
emit qmlEngine(this)->singletonInstance<MediaFX*>(MediaFX::typeId)->session()->exitApp(1);
return;
}
if (clipStart() == -1)
setClipStart(0);
if (clipEnd() == -1) {
setClipEnd(duration() - clipStart());
}
// We don't know frame duration yet, it will be initialized on first render, set to -1
setNextClipTime(QMediaTimeRange::Interval(clipStart(), -1));

setNextClipTime(QMediaTimeRange::Interval(
clipStart(),
qmlEngine(this)->singletonInstance<MediaFX*>(MediaFX::typeId)->session()->frameDuration()));
}
17 changes: 9 additions & 8 deletions mediafx/clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ class Clip : public QObject, public QQmlParserStatus {
Q_PROPERTY(QUrl source READ source WRITE setSource REQUIRED FINAL)
// QML doesn't support qint64, so declare these as int.
// This is what QML MediaPlayer/QMediaPlayer does
Q_PROPERTY(int duration READ duration)
Q_PROPERTY(int clipStart READ clipStart WRITE setClipStart FINAL)
Q_PROPERTY(int clipEnd READ clipEnd WRITE setClipEnd FINAL)
Q_PROPERTY(int clipStart READ clipStart WRITE setClipStart NOTIFY clipStartChanged FINAL)
Q_PROPERTY(int clipEnd READ clipEnd WRITE setClipEnd NOTIFY clipEndChanged FINAL)
QML_ELEMENT
QML_UNCREATABLE("Clip is an abstract base class.")

signals:
void clipStartChanged();
void clipEndChanged();

public:
using QObject::QObject;

Expand All @@ -38,8 +41,6 @@ class Clip : public QObject, public QQmlParserStatus {
QUrl source() const { return m_source; };
void setSource(const QUrl&);

virtual qint64 duration() const = 0;

qint64 clipStart() const { return m_clipStart; };
void setClipStart(qint64);

Expand All @@ -53,20 +54,20 @@ class Clip : public QObject, public QQmlParserStatus {

protected:
QMediaTimeRange::Interval clipSegment() const { return QMediaTimeRange::Interval(m_clipStart, m_clipEnd); };

QMediaTimeRange::Interval nextClipTime() const { return m_nextClipTime; };
void setNextClipTime(const QMediaTimeRange::Interval& time) { m_nextClipTime = time; };

QMediaTimeRange::Interval currentGlobalTime() const { return m_currentGlobalTime; };
void setCurrentGlobalTime(const QMediaTimeRange::Interval& currentTime) { m_currentGlobalTime = currentTime; };

virtual void setActive(bool active) = 0;
virtual void setActive(bool active);
virtual bool active() = 0;

virtual void loadMedia(const QUrl&) = 0;

virtual bool renderClip(const QMediaTimeRange::Interval& globalTime) = 0;

QMediaTimeRange::Interval nextClipTime() const { return m_nextClipTime; };

virtual void stop();

bool isComponentComplete() { return m_componentComplete; };
Expand Down
13 changes: 6 additions & 7 deletions mediafx/image_clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// found in the LICENSE file.

#include "image_clip.h"
#include "mediafx.h"
#include "session.h"
#include "visual_clip.h"
#include <QCoreApplication>
#include <QDebug>
Expand All @@ -13,10 +15,11 @@

void ImageClip::loadMedia(const QUrl& url)
{
auto session = qmlEngine(this)->singletonInstance<MediaFX*>(MediaFX::typeId)->session();
QImage image;
if (!url.isLocalFile() || !image.load(url.toLocalFile())) {
qCritical("ImageClip can only load local file urls");
QCoreApplication::exit(1);
emit session->exitApp(1);
return;
}
videoFrame = QVideoFrame(QVideoFrameFormat(image.size(), QVideoFrameFormat::Format_RGBA8888));
Expand All @@ -25,20 +28,16 @@ void ImageClip::loadMedia(const QUrl& url)
}
if (!videoFrame.map(QVideoFrame::MapMode::WriteOnly)) {
qCritical("ImageClip can not convert image");
QCoreApplication::exit(1);
emit session->exitApp(1);
return;
}
memcpy(videoFrame.bits(0), image.constBits(), videoFrame.mappedBytes(0));
videoFrame.unmap();
}

qint64 ImageClip::duration() const
{
return clipEnd();
}

bool ImageClip::prepareNextVideoFrame()
{
// XXX timestamp frame based on nextClipTime() ?
setCurrentVideoFrame(videoFrame);
return true;
}
Expand Down
2 changes: 0 additions & 2 deletions mediafx/image_clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ class ImageClip : public VisualClip {
public:
using VisualClip::VisualClip;

qint64 duration() const override;

protected:
void loadMedia(const QUrl&) override;

Expand Down
11 changes: 1 addition & 10 deletions mediafx/mediafx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,8 @@
#include <QmlTypeAndRevisionsRegistration>
class QVideoSink;

// Can't use this, so we defer setting typeId
// https://bugreports.qt.io/browse/QTBUG-118165
// int MediaFX::typeId = qmlTypeId("stream.mediafx", 254, 254, "MediaFX");
int MediaFX::typeId = -1;

MediaFX::MediaFX()
: QObject()
{
MediaFX::typeId = qmlTypeId("stream.mediafx", 254, 254, "MediaFX");
}

void MediaFX::registerClip(Clip* clip)
{
if (clip && !activeClips.contains(clip)) {
Expand All @@ -34,7 +25,7 @@ void MediaFX::registerClip(Clip* clip)
for (const auto clip : activeClips) {
for (const auto sink : clip->videoSinks()) {
if (set.contains(sink)) {
qWarning() << "Warning: duplicate QVideoSink found on " << clip;
qmlWarning(clip) << "Warning: duplicate QVideoSink found";
return;
}
set.insert(sink);
Expand Down
52 changes: 50 additions & 2 deletions mediafx/mediafx.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,71 @@
#include <QList>
#include <QMediaTimeRange>
#include <QObject>
#include <QQmlEngine>
#include <QtQmlIntegration>
class Clip;
class Session;

class MediaFX : public QObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON

public:
MediaFX();
static int typeId;
using QObject::QObject;
MediaFX(Session* session, QObject* parent = nullptr)
: QObject(parent)
, m_session(session)
{
}

Session* session() { return m_session; };

void registerClip(Clip* clip);
void unregisterClip(Clip* clip);

bool renderVideoFrame(const QMediaTimeRange::Interval& frameTimeRange);

static int typeId;

private:
Session* m_session;
QList<Clip*> activeClips;
};

// https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON
struct MediaFXForeign {
Q_GADGET
QML_FOREIGN(MediaFX)
QML_SINGLETON
QML_NAMED_ELEMENT(MediaFX)
public:
// Initialize this using myObject where you would previously
// call qmlRegisterSingletonInstance().
inline static MediaFX* s_singletonInstance = nullptr;

static MediaFX* create(QQmlEngine*, QJSEngine* engine)
{
// The instance has to exist before it is used. We cannot replace it.
Q_ASSERT(s_singletonInstance);

// The engine has to have the same thread affinity as the singleton.
Q_ASSERT(engine->thread() == s_singletonInstance->thread());

// There can only be one engine accessing the singleton.
if (s_engine)
Q_ASSERT(engine == s_engine);
else
s_engine = engine;

MediaFX::typeId = qmlTypeId("stream.mediafx", 254, 254, "MediaFX");

// Explicitly specify C++ ownership so that the engine doesn't delete
// the instance.
QJSEngine::setObjectOwnership(s_singletonInstance, QJSEngine::CppOwnership);
return s_singletonInstance;
}

private:
inline static QJSEngine* s_engine = nullptr;
};
18 changes: 10 additions & 8 deletions mediafx/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,26 @@ QEvent::Type Session::renderEventType = static_cast<QEvent::Type>(QEvent::regist

Session::Session(QUrl& url, QSize& size, qint64 frameDuration)
: QObject()
, frameDuration(frameDuration)
, m_frameDuration(frameDuration)
, frameTime(0, frameDuration)
, quickView(QUrl(), &renderControl)
{
connect(this, &Session::exitApp, qApp, &QCoreApplication::exit, Qt::QueuedConnection);

MediaFXForeign::s_singletonInstance = new MediaFX(this, this);

quickView.setResizeMode(QQuickView::ResizeMode::SizeRootObjectToView);
quickView.resize(size);
if (!renderControl.install(quickView, size)) {
qCritical() << "Failed to install QQuickRenderControl";
QCoreApplication::exit(1);
emit exitApp(1);
return;
}
connect(&quickView, &QQuickView::statusChanged, this, &Session::quickViewStatusChanged);
connect(quickView.engine(), &QQmlEngine::warnings, this, &Session::engineWarnings);

// Workaround https://bugreports.qt.io/browse/QTBUG-118165
// mediaFX = quickView.engine()->singletonInstance<MediaFX*>(MediaFX::typeId);
// Workaround https://bugreports.qt.io/browse/QTBUG-118165 - we can't just use qmlTypeId()
// This will call MediaFXForeign::create() which will initialize typeId
mediaFX = quickView.engine()->singletonInstance<MediaFX*>("stream.mediafx", "MediaFX");

quickView.setSource(url);
Expand All @@ -46,8 +50,7 @@ Session::Session(QUrl& url, QSize& size, qint64 frameDuration)
void Session::quickViewStatusChanged(QQuickView::Status status)
{
if (status == QQuickView::Error) {
engineWarnings(quickView.errors());
QCoreApplication::exit(1);
emit exitApp(1);
} else if (status == QQuickView::Ready) {
render();
}
Expand Down Expand Up @@ -88,10 +91,9 @@ void Session::render()
}
/**** XXX ****/

frameTime = frameTime.translated(frameDuration);
frameTime = frameTime.translated(frameDuration());
}

// XXX should we post this from Clip when new frames are available? but what about producers
// XXX need to know when we're done - how do we determine total duration?
// XXX QML could signal a mediaFX slot when done
QCoreApplication::postEvent(this, new QEvent(renderEventType));
Expand Down
7 changes: 6 additions & 1 deletion mediafx/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@ class Session : public QObject {
public:
Session(QUrl& url, QSize& size, qint64 frameDuration);

qint64 frameDuration() { return m_frameDuration; };

void render();

bool event(QEvent* event) override;

signals:
void exitApp(int returnCode = 0);

private slots:
void quickViewStatusChanged(QQuickView::Status status);
void engineWarnings(const QList<QQmlError>& warnings);

private:
static QEvent::Type renderEventType;
qint64 frameDuration;
qint64 m_frameDuration;
QMediaTimeRange::Interval frameTime;
RenderControl renderControl;
QQuickView quickView;
Expand Down
Loading

0 comments on commit 778279c

Please sign in to comment.