Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1635,6 +1635,8 @@ add_library(
src/widget/wwidget.cpp
src/widget/wwidgetgroup.cpp
src/widget/wwidgetstack.cpp
src/controllers/scripting/javascriptplayerproxy.cpp
src/controllers/scripting/javascriptplayerproxy.h
)
set(MIXXX_COMMON_PRECOMPILED_HEADER src/util/assert.h)
set(
Expand Down
89 changes: 89 additions & 0 deletions res/controllers/engine-api.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
declare interface QtSlot<F extends (...args: any[]) => void> {
connect(callback: F): void
}

/** ScriptConnectionJSProxy */

Expand Down Expand Up @@ -31,10 +34,96 @@ declare interface ScriptConnection {
readonly isConnected: boolean;
}

/** JavascriptPlayerProxy */

declare interface Player {
/** Track's artist or empty string if no track is loaded */
readonly artist: string
/** Track's title or empty string if no track is loaded */
readonly title: string
/** Track's album or empty string if no track is loaded */
readonly album: string
/** Track's album artist or empty string if no track is loaded */
readonly albumArtist: string
/** Track's genre or empty string if no track is loaded */
readonly genre: string
/** Track's composer or empty string if no track is loaded */
readonly composer: string
/** Track's grouping or empty string if no track is loaded */
readonly grouping: string
/** Track's year of release or empty string if no track is loaded */
readonly year: string
/** Track's number or empty string if no track is loaded */
readonly trackNumber: string
/** Total number of tracks in track's album or empty string if no track is loaded */
readonly trackTotal: string

/** Emitted when the track is unloaded from the player. */
trackUnloaded: QtSlot<() => void>
Comment thread
christophehenry marked this conversation as resolved.

/**
* Emitted with the new track's artist when a new track is loaded
* to the player or when the current track's metadata change.
*/
artistChanged: QtSlot<(newArtist: string) => void>
/**
* Emitted with the new track title when a new track is loaded
* to the player or when the current track's metadata change.
*/
titleChanged: QtSlot<(newTitle: string) => void>
/**
* Emitted with the new track album when a new track is loaded
* to the player or when the current track's metadata change.
*/
albumChanged: QtSlot<(newAlbum: string) => void>
/**
* Emitted with the new track album artist when a new track is loaded
* to the player or when the current track's metadata change.
*/
albumArtistChanged: QtSlot<(newAlbumArtist: string) => void>
/**
* Emitted with the new track genre when a new track is loaded
* to the player or when the current track's metadata change.
*/
genreChanged: QtSlot<(newGenre: string) => void>
/**
* Emitted with the new track's composer when a new track is loaded
* to the player or when the current track's metadata change.
*/
composerChanged: QtSlot<(newComposer: string) => void>
/**
* Emitted with the new track's grouping when a new track is loaded
* to the player or when the current track's metadata change.
*/
groupingChanged: QtSlot<(newGrouping: string) => void>
/**
* Emitted with the new track year of release when a new track is loaded
* to the player or when the current track's metadata change.
*/
yearChanged: QtSlot<(newYear: string) => void>
/**
* Emitted with the new track number when a new track is loaded
* to the player or when the current track's metadata change.
*/
trackNumberChanged: QtSlot<(newTrackNumber: string) => void>
/**
* Emitted with the new number of track in track's album when a new track
* is loaded to the player or when the current track's metadata change.
*/
trackTotalChanged: QtSlot<(newTrackTotal: string) => void>
}

/** ControllerScriptInterfaceLegacy */

declare namespace engine {
/**
* Obtain the player associated with this deck.
* @param group The midi group for this deck; e.g. '[Channel1]' for deck 1.
* @returns The player providing track information and signals, or undefined
* if not player associated with this group was found.
*/
function getPlayer(group: string): Player | undefined

type SettingValue = string | number | boolean;
/**
* Gets the value of a controller setting
Expand Down
25 changes: 24 additions & 1 deletion src/controllers/scripting/controllerscriptenginebase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ ControllerScriptEngineBase::ControllerScriptEngineBase(
qRegisterMetaType<QMessageBox::StandardButton>("QMessageBox::StandardButton");
}

#ifdef MIXXX_USE_QML
void ControllerScriptEngineBase::registerPlayerManager(
Comment thread
christophehenry marked this conversation as resolved.
std::shared_ptr<PlayerManager> pPlayerManager) {
ControllerScriptEngineBase::s_pPlayerManager = pPlayerManager;
}
Comment thread
christophehenry marked this conversation as resolved.

void ControllerScriptEngineBase::registerTrackCollectionManager(
std::shared_ptr<TrackCollectionManager> pTrackCollectionManager) {
s_pTrackCollectionManager = std::move(pTrackCollectionManager);
}

#ifdef MIXXX_USE_QML
void ControllerScriptEngineBase::handleQMLErrors(const QList<QQmlError>& qmlErrors) {
for (const QQmlError& error : std::as_const(qmlErrors)) {
showQMLExceptionDialog(error, m_bErrorsAreFatal);
Expand Down Expand Up @@ -116,6 +121,24 @@ void ControllerScriptEngineBase::reload() {
initialize();
}

QObject* ControllerScriptEngineBase::getPlayer(const QString& group) {
VERIFY_OR_DEBUG_ASSERT(s_pPlayerManager != nullptr) {
qCritical() << "Uninitialized PlayerManager";
return nullptr;
}
auto* const player = s_pPlayerManager->getPlayer(group);
if (!player) {
qWarning() << "PlayerManagerProxy failed to find player for group" << group;
return nullptr;
}

// Don't set a parent here, so that the QML engine deletes the object when
// the corresponding JS object is garbage collected.
JavascriptPlayerProxy* pPlayerProxy = new JavascriptPlayerProxy(player, nullptr);
QJSEngine::setObjectOwnership(pPlayerProxy, QJSEngine::JavaScriptOwnership);
return pPlayerProxy;
}

bool ControllerScriptEngineBase::executeFunction(
QJSValue* pFunctionObject, const QJSValueList& args) {
// This function is called from outside the controller engine, so we can't
Expand Down
14 changes: 9 additions & 5 deletions src/controllers/scripting/controllerscriptenginebase.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
#include <QWaitCondition>
#include <memory>

#include "javascriptplayerproxy.h"
#include "mixer/playermanager.h"
#include "util/runtimeloggingcategory.h"
#ifdef MIXXX_USE_QML
#include "controllers/controllerenginethreadcontrol.h"
#endif

class Controller;
class QJSEngine;
#ifdef MIXXX_USE_QML
class TrackCollectionManager;
#endif

/// ControllerScriptEngineBase manages the JavaScript engine for controller scripts.
/// ControllerScriptModuleEngine implements the current system using JS modules.
Expand All @@ -32,6 +32,8 @@ class ControllerScriptEngineBase : public QObject {

bool executeFunction(QJSValue* pFunctionObject, const QJSValueList& arguments = {});

QObject* getPlayer(const QString& group);

/// Shows a UI dialog notifying of a script evaluation error.
/// Precondition: QJSValue.isError() == true
void showScriptExceptionDialog(const QJSValue& evaluationResult, bool bFatal = false);
Expand All @@ -53,10 +55,11 @@ class ControllerScriptEngineBase : public QObject {
return m_bTesting;
}

#ifdef MIXXX_USE_QML
static void registerPlayerManager(std::shared_ptr<PlayerManager> pPlayerManager);

static void registerTrackCollectionManager(
std::shared_ptr<TrackCollectionManager> pTrackCollectionManager);
#endif

signals:
void beforeShutdown();

Expand Down Expand Up @@ -91,10 +94,11 @@ class ControllerScriptEngineBase : public QObject {
#endif
bool m_bTesting;

#ifdef MIXXX_USE_QML
private:
static inline std::shared_ptr<PlayerManager> s_pPlayerManager;
static inline std::shared_ptr<TrackCollectionManager> s_pTrackCollectionManager;

#ifdef MIXXX_USE_QML
protected:
/// Pause the GUI main thread. Pause is required by rendering
/// thread (https://doc.qt.io/qt-6/qquickrendercontrol.html#sync). This
Expand Down
119 changes: 119 additions & 0 deletions src/controllers/scripting/javascriptplayerproxy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "javascriptplayerproxy.h"

#include "moc_javascriptplayerproxy.cpp"

JavascriptPlayerProxy::JavascriptPlayerProxy(BaseTrackPlayer* pTrackPlayer, QObject* parent)
: QObject(parent),
m_pTrackPlayer(pTrackPlayer) {
if (m_pTrackPlayer && m_pTrackPlayer->getLoadedTrack()) {
slotTrackLoaded(pTrackPlayer->getLoadedTrack());
}

connect(m_pTrackPlayer,
&BaseTrackPlayer::loadingTrack,
this,
&JavascriptPlayerProxy::slotLoadingTrack);
connect(m_pTrackPlayer,
&BaseTrackPlayer::newTrackLoaded,
this,
&JavascriptPlayerProxy::slotTrackLoaded);
connect(m_pTrackPlayer,
&BaseTrackPlayer::playerEmpty,
this,
[this]() {
disconnectTrack();
emit trackUnloaded();
});
}

void JavascriptPlayerProxy::slotTrackLoaded(TrackPointer pTrack) {
m_pCurrentTrack = pTrack;
if (pTrack == nullptr) {
emit trackUnloaded();
return;
}

connect(pTrack.get(),
&Track::artistChanged,
this,
&JavascriptPlayerProxy::artistChanged);
connect(pTrack.get(),
&Track::titleChanged,
this,
&JavascriptPlayerProxy::titleChanged);
connect(pTrack.get(),
&Track::albumChanged,
this,
&JavascriptPlayerProxy::albumChanged);
connect(pTrack.get(),
&Track::albumArtistChanged,
this,
&JavascriptPlayerProxy::albumArtistChanged);
connect(pTrack.get(),
&Track::genreChanged,
this,
&JavascriptPlayerProxy::genreChanged);
connect(pTrack.get(),
&Track::composerChanged,
this,
&JavascriptPlayerProxy::composerChanged);
connect(pTrack.get(),
&Track::groupingChanged,
this,
&JavascriptPlayerProxy::groupingChanged);
connect(pTrack.get(),
&Track::yearChanged,
this,
&JavascriptPlayerProxy::yearChanged);
connect(pTrack.get(),
&Track::trackNumberChanged,
this,
&JavascriptPlayerProxy::trackNumberChanged);
connect(pTrack.get(),
&Track::trackTotalChanged,
this,
&JavascriptPlayerProxy::trackTotalChanged);

emit artistChanged(m_pCurrentTrack->getArtist());
emit titleChanged(m_pCurrentTrack->getTitle());
emit albumChanged(m_pCurrentTrack->getAlbum());
emit albumArtistChanged(m_pCurrentTrack->getAlbumArtist());
emit genreChanged(m_pCurrentTrack->getGenre());
emit composerChanged(m_pCurrentTrack->getComposer());
emit groupingChanged(m_pCurrentTrack->getGrouping());
emit yearChanged(m_pCurrentTrack->getYear());
emit trackNumberChanged(m_pCurrentTrack->getTrackNumber());
emit trackTotalChanged(m_pCurrentTrack->getTrackTotal());
Comment thread
christophehenry marked this conversation as resolved.
}

void JavascriptPlayerProxy::slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack) {
VERIFY_OR_DEBUG_ASSERT(pOldTrack == m_pCurrentTrack) {
qWarning() << "Javascript Player proxy was expected to contain "
<< pOldTrack.get() << "as active track but got"
<< m_pCurrentTrack.get();
}

if (pNewTrack == m_pCurrentTrack) {
return;
}

disconnectTrack();
m_pCurrentTrack = pNewTrack;
}

void JavascriptPlayerProxy::disconnectTrack() {
if (m_pCurrentTrack != nullptr) {
m_pCurrentTrack->disconnect(this);
}
}

PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, artist, getArtist)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, title, getTitle)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, album, getAlbum)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, albumArtist, getAlbumArtist)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, genre, getGenre)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, composer, getComposer)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, grouping, getGrouping)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, year, getYear)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, trackNumber, getTrackNumber)
PROPERTY_IMPL_GETTER(JavascriptPlayerProxy, QString, trackTotal, getTrackTotal)
62 changes: 62 additions & 0 deletions src/controllers/scripting/javascriptplayerproxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include "mixer/basetrackplayer.h"
#include "track/track.h"

#define PROPERTY_IMPL_GETTER(NAMESPACE, TYPE, NAME, GETTER) \
TYPE NAMESPACE::GETTER() const { \
const TrackPointer pTrack = m_pCurrentTrack; \
if (pTrack == nullptr) { \
return TYPE(); \
} \
return pTrack->GETTER(); \
}

class JavascriptPlayerProxy : public QObject {
Q_OBJECT
Q_PROPERTY(QString artist READ getArtist NOTIFY artistChanged)
Q_PROPERTY(QString title READ getTitle NOTIFY titleChanged)
Q_PROPERTY(QString album READ getAlbum NOTIFY albumChanged)
Q_PROPERTY(QString albumArtist READ getAlbumArtist NOTIFY albumArtistChanged)
Q_PROPERTY(QString genre READ getGenre STORED false NOTIFY genreChanged)
Q_PROPERTY(QString composer READ getComposer NOTIFY composerChanged)
Q_PROPERTY(QString grouping READ getGrouping NOTIFY groupingChanged)
Q_PROPERTY(QString year READ getYear NOTIFY yearChanged)
Q_PROPERTY(QString trackNumber READ getTrackNumber NOTIFY trackNumberChanged)
Q_PROPERTY(QString trackTotal READ getTrackTotal NOTIFY trackTotalChanged)
public:
explicit JavascriptPlayerProxy(BaseTrackPlayer* pTrackPlayer, QObject* parent);

QString getTitle() const;
QString getArtist() const;
QString getAlbum() const;
QString getAlbumArtist() const;
QString getGenre() const;
QString getComposer() const;
QString getGrouping() const;
QString getYear() const;
QString getTrackNumber() const;
QString getTrackTotal() const;

public slots:
void slotTrackLoaded(TrackPointer pTrack);
void slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack);

signals:
void trackUnloaded();
void albumChanged(QString newAlbum);
void titleChanged(QString newTitle);
void artistChanged(QString newArtist);
void albumArtistChanged(QString newAlbumArtist);
void genreChanged(QString newGenre);
void composerChanged(QString newComposer);
void groupingChanged(QString grouping);
void yearChanged(QString newYear);
void trackNumberChanged(QString newTrackNumber);
void trackTotalChanged(QString newTrackTotal);

protected:
void disconnectTrack();
QPointer<BaseTrackPlayer> m_pTrackPlayer;
TrackPointer m_pCurrentTrack;
};
Loading