Invoke a QML function from C++ - c++

I am using Qt on my RPI3.
I found a QML example which is Tesla Car instrument cluster. You can access full code from here or here.
I successfully created a project and debugged it. Now I am trying to change a value in the QML code from C++ side. There is a timer in my C++ code every 30 seconds I am trying to change speed value in the QML code with using QMetaObject::inokeMethod(): function. I read all examples in here.
Here is my C ++ code
#ifndef MYTIMER_H
#define MYTIMER_H
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QTimer>
#include <QtDebug>
class MyTimer : public QObject
{
Q_OBJECT
public:
MyTimer();
QTimer *timer;
int i=0;
public slots:
void MySlot();
};
#endif // MYTIMER_H
#include "mytimer.h"
MyTimer::MyTimer()
{
timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(MySlot()));
timer->start(10000);
}
void MyTimer::MySlot()
{
i++;
if(i==3)
{
i=0;
QQmlEngine engine;
QQmlComponent component(&engine,QUrl(QStringLiteral("qrc:/Speedometer.qml")));
QObject *object = component.create();
QVariant speeds=100;
QVariant returnedValue;
QMetaObject::invokeMethod(object,"speedNeedleValue",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, speeds));
qDebug() << "From QML"<< returnedValue.toString();
delete object;
}
}
Here is QML code:
import QtQuick 2.4
import QtGraphicalEffects 1.0
Rectangle {
color: "transparent"
SpeedNeedle {
id: speedoNeedle
anchors.verticalCenterOffset: 0
anchors.centerIn: parent
focus: true
Keys.onPressed: {
if (event.key == Qt.Key_A) {
speedNeedleValue(100)
drive()
}
}
function speedNeedleValue(speeds) {
speedoNeedle.value = speeds
return ": I am here"
}
}
If I press the "A" button my speedNeedleValue(); function is working. And in debug page I can get the return data return ": I am here".
Problem is I can't set the speeds argument with invoke function.
Here is the debug page:
"https://preview.ibb.co/kqpvWS/rpi.png"
Every time interrupt I can get "I am here". but I also get " JIT is disabled.... " warning too.
Thank you for your answers.

Related

Qt SLOT not triggered by the SIGNAL emitted from std::async in QTest environment

I was investigating the topic of async programming with Qt and I reached the conclusion that it is safe to emit signals from whatever kind of threads (although QT docs only mention QThread), as more or less described here. Now I faced the problem testing my application. To simplify as much as possible: I have the async operation, which might notify MainWindow by emitting the SIGNAL. It works fine in the production, but it doesn't work in unit-test environment with QTest. Complete example (project structure flat, no subdirs):
CMakeLists.txt
cmake_minimum_required(VERSION 3.0.0)
project(QtFailure)
enable_testing()
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
if(CMAKE_VERSION VERSION_LESS "3.7.0")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
endif()
find_package(Qt5 COMPONENTS Core Widgets Test REQUIRED)
add_library(QtFailure source.cpp header.hpp)
target_include_directories(QtFailure PUBLIC .)
target_link_libraries(QtFailure
pthread
Qt5::Core
Qt5::Widgets
)
add_executable(main main.cpp)
target_link_libraries(main QtFailure)
add_executable(QtFailureTest test.cpp)
target_link_libraries(QtFailureTest
QtFailure
Qt5::Test
)
header.hpp
#pragma once
#include <QMainWindow>
#include <QWidget>
#include <future>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
void start();
int counter_value();
signals:
void sendSignal();
private slots:
bool triggerSlot();
private:
bool stop_;
std::future<void> async_oper_;
};
source.cpp
#include "header.hpp"
#include <QMainWindow>
#include <QWidget>
#include <QObject>
#include <QDebug>
#include <future>
#include <chrono>
#include <thread>
static int counter = 0;
MainWindow::MainWindow(QWidget* parent):
QMainWindow(parent),
stop_(false),
async_oper_()
{
QObject::connect(this, SIGNAL(sendSignal()), this, SLOT(triggerSlot()));
}
MainWindow::~MainWindow()
{
stop_ = true;
}
int MainWindow::counter_value()
{
return counter;
}
void MainWindow::start()
{
if (async_oper_.valid()) return;
emit sendSignal(); // this one works
async_oper_ = std::async(std::launch::async, [this]()
{
while (!stop_)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
emit sendSignal(); // this one doesn't work in tests
}
});
}
bool MainWindow::triggerSlot()
{
qDebug() << "triggerSlot: " << counter;
counter++;
}
test.cpp
#include "header.hpp"
#include <QSignalSpy>
#include <QDebug>
#include <QtTest/QtTest>
#include <memory>
#include <chrono>
#include <thread>
class MyFixture: public QObject
{
Q_OBJECT
private:
std::unique_ptr<MainWindow> sut_;
private slots:
void init()
{
qDebug("MyFixture init");
sut_.reset(new MainWindow);
}
void cleanup()
{
qDebug("MyFixture cleanup");
sut_.reset();
}
void example_test()
{
QSignalSpy spy(sut_.get(), SIGNAL(sendSignal()));
sut_->start();
std::this_thread::sleep_for(std::chrono::seconds(1));
qDebug() << "num signals: " << spy.count();
qDebug() << "counter value: " << sut_->counter_value();
}
};
QTEST_MAIN(MyFixture)
#include "test.moc"
main.cpp
#include <QApplication>
#include "header.hpp"
int main(int argc, char** argv)
{
QApplication a(argc, argv);
MainWindow w;
w.start();
w.show();
return a.exec();
}
The output from my test is
PASS : MyFixture::initTestCase()
QDEBUG : MyFixture::example_test() MyFixture init
QDEBUG : MyFixture::example_test() triggerSlot: 0
QDEBUG : MyFixture::example_test() num signals: 10
QDEBUG : MyFixture::example_test() counter value: 1
QDEBUG : MyFixture::example_test() MyFixture cleanup
PASS : MyFixture::example_test()
PASS : MyFixture::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 1003ms
which means that the slot was triggered only once, by the signal emitted from the main thread.
The output from main is:
...
triggerSlot: 25
triggerSlot: 26
triggerSlot: 27
etc ...
which is the expected behavior. Why is there a difference between QTest environment and normal QApplication in main? What should I do to correct tests behavior?
I must use standard C++ threads, because my GUI is just a facade to real non-Qt related system, which has different kinds of async opers, callbacks etc.
Sorry for the amount of code, but with QT I cannot really squeeze it in a few lines.
=== EDIT ===
Specifying Qt::DirectConnection connection attribute will "fix" the issue in the QTest environment. But this is something I cannot do in most cases, because GUI actions must take place in the main thread (e.g. scync oper emits signal to trigger QPixmap refresh);
The signal emit code is correct. Queued Signal-Slot connection requires event loop to be running in the receiver thread for signal delivery:
Events to that object are dispatched by that (receiver) thread's event loop.
Event loop of the receiver thread aren't working because you block it in the line
// just suspends current thread without running any event loop
std::this_thread::sleep_for(std::chrono::seconds(1));
That is, event loop of the receiver thread are blocking throughout the whole example_test() method body. There is no one line which runs an event loop within itself. That is not Qt Test or QTEST_MAIN() issue.
Here is how you can fix that:
void example_test()
{
QSignalSpy spy(sut_.get(), SIGNAL(sendSignal()));
sut_->start();
QElapsedTimer timer;
timer.start();
while(timer.elapsed() < 1000)
{
spy.wait(10); // event loop runs here
}
qDebug() << "num signals: " << spy.count();
qDebug() << "counter value: " << sut_->counter_value();
}
QSignalSpy::wait():
Starts an event loop that runs until the given signal is received.
Optionally the event loop can return earlier on a timeout (in
milliseconds).
Returns true if the signal was emitted at least once in timeout milliseconds, otherwise returns false.

Qt WiFi network scan

For some application I need to scan WiFi searching for available network (SSID). On Ubuntu 18.04 LTS I don't have any issue with the fallowing code. It return all available SSID as expected but when running on Raspbian stretch (Rasberry PI 3 b) the fallowing code is returning the interface wlan0 and eth0 and not the SSID. Does someone have any clue why? or can tell me what I miss? On the other hand, is it better not to use Qt network lib and use a QProcess and parse what sudo iwlist wlan0 scan is returning?
.cpp
void wificonfig::findavailableWifinetwork( void )
{
QNetworkConfigurationManager ncm;
netconflist = ncm.allConfigurations(QNetworkConfiguration::Discovered); //
int i =0;
awifilist.clear();
for(auto &x : netconflist){
if ((x.bearerType() == QNetworkConfiguration::BearerWLAN) ){
if(x.name() == "")
awifilist << "Unknown(Other Network)";
else{
ui->wificonnection_tw->setItem(i, 0, new QTableWidgetItem(x.name()));
i++;
}
//qDebug() << x.type();
}
}
ui->wificonnection_tw->setRowCount(i);
}
.h
#ifndef WIFICONFIG_H
#define WIFICONFIG_H
#include <QList>
#include <QTimer>
#include <QDialog>
#include <QNetworkConfiguration>
#include <QNetworkConfigurationManager>
#include <QNetworkSession>
namespace Ui {
class wificonfig;
}
class wificonfig : public QDialog
{
Q_OBJECT
public:
explicit wificonfig(QWidget *parent = 0);
~wificonfig();
int connection_count;
QNetworkConfiguration netconf;
QStringList awifilist;
QList<QNetworkConfiguration>netconflist;
public slots:
void findavailableWifinetwork( void );
private slots:
void on_wifi_scan_butt_clicked();
private:
Ui::wificonfig *ui;
QTime *scan_timeout_timer;
QNetworkSession *session;
};
#endif // WIFICONFIG_H

QCompleter::activated disconnected after losing and gaining focus

I found a strange case which I do not understand. Maybe it is a Qt bug, maybe I am doing something wrong.
A header:
// File mylineedit.h
#pragma once
#include <QLineEdit>
#include <QDebug>
class MyLineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit MyLineEdit(QWidget *parent = nullptr) : QLineEdit(parent) { }
public slots:
void onCompleterActivated(const QString& text) { qDebug() << "MyLineEdit" << text; }
};
And the main source file:
// File main.cpp
#include <QApplication>
#include <QWidget>
#include <QStringListModel>
#include <QCompleter>
#include <QVBoxLayout>
#include "mylineedit.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QLineEdit* lineEdit1 = new MyLineEdit();
QLineEdit* lineEdit2 = new MyLineEdit();
auto layout = new QVBoxLayout(&w);
layout->addWidget(lineEdit1);
layout->addWidget(lineEdit2);
lineEdit1->setCompleter(new QCompleter());
auto model = new QStringListModel(QStringList() << "A" << "B" << "C");
lineEdit1->completer()->setModel(model);
QObject::connect(lineEdit1->completer(), SIGNAL(activated(QString)), lineEdit1, SLOT(onCompleterActivated(QString)));
w.show();
return a.exec();
}
Once you run this, you can get a completer with values "A", "B", "C" in the first line edit. When you select any of these values, it will print the message to the console. This is correct. But then change focus to the other line edit and then back. Try picking "A", "B", "C" again. No message is printed out, the signal/slot seems disconnected. Any ideas?
Tested with MinGW 5.3.0 and MSVC 2015 using with Qt 5.9.2.
I modified your sample slightly and tried to reproduce the behavior you described but I couldn't:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QWidget qWin;
QVBoxLayout qVBox;
QLineEdit qEdit1;
qVBox.addWidget(&qEdit1);
QCompleter qCompl1;
QStringListModel qComplModel(QStringList() << "A" << "B" << "C");
qCompl1.setModel(&qComplModel);
qEdit1.setCompleter(&qCompl1);
QLineEdit qEdit2;
qVBox.addWidget(&qEdit2);
qWin.setLayout(&qVBox);
qWin.show();
// install signal handlers
QObject::connect(&qCompl1,
static_cast<void(QCompleter::*)(const QString&)>(
&QCompleter::activated),
[](const QString &text)
{
qDebug() << "Activated: " << text;
});
// run-time loop
return app.exec();
}
The significant differences in my sample:
I used Qt5 style signal handlers (as I'm used to).
I didn't new the Qt widgets (as I'm used to when I write minimal samples).
For me, it's hard to believe that one of these changes makes your described issue unbroken.
I compiled and tested with VS2013, Qt 5.9.2 on Windows 10 (64 bit):
I did the following interactions:
click in qEdit1
type A and click on item A in the popup menu
Output:
Activated: "A"
I continued with:
click in qEdit2
type B
click in qEdit1
erase text, type B, and click on item B in the popup menu
Output:
Activated: "B"
It works like expected.
After some conversation, I had a look at woboq.org in the source code of QLineEdit:
The interesting part is in QLineEdit::focusOutEvent:
if (d->control->completer()) {
QObject::disconnect(d->control->completer(), 0, this, 0);
}
If I read this right, all signal handlers of QLineEdit (emitted by the set completer) are disconnected. I believe this is the reason for the observed issue. (Therefore, it works for lambdas and methods of other classes.)

QML custom item state does not change when binded variable changes on C++ state

I have following custom QML item with two states:
import QtQuick 2.0
import si.testfirm 1.0
Item
{
id: ueDatabaseStatusIndicator
property string ueParamImageStatusOn
property string ueParamImageStatusOff
state: ueApplicationStatus.m_ueBluetoothPrinterConnectionStatus===UeTypeBluetootPrinterConnectionStatus.NOT_PAIRED?
"ueStatusIndicatorBluetoothNotConnected":
"ueStatusIndicatorBluetoothConnected"
Image
{
id: ueStatusIndicatorCurrentImage
smooth: true
fillMode: Image.PreserveAspectFit
width: 96
height: 96
sourceSize.width: 96
sourceSize.height: 96
} // Image
states:
[
State
{
name: "ueStatusIndicatorBluetoothConnected"
PropertyChanges
{
target: ueStatusIndicatorCurrentImage
source: ueParamImageStatusOn
} // PropertyChanges
}, // State
State
{
name: "ueStatusIndicatorBluetoothNotConnected"
PropertyChanges
{
target: ueStatusIndicatorCurrentImage
source: ueParamImageStatusOff
} // PropertyChanges
} // State
] // states
} // Item
In first state, named ueStatusIndicatorBluetoothNotConnected, it shows red icon, which resembles bluetooth printer is not paired. In second state, named ueStatusIndicatorBluetoothConnected, it shows blue icon, which resembles bluetooth printer is paired with app. Now, when I run this app with following main.cpp file:
#include <QtQml>
#include <QApplication>
#include <QQmlApplicationEngine>
#include "database/uepeoplemodel.h"
#include "core/ueapplicationstatus.h"
#include "core/uedatabaseconnectionstatus.h"
#include "core/uebluetoothmanager.h"
#include "core/uebluetoothprinterconnectionstatus.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
UeApplicationStatus* ueApplicationStatus=new UeApplicationStatus(qApp);
UePeopleModel* uePeopleModel=new UePeopleModel(qApp);
UeBluetoothManager* ueBtManager=new UeBluetoothManager(qApp);
QObject::connect(uePeopleModel,
SIGNAL(ueSignalDatabaseConnectionChanged(UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus)),
ueApplicationStatus,
SLOT(ueSlotDatabaseConnectionChanged(UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus)));
QObject::connect(ueBtManager,
SIGNAL(ueSignalBtPrinterConnectionChanged(UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus)),
ueApplicationStatus,
SLOT(ueSlotBtPrinterConnectionChanged(UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus)));
engine.rootContext()->setContextProperty("uePeopleModel",
uePeopleModel);
engine.rootContext()->setContextProperty("ueApplicationStatus",
ueApplicationStatus);
engine.rootContext()->setContextProperty("ueBtManager",
ueBtManager);
engine.addImageProvider(QLatin1String("uePeopleModel"),
uePeopleModel);
qmlRegisterUncreatableType<UeDatabaseConnectionStatus>("si.testfirm",
1,
0,
"UeTypeDatabaseConnectionStatus",
"Database Connection Status");
qmlRegisterUncreatableType<UeBluetoothPrinterConnectionStatus>("si.testfirm",
1,
0,
"UeTypeBluetootPrinterConnectionStatus",
"Bluetooth Printer Connection Status");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
uePeopleModel->ueConnectToDatabase();
ueBtManager->ueStartPairing();
ueApplicationStatus->ueUpdate(uePeopleModel->ueFetchUsers());
return app.exec();
}
in the app red icon is showed, which is ok, since printer is not paired, however, with the line ueBtManager->ueStartPairing(); I start searching for bluetooth printer and the printer is found and paired, since slot void UeBluetoothManager::ueSlotPairingFinished(const QBluetoothAddress& address,QBluetoothLocalDevice::Pairing pairing) gets called as you can see from screenshot:
In the last line, as you can see from screenshot, I emit signal UeBluetoothManager::void ueSignalBtPrinterConnectionChanged(const UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus& newStatus); which is called in main.cpp by ueApplicationStatus object to update its status. Here is UeApplicationStatus header:
#ifndef UEAPPLICATIONSTATUS_H
#define UEAPPLICATIONSTATUS_H
#include <QObject>
#include <QList>
#include <QDebug>
#include "../core/uetypes.h"
#include "../core/uedatabaseconnectionstatus.h"
#include "../core/uebluetoothprinterconnectionstatus.h"
class UeApplicationStatus : public QObject
{
Q_OBJECT
Q_PROPERTY(UeTypeUsers* m_ueUsers
READ ueUsers
WRITE ueSetUsers
NOTIFY ueSignalUsersChanged)
Q_PROPERTY(UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus m_ueDatabaseConnectionStatus
READ ueDbConnectionStatus
WRITE ueSetDbConnectionStatus
NOTIFY ueSignalDatabaseConnectionChanged)
Q_PROPERTY(UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus m_ueBluetoothPrinterConnectionStatus
READ ueBtPrinterConnectionStatus
WRITE ueSetBtPrinterConnectionStatus
NOTIFY ueSignalBtPrinterConnectionChanged)
private:
UeTypeUsers* m_ueUsers;
UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus m_ueDatabaseConnectionStatus;
UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus m_ueBluetoothPrinterConnectionStatus;
public:
explicit UeApplicationStatus(QObject *parent = 0);
~UeApplicationStatus();
inline UeTypeUsers* ueUsers() const
{ return this->m_ueUsers; }
inline UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus ueDbConnectionStatus() const
{ return this->m_ueDatabaseConnectionStatus; }
inline UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus ueBtPrinterConnectionStatus() const
{ return this->m_ueBluetoothPrinterConnectionStatus; }
inline void ueSetUsers(UeTypeUsers* const users)
{ this->m_ueUsers=users; }
inline void ueSetDbConnectionStatus(const UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus& status)
{ this->m_ueDatabaseConnectionStatus=status; }
inline void ueSetBtPrinterConnectionStatus(const UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus status)
{ this->m_ueBluetoothPrinterConnectionStatus=status; }
signals:
void ueSignalUsersChanged();
void ueSignalDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus& newStatus);
void ueSignalBtPrinterConnectionChanged(const UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus& newStatus);
public slots:
void ueSlotDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus& newStatus);
void ueSlotBtPrinterConnectionChanged(const UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus& newStatus);
void ueUpdate(UeTypeUsers* const users);
};
#endif // UEAPPLICATIONSTATUS_H
and its implementation:
#include "ueapplicationstatus.h"
UeApplicationStatus::UeApplicationStatus(QObject *parent)
: QObject(parent)
{
this->ueSetUsers(new UeTypeUsers());
this->ueSetDbConnectionStatus(UeDatabaseConnectionStatus::NOT_CONNECTED);
this->ueSetBtPrinterConnectionStatus(UeBluetoothPrinterConnectionStatus::NOT_PAIRED);
connect(this,
SIGNAL(ueSignalDatabaseConnectionChanged(UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus)),
this,
SLOT(ueSlotDatabaseConnectionChanged(UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus)));
connect(this,
SIGNAL(ueSignalBtPrinterConnectionChanged(UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus)),
this,
SLOT(ueSlotBtPrinterConnectionChanged(UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus)));
} // constructor
UeApplicationStatus::~UeApplicationStatus()
{
delete this->ueUsers();
} // destructor
void UeApplicationStatus::ueSlotDatabaseConnectionChanged(const UeDatabaseConnectionStatus::UeTypeDatabaseConnectionStatus& newStatus)
{
this->ueSetDbConnectionStatus(newStatus);
} // ueSignalDatabaseConnectionChanged
void UeApplicationStatus::ueSlotBtPrinterConnectionChanged(const UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus& newStatus)
{
this->ueSetBtPrinterConnectionStatus(newStatus);
} // ueSlotBtPrinterConnectionChanged
void UeApplicationStatus::ueUpdate(UeTypeUsers* const users)
{
this->ueSetUsers(users);
} // ueUpdate
The question is, why images are not switched on QML side inside custom item ueDatabaseStatusIndicator, which code is listed first?
It could be because of the way you are initialising your application. You are starting the event loop after emitting the signals. Try this.
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QTimer::singleshot(150, this, [&]() {
uePeopleModel->ueConnectToDatabase();
ueBtManager->ueStartPairing();
ueApplicationStatus->ueUpdate(uePeopleModel->ueFetchUsers());
});
return app.exec();
This is just my guess. Best of luck.
update
inline void ueSetBtPrinterConnectionStatus(const UeBluetoothPrinterConnectionStatus::UeTypeBluetootPrinterConnectionStatus status)
{
m_ueBluetoothPrinterConnectionStatus=status;
emit ueSignalBtPrinterConnectionChanged(m_ueBluetoothPrinterConnectionStatus);
}
You must remove the connect calls as this creates a infinite loop.

How do i play a stream with QMediaPlayer

I have set up a server and video streaming so that I can connect to the stream with ffplay using the following command line:
ffplay rtmp://<IP>/path
Is it possible to use QMediaPlayer QMediaContent or something to connect to this stream?
Or maybe any other kind of stream I can create with ffserver.
using the same path as with ffplay results in "Unsupported url scheme!"
With further experiments i have tried ffserver http server streaming, but that ended with Qt crashing in MFStreamer::doRead()
Apparently it should have called BeginRead for MFStreamer but it didn't.
How do i play video streams with QMediaPlayer?
Edit: here's my code
videotest.cpp
#include "videotest.h"
#include <QVBoxLayout>
#include <QVideoWidget>
#include <qmediaplayer.h>
#include <QMediaContent>
#include <QNetworkAccessManager>
#include <QNetworkReply>
struct VideoTest::Private
{
QMediaPlayer * mediaPlayer;
QNetworkAccessManager * networkAccessManager;
QNetworkReply * reply;
};
VideoTest::VideoTest(QWidget *parent)
: QMainWindow(parent)
{
d = new Private;
d->mediaPlayer = new QMediaPlayer(this, QMediaPlayer::StreamPlayback);
d->networkAccessManager = new QNetworkAccessManager(this);
ui.setupUi(this);
QVideoWidget * videoWidget = new QVideoWidget(ui.centralWidget);
videoWidget->show();
QPalette palette = videoWidget->palette();
palette.setColor(QPalette::Background, QColor(0, 0, 0));
videoWidget->setPalette(palette);
ui.videoLayout->addWidget(videoWidget);
d->mediaPlayer->setVideoOutput(videoWidget);
connect(ui.playButton, SIGNAL(clicked()), d->mediaPlayer, SLOT(play()));
connect(ui.pauseButton, SIGNAL(clicked()), d->mediaPlayer, SLOT(pause()));
connect(ui.videoUrlEdit, SIGNAL(editingFinished()), this, SLOT(sourceChanged()));
connect(d->mediaPlayer, SIGNAL(error()), this, SLOT(stateChanged()));
connect(d->mediaPlayer, SIGNAL(stateChanged), this, SLOT(stateChanged()));
}
VideoTest::~VideoTest()
{
delete d;
}
void VideoTest::sourceChanged()
{
d->reply = d->networkAccessManager->get(QNetworkRequest(ui.videoUrlEdit->text()));
if(d->reply)
{
connect(d->reply, SIGNAL(readyRead()), this, SLOT(networkRequestReady()));
}
}
void VideoTest::stateChanged()
{
QString text = ui.textEdit->toPlainText();
text.append("\n").append(d->mediaPlayer->errorString()).append(" : ").append(d->mediaPlayer->mediaStatus());
ui.textEdit->setText(text);
}
void VideoTest::networkRequestReady()
{
d->mediaPlayer->setMedia(QMediaContent(), d->reply);
}
videotest.h
#ifndef VIDEOTEST_H
#define VIDEOTEST_H
#include <QtWidgets/QMainWindow>
#include "ui_videotest.h"
class VideoTest : public QMainWindow
{
Q_OBJECT
public:
VideoTest(QWidget *parent = 0);
~VideoTest();
public slots:
void sourceChanged();
void stateChanged();
void networkRequestReady();
private:
Ui::VideoTestClass ui;
struct Private;
Private * d;
};
#endif // VIDEOTEST_H
I found a way to make it work.
I gave up on Qt. The guys at Qt were insistent that it should work, but were unable to produce any configuration that works. They said that it should work if you stream from VLC, but I didn't get it to work. I also tried ffmpeg, ffserver and nginx rtmp streaming. I got these things working with mplayer, ffplay, VLC and some even with Windows Media Player, but never QMediaPlayer.
I tried to just give the URL to setMedia.
I tried to make a custom QIODevice to read the stream data and give that data to QMediaPlayer which was initialized with StreamPlayback, but it just would not succeed in reading the data.
In the end, all I needed was something to play a stream, is a QWidget and isn't GPL licensed.
I used libVLC and vlc-qt both of which work wonderfully.
Following these instructions was easy, but you need to remember to copy the header files from vlc-qt/windows/vlc_headers/2.2/ to vlc/sdk/include/vlc/plugins (sic). This is important, if you don't do this you might get errors during compilation. Note that these paths might be different if you have different versions of your platform doesn't match mine. Also, it might not be necessary when you read this.
VideoTest.h
#ifndef VIDEOTEST_H_
#define VIDEOTEST_H_
#include <QtWidgets/QMainWindow>
#include "ui_videotest.h"
class VideoTest: public QMainWindow
{
Q_OBJECT
public:
VideoTest(QWidget * p_parent = 0);
~VideoTest();
public slots:
void sourceChanged();
private:
struct Private;
Private * d;
Ui::VideoTestClass ui;
};
#endif
videotest.cpp
#include "videotest.h"
#include <vlc-qt/Common.h>
#include <vlc-qt/Instance.h>
#include <vlc-qt/Media.h>
#include <vlc-qt/MediaPlayer.h>
#include <vlc-qt/WidgetVideo.h>
struct VideoTest::Private
{
VlcInstance * vlcInstance;
VlcMediaPlayer * vlcMediaPlayer;
VlcMedia * vlcMedia;
VlcWidgetVideo * vlcVideoWidget;
};
VideoTest::VideoTest(QWidget * p_parent)
{
d = new Private();
ui.setupUi(this);
d->vlcMedia = 0;
d->vlcInstance = new VlcInstance(VlcCommon::args(), this);
d->vlcMediaPlayer = new VlcMediaPlayer(d->vlcInstance);
d->vlcVideoWidget = new VlcWidgetVideo(this);
d->vlcMediaPlayer->setVideoWidget(d->vlcVideoWidget);
d->vlcVideoWidget->setMediaPlayer(d->vlcMediaPlayer);
ui.videoLayout->addWidget(d->vlcVideoWidget);
connect(ui.playButton, SIGNAL(clicked()), d->vlcMediaPlayer, SLOT(play()));
connect(ui.pauseButton, SIGNAL(clicked()), d->vlcMediaPlayer, SLOT(pause()));
connect(ui.videoUrlEdit, SIGNAL(editingFinished()), this, SLOT(sourceChanged()));
}
VideoTest::~VideoTest()
{
delete d->vlcInstance;
delete d->vlcMediaPlayer;
delete d->vlcMedia;
delete d;
}
VideoTest::sourceChanged()
{
QUrl url(ui.videoUrlEdit->test());
if(url.isValid() == false)
{
return;
}
d->vlcMediaPlayer->stop();
delete d->vlcMedia;
d->vlcMedia = new VlcMedia(url.toString(), d->vlcInstance);
d->vlcMediaPlayer->open(d->vlcMedia);
}
VideoTest.ui
Make your own, I don't work for you :D
Just make sure that it has pauseButton, playButton, videoUrlEdit(QLineEdit) and videoLayout where the video widget will be inserted.
I've just managed to play stream in QML VideoOutput with C++ QMediaPlayer using the setMedia(QUrl("http://127.0.0.1:8080"));
The stream was created by VLC media player using the HTTP to the 8080 port. I've also succeeded in playing the stream created by VLC media player to local file by passing it to QMediaPlayer via setMedia(QMediaContent(), &localFile);.