3
votes

I've recently started working with Qt and am trying to play a sound file using QMediaPlayer.

My program compiles and runs but the sound file is not played, the QMediaPlayer seems stuck in the QMediaPlayer::LoadingMedia state.

Also - and possibly related - the QMediaPlayer doesn't ever seem to emit its mediaStatusChanged or its error signals (though perhaps this is me not connecting them properly?)

When I run the program as below, it reaches the while loop and never leaves. If I query for player->mediaStatus() inside the loop, it constantly returns 2 (QMediaPlayer::LoadingMedia).

When I run it with the while loop omitted, the program continues to run until it reaches end of execution with no run-time errors but - as you may expect - the file is not played.

Interestingly, the two couts before the while loop, which report player's mediaStatus and state show that the mediaStatus changes from 1 (in the first instance, before setting the media) to 2 (after setting the media) but my ChangedStatus slot is never called, despite connecting to the mediaStatusChanged at the start of the run function.

Running: Debian Jessie, Qt5.7/Qt5.9

AudioPlayer.h

#include <QThread>
#include <QMediaPlayer>

class AudioPlayer : public QThread {
    Q_OBJECT

public:
    AudioPlayer();

public slots:
    void ChangedStatus(QMediaPlayer::MediaStatus);
    void MediaError(QMediaPlayer::Error);

protected:
    void run();
};

AudioPlayer.cpp:

AudioPlayer::AudioPlayer(){}    
void AudioPlayer::run()
{
    QMediaPlayer* player = new QMediaPlayer();
    connect(player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(ChangedStatus(QMediaPlayer::MediaStatus)));
    connect(player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(MediaError(QMediaPlayer::Error)));
    std::cout << "Got player!" << std::endl;

    std::cout << "\n\n\tPlayer state: " << player->state() << "\n\tMediaState: " << player->mediaStatus() << std::endl;

    player->setMedia(QUrl::fromLocalFile("/home/me/test.wav") );
    std::cout << "Set source" << std::endl;

    std::cout << "\n\n\tPlayer state: " << player->state() << "\n\tMediaState: " << player->mediaStatus() << std::endl;

    while(player->mediaStatus() != QMediaPlayer::MediaStatus::LoadedMedia)
    {//
    }

    player->setVolume(100);
    std::cout << "Set volume" << std::endl;

    player->play();
    std::cout << "Played" << std::endl;    
}    
void AudioPlayer::MediaError(QMediaPlayer::Error error)
{
    std::cout << "Media Error: " << error << std::endl;
}    
void AudioPlayer::ChangedStatus(QMediaPlayer::MediaStatus status)
{
    std::cout << "Status changed to: " << status << std::endl;
}

main.cpp:

#include "audioplayer.h"

using namespace std;

int main()
{
    cout << "Hello World!" << endl;

    AudioPlayer myAudioPlayer;
    myAudioPlayer.start();
    std::cout << "myAudioPlayer started. Waiting...." << std::endl;
    myAudioPlayer.wait();

    std::cout << "myAudioPlayer returned." << std::endl;

    return 0;
}

Extra Info:

Now, initially, I hadn't used QThread and was trying to do this all in main.cpp (just declaring a QMediaPlayer and attempting to set the media and play) but this was giving me QObject::startTimer: timers cannot be started from another thread warning run-time errors in a couple of places (declaration of the QMediaPlayer and, I think, the play command), so I adopted the above approach - although I'm not sure that subclassing QThread is, necessarily, the best way. This is also why everything (declarations etc.) is done in the run function - having the QMediaPlayer as a member of AudioPlayer and initialising it in the constructor gave the same error.

I have compiled and run Qt's Player example (from multimediawidgets) and, by browsing and selecting my test.wav, it can play the file so I don't think it's a compatibility issue. I looked through the Player example source but couldn't see anything jumping out which I had missed and which looked like the cause of my problem.

1
Where is the mandatory QApplication instance? There is no message loop i.e. neither myAudioPlayer.exec() nor QApplication::exec() call some where. So signals can never reach one of your slots.bkausbk
@bkausbk thank you for this - it certainly helped get the signals working (see my comment on your answer, below). Unfortunately, I was following Qt's overview of audio which doesn't mention the necessity of QApplication (only mentions needing to creat a media player, set the media source, and play) so I hadn't realised it was mandatory.cprlkleg
I didn't find an example where QApplication is explicitely not used either. It is silently assumed that "QApplication" and corresponding message loop is always present.bkausbk

1 Answers

1
votes

You should create an QApplication object and use it's message loop. I would suggest you to test following:

#include "audioplayer.h"

using namespace std;

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    AudioPlayer myAudioPlayer;
    myAudioPlayer.start();

    return a.exec();
}

You will at least get your signals raised. If the media state reaches QMediaPlayer::StoppedState or any error occured, you could call QApplication::instance()->quit() to stop your application.

Edit: Better use the new style connections like:

connect(player, &QMediaPlayer::mediaStatusChanged, this, &QMediaPlayer::ChangedStatus);

It is more reliable and you don't have to register specific parameter types like QMediaPlayer::MediaStatus with Q_DECLARE_METATYPE()

Because QMediaPlayer class contains another method called error with a different signature, signal connection is a bit more complicated. This is because the compiler don't know which error method you are referring to. In this case static_cast is the way to solve this ambiguity:

connect(
    player,
    static_cast<void(QMediaPlayer::*)(QMediaPlayer::Error )>(&QMediaPlayer::error),
    this,
    &AudioPlayer::MediaError
);

Please note, a Wave file is only a container file that can contain an arbitrary compressed data stream. It may be necessary to install the appropriate operating system multimedia codec first. In Microsoft Windows Qt-Framework relies on installed multimedia codecs (.ax file).

Your AudioPlayer::run method will end without waiting for the media being played. So you should wait for the Stopped status some where before the thread ends. However it is better not to use the run method directly but using QThreads message loop instead.

class AudioPlayer : public QThread {
public:
    AudioPlayer() : _Player(nullptr) {
        moveToThread(this); // AudioPlayer object become part of this new thread
    }

public slots:

    void setVolume(int);
    void load(QString Media);
    // ...

    void play() {
        // Never directly access any members since they may belong to a different thread
        if (thread() != QThread::currentThread()) {
            QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection);
        } else {
            _Player->play();
        }
    }

    void stop() {
        quit(); // Quiting thread message loop
    }

private:
    QMediaPlayer* _Player;

    void run() {
        _Player = new QMediaPlayer(this);

        connect(...) // All connections go here...

        int Result = QThread::exec();

        _Player->stop();

        delete _Player;
    }

private slots:

    void HandleStatusChange(QMediaPlayer::MediaStatus Status) {
        emit statusChanged(Status); // Redirect so that the main application can handle this signal too
    }

signals:
    void statusChanged((QMediaPlayer::MediaStatus);
};