cprlkleg cprlkleg - 2 years ago 236
C++ Question

QMediaPlayer Not Loading Media And Not Emitting mediaStatusChanged() Signals

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
cout
s 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
connect
ing 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.

Answer Source

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, WAV is only a container file format that can contain an arbitrary compressed data stream. It may be necessary to install the appropriate windows codec first.

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() {
        moveToThread(this); // AudioPlayer object become part of this new thread
    }

public slots:

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

    void play() {
        // No direct access to 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 message loop
    }

private:
    QMediaPlayer* _Player;

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

        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);
};
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download