QmlInterface

The header of our custom class features a number of additions to make properties and methods visible to the QML code:

#include <QtCore/QObject> 
#include <QMediaPlayer> 
#include <QByteArray> 
 
 
class QmlInterface : public QObject { 
    Q_OBJECT     
    Q_PROPERTY(QString durationTotal READ getDurationTotal NOTIFY durationTotalChanged) 
    Q_PROPERTY(QString durationLeft READ getDurationLeft NOTIFY durationLeftChanged) 
 

The Q_PROPERTY tag tells the qmake parser that this class contains a property (variable) that should be made visible to the QML code, with the parameters specifying the name of the variable, the methods used for reading and writing the variable (if desired), and finally the signal that is emitted whenever the property has changed.

This allows for an automatic update feature to be set up to keep this property synchronized between the C++ code and the QML side:

     
    QString formatDuration(qint64 milliseconds); 
     
    QMediaPlayer mediaPlayer; 
    QByteArray magnitudeArray; 
    const int millisecondsPerBar = 68; 
    QString durationTotal; 
    QString durationLeft; 
    qint64 trackDuration; 
     
public: 
    explicit QmlInterface(QObject *parent = nullptr); 
 
    Q_INVOKABLE bool isHoverEnabled() const; 
    Q_INVOKABLE void setPlaying(); 
   Q_INVOKABLE void setStopped(); 
   Q_INVOKABLE void setPaused(); 
    Q_INVOKABLE qint64 duration(); 
    Q_INVOKABLE qint64 position(); 
    Q_INVOKABLE double getNextAudioLevel(int offsetMs); 
     
    QString getDurationTotal() { return durationTotal; } 
    QString getDurationLeft() { return durationLeft; } 
 
public slots: 
    void mediaStatusChanged(QMediaPlayer::MediaStatus status); 
    void durationChanged(qint64 duration); 
    void positionChanged(qint64 position); 
     
signals: 
    void start(); 
    void stopped(); 
    void paused(); 
    void playing(); 
    void durationTotalChanged(); 
    void durationLeftChanged(); 
}; 

Similarly, the Q_INVOKABLE tag ensures that these methods are made visible to the QML side and can be called from there.

Here is the implementation:

#include "interface.h" 
#include <QtGui/QTouchDevice> 
#include <QDebug> 
#include <QFile> 
#include <QtMath> 
 
QmlInterface::QmlInterface(QObject *parent) : QObject(parent) { 
    // Set track for media player. 
    mediaPlayer.setMedia(QUrl("qrc:/music/tiltshifted_lost_neon_sun.mp3")); 
     
    // Load magnitude file for the audio track. 
    QFile magFile(":/music/visualization.raw", this); 
    magFile.open(QFile::ReadOnly); 
    magnitudeArray = magFile.readAll(); 
     
    // Media player connections. 
    connect(&mediaPlayer, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(mediaStatusChanged(QMediaPlayer::MediaStatus))); 
    connect(&mediaPlayer, SIGNAL(durationChanged(qint64)), this, SLOT(durationChanged(qint64))); 
    connect(&mediaPlayer, SIGNAL(positionChanged(qint64)), this, SLOT(positionChanged(qint64))); 
} 

The constructor got changed considerably from the original example project, with the media player instance being created here, along with its connections.

We load the same music file here as was used with the original project. When integrating the code into the infotainment project or a similar project, you would make this dynamic. Similarly, the file that we are also loading here to get the amplitude for the music file with the visualization would likely be omitted in a full integration, instead opting to generate the amplitude values dynamically:

bool QmlInterface::isHoverEnabled() const { 
#if defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || defined(Q_OS_QNX) || defined(Q_OS_WINRT) 
    return false; 
#else 
    bool isTouch = false; 
    foreach (const QTouchDevice *dev, QTouchDevice::devices()) { 
        if (dev->type() == QTouchDevice::TouchScreen) { 
            isTouch = true; 
            break; 
        } 
    } 
     
    bool isMobile = false; 
    if (qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MOBILE")) { 
        isMobile = true; 
    } 
     
    return !isTouch && !isMobile; 
#endif 
} 

This was the only method that previously existed in the QML context class. It's used to detect whether the code runs on a mobile device with a touch screen:

void QmlInterface::setPlaying() { 
   mediaPlayer.play(); 
} 
 
void QmlInterface::setStopped() { 
   mediaPlayer.stop(); 
} 
 
void QmlInterface::setPaused() { 
   mediaPlayer.pause(); 
} 

We got a number of control methods that connect to the buttons in the UI to allow for control of the media player instance:

void QmlInterface::mediaStatusChanged(QMediaPlayer::MediaStatus status) { 
    if (status == QMediaPlayer::EndOfMedia) { 
        emit stopped(); 
    } 
} 

This slot method is used to detect when the media player has reached the end of the active track, so that the UI can be signaled that it should update to indicate this:

void QmlInterface::durationChanged(qint64 duration) { 
    qDebug() << "Duration changed: " << duration; 
     
    durationTotal = formatDuration(duration); 
    durationLeft = "-" + durationTotal; 
    trackDuration = duration; 
    emit start(); 
    emit durationTotalChanged(); 
    emit durationLeftChanged(); 
} 
 
void QmlInterface::positionChanged(qint64 position) { 
    qDebug() << "Position changed: " << position; 
    durationLeft = "-" + formatDuration((trackDuration - position)); 
    emit durationLeftChanged(); 
} 

These two slot methods are connected to the media player instance. The duration slot is required because the length (duration) of a newly loaded track will not be immediately available. Instead, it's an asynchronously updated property.

As a result, we have to wait until the media player has finished with this and emits the signal that it has completed this process.

Next, to allow us to update the time remaining on the current track, we also get constant updates on the current position from the media player so that we can update the UI with the new value.

Both the duration and position properties are updated in the UI using the linkage method we saw in the description of the header file for this class.

Finally, we emit a start() signal, which is linked into a slot in the QML code that will start the visualization process, as we will see later on in the QML code:

qint64 QmlInterface::duration() { 
    qDebug() << "Returning duration value: " << mediaPlayer.duration(); 
    return mediaPlayer.duration(); 
} 
 
qint64 QmlInterface::position() { 
    qDebug() << "Returning position value: " << mediaPlayer.position(); 
    return mediaPlayer.position(); 
} 

The duration property is also used by the visualization code. Here, we allow it to be obtained directly. Similarly, we make the position property available as well with a direct call:

double QmlInterface::getNextAudioLevel(int offsetMs) { 
    // Calculate the integer index position in to the magnitude array 
    qint64 index = ((mediaPlayer.position() + offsetMs) / millisecondsPerBar) | 0; 
 
    if (index < 0 || index >= (magnitudeArray.length() / 2)) { 
        return 0.0; 
    } 
 
    return (((quint16*) magnitudeArray.data())[index] / 63274.0); 
} 

This method was ported from the JavaScript code in the original project, performing the same task of determining the audio level based on the amplitude data we read in previously from the file:

QString QmlInterface::formatDuration(qint64 milliseconds) { 
    qint64 minutes = floor(milliseconds / 60000); 
    milliseconds -= minutes * 60000; 
    qint64 seconds = milliseconds / 1000; 
    seconds = round(seconds); 
    if (seconds < 10) { 
        return QString::number(minutes) + ":0" + QString::number(seconds); 
    } 
    else { 
        return QString::number(minutes) + ":" + QString::number(seconds); 
    } 
} 

Similarly, this method was also ported from the original project's JavaScript code, since we moved the code that relies on it into the C++ code. It takes in the millisecond count for the track duration or position and converts it into a string containing the minutes and seconds, matching the original value.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.105.255