summaryrefslogtreecommitdiffstats
path: root/tests/auto/integration/qmediaplayerbackend
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/integration/qmediaplayerbackend')
-rw-r--r--tests/auto/integration/qmediaplayerbackend/BLACKLIST26
-rw-r--r--tests/auto/integration/qmediaplayerbackend/CMakeLists.txt39
-rw-r--r--tests/auto/integration/qmediaplayerbackend/LazyLoad.qml52
-rw-r--r--tests/auto/integration/qmediaplayerbackend/fake.h29
-rw-r--r--tests/auto/integration/qmediaplayerbackend/fixture.h95
-rw-r--r--tests/auto/integration/qmediaplayerbackend/mediaplayerstate.h167
-rw-r--r--tests/auto/integration/qmediaplayerbackend/qmediaplayerbackend.pro20
-rw-r--r--tests/auto/integration/qmediaplayerbackend/server.h48
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/15s.mkvbin0 -> 61283 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4bin0 -> 37261 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/BigBuckBunny.mp4bin0 -> 36724 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4bin0 -> 37930 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4bin0 -> 37436 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webmbin0 -> 16108 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/busMpeg4.mp4bin0 -> 25954 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4bin0 -> 21412 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4bin0 -> 21412 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4bin0 -> 21412 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4bin0 -> 21412 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webmbin0 -> 34940 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.movbin0 -> 20164 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkvbin0 -> 153449 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkvbin0 -> 153452 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3bin62715 -> 33988 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4bin0 -> 1589 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4bin0 -> 1656 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4bin0 -> 1652 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkvbin0 -> 17398 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp4624
29 files changed, 4085 insertions, 1015 deletions
diff --git a/tests/auto/integration/qmediaplayerbackend/BLACKLIST b/tests/auto/integration/qmediaplayerbackend/BLACKLIST
index e91f47755..1f24e07a1 100644
--- a/tests/auto/integration/qmediaplayerbackend/BLACKLIST
+++ b/tests/auto/integration/qmediaplayerbackend/BLACKLIST
@@ -1,32 +1,6 @@
-# QTBUG-46368
-
-osx
-windows-7
-windows-7sp1
-windows-10 msvc-2015
-windows-10 msvc-2017
-windows-10 msvc-2019
# Media player plugin not built at the moment on this platform
opensuse-13.1 64bit
-[loadMedia]
-windows 64bit developer-build
-
-[unloadMedia]
-windows 64bit developer-build
-
-[playPauseStop]
-windows 64bit developer-build
-
-[processEOS]
-windows 64bit developer-build
-
-[deleteLaterAtEOS]
-windows 64bit developer-build
-
-[initialVolume]
-windows 64bit developer-build
-
[playlist]
redhatenterpriselinuxworkstation-6.6
diff --git a/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt b/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt
new file mode 100644
index 000000000..3a9d25926
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qmediaplayerbackend.pro.
+
+#####################################################################
+## tst_qmediaplayerbackend Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ testdata/*)
+list(APPEND testdata_resource_files ${test_data_glob})
+
+qt_internal_add_test(tst_qmediaplayerbackend
+ SOURCES
+ ../shared/mediafileselector.h
+ ../shared/mediabackendutils.h
+ ../shared/testvideosink.h
+ mediaplayerstate.h
+ fake.h
+ fixture.h
+ server.h
+ tst_qmediaplayerbackend.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+ Qt::MultimediaQuickPrivate
+ Qt::Qml
+ Qt::Quick
+ Qt::QuickPrivate
+ BUILTIN_TESTDATA
+ TESTDATA
+ ${testdata_resource_files}
+ "LazyLoad.qml"
+ INCLUDE_DIRECTORIES
+ ../shared/
+)
diff --git a/tests/auto/integration/qmediaplayerbackend/LazyLoad.qml b/tests/auto/integration/qmediaplayerbackend/LazyLoad.qml
new file mode 100644
index 000000000..04af13186
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/LazyLoad.qml
@@ -0,0 +1,52 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtMultimedia
+
+Rectangle {
+ id: root
+ width: 600
+ height: 800
+ color: "black"
+
+ Component {
+ id: videoOutputComponent
+
+ Item {
+ objectName: "videoPlayer"
+ property alias mediaPlayer: mediaPlayer
+ property alias videoOutput: videoOutput
+ property alias videoSink: videoOutput.videoSink
+
+ property alias playbackState: mediaPlayer.playbackState
+ property alias error: mediaPlayer.error
+
+
+ MediaPlayer {
+ id: mediaPlayer
+ objectName: "mediaPlayer"
+ source: "qrc:/testdata/colors.mp4"
+ }
+ VideoOutput {
+ id: videoOutput
+ objectName: "videoOutput"
+ anchors.fill: parent
+ }
+ }
+ }
+
+ Loader {
+ id: loader
+ objectName: "loader"
+ sourceComponent: videoOutputComponent
+ anchors.fill: parent
+ active: false
+ onActiveChanged: {
+ if (active) {
+ loader.item.mediaPlayer.videoOutput = loader.item.videoOutput
+ loader.item.mediaPlayer.play()
+ }
+ }
+ }
+}
diff --git a/tests/auto/integration/qmediaplayerbackend/fake.h b/tests/auto/integration/qmediaplayerbackend/fake.h
new file mode 100644
index 000000000..9a68741d0
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/fake.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef FAKE_H
+#define FAKE_H
+
+#include <testvideosink.h>
+
+QT_USE_NAMESPACE
+
+class TestVideoOutput : public QObject
+{
+ Q_OBJECT
+public:
+ TestVideoOutput() = default;
+
+ Q_INVOKABLE QVideoSink *videoSink() { return &m_sink; }
+
+ TestVideoSink m_sink;
+};
+
+inline void setVideoSinkAsyncFramesCounter(QVideoSink &sink, std::atomic_int &counter)
+{
+ QObject::connect(
+ &sink, &QVideoSink::videoFrameChanged, &sink, [&counter]() { ++counter; },
+ Qt::DirectConnection);
+}
+
+#endif // FAKE_H
diff --git a/tests/auto/integration/qmediaplayerbackend/fixture.h b/tests/auto/integration/qmediaplayerbackend/fixture.h
new file mode 100644
index 000000000..883330513
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/fixture.h
@@ -0,0 +1,95 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef FIXTURE_H
+#define FIXTURE_H
+
+#include <qmediaplayer.h>
+#include <qaudiooutput.h>
+#include <qtest.h>
+#include <qsignalspy.h>
+
+#include "fake.h"
+#include "testvideosink.h"
+
+QT_USE_NAMESPACE
+
+struct Fixture : QObject
+{
+ Q_OBJECT
+public:
+ Fixture()
+ : playbackStateChanged(&player, &QMediaPlayer::playbackStateChanged),
+ errorOccurred(&player, &QMediaPlayer::errorOccurred),
+ sourceChanged(&player, &QMediaPlayer::sourceChanged),
+ mediaStatusChanged(&player, &QMediaPlayer::mediaStatusChanged),
+ positionChanged(&player, &QMediaPlayer::positionChanged),
+ durationChanged(&player, &QMediaPlayer::durationChanged),
+ playbackRateChanged(&player, &QMediaPlayer::playbackRateChanged),
+ metadataChanged(&player, &QMediaPlayer::metaDataChanged),
+ volumeChanged(&output, &QAudioOutput::volumeChanged),
+ mutedChanged(&output, &QAudioOutput::mutedChanged),
+ bufferProgressChanged(&player, &QMediaPlayer::bufferProgressChanged),
+ destroyed(&player, &QObject::destroyed)
+ {
+ setVideoSinkAsyncFramesCounter(surface, framesCount);
+
+ player.setAudioOutput(&output);
+ player.setVideoOutput(&surface);
+ }
+
+ void clearSpies()
+ {
+ playbackStateChanged.clear();
+ errorOccurred.clear();
+ sourceChanged.clear();
+ mediaStatusChanged.clear();
+ positionChanged.clear();
+ durationChanged.clear();
+ playbackRateChanged.clear();
+ metadataChanged.clear();
+ volumeChanged.clear();
+ mutedChanged.clear();
+ bufferProgressChanged.clear();
+ destroyed.clear();
+ }
+
+ QMediaPlayer player;
+ QAudioOutput output;
+ TestVideoSink surface;
+ std::atomic_int framesCount = 0;
+
+ QSignalSpy playbackStateChanged;
+ QSignalSpy errorOccurred;
+ QSignalSpy sourceChanged;
+ QSignalSpy mediaStatusChanged;
+ QSignalSpy positionChanged;
+ QSignalSpy durationChanged;
+ QSignalSpy playbackRateChanged;
+ QSignalSpy metadataChanged;
+ QSignalSpy volumeChanged;
+ QSignalSpy mutedChanged;
+ QSignalSpy bufferProgressChanged;
+ QSignalSpy destroyed;
+};
+
+// Helper to create an object that is comparable to a QSignalSpy
+using SignalList = QList<QList<QVariant>>;
+
+struct TestSubtitleSink : QObject
+{
+ Q_OBJECT
+
+public Q_SLOTS:
+ void addSubtitle(QString string)
+ {
+ QMetaObject::invokeMethod(this, [this, string = std::move(string)]() mutable {
+ subtitles.append(std::move(string));
+ });
+ }
+
+public:
+ QStringList subtitles;
+};
+
+#endif // FIXTURE_H
diff --git a/tests/auto/integration/qmediaplayerbackend/mediaplayerstate.h b/tests/auto/integration/qmediaplayerbackend/mediaplayerstate.h
new file mode 100644
index 000000000..d9f2cc875
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/mediaplayerstate.h
@@ -0,0 +1,167 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef MEDIAPLAYERSTATE_H
+#define MEDIAPLAYERSTATE_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qurl.h>
+#include <QtMultimedia/qaudiooutput.h>
+#include <QtMultimedia/qmediametadata.h>
+#include <QtMultimedia/qmediaplayer.h>
+#include <QtMultimedia/qmediatimerange.h>
+#include <QtTest/qtestcase.h>
+
+#include <optional>
+
+QT_USE_NAMESPACE
+
+/*!
+ * Helper class that simplifies testing the state of
+ * a media player against an expected state.
+ *
+ * Use the COMPARE_MEDIA_PLAYER_STATE_EQ macro to compare
+ * the media player state against the expected state.
+ *
+ * Individual properties can be ignored by comparison by
+ * assigning the std::nullopt value to the property of the
+ * expected state.
+ */
+struct MediaPlayerState
+{
+ std::optional<QList<QMediaMetaData>> audioTracks;
+ std::optional<QList<QMediaMetaData>> videoTracks;
+ std::optional<QList<QMediaMetaData>> subtitleTracks;
+ std::optional<int> activeAudioTrack;
+ std::optional<int> activeVideoTrack;
+ std::optional<int> activeSubtitleTrack;
+ std::optional<QAudioOutput*> audioOutput;
+ std::optional<QObject*> videoOutput;
+ std::optional<QVideoSink*> videoSink;
+ std::optional<QUrl> source;
+ std::optional<QIODevice const*> sourceDevice;
+ std::optional<QMediaPlayer::PlaybackState> playbackState;
+ std::optional<QMediaPlayer::MediaStatus> mediaStatus;
+ std::optional<qint64> duration;
+ std::optional<qint64> position;
+ std::optional<bool> hasAudio;
+ std::optional<bool> hasVideo;
+ std::optional<float> bufferProgress;
+ std::optional<QMediaTimeRange> bufferedTimeRange;
+ std::optional<bool> isSeekable;
+ std::optional<qreal> playbackRate;
+ std::optional<bool> isPlaying;
+ std::optional<int> loops;
+ std::optional<QMediaPlayer::Error> error;
+ std::optional<bool> isAvailable;
+ std::optional<QMediaMetaData> metaData;
+
+ /*!
+ * Read the state from an existing media player
+ */
+ explicit MediaPlayerState(const QMediaPlayer &player)
+ : audioTracks{ player.audioTracks() },
+ videoTracks{ player.videoTracks() },
+ subtitleTracks{ player.subtitleTracks() },
+ activeAudioTrack{ player.activeAudioTrack() },
+ activeVideoTrack{ player.activeVideoTrack() },
+ activeSubtitleTrack{ player.activeSubtitleTrack() },
+ audioOutput{ player.audioOutput() },
+ videoOutput{ player.videoOutput() },
+ videoSink{ player.videoSink() },
+ source{ player.source() },
+ sourceDevice{ player.sourceDevice() },
+ playbackState{ player.playbackState() },
+ mediaStatus{ player.mediaStatus() },
+ duration{ player.duration() },
+ position{ player.position() },
+ hasAudio{ player.hasAudio() },
+ hasVideo{ player.hasVideo() },
+ bufferProgress{ player.bufferProgress() },
+ bufferedTimeRange{ player.bufferedTimeRange() },
+ isSeekable{ player.isSeekable() },
+ playbackRate{ player.playbackRate() },
+ isPlaying{ player.isPlaying() },
+ loops{ player.loops() },
+ error{ player.error() },
+ isAvailable{ player.isAvailable() },
+ metaData{ player.metaData() }
+ {
+ }
+
+ /*!
+ * Creates the default state of a media player. The default state
+ * is the state the player should have when it is default constructed.
+ */
+ static MediaPlayerState defaultState()
+ {
+ MediaPlayerState state{};
+ state.audioTracks = QList<QMediaMetaData>{};
+ state.videoTracks = QList<QMediaMetaData>{};
+ state.subtitleTracks = QList<QMediaMetaData>{};
+ state.activeAudioTrack = -1;
+ state.activeVideoTrack = -1;
+ state.activeSubtitleTrack = -1;
+ state.audioOutput = nullptr;
+ state.videoOutput = nullptr;
+ state.videoSink = nullptr;
+ state.source = QUrl{};
+ state.sourceDevice = nullptr;
+ state.playbackState = QMediaPlayer::StoppedState;
+ state.mediaStatus = QMediaPlayer::NoMedia;
+ state.duration = 0;
+ state.position = 0;
+ state.hasAudio = false;
+ state.hasVideo = false;
+ state.bufferProgress = 0.0f;
+ state.bufferedTimeRange = QMediaTimeRange{};
+ state.isSeekable = false;
+ state.playbackRate = static_cast<qreal>(1);
+ state.isPlaying = false;
+ state.loops = 1;
+ state.error = QMediaPlayer::NoError;
+ state.isAvailable = true;
+ state.metaData = QMediaMetaData{};
+ return state;
+ }
+
+private:
+ MediaPlayerState() = default;
+
+};
+
+#define COMPARE_EQ_IGNORE_OPTIONAL(actual, expected) \
+ do { \
+ if ((expected).has_value()) { \
+ QCOMPARE_EQ(actual, expected); \
+ } \
+ } while (false)
+
+#define COMPARE_MEDIA_PLAYER_STATE_EQ(actual, expected) \
+ do { \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).audioTracks, (expected).audioTracks); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).videoTracks, (expected).videoTracks); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).subtitleTracks, (expected).subtitleTracks); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).activeAudioTrack, (expected).activeAudioTrack); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).activeVideoTrack, (expected).activeVideoTrack); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).activeSubtitleTrack, (expected).activeSubtitleTrack); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).source, (expected).source); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).sourceDevice, (expected).sourceDevice); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).playbackState, (expected).playbackState); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).mediaStatus, (expected).mediaStatus); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).duration, (expected).duration); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).position, (expected).position); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).hasAudio, (expected).hasAudio); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).hasVideo, (expected).hasVideo); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).bufferProgress, (expected).bufferProgress); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).bufferedTimeRange, (expected).bufferedTimeRange); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).isSeekable, (expected).isSeekable); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).playbackRate, (expected).playbackRate); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).isPlaying, (expected).isPlaying); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).loops, (expected).loops); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).error, (expected).error); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).isAvailable, (expected).isAvailable); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).metaData, (expected).metaData); \
+ } while (false)
+
+#endif // MEDIAPLAYERSTATE_H
diff --git a/tests/auto/integration/qmediaplayerbackend/qmediaplayerbackend.pro b/tests/auto/integration/qmediaplayerbackend/qmediaplayerbackend.pro
deleted file mode 100644
index 6dd1e8d62..000000000
--- a/tests/auto/integration/qmediaplayerbackend/qmediaplayerbackend.pro
+++ /dev/null
@@ -1,20 +0,0 @@
-TARGET = tst_qmediaplayerbackend
-
-QT += multimedia-private testlib
-
-# This is more of a system test
-CONFIG += testcase
-
-
-SOURCES += \
- tst_qmediaplayerbackend.cpp
-
-HEADERS += \
- ../shared/mediafileselector.h
-
-TESTDATA += testdata/*
-
-boot2qt: {
- # OGV testing is unstable with qemu
- QMAKE_CXXFLAGS += -DSKIP_OGV_TEST
-}
diff --git a/tests/auto/integration/qmediaplayerbackend/server.h b/tests/auto/integration/qmediaplayerbackend/server.h
new file mode 100644
index 000000000..a6fde1a7a
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/server.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SERVER_H
+#define SERVER_H
+
+#include <private/qglobal_p.h>
+
+#ifdef QT_FEATURE_network
+
+#include <qstring.h>
+#include <qtcpserver.h>
+#include <qtest.h>
+#include <qurl.h>
+
+QT_USE_NAMESPACE
+
+class UnResponsiveRtspServer : public QObject
+{
+ Q_OBJECT
+public:
+ UnResponsiveRtspServer() : m_server{ new QTcpServer{ this } }
+ {
+ connect(m_server, &QTcpServer::newConnection, this, [&] { m_connected = true; });
+ }
+
+ bool listen() { return m_server->listen(QHostAddress::LocalHost); }
+
+ bool waitForConnection()
+ {
+ return QTest::qWaitFor([this] { return m_connected; });
+ }
+
+ QUrl address() const
+ {
+ return QUrl{ QString{ "rtsp://%1:%2" }
+ .arg(m_server->serverAddress().toString())
+ .arg(m_server->serverPort()) };
+ }
+
+private:
+ QTcpServer *m_server;
+ bool m_connected = false;
+};
+
+#endif // QT_FEATURE_network
+
+#endif // SERVER_H
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/15s.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/15s.mkv
new file mode 100644
index 000000000..80ee0f923
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/15s.mkv
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4
new file mode 100644
index 000000000..96ae2e4e3
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/BigBuckBunny.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/BigBuckBunny.mp4
new file mode 100644
index 000000000..6cf08011c
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/BigBuckBunny.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4
new file mode 100644
index 000000000..dfeec1546
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4
new file mode 100644
index 000000000..147adf777
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm b/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm
new file mode 100644
index 000000000..048d02e8a
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/busMpeg4.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/busMpeg4.mp4
new file mode 100644
index 000000000..8824fe8c9
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/busMpeg4.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4
new file mode 100644
index 000000000..a3661b9d2
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4
new file mode 100644
index 000000000..9a60850d6
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4
new file mode 100644
index 000000000..b3ecd486d
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4
new file mode 100644
index 000000000..dc620d05f
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm b/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm
new file mode 100644
index 000000000..87b737949
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov b/tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov
new file mode 100644
index 000000000..c5a508a1f
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv
new file mode 100644
index 000000000..1962f00c1
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv
new file mode 100644
index 000000000..a3c2e9bb9
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3 b/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3
index 2435f65b8..892c2a89e 100644
--- a/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4
new file mode 100644
index 000000000..6b67a3433
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4
new file mode 100644
index 000000000..b0d9b3593
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4
new file mode 100644
index 000000000..55baed13e
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv
new file mode 100644
index 000000000..2051e4df5
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
index b0d84b836..b212b4b63 100644
--- a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
+++ b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
@@ -1,48 +1,73 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QDebug>
-#include <qabstractvideosurface.h>
-#include "qmediaservice.h"
#include "qmediaplayer.h"
-#include "qaudioprobe.h"
-#include "qvideoprobe.h"
-#include <qmediaplaylist.h>
+#include "mediaplayerstate.h"
+#include "fake.h"
+#include "fixture.h"
+#include "server.h"
#include <qmediametadata.h>
-
-#include "../shared/mediafileselector.h"
-//TESTED_COMPONENT=src/multimedia
-
+#include <qaudiobuffer.h>
+#include <qaudiodevice.h>
+#include <qvideosink.h>
+#include <qvideoframe.h>
+#include <qaudiooutput.h>
+#include <qmediadevices.h>
+#if QT_CONFIG(process)
+#include <qprocess.h>
+#endif
+#include <private/qglobal_p.h>
+#ifdef QT_FEATURE_network
+#include <qtcpserver.h>
+#endif
+#include <qmediatimerange.h>
+#include <private/qplatformvideosink_p.h>
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlproperty.h>
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/qquickview.h>
+#include <QtQuick/private/qquickloader_p.h>
+
+#include "mediafileselector.h"
+#include "mediabackendutils.h"
#include <QtMultimedia/private/qtmultimedia-config_p.h>
+#include "private/qquickvideooutput_p.h"
+
+#include <array>
QT_USE_NAMESPACE
+using namespace Qt::Literals;
+
+namespace {
+static qreal colorDifference(QRgb first, QRgb second)
+{
+ const auto diffVector = QVector3D(qRed(first), qGreen(first), qBlue(first))
+ - QVector3D(qRed(second), qGreen(second), qBlue(second));
+ static const auto normalizationFactor = 1. / (255 * qSqrt(3.));
+ return diffVector.length() * normalizationFactor;
+}
+
+template <typename It>
+It findSimilarColor(It it, It end, QRgb color)
+{
+ return std::min_element(it, end, [color](QRgb first, QRgb second) {
+ return colorDifference(first, color) < colorDifference(second, color);
+ });
+}
+
+template <typename Colors>
+auto findSimilarColorIndex(const Colors &colors, QRgb color)
+{
+ return std::distance(std::begin(colors),
+ findSimilarColor(std::begin(colors), std::end(colors), color));
+}
+}
+
/*
This is the backend conformance test.
@@ -54,529 +79,1991 @@ class tst_QMediaPlayerBackend : public QObject
{
Q_OBJECT
public slots:
- void init();
- void cleanup();
void initTestCase();
+ void init() { m_fixture = std::make_unique<Fixture>(); }
+ void cleanup() { m_fixture = nullptr; }
private slots:
- void construction();
- void loadMedia();
- void unloadMedia();
- void loadMediaInLoadingState();
- void playPauseStop();
+ void testMediaFilesAreSupported();
+ void destructor_cancelsPreviousSetSource_whenServerDoesNotRespond();
+ void destructor_emitsOnlyQObjectDestroyedSignal_whenPlayerIsRunning();
+
+ void getters_returnExpectedValues_whenCalledWithDefaultConstructedPlayer_data() const;
+ void getters_returnExpectedValues_whenCalledWithDefaultConstructedPlayer() const;
+
+ void setSource_emitsSourceChanged_whenCalledWithInvalidFile();
+ void setSource_emitsError_whenCalledWithInvalidFile();
+ void setSource_emitsMediaStatusChange_whenCalledWithInvalidFile();
+ void setSource_doesNotEmitPlaybackStateChange_whenCalledWithInvalidFile();
+ void setSource_setsSourceMediaStatusAndError_whenCalledWithInvalidFile();
+ void setSource_initializesExpectedDefaultState();
+ void setSource_initializesExpectedDefaultState_data();
+ void setSource_silentlyCancelsPreviousCall_whenServerDoesNotRespond();
+ void setSource_changesSourceAndMediaStatus_whenCalledWithValidFile();
+ void setSource_updatesExpectedAttributes_whenMediaHasLoaded();
+ void setSource_stopsAndEntersErrorState_whenPlayerWasPlaying();
+ void setSource_loadsAudioTrack_whenCalledWithValidWavFile();
+ void setSource_resetsState_whenCalledWithEmptyUrl();
+ void setSource_resetsState_whenCalledWithEmptyUrl_data();
+ void setSource_loadsNewMedia_whenPreviousMediaWasFullyLoaded();
+ void setSource_loadsCorrectTracks_whenLoadingMediaInSequence();
+ void setSource_remainsInStoppedState_whenPlayerWasStopped();
+ void setSource_entersStoppedState_whenPlayerWasPlaying();
+ void setSource_emitsError_whenSdpFileIsLoaded();
+ void setSource_updatesTrackProperties_data();
+ void setSource_updatesTrackProperties();
+ void setSource_emitsTracksChanged_data();
+ void setSource_emitsTracksChanged();
+
+ void setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio_data();
+ void setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio();
+
+ void pause_doesNotChangePlayerState_whenInvalidFileLoaded();
+ void pause_doesNothing_whenMediaIsNotLoaded();
+ void pause_entersPauseState_whenPlayerWasPlaying();
+ void pause_initializesExpectedDefaultState();
+ void pause_initializesExpectedDefaultState_data();
+ void pause_doesNotAdvancePosition();
+ void pause_playback_resumesFromPausedPosition();
+
+ void play_resetsErrorState_whenCalledWithInvalidFile();
+ void play_resumesPlaying_whenValidMediaIsProvidedAfterInvalidMedia();
+ void play_doesNothing_whenMediaIsNotLoaded();
+ void play_setsPlaybackStateAndMediaStatus_whenValidFileIsLoaded();
+ void play_startsPlaybackAndChangesPosition_whenValidFileIsLoaded();
+ void play_doesNotEnterMediaLoadingState_whenResumingPlayingAfterStop();
+ void playAndSetSource_emitsExpectedSignalsAndStopsPlayback_whenSetSourceWasCalledWithEmptyUrl();
+ void play_createsFramesWithExpectedContentAndIncreasingFrameTime_whenPlayingRtspMediaStream();
+ void play_waitsForLastFrameEnd_whenPlayingVideoWithLongFrames();
+ void play_startsPlayback_withAndWithoutOutputsConnected();
+ void play_startsPlayback_withAndWithoutOutputsConnected_data();
+ void play_playsRtpStream_whenSdpFileIsLoaded();
+ void play_succeedsFromSourceDevice();
+ void play_succeedsFromSourceDevice_data();
+
+ void stop_entersStoppedState_whenPlayerWasPaused();
+ void stop_entersStoppedState_whenPlayerWasPaused_data();
+ void stop_setsPositionToZero_afterPlayingToEndOfMedia();
+
+ void playbackRate_returnsOne_byDefault();
+ void setPlaybackRate_changesPlaybackRateAndEmitsSignal_data();
+ void setPlaybackRate_changesPlaybackRateAndEmitsSignal();
+ void setPlaybackRate_changesPlaybackDuration();
+ void setPlaybackRate_changesPlaybackDuration_data();
+
+ void setVolume_changesVolume_whenVolumeIsInRange();
+ void setVolume_clampsToRange_whenVolumeIsOutsideRange();
+ void setVolume_doesNotChangeMutedState();
+
+ void setMuted_changesMutedState_whenMutedStateChanged();
+ void setMuted_doesNotChangeVolume();
+
void processEOS();
void deleteLaterAtEOS();
- void volumeAndMuted();
+
void volumeAcrossFiles_data();
void volumeAcrossFiles();
void initialVolume();
void seekPauseSeek();
void seekInStoppedState();
void subsequentPlayback();
- void probes();
- void playlist();
- void playlistObject();
- void surfaceTest_data();
void surfaceTest();
- void multipleSurfaces();
void metadata();
+ void metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail_data();
+ void metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail();
+ void metadata_returnsMetadataWithHasHdrContent_whenMediaHasHdrContent_data();
+ void metadata_returnsMetadataWithHasHdrContent_whenMediaHasHdrContent();
void playerStateAtEOS();
void playFromBuffer();
+ void audioVideoAvailable();
+ void audioVideoAvailable_updatedOnNewMedia();
+ void isSeekable();
+ void positionAfterSeek();
+ void pause_rendersVideoAtCorrectResolution_data();
+ void pause_rendersVideoAtCorrectResolution();
+ void position();
+ void multipleMediaPlayback();
+ void multiplePlaybackRateChangingStressTest();
+ void multipleSeekStressTest();
+ void setPlaybackRate_changesActualRateAndFramesRenderingTime_data();
+ void setPlaybackRate_changesActualRateAndFramesRenderingTime();
+ void durationDetectionIssues_data();
+ void durationDetectionIssues();
+ void finiteLoops();
+ void infiniteLoops();
+ void seekOnLoops();
+ void changeLoopsOnTheFly();
+ void seekAfterLoopReset();
+ void changeVideoOutputNoFramesLost();
+ void cleanSinkAndNoMoreFramesAfterStop();
+ void lazyLoadVideo();
+ void videoSinkSignals();
+ void nonAsciiFileName();
+ void setMedia_setsVideoSinkSize_beforePlaying();
+ void play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata_data();
+ void play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata();
+
+ void setVideoOutput_doesNotStopPlayback_data();
+ void setVideoOutput_doesNotStopPlayback();
+ void setAudioOutput_doesNotStopPlayback_data();
+ void setAudioOutput_doesNotStopPlayback();
+ void swapAudioDevice_doesNotStopPlayback_data();
+ void swapAudioDevice_doesNotStopPlayback();
+
+ void play_readsSubtitle();
+ void multiTrack_validateMetadata();
+ void play_readsSubtitle_fromMultiTrack();
+ void play_readsSubtitle_fromMultiTrack_data();
+
+ void setActiveSubtitleTrack_switchesSubtitles();
+ void setActiveSubtitleTrack_switchesSubtitles_data();
+
+ void setActiveVideoTrack_switchesVideoTrack();
+
+ void disablingAllTracks_doesNotStopPlayback();
+ void disablingAllTracks_beforeTracksChanged_doesNotStopPlayback();
private:
- QMediaContent selectVideoFile(const QStringList& mediaCandidates);
- bool isWavSupported();
-
- //one second local wav file
- QMediaContent localWavFile;
- QMediaContent localWavFile2;
- QMediaContent localVideoFile;
- QMediaContent localCompressedSoundFile;
- QMediaContent localFileWithMetadata;
+ QUrl selectVideoFile(const QStringList &mediaCandidates);
- bool m_inCISystem;
+ bool canCreateRtpStream() const;
+#if QT_CONFIG(process)
+ std::unique_ptr<QProcess> createRtpStreamProcess(QString fileName, QString sdpUrl);
+#endif
+ void detectVlcCommand();
+
+ // one second local wav file
+ MaybeUrl m_localWavFile = QUnexpect{};
+ MaybeUrl m_localWavFile2 = QUnexpect{};
+ MaybeUrl m_localVideoFile = QUnexpect{};
+ MaybeUrl m_localVideoFile2 = QUnexpect{};
+ MaybeUrl m_av1File = QUnexpect{};
+ MaybeUrl m_videoDimensionTestFile = QUnexpect{};
+ MaybeUrl m_localCompressedSoundFile = QUnexpect{};
+ MaybeUrl m_localFileWithMetadata = QUnexpect{};
+ MaybeUrl m_localVideoFile3ColorsWithSound = QUnexpect{};
+ MaybeUrl m_videoFileWithJpegThumbnail = QUnexpect{};
+ MaybeUrl m_videoFileWithPngThumbnail = QUnexpect{};
+ MaybeUrl m_oneRedFrameVideo = QUnexpect{};
+ MaybeUrl m_192x108_PAR_2_3_Video = QUnexpect{};
+ MaybeUrl m_192x108_PAR_3_2_Video = QUnexpect{};
+ MaybeUrl m_colorMatrixVideo = QUnexpect{};
+ MaybeUrl m_colorMatrix90degClockwiseVideo = QUnexpect{};
+ MaybeUrl m_colorMatrix180degClockwiseVideo = QUnexpect{};
+ MaybeUrl m_colorMatrix270degClockwiseVideo = QUnexpect{};
+ MaybeUrl m_hdrVideo = QUnexpect{};
+ MaybeUrl m_15sVideo = QUnexpect{};
+ MaybeUrl m_subtitleVideo = QUnexpect{};
+ MaybeUrl m_multitrackVideo = QUnexpect{};
+ MaybeUrl m_multitrackSubtitleStartsAtZeroVideo = QUnexpect{};
+
+ MediaFileSelector m_mediaSelector;
+
+ const std::array<QRgb, 3> m_video3Colors = { { 0xFF0000, 0x00FF00, 0x0000FF } };
+ QString m_vlcCommand;
+
+ std::unique_ptr<Fixture> m_fixture;
};
-/*
- This is a simple video surface which records all presented frames.
-*/
-class TestVideoSurface : public QAbstractVideoSurface
+static bool commandExists(const QString &command)
{
- Q_OBJECT
-public:
- explicit TestVideoSurface(bool storeFrames = true);
- void setSupportedFormats(const QList<QVideoFrame::PixelFormat>& formats) { m_supported = formats; }
+#if defined(Q_OS_WINDOWS)
+ static constexpr QChar separator = ';';
+#else
+ static constexpr QChar separator = ':';
+#endif
+ static const QStringList pathDirs = qEnvironmentVariable("PATH").split(separator);
+ return std::any_of(pathDirs.cbegin(), pathDirs.cend(), [&command](const QString &dir) {
+ QString fullPath = QDir(dir).filePath(command);
+ return QFile::exists(fullPath);
+ });
+}
- //video surface
- QList<QVideoFrame::PixelFormat> supportedPixelFormats(
- QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
+static std::unique_ptr<QTemporaryFile> copyResourceToTemporaryFile(QString resource,
+ QString filePattern)
+{
+ QFile resourceFile(resource);
+ if (!resourceFile.open(QIODeviceBase::ReadOnly))
+ return nullptr;
- bool start(const QVideoSurfaceFormat &format);
- void stop();
- bool present(const QVideoFrame &frame);
+ auto temporaryFile = std::make_unique<QTemporaryFile>(filePattern);
+ if (!temporaryFile->open())
+ return nullptr;
- QList<QVideoFrame> m_frameList;
- int m_totalFrames; // used instead of the list when frames are not stored
+ QByteArray bytes = resourceFile.readAll();
+ QDataStream stream(temporaryFile.get());
+ stream.writeRawData(bytes.data(), bytes.length());
-private:
- bool m_storeFrames;
- QList<QVideoFrame::PixelFormat> m_supported;
-};
+ temporaryFile->close();
+
+ return temporaryFile;
+}
-class ProbeDataHandler : public QObject
+void tst_QMediaPlayerBackend::detectVlcCommand()
{
- Q_OBJECT
+ m_vlcCommand = qEnvironmentVariable("QT_VLC_COMMAND");
-public:
- ProbeDataHandler() : isVideoFlushCalled(false) { }
+ if (!m_vlcCommand.isEmpty())
+ return;
- QList<QVideoFrame> m_frameList;
- QList<QAudioBuffer> m_bufferList;
- bool isVideoFlushCalled;
+#if defined(Q_OS_WINDOWS)
+ m_vlcCommand = "vlc.exe";
+#else
+ m_vlcCommand = "vlc";
+#endif
+ if (commandExists(m_vlcCommand))
+ return;
-public slots:
- void processFrame(const QVideoFrame&);
- void processBuffer(const QAudioBuffer&);
- void flushVideo();
- void flushAudio();
-};
+ m_vlcCommand.clear();
-void tst_QMediaPlayerBackend::init()
+#if defined(Q_OS_MACOS)
+ m_vlcCommand = "/Applications/VLC.app/Contents/MacOS/VLC";
+#elif defined(Q_OS_WINDOWS)
+ m_vlcCommand = "C:/Program Files/VideoLAN/VLC/vlc.exe";
+#endif
+
+ if (!QFile::exists(m_vlcCommand))
+ m_vlcCommand.clear();
+}
+
+bool tst_QMediaPlayerBackend::canCreateRtpStream() const
{
+ return !m_vlcCommand.isEmpty();
}
-QMediaContent tst_QMediaPlayerBackend::selectVideoFile(const QStringList& mediaCandidates)
+void tst_QMediaPlayerBackend::initTestCase()
{
- // select supported video format
QMediaPlayer player;
- TestVideoSurface *surface = new TestVideoSurface;
- player.setVideoOutput(surface);
+ if (!player.isAvailable())
+ QSKIP("Media player service is not available");
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ qRegisterMetaType<MaybeUrl>();
- for (const QString &s : mediaCandidates) {
- QFileInfo videoFile(s);
- if (!videoFile.exists())
- continue;
- QMediaContent media = QMediaContent(QUrl::fromLocalFile(videoFile.absoluteFilePath()));
- player.setMedia(media);
- player.pause();
+ m_localWavFile = m_mediaSelector.select("qrc:/testdata/test.wav");
+ m_localWavFile2 = m_mediaSelector.select("qrc:/testdata/_test.wav");
- for (int i = 0; i < 2000 && surface->m_frameList.isEmpty() && errorSpy.isEmpty(); i+=50) {
- QTest::qWait(50);
- }
+ m_localVideoFile =
+ m_mediaSelector.select("qrc:/testdata/colors.mp4", "qrc:/testdata/colors.ogv");
- if (!surface->m_frameList.isEmpty() && errorSpy.isEmpty()) {
- return media;
- }
- errorSpy.clear();
- }
+ m_localVideoFile3ColorsWithSound =
+ m_mediaSelector.select("qrc:/testdata/3colors_with_sound_1s.mp4");
+
+ m_videoFileWithJpegThumbnail =
+ m_mediaSelector.select("qrc:/testdata/audio_video_with_jpg_thumbnail.mp4");
+
+ m_videoFileWithPngThumbnail =
+ m_mediaSelector.select("qrc:/testdata/audio_video_with_png_thumbnail.mp4");
- return QMediaContent();
+#ifndef Q_OS_MACOS // QTBUG-119711 Add support for AV1 decoding with the FFmpeg backend in online installer
+ m_av1File = m_mediaSelector.select("qrc:/testdata/busAv1.webm");
+#endif
+
+ m_localVideoFile2 =
+ m_mediaSelector.select("qrc:/testdata/BigBuckBunny.mp4", "qrc:/testdata/busMpeg4.mp4");
+
+ m_videoDimensionTestFile = m_mediaSelector.select("qrc:/testdata/BigBuckBunny.mp4");
+
+ m_localCompressedSoundFile =
+ m_mediaSelector.select("qrc:/testdata/nokia-tune.mp3", "qrc:/testdata/nokia-tune.mkv");
+
+ m_localFileWithMetadata = m_mediaSelector.select("qrc:/testdata/nokia-tune.mp3");
+
+ m_oneRedFrameVideo = m_mediaSelector.select("qrc:/testdata/one_red_frame.mp4");
+
+ m_192x108_PAR_2_3_Video = m_mediaSelector.select("qrc:/testdata/par_2_3.mp4");
+ m_192x108_PAR_3_2_Video = m_mediaSelector.select("qrc:/testdata/par_3_2.mp4");
+
+ m_colorMatrixVideo = m_mediaSelector.select("qrc:/testdata/color_matrix.mp4");
+ m_colorMatrix90degClockwiseVideo =
+ m_mediaSelector.select("qrc:/testdata/color_matrix_90_deg_clockwise.mp4");
+ m_colorMatrix180degClockwiseVideo =
+ m_mediaSelector.select("qrc:/testdata/color_matrix_180_deg_clockwise.mp4");
+ m_colorMatrix270degClockwiseVideo =
+ m_mediaSelector.select("qrc:/testdata/color_matrix_270_deg_clockwise.mp4");
+
+ m_hdrVideo = m_mediaSelector.select("qrc:/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov");
+ m_15sVideo = m_mediaSelector.select("qrc:/testdata/15s.mkv");
+ m_subtitleVideo = m_mediaSelector.select("qrc:/testdata/subtitletest.mkv");
+ m_multitrackVideo = m_mediaSelector.select("qrc:/testdata/multitrack.mkv");
+ m_multitrackSubtitleStartsAtZeroVideo =
+ m_mediaSelector.select("qrc:/testdata/multitrack-subtitle-start-at-zero.mkv");
+
+ detectVlcCommand();
}
-bool tst_QMediaPlayerBackend::isWavSupported()
+void tst_QMediaPlayerBackend::testMediaFilesAreSupported()
{
- return !localWavFile.isNull();
+ const auto mediaSelectionErrors = m_mediaSelector.dumpErrors();
+ if (!mediaSelectionErrors.isEmpty())
+ qDebug().noquote() << "Dump media selection errors:\n" << mediaSelectionErrors;
+
+ // TODO: probalbly, we should check errors anyway; TBD.
+ QCOMPARE(m_mediaSelector.failedSelectionsCount(), 0);
}
-void tst_QMediaPlayerBackend::initTestCase()
+void tst_QMediaPlayerBackend::destructor_cancelsPreviousSetSource_whenServerDoesNotRespond()
{
- QMediaPlayer player;
- if (!player.isAvailable())
- QSKIP("Media player service is not available");
+#ifdef QT_FEATURE_network
+ UnResponsiveRtspServer server;
+ QVERIFY(server.listen());
- qRegisterMetaType<QMediaContent>();
+ auto player = std::make_unique<QMediaPlayer>();
+ player->setSource(server.address());
- localWavFile = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/test.wav"));
- localWavFile2 = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/_test.wav"));;
+ QVERIFY(server.waitForConnection());
- QStringList mediaCandidates;
- mediaCandidates << QFINDTESTDATA("testdata/colors.mp4");
-#ifndef SKIP_OGV_TEST
- mediaCandidates << QFINDTESTDATA("testdata/colors.ogv");
+ // Cancel connection (should be fast, but can't be reliably verified
+ // in a test. For now we just verify that we don't crash.
+ player = nullptr;
+#else
+ QSKIP("Test requires network feature");
#endif
- localVideoFile = MediaFileSelector::selectMediaFile(mediaCandidates);
+}
- mediaCandidates.clear();
- mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mp3");
- mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mkv");
- localCompressedSoundFile = MediaFileSelector::selectMediaFile(mediaCandidates);
+void tst_QMediaPlayerBackend::destructor_emitsOnlyQObjectDestroyedSignal_whenPlayerIsRunning()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+
+ // Wait for started
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.~QMediaPlayer();
+ new (&m_fixture->player) QMediaPlayer;
+
+ // Assert
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 0);
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+ QCOMPARE(m_fixture->sourceChanged.size(), 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.size(), 0);
+ QCOMPARE(m_fixture->positionChanged.size(), 0);
+ QCOMPARE(m_fixture->durationChanged.size(), 0);
+ QCOMPARE(m_fixture->metadataChanged.size(), 0);
+ QCOMPARE(m_fixture->volumeChanged.size(), 0);
+ QCOMPARE(m_fixture->mutedChanged.size(), 0);
+ QCOMPARE(m_fixture->bufferProgressChanged.size(), 0);
+ QCOMPARE(m_fixture->destroyed.size(), 1);
+}
+
+void tst_QMediaPlayerBackend::
+ getters_returnExpectedValues_whenCalledWithDefaultConstructedPlayer_data() const
+{
+ QTest::addColumn<bool>("hasAudioOutput");
+ QTest::addColumn<bool>("hasVideoOutput");
+ QTest::addColumn<bool>("hasVideoSink");
+
+ QTest::newRow("noOutput") << false << false << false;
+ QTest::newRow("withAudioOutput") << true << false << false;
+ QTest::newRow("withVideoOutput") << false << true << false;
+ QTest::newRow("withVideoSink") << false << false << true;
+ QTest::newRow("withAllOutputs") << true << true << true;
+}
- localFileWithMetadata = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/nokia-tune.mp3"));
+void tst_QMediaPlayerBackend::getters_returnExpectedValues_whenCalledWithDefaultConstructedPlayer()
+ const
+{
+ QFETCH(const bool, hasAudioOutput);
+ QFETCH(const bool, hasVideoOutput);
+ QFETCH(const bool, hasVideoSink);
+
+ QAudioOutput audioOutput;
+ TestVideoOutput videoOutput;
+
+ QMediaPlayer player;
- qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10);
+ if (hasAudioOutput)
+ player.setAudioOutput(&audioOutput);
+
+ if (hasVideoOutput)
+ player.setVideoOutput(&videoOutput);
+
+ if (hasVideoSink)
+ player.setVideoSink(videoOutput.videoSink());
+
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ expectedState.audioOutput = hasAudioOutput ? &audioOutput : nullptr;
+ expectedState.videoOutput = (hasVideoOutput && !hasVideoSink) ? &videoOutput : nullptr;
+ expectedState.videoSink = (hasVideoSink || hasVideoOutput) ? videoOutput.videoSink() : nullptr;
+
+ const MediaPlayerState actualState{ player };
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
}
-void tst_QMediaPlayerBackend::cleanup()
+void tst_QMediaPlayerBackend::setSource_emitsSourceChanged_whenCalledWithInvalidFile()
{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ QCOMPARE_EQ(m_fixture->sourceChanged, SignalList({ { QUrl("Some not existing media") } }));
}
-void tst_QMediaPlayerBackend::construction()
+void tst_QMediaPlayerBackend::setSource_emitsError_whenCalledWithInvalidFile()
{
- QMediaPlayer player;
- QTRY_VERIFY(player.isAvailable());
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ QCOMPARE_EQ(m_fixture->errorOccurred[0][0], QMediaPlayer::ResourceError);
}
-void tst_QMediaPlayerBackend::loadMedia()
+void tst_QMediaPlayerBackend::setSource_emitsMediaStatusChange_whenCalledWithInvalidFile()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
- QMediaPlayer player;
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadingMedia }, { QMediaPlayer::InvalidMedia } }));
+}
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
+void tst_QMediaPlayerBackend::setSource_doesNotEmitPlaybackStateChange_whenCalledWithInvalidFile()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent)));
- QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent)));
+ QVERIFY(m_fixture->playbackStateChanged.empty());
+}
- player.setMedia(localWavFile);
+void tst_QMediaPlayerBackend::setSource_setsSourceMediaStatusAndError_whenCalledWithInvalidFile()
+{
+ const QUrl invalidFile{ "Some not existing media" };
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ m_fixture->player.setSource(invalidFile);
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
- QVERIFY(player.mediaStatus() != QMediaPlayer::NoMedia);
- QVERIFY(player.mediaStatus() != QMediaPlayer::InvalidMedia);
- QVERIFY(player.media() == localWavFile);
- QVERIFY(player.currentMedia() == localWavFile);
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ expectedState.source = invalidFile;
+ expectedState.mediaStatus = QMediaPlayer::InvalidMedia;
+ expectedState.error = QMediaPlayer::ResourceError;
- QCOMPARE(stateSpy.count(), 0);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(mediaSpy.last()[0].value<QMediaContent>(), localWavFile);
- QCOMPARE(currentMediaSpy.last()[0].value<QMediaContent>(), localWavFile);
+ const MediaPlayerState actualState{ m_fixture->player };
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+}
+
+void tst_QMediaPlayerBackend::setSource_initializesExpectedDefaultState()
+{
+ QFETCH(MaybeUrl, url);
+ CHECK_SELECTED_URL(url);
+
+ QMediaPlayer &player = m_fixture->player;
+ player.setSource(*url);
+
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ expectedState.source = *url;
+ expectedState.mediaStatus = QMediaPlayer::LoadingMedia;
+
+ if (isGStreamerPlatform()) {
+ // gstreamer initializes the tracks
+ expectedState.audioTracks = std::nullopt;
+ expectedState.videoTracks = std::nullopt;
+ expectedState.activeAudioTrack = std::nullopt;
+ expectedState.activeVideoTrack = std::nullopt;
+ expectedState.hasAudio = std::nullopt;
+ expectedState.hasVideo = std::nullopt;
+
+ expectedState.isSeekable = true;
+ }
- QVERIFY(player.isAudioAvailable());
- QVERIFY(!player.isVideoAvailable());
+ const MediaPlayerState actualState{ m_fixture->player };
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
}
-void tst_QMediaPlayerBackend::unloadMedia()
+void tst_QMediaPlayerBackend::setSource_initializesExpectedDefaultState_data()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ QTest::addColumn<MaybeUrl>("url");
- QMediaPlayer player;
- player.setNotifyInterval(50);
+ QTest::addRow("with wave file") << m_localWavFile;
+ QTest::addRow("with video file") << m_localVideoFile;
+ QTest::addRow("with av1 file") << m_av1File;
+ QTest::addRow("with compressed sound file") << m_localCompressedSoundFile;
+}
+
+void tst_QMediaPlayerBackend::setSource_silentlyCancelsPreviousCall_whenServerDoesNotRespond()
+{
+#ifdef QT_FEATURE_network
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ UnResponsiveRtspServer server;
+
+ QVERIFY(server.listen());
+
+ m_fixture->player.setSource(server.address());
+ QVERIFY(server.waitForConnection());
+
+ m_fixture->player.setSource(*m_localVideoFile);
+
+ // Cancellation can not be reliably verified due to relatively short timeout,
+ // but we can verify that the player is in the correct state.
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Cancellation is silent
+ QVERIFY(m_fixture->errorOccurred.empty());
+
+ // Media status is emitted as if only one file was loaded
+ const SignalList expectedMediaStatus = { { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia } };
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged, expectedMediaStatus);
+
+ // Two media source changed signals should be emitted still
+ const SignalList expectedSource = { { server.address() }, { *m_localVideoFile } };
+ QCOMPARE_EQ(m_fixture->sourceChanged, expectedSource);
+
+#else
+ QSKIP("Test requires network feature");
+#endif
+}
+
+void tst_QMediaPlayerBackend::setSource_changesSourceAndMediaStatus_whenCalledWithValidFile()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ m_fixture->player.setSource(*m_localVideoFile);
+
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged, SignalList({ { QMediaPlayer::LoadingMedia } }));
+
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ expectedState.source = *m_localVideoFile;
+ expectedState.mediaStatus = QMediaPlayer::LoadingMedia;
+
+ if (isGStreamerPlatform()) // gstreamer synchronously identifies file streams as seekable
+ expectedState.isSeekable = true;
+
+ MediaPlayerState actualState{ m_fixture->player };
+
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures");
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+}
+
+void tst_QMediaPlayerBackend::setSource_updatesExpectedAttributes_whenMediaHasLoaded()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ m_fixture->player.setSource(*m_localVideoFile);
+
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+
+ // Modify all attributes that are supposed to change with this media file
+ // All other state variables are verified to be unchanged.
+ expectedState.source = *m_localVideoFile;
+ expectedState.mediaStatus = QMediaPlayer::LoadedMedia;
+ expectedState.audioTracks = std::nullopt; // Don't compare
+ expectedState.videoTracks = std::nullopt; // Don't compare
+ expectedState.activeAudioTrack = 0;
+ expectedState.activeVideoTrack = 0;
+
+ if (isGStreamerPlatform())
+ expectedState.duration = 15019;
+ else if (isDarwinPlatform())
+ expectedState.duration = 15000;
+ else
+ expectedState.duration = 15018;
+ expectedState.hasAudio = true;
+ expectedState.hasVideo = true;
+ expectedState.isSeekable = true;
+ expectedState.metaData = std::nullopt; // Don't compare
+
+ if (isGStreamerPlatform())
+ expectedState.bufferProgress = std::nullopt; // QTBUG-124633: can change before play()
+
+ MediaPlayerState actualState{ m_fixture->player };
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+}
+
+void tst_QMediaPlayerBackend::setSource_stopsAndEntersErrorState_whenPlayerWasPlaying()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->framesCount > 0);
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+
+ // Act
+ m_fixture->player.setSource(QUrl("Some not existing media"));
+
+ // Assert
+ const int savedFramesCount = m_fixture->framesCount;
+
+ QCOMPARE(m_fixture->player.source(), QUrl("Some not existing media"));
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent)));
- QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
- QSignalSpy durationSpy(&player, SIGNAL(positionChanged(qint64)));
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::InvalidMedia);
+ QTRY_COMPARE(m_fixture->player.error(), QMediaPlayer::ResourceError);
- player.setMedia(localWavFile);
+ QVERIFY(!m_fixture->surface.videoFrame().isValid());
+
+ QCOMPARE(m_fixture->errorOccurred.size(), 1);
+
+ QTest::qWait(20);
+ QCOMPARE(m_fixture->framesCount, savedFramesCount);
+}
+
+void tst_QMediaPlayerBackend::setSource_loadsAudioTrack_whenCalledWithValidWavFile()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ m_fixture->player.setSource(*m_localWavFile);
+
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ QVERIFY(m_fixture->player.mediaStatus() != QMediaPlayer::NoMedia);
+ QVERIFY(m_fixture->player.mediaStatus() != QMediaPlayer::InvalidMedia);
+ QVERIFY(m_fixture->player.source() == *m_localWavFile);
+
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 0);
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->sourceChanged.size(), 1);
+ QCOMPARE(m_fixture->sourceChanged.last()[0].value<QUrl>(), *m_localWavFile);
+
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ QVERIFY(m_fixture->player.hasAudio());
+ QVERIFY(!m_fixture->player.hasVideo());
+}
+
+void tst_QMediaPlayerBackend::setSource_resetsState_whenCalledWithEmptyUrl()
+{
+ QFETCH(MaybeUrl, url);
+ CHECK_SELECTED_URL(url);
+
+ QMediaPlayer &player = m_fixture->player;
+
+ // Load valid media and start playing
+ player.setSource(*url);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QVERIFY(player.position() == 0);
- QVERIFY(player.duration() > 0);
+ QCOMPARE(player.position(), 0);
+
+ if (isQNXPlatform())
+ // QNX mm-renderer updates the duration when 'play' is triggered
+ QCOMPARE(player.duration(), 0);
+ else
+ QCOMPARE_GT(player.duration(), 0);
player.play();
- QTRY_VERIFY(player.position() > 0);
- QVERIFY(player.duration() > 0);
+ QTRY_COMPARE_GT(player.position(), 0);
+ if (isGStreamerPlatform())
+ QTRY_COMPARE_GT(player.duration(), 0); // duration update is asynchronous
+ else
+ QCOMPARE_GT(player.duration(), 0);
- stateSpy.clear();
- statusSpy.clear();
- mediaSpy.clear();
- currentMediaSpy.clear();
- positionSpy.clear();
- durationSpy.clear();
+ // Set empty URL and verify that state is fully reset to default
+ m_fixture->clearSpies();
+
+ m_fixture->player.setSource(QUrl());
- player.setMedia(QMediaContent());
+ QVERIFY(!m_fixture->mediaStatusChanged.isEmpty());
+ QVERIFY(!m_fixture->sourceChanged.isEmpty());
- QVERIFY(player.position() <= 0);
- QVERIFY(player.duration() <= 0);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
- QCOMPARE(player.media(), QMediaContent());
- QCOMPARE(player.currentMedia(), QMediaContent());
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ if (isGStreamerPlatform()) // QTBUG-124005: no buffer progress update
+ expectedState.bufferProgress = std::nullopt;
+ const MediaPlayerState actualState{ player };
- QVERIFY(!stateSpy.isEmpty());
- QVERIFY(!statusSpy.isEmpty());
- QVERIFY(!mediaSpy.isEmpty());
- QVERIFY(!currentMediaSpy.isEmpty());
- QVERIFY(!positionSpy.isEmpty());
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
}
-void tst_QMediaPlayerBackend::loadMediaInLoadingState()
+void tst_QMediaPlayerBackend::setSource_resetsState_whenCalledWithEmptyUrl_data()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ QTest::addColumn<MaybeUrl>("url");
- QMediaPlayer player;
- player.setMedia(localWavFile);
- player.play();
- QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia);
- // Sets new media while old has not been finished.
- player.setMedia(localWavFile);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QTest::addRow("with wave file") << m_localWavFile;
+ QTest::addRow("with video file") << m_localVideoFile;
}
-void tst_QMediaPlayerBackend::playPauseStop()
+void tst_QMediaPlayerBackend::setSource_loadsNewMedia_whenPreviousMediaWasFullyLoaded()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
+ CHECK_SELECTED_URL(m_localWavFile2);
+
+ // Load media and wait for it to completely load
+ m_fixture->player.setSource(*m_localWavFile2);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadingMedia);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Load another media file, play it, and wait for it to enter playing state
+ m_fixture->player.setSource(*m_localWavFile);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadingMedia);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ // Load first file again, and wait for it to start loading
+ m_fixture->player.setSource(*m_localWavFile2);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadingMedia);
+}
- QMediaPlayer player;
- player.setNotifyInterval(50);
+void tst_QMediaPlayerBackend::setSource_loadsCorrectTracks_whenLoadingMediaInSequence()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+ CHECK_SELECTED_URL(m_localWavFile2);
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ // Load audio/video file, play it, and verify that both tracks are loaded
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+ QTRY_COMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QVERIFY(m_fixture->surface.waitForFrame().isValid());
+ QVERIFY(m_fixture->player.hasAudio());
+ QVERIFY(m_fixture->player.hasVideo());
- // Check play() without a media
- player.play();
+ m_fixture->clearSpies();
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
- QCOMPARE(player.error(), QMediaPlayer::NoError);
- QCOMPARE(player.position(), 0);
- QCOMPARE(stateSpy.count(), 0);
- QCOMPARE(statusSpy.count(), 0);
- QCOMPARE(positionSpy.count(), 0);
- QCOMPARE(errorSpy.count(), 0);
+ // Load an audio file, and verify that only audio track is loaded
+ m_fixture->player.setSource(*m_localWavFile2);
- // Check pause() without a media
- player.pause();
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
- QCOMPARE(player.error(), QMediaPlayer::NoError);
- QCOMPARE(player.position(), 0);
- QCOMPARE(stateSpy.count(), 0);
- QCOMPARE(statusSpy.count(), 0);
- QCOMPARE(positionSpy.count(), 0);
- QCOMPARE(errorSpy.count(), 0);
+ QCOMPARE(m_fixture->player.source(), *m_localWavFile2);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 1);
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+ QVERIFY(m_fixture->player.hasAudio());
+ QVERIFY(!m_fixture->player.hasVideo());
+ QVERIFY(!m_fixture->surface.videoFrame().isValid());
- // The rest is with a valid media
+ m_fixture->player.play();
- player.setMedia(localWavFile);
+ // Load video only file, and verify that only video track is loaded
+ m_fixture->player.setSource(*m_localVideoFile2);
- QCOMPARE(player.position(), qint64(0));
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
- player.play();
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QVERIFY(m_fixture->player.hasVideo());
+ QVERIFY(!m_fixture->player.hasAudio());
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+}
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
+void tst_QMediaPlayerBackend::setSource_remainsInStoppedState_whenPlayerWasStopped()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+ CHECK_SELECTED_URL(m_localWavFile2);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ m_fixture->player.stop();
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.setSource(*m_localWavFile2);
+
+ // Assert
+ QTRY_VERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadingMedia }, { QMediaPlayer::LoadedMedia } }));
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QVERIFY(m_fixture->playbackStateChanged.empty());
+}
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
+void tst_QMediaPlayerBackend::setSource_entersStoppedState_whenPlayerWasPlaying()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+ CHECK_SELECTED_URL(m_localWavFile2);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile2);
+ m_fixture->clearSpies();
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+
+ // Act
+ m_fixture->player.setSource(*m_localWavFile);
+
+ // Assert
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QTRY_COMPARE(m_fixture->mediaStatusChanged,
+ SignalList({
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia },
+ }));
+
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(m_fixture->playbackStateChanged,
+ SignalList({ { QMediaPlayer::PlayingState }, { QMediaPlayer::StoppedState } }));
+
+ QTRY_VERIFY(!m_fixture->positionChanged.empty()
+ && m_fixture->positionChanged.last()[0].value<qint64>() == 0);
+
+ QCOMPARE(m_fixture->player.position(), 0);
+}
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PlayingState);
- QTRY_VERIFY(statusSpy.count() > 0 &&
- statusSpy.last()[0].value<QMediaPlayer::MediaStatus>() == QMediaPlayer::BufferedMedia);
+void tst_QMediaPlayerBackend::setSource_emitsError_whenSdpFileIsLoaded()
+{
+#if !QT_CONFIG(process)
+ QSKIP("This test requires QProcess support");
+#else
+ // NOTE: This test checks that playing rtp streams using local .sdp file as a source is blocked
+ // by default. For when the user wants to override these defaults, see
+ // play_playsRtpStream_whenSdpFileIsLoaded
- QTRY_VERIFY(player.position() > 100);
- QVERIFY(player.duration() > 0);
- QVERIFY(positionSpy.count() > 0);
- QVERIFY(positionSpy.last()[0].value<qint64>() > 0);
+ if (!isFFMPEGPlatform())
+ QSKIP("This test is only for FFmpeg backend");
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ // Create stream
+ if (!canCreateRtpStream())
+ QSKIP("Rtp stream cannot be created");
+
+ // Make sure the default whitelist is used
+ qunsetenv("QT_FFMPEG_PROTOCOL_WHITELIST");
+
+ auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4");
+ QVERIFY(temporaryFile);
+
+ // Pass a "file:" URL to VLC in order to generate an .sdp file
+ const QUrl sdpUrl = QUrl::fromLocalFile(QFileInfo("test.sdp").absoluteFilePath());
+
+ auto process = createRtpStreamProcess(temporaryFile->fileName(), sdpUrl.toString());
+ QVERIFY2(process, "Cannot start rtp process");
- qint64 positionBeforePause = player.position();
+ auto processCloser = qScopeGuard([&process, &sdpUrl]() {
+ // End stream
+ process->close();
+
+ // Remove .sdp file created by VLC
+ QFile(sdpUrl.toLocalFile()).remove();
+ });
+
+ m_fixture->player.setSource(sdpUrl);
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+#endif // QT_CONFIG(process)
+}
+
+void tst_QMediaPlayerBackend::setSource_updatesTrackProperties_data()
+{
+ QTest::addColumn<MaybeUrl>("url");
+ QTest::addColumn<int>("numberOfVideoTracks");
+ QTest::addColumn<int>("numberOfAudioTracks");
+ QTest::addColumn<int>("numberOfSubtitleTracks");
+
+ QTest::addRow("video file with audio") << m_localVideoFile3ColorsWithSound << 1 << 1 << 0;
+ QTest::addRow("video file without audio") << m_colorMatrixVideo << 1 << 0 << 0;
+ QTest::addRow("uncompressed audio file") << m_localWavFile << 0 << 1 << 0;
+ QTest::addRow("compressed audio file") << m_localCompressedSoundFile << 0 << 1 << 0;
+ QTest::addRow("video with subtitle") << m_subtitleVideo << 1 << 1 << 1;
+ QTest::addRow("video with multiple streams") << m_multitrackVideo << 2 << 2 << 2;
+}
+
+void tst_QMediaPlayerBackend::setSource_updatesTrackProperties()
+{
+ QFETCH(MaybeUrl, url);
+ QFETCH(int, numberOfVideoTracks);
+ QFETCH(int, numberOfAudioTracks);
+ QFETCH(int, numberOfSubtitleTracks);
+
+ QMediaPlayer &player = m_fixture->player;
+
+ CHECK_SELECTED_URL(url);
+
+ player.setSource(*url);
+
+ QTRY_COMPARE(player.videoTracks().size(), numberOfVideoTracks);
+ QTRY_COMPARE(player.audioTracks().size(), numberOfAudioTracks);
+ QTRY_COMPARE(player.subtitleTracks().size(), numberOfSubtitleTracks);
+}
+
+void tst_QMediaPlayerBackend::setSource_emitsTracksChanged_data()
+{
+ QTest::addColumn<MaybeUrl>("url");
+ QTest::addColumn<int>("numberOfVideoTracks");
+ QTest::addColumn<int>("numberOfAudioTracks");
+ QTest::addColumn<int>("numberOfSubtitleTracks");
+
+ QTest::addRow("video file with audio") << m_localVideoFile3ColorsWithSound << 1 << 1 << 0;
+ QTest::addRow("video file without audio") << m_colorMatrixVideo << 1 << 0 << 0;
+ QTest::addRow("uncompressed audio file") << m_localWavFile << 0 << 1 << 0;
+ QTest::addRow("compressed audio file") << m_localCompressedSoundFile << 0 << 1 << 0;
+ QTest::addRow("video with subtitle") << m_subtitleVideo << 1 << 1 << 1;
+ QTest::addRow("video with multiple streams") << m_multitrackVideo << 2 << 2 << 2;
+}
+
+void tst_QMediaPlayerBackend::setSource_emitsTracksChanged()
+{
+ QFETCH(MaybeUrl, url);
+ QFETCH(int, numberOfVideoTracks);
+ QFETCH(int, numberOfAudioTracks);
+ QFETCH(int, numberOfSubtitleTracks);
+
+ QMediaPlayer &player = m_fixture->player;
+
+ CHECK_SELECTED_URL(url);
+
+ QSignalSpy tracksChanged(&player, &QMediaPlayer::tracksChanged);
+ player.setSource(*url);
+
+ QVERIFY(tracksChanged.wait());
+
+ QCOMPARE(player.videoTracks().size(), numberOfVideoTracks);
+ QCOMPARE(player.audioTracks().size(), numberOfAudioTracks);
+ QCOMPARE(player.subtitleTracks().size(), numberOfSubtitleTracks);
+}
+
+void tst_QMediaPlayerBackend::
+ setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio_data()
+{
+ QTest::addColumn<MaybeUrl>("url");
+ QTest::addColumn<QSize>("expectedVideoSize");
+
+ QTest::addRow("Horizontal expanding (par=3/2)")
+ << m_192x108_PAR_3_2_Video << QSize(192 * 3 / 2, 108);
+
+ if (isGStreamerPlatform())
+ // QTBUG-125249: gstreamer tries "to keep the input height (because of interlacing)"
+ QTest::addRow("Horizontal shrinking (par=2/3)")
+ << m_192x108_PAR_2_3_Video << QSize(192 * 2 / 3, 108);
+ else
+ QTest::addRow("Vertical expanding (par=2/3)")
+ << m_192x108_PAR_2_3_Video << QSize(192, 108 * 3 / 2);
+}
+
+void tst_QMediaPlayerBackend::
+ setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio()
+{
+#ifdef Q_OS_ANDROID
+ QSKIP("SKIP initTestCase on CI, because of QTBUG-126428");
+#endif
+ if (isGStreamerPlatform() && isCI())
+ QSKIP("QTBUG-124005: Fails with gstreamer on CI");
+
+ QFETCH(MaybeUrl, url);
+ QFETCH(QSize, expectedVideoSize);
+
+ CHECK_SELECTED_URL(url);
+
+ m_fixture->player.setSource(*url);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE(m_fixture->player.metaData().value(QMediaMetaData::Resolution), QSize(192, 108));
+
+ QCOMPARE(m_fixture->surface.videoSize(), expectedVideoSize);
+
+ m_fixture->player.play();
+
+ auto frame = m_fixture->surface.waitForFrame();
+ QVERIFY(frame.isValid());
+ QCOMPARE(frame.size(), expectedVideoSize);
+ QCOMPARE(frame.surfaceFormat().frameSize(), expectedVideoSize);
+ QCOMPARE(frame.surfaceFormat().viewport(), QRect(QPoint(), expectedVideoSize));
+
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
+
+ auto image = frame.toImage();
+ QCOMPARE(frame.size(), expectedVideoSize);
+
+ // clang-format off
+
+ // Video schema:
+ //
+ // 192
+ // *---------------------*
+ // | White | |
+ // | | |
+ // |----------/ | 108
+ // | Red |
+ // | |
+ // *---------------------*
+
+ // clang-format on
+
+ // check the proper scaling
+ const std::vector<QRgb> colors = { 0xFFFFFF, 0xFF0000, 0xFF00, 0xFF, 0x0 };
+
+ const auto pixelsOffset = 4;
+ const auto halfSize = expectedVideoSize / 2;
+
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(0, 0)), 0);
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(halfSize.width() - pixelsOffset, 0)), 0);
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(0, halfSize.height() - pixelsOffset)), 0);
+ QCOMPARE(findSimilarColorIndex(colors,
+ image.pixel(halfSize.width() - pixelsOffset,
+ halfSize.height() - pixelsOffset)),
+ 0);
+
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(halfSize.width() + pixelsOffset, 0)), 1);
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(0, halfSize.height() + pixelsOffset)), 1);
+ QCOMPARE(findSimilarColorIndex(colors,
+ image.pixel(halfSize.width() + pixelsOffset,
+ halfSize.height() + pixelsOffset)),
+ 1);
+}
+
+void tst_QMediaPlayerBackend::pause_doesNotChangePlayerState_whenInvalidFileLoaded()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ const MediaPlayerState expectedState{ m_fixture->player };
+
+ m_fixture->player.pause();
+
+ const MediaPlayerState actualState{ m_fixture->player };
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+}
+
+void tst_QMediaPlayerBackend::pause_doesNothing_whenMediaIsNotLoaded()
+{
+ m_fixture->player.pause();
+
+ const MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ const MediaPlayerState actualState{ m_fixture->player };
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+
+ QVERIFY(m_fixture->playbackStateChanged.empty());
+ QVERIFY(m_fixture->mediaStatusChanged.empty());
+ QVERIFY(m_fixture->positionChanged.empty());
+ QVERIFY(m_fixture->errorOccurred.empty());
+}
+
+void tst_QMediaPlayerBackend::pause_entersPauseState_whenPlayerWasPlaying()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile);
+ m_fixture->player.play();
+ QTRY_COMPARE_GT(m_fixture->player.position(), 100);
+ m_fixture->clearSpies();
+ const qint64 positionBeforePause = m_fixture->player.position();
+
+ // Act
+ m_fixture->player.pause();
+
+ // Assert
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PausedState);
+ QCOMPARE_EQ(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PausedState } }));
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
+
+ QTRY_COMPARE_LT(qAbs(m_fixture->player.position() - positionBeforePause), 200);
+
+ QTest::qWait(500);
+
+ QTRY_COMPARE_LT(qAbs(m_fixture->player.position() - positionBeforePause), 200);
+}
+
+void tst_QMediaPlayerBackend::pause_initializesExpectedDefaultState()
+{
+ QFETCH(MaybeUrl, url);
+ QFETCH(bool, hasVideo);
+ QFETCH(bool, hasAudio);
+ CHECK_SELECTED_URL(url);
+
+ if (isFFMPEGPlatform() && url->path().contains("Av1"))
+ QSKIP("QTBUG-119711: ffmpeg's binaries on CI do not support av1");
+
+ QMediaPlayer &player = m_fixture->player;
+ player.setSource(*url);
player.pause();
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PausedState);
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PausedState);
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ expectedState.source = *url;
+ expectedState.playbackState = QMediaPlayer::PausedState;
+ expectedState.isSeekable = true;
- QTest::qWait(2000);
+ expectedState.mediaStatus = std::nullopt;
+ expectedState.duration = std::nullopt;
+ expectedState.bufferProgress = std::nullopt;
- QVERIFY(qAbs(player.position() - positionBeforePause) < 150);
- QCOMPARE(positionSpy.count(), 1);
+ expectedState.audioTracks = std::nullopt;
+ expectedState.videoTracks = std::nullopt;
+ expectedState.metaData = std::nullopt;
- stateSpy.clear();
- statusSpy.clear();
+ if (hasVideo) {
+ expectedState.activeVideoTrack = 0;
+ expectedState.hasVideo = std::nullopt;
+ }
- player.stop();
+ if (hasAudio) {
+ expectedState.activeAudioTrack = 0;
+ expectedState.hasAudio = std::nullopt;
+ }
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ const MediaPlayerState actualState{ player };
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
- //it's allowed to emit statusChanged() signal async
- QTRY_COMPARE(statusSpy.count(), 1);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia);
+ QVERIFY(actualState.mediaStatus == QMediaPlayer::BufferingMedia
+ || actualState.mediaStatus == QMediaPlayer::BufferedMedia);
- //ensure the position is reset to 0 at stop and positionChanged(0) is emitted
- QCOMPARE(player.position(), qint64(0));
- QCOMPARE(positionSpy.last()[0].value<qint64>(), qint64(0));
- QVERIFY(player.duration() > 0);
+ if (hasVideo)
+ QCOMPARE(actualState.videoTracks->size(), 1);
+ if (hasAudio)
+ QCOMPARE(actualState.audioTracks->size(), 1);
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ QEXPECT_FAIL_GSTREAMER("", "GStreamer doesn't update bufferProgress while paused", Continue);
- player.play();
+ QTRY_COMPARE_GT(actualState.bufferProgress, 0);
+}
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PlayingState);
- QCOMPARE(statusSpy.count(), 1); // Should not go through Loading again when play -> stop -> play
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
+void tst_QMediaPlayerBackend::pause_initializesExpectedDefaultState_data()
+{
+ QTest::addColumn<MaybeUrl>("url");
+ QTest::addColumn<bool>("hasVideo");
+ QTest::addColumn<bool>("hasAudio");
+
+ QTest::addRow("with wave file") << m_localWavFile << false << true;
+ QTest::addRow("with video file") << m_localVideoFile << true << true;
+ QTest::addRow("with av1 file") << m_av1File << true << false;
+ QTest::addRow("with compressed sound file") << m_localCompressedSoundFile << false << true;
+}
- player.stop();
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+void tst_QMediaPlayerBackend::pause_doesNotAdvancePosition()
+{
+ using namespace std::chrono_literals;
- player.setMedia(localWavFile2);
+ CHECK_SELECTED_URL(m_localVideoFile);
- QTRY_VERIFY(statusSpy.count() > 0);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 0);
+ QMediaPlayer &player = m_fixture->player;
+ player.setSource(*m_localVideoFile);
+
+ player.pause();
+
+ QTest::qWait(1s);
+
+ QTRY_COMPARE_EQ(player.position(), 0);
+}
+
+void tst_QMediaPlayerBackend::pause_playback_resumesFromPausedPosition()
+{
+ using namespace std::chrono_literals;
+
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ QMediaPlayer &player = m_fixture->player;
+ player.setSource(*m_localVideoFile);
player.play();
- QTRY_VERIFY(player.position() > 100);
+ QTRY_COMPARE_GT(player.position(), 100);
- player.setMedia(localWavFile);
+ player.pause();
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
- QCOMPARE(player.position(), 0);
- QCOMPARE(positionSpy.last()[0].value<qint64>(), 0);
+ qint64 pausePos = player.position();
+ QTest::qWait(1s);
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ QCOMPARE_EQ(player.position(), pausePos);
player.play();
- QTRY_VERIFY(player.position() > 100);
+ // Make sure the media player does not make up for the lost time
+ m_fixture->positionChanged.wait();
+ m_fixture->positionChanged.wait();
- player.setMedia(QMediaContent());
+ QCOMPARE_LT(player.position(), pausePos + 500);
+}
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::NoMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
- QCOMPARE(player.position(), 0);
- QCOMPARE(positionSpy.last()[0].value<qint64>(), 0);
- QCOMPARE(player.duration(), 0);
+void tst_QMediaPlayerBackend::play_resetsErrorState_whenCalledWithInvalidFile()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ MediaPlayerState expectedState{ m_fixture->player };
+
+ m_fixture->player.play();
+
+ expectedState.error = QMediaPlayer::NoError;
+ COMPARE_MEDIA_PLAYER_STATE_EQ(MediaPlayerState{ m_fixture->player }, expectedState);
+
+ QTest::qWait(150); // wait a bit and check position is not changed
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(MediaPlayerState{ m_fixture->player }, expectedState);
+ QCOMPARE(m_fixture->surface.m_totalFrames, 0);
}
+void tst_QMediaPlayerBackend::play_resumesPlaying_whenValidMediaIsProvidedAfterInvalidMedia()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->framesCount > 0);
+ m_fixture->player.setSource(QUrl("Some not existing media"));
+ QTRY_COMPARE(m_fixture->player.error(), QMediaPlayer::ResourceError);
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ // Act
+ m_fixture->player.play();
+
+ // Assert
+ QTRY_VERIFY(m_fixture->framesCount > 0);
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+ QCOMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QCOMPARE(m_fixture->player.error(), QMediaPlayer::NoError);
+}
-void tst_QMediaPlayerBackend::processEOS()
+void tst_QMediaPlayerBackend::play_doesNothing_whenMediaIsNotLoaded()
+{
+ m_fixture->player.play();
+
+ const MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ const MediaPlayerState actualState{ m_fixture->player };
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+
+ QVERIFY(m_fixture->playbackStateChanged.empty());
+ QVERIFY(m_fixture->mediaStatusChanged.empty());
+ QVERIFY(m_fixture->positionChanged.empty());
+ QVERIFY(m_fixture->errorOccurred.empty());
+}
+
+void tst_QMediaPlayerBackend::play_setsPlaybackStateAndMediaStatus_whenValidFileIsLoaded()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localVideoFile);
+ m_fixture->player.setSource(*m_localVideoFile);
+ m_fixture->player.play();
+
+ QTRY_COMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PlayingState } }));
+
+ auto expectedMediaStatus = SignalList{
+ { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ };
+
+ QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged.first(4), expectedMediaStatus);
+
+ QTRY_COMPARE_GT(m_fixture->bufferProgressChanged.size(), 0);
+ QTRY_COMPARE_NE(m_fixture->bufferProgressChanged.front().front(), 0.f);
+ QTRY_COMPARE(m_fixture->bufferProgressChanged.back().front(), 1.f);
+}
+
+void tst_QMediaPlayerBackend::play_startsPlaybackAndChangesPosition_whenValidFileIsLoaded()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ m_fixture->player.setSource(*m_localVideoFile);
+ m_fixture->player.play();
+
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ QTRY_VERIFY(!m_fixture->durationChanged.empty());
+ QTRY_VERIFY(!m_fixture->positionChanged.empty());
+ QTRY_VERIFY(m_fixture->positionChanged.last()[0].value<qint64>() > 100);
+}
+
+void tst_QMediaPlayerBackend::play_doesNotEnterMediaLoadingState_whenResumingPlayingAfterStop()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ // Arrange: go through a play->pause->stop sequence
+ m_fixture->player.setSource(*m_localWavFile);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ m_fixture->player.pause();
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ m_fixture->player.stop();
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.play();
+
+ // Assert
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+ QTRY_VERIFY(m_fixture->playbackStateChanged.contains({ QMediaPlayer::PlayingState }));
+
+ // Note: Should not go through Loading again when play -> stop -> play
+ if (!isGStreamerPlatform()) {
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ }));
+ } else {
+ QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged,
+ // gstreamer may see EndOfMedia
+ SignalList({
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::EndOfMedia },
+ }));
+ }
+}
+
+void tst_QMediaPlayerBackend::playAndSetSource_emitsExpectedSignalsAndStopsPlayback_whenSetSourceWasCalledWithEmptyUrl()
+{
+ CHECK_SELECTED_URL(m_localWavFile2);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile2);
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ m_fixture->player.setSource(QUrl());
+
+ // Assert
+ const MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ const MediaPlayerState actualState{ m_fixture->player };
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+
+ QList allowedSignalSequences = {
+ SignalList{
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::NoMedia },
+ },
+ SignalList{
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::EndOfMedia }, // EndOfMedia can be reached before setSource({})
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::NoMedia },
+ },
+ };
+
+ QTRY_VERIFY(allowedSignalSequences.contains(m_fixture->mediaStatusChanged));
+
+ QTRY_COMPARE_EQ(m_fixture->playbackStateChanged,
+ SignalList({ { QMediaPlayer::PlayingState }, { QMediaPlayer::StoppedState } }));
+
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QCOMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), 0);
+}
+
+void tst_QMediaPlayerBackend::
+ play_createsFramesWithExpectedContentAndIncreasingFrameTime_whenPlayingRtspMediaStream()
+{
+#if !QT_CONFIG(process)
+ QSKIP("This test requires QProcess support");
+#else
+ if (!canCreateRtpStream())
+ QSKIP("Rtsp stream cannot be created");
+
+ QSKIP_GSTREAMER("GStreamer tests fail");
+
+ auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4");
+ QVERIFY(temporaryFile);
+
+ const QString streamUrl = "rtsp://localhost:8083/stream";
+
+ auto process = createRtpStreamProcess(temporaryFile->fileName(), streamUrl);
+ QVERIFY2(process, "Cannot start rtsp process");
+
+ auto processCloser = qScopeGuard([&process]() { process->close(); });
+
+ TestVideoSink surface(false);
QMediaPlayer player;
- player.setNotifyInterval(50);
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ QSignalSpy errorSpy(&player, &QMediaPlayer::errorOccurred);
- player.setMedia(localWavFile);
+ player.setVideoSink(&surface);
+ // Ignore audio output to check timings accuratelly
+ // player.setAudioOutput(&output);
+
+ player.setSource(streamUrl);
player.play();
- player.setPosition(900);
- //wait up to 5 seconds for EOS
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
+ const auto colors = { qRgb(0, 0, 0xFF), qRgb(0xFF, 0, 0), qRgb(0, 0xFE, 0) };
+ const auto colorInterval = 5000;
- //at EOS the position stays at the end of file
- QCOMPARE(player.position(), player.duration());
- QVERIFY(positionSpy.count() > 0);
- QCOMPARE(positionSpy.last()[0].value<qint64>(), player.duration());
+ for (auto pos : { colorInterval / 2, colorInterval + 100 }) {
+ qDebug() << "Waiting for position:" << pos;
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ QTRY_COMPARE_GT(player.position(), pos);
+
+ auto frame1 = surface.waitForFrame();
+ QVERIFY(frame1.isValid());
+ QCOMPARE(frame1.size(), QSize(213, 120));
+
+ QCOMPARE_GT(frame1.startTime(), pos * 1000);
+
+ auto frameTime = frame1.startTime();
+ const auto coloIndex = frameTime / (colorInterval * 1000);
+ QCOMPARE_LT(coloIndex, 2);
+
+ const auto image1 = frame1.toImage();
+ QVERIFY(!image1.isNull());
+ QCOMPARE(findSimilarColorIndex(colors, image1.pixel(1, 1)), coloIndex);
+ QCOMPARE(findSimilarColorIndex(colors, image1.pixel(100, 100)), coloIndex);
+
+ auto frame2 = surface.waitForFrame();
+ QVERIFY(frame2.isValid());
+ QCOMPARE_GT(frame2.startTime(), frame1.startTime());
+ }
+
+ player.stop();
+
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(errorSpy.size(), 0);
+#endif //QT_CONFIG(process)
+}
+
+void tst_QMediaPlayerBackend::play_waitsForLastFrameEnd_whenPlayingVideoWithLongFrames()
+{
+#ifdef Q_OS_ANDROID
+ QSKIP("SKIP initTestCase on CI, because of QTBUG-126428");
+#endif
+ if (isCI() && isGStreamerPlatform())
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer");
+
+ CHECK_SELECTED_URL(m_oneRedFrameVideo);
+
+ m_fixture->surface.setStoreFrames(true);
+
+ m_fixture->player.setSource(*m_oneRedFrameVideo);
+ m_fixture->player.play();
+
+ QTRY_COMPARE_GT(m_fixture->surface.m_totalFrames, 0);
+ QVERIFY(m_fixture->surface.m_frameList.front().isValid());
+
+ QElapsedTimer timer;
+ timer.start();
+
+ QTRY_COMPARE_GT(m_fixture->surface.m_totalFrames, 1);
+ const auto elapsed = timer.elapsed();
+
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124005: GStreamer timing seems to be off
+
+ // 1000 is expected
+ QCOMPARE_GT(elapsed, 850);
+ QCOMPARE_LT(elapsed, 1400);
+ }
+
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->surface.m_totalFrames, 2);
+ QVERIFY(!m_fixture->surface.m_frameList.back().isValid());
+}
+
+void tst_QMediaPlayerBackend::play_startsPlayback_withAndWithoutOutputsConnected()
+{
+ QFETCH(const bool, audioConnected);
+ QFETCH(const bool, videoConnected);
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ if (!videoConnected && !audioConnected)
+ QSKIP_FFMPEG("FFMPEG backend playback fails when no output is connected");
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ if (!audioConnected)
+ m_fixture->player.setAudioOutput(nullptr);
+
+ if (!videoConnected)
+ m_fixture->player.setVideoOutput(nullptr);
+
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.play();
+
+ // Assert
+ QTRY_VERIFY(!m_fixture->mediaStatusChanged.empty()
+ && m_fixture->mediaStatusChanged.back()
+ == QList<QVariant>{ QMediaPlayer::EndOfMedia });
+
+ QTRY_COMPARE_EQ(m_fixture->playbackStateChanged,
+ SignalList({
+ { QMediaPlayer::PlayingState },
+ { QMediaPlayer::StoppedState },
+ }));
+}
+
+void tst_QMediaPlayerBackend::play_startsPlayback_withAndWithoutOutputsConnected_data()
+{
+ QTest::addColumn<bool>("videoConnected");
+ QTest::addColumn<bool>("audioConnected");
+
+ QTest::addRow("all connected") << true << true;
+ QTest::addRow("video connected") << true << false;
+ QTest::addRow("audio connected") << false << true;
+ QTest::addRow("no output connected") << false << false;
+}
+
+void tst_QMediaPlayerBackend::play_playsRtpStream_whenSdpFileIsLoaded()
+{
+#if !QT_CONFIG(process)
+ QSKIP("This test requires QProcess support");
+#else
+ if (!isFFMPEGPlatform())
+ QSKIP("This test is only for FFmpeg backend");
+
+ // Create stream
+ if (!canCreateRtpStream())
+ QSKIP("Rtp stream cannot be created");
+
+ auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4");
+ QVERIFY(temporaryFile);
+
+ // Pass a "file:" URL to VLC in order to generate an .sdp file
+ const QUrl sdpUrl = QUrl::fromLocalFile(QFileInfo("test.sdp").absoluteFilePath());
+
+ auto process = createRtpStreamProcess(temporaryFile->fileName(), sdpUrl.toString());
+ QVERIFY2(process, "Cannot start rtp process");
+
+ // Set reasonable protocol whitelist that includes rtp and udp
+ qputenv("QT_FFMPEG_PROTOCOL_WHITELIST", "file,crypto,data,rtp,udp");
+
+ auto processCloser = qScopeGuard([&process, &sdpUrl]() {
+ // End stream
+ process->close();
+
+ // Remove .sdp file created by VLC
+ QFile(sdpUrl.toLocalFile()).remove();
+
+ // Unset environment variable
+ qunsetenv("QT_FFMPEG_PROTOCOL_WHITELIST");
+ });
+
+ m_fixture->player.setSource(sdpUrl);
+
+ // Play
+ m_fixture->player.play();
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+#endif // QT_CONFIG(process)
+}
+
+void tst_QMediaPlayerBackend::play_succeedsFromSourceDevice()
+{
+ QFETCH(const MaybeUrl, mediaUrl);
+ QFETCH(bool, streamOutlivesPlayer);
+
+ CHECK_SELECTED_URL(mediaUrl);
+
+ auto *stream = new QFile(u":"_s + mediaUrl->path());
+
+ QVERIFY(stream->open(QFile::ReadOnly));
+
+ QMediaPlayer &player = m_fixture->player;
+
+ player.setSourceDevice(stream);
player.play();
+ QTRY_COMPARE_GT(player.position(), 100);
- //position is reset to start
- QTRY_VERIFY(player.position() < 100);
- QTRY_VERIFY(positionSpy.count() > 0);
- QCOMPARE(positionSpy.first()[0].value<qint64>(), 0);
+ if (streamOutlivesPlayer)
+ stream->setParent(&player);
+ else
+ delete stream;
+}
+
+void tst_QMediaPlayerBackend::play_succeedsFromSourceDevice_data()
+{
+ QTest::addColumn<MaybeUrl>("mediaUrl");
+ QTest::addColumn<bool>("streamOutlivesPlayer");
+
+ QTest::addRow("audio file") << m_localWavFile << true;
+ QTest::addRow("video file") << m_localVideoFile << true;
+
+ // QMediaPlayer crashes when we delete the stream during playback
+ constexpr bool validateStreamDestructionDuringPlayback = false;
+ if constexpr (validateStreamDestructionDuringPlayback) {
+ QTest::addRow("audio file, stream destroyed during playback") << m_localWavFile << false;
+ QTest::addRow("video file, stream destroyed during playback") << m_localVideoFile << false;
+ }
+}
+
+void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused()
+{
+ QFETCH(const MaybeUrl, mediaUrl);
+
+ CHECK_SELECTED_URL(mediaUrl);
+ QMediaPlayer &player = m_fixture->player;
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
+ // Arrange
+ player.setSource(*mediaUrl);
+ player.play();
+ QTRY_COMPARE_GT(player.position(), 100);
+ player.pause();
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ m_fixture->clearSpies();
+
+ if (!isGStreamerPlatform()) // Gstreamer may see EOS already
+ QCOMPARE_GT(player.position(), 100);
+
+ // Act
+ player.stop();
+
+ // Assert
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::StoppedState } }));
+ // it's allowed to emit statusChanged() signal async
+ QTRY_COMPARE(m_fixture->mediaStatusChanged, SignalList({ { QMediaPlayer::LoadedMedia } }));
+
+ if (isGStreamerPlatform() && *mediaUrl == *m_localWavFile) {
+ // QTBUG-124517: for some media types gstreamer does not emit buffer progress messages
+ } else {
+ QCOMPARE(m_fixture->bufferProgressChanged, SignalList({ { 0.f } }));
+ }
+
+ QTRY_COMPARE(m_fixture->player.position(), qint64(0));
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PlayingState);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
+ QTRY_VERIFY(!m_fixture->positionChanged.empty());
+ QCOMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), qint64(0));
+ QVERIFY(player.duration() > 0);
+}
+
+void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused_data()
+{
+ QTest::addColumn<MaybeUrl>("mediaUrl");
+
+ QTest::addRow("audio file") << m_localWavFile;
+ QTest::addRow("video file") << m_localVideoFile;
+}
+
+void tst_QMediaPlayerBackend::stop_setsPositionToZero_afterPlayingToEndOfMedia()
+{
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ // Act
+ m_fixture->player.stop();
+
+ // Assert
+ QCOMPARE(m_fixture->player.position(), qint64(0));
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ m_fixture->player.play();
+
+ if (isGStreamerPlatform())
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer");
+
+ QVERIFY(m_fixture->surface.waitForFrame().isValid());
+}
+
+
+void tst_QMediaPlayerBackend::playbackRate_returnsOne_byDefault()
+{
+ QCOMPARE_EQ(m_fixture->player.playbackRate(), static_cast<qreal>(1.0f));
+}
+
+void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackRateAndEmitsSignal_data()
+{
+ QTest::addColumn<float>("initialPlaybackRate");
+ QTest::addColumn<float>("targetPlaybackRate");
+ QTest::addColumn<float>("expectedPlaybackRate");
+ QTest::addColumn<bool>("signalExpected");
+
+ QTest::addRow("Increase") << 1.0f << 2.0f << 2.0f << true;
+ QTest::addRow("Decrease") << 1.0f << 0.5f << 0.5f << true;
+ QTest::addRow("Keep") << 0.5f << 0.5f << 0.5f << false;
+
+ bool backendSupportsNegativePlayback =
+ isWindowsPlatform() || isDarwinPlatform() || isGStreamerPlatform();
+
+ if (backendSupportsNegativePlayback) {
+ QTest::addRow("DecreaseBelowZero") << 0.5f << -0.5f << -0.5f << true;
+ QTest::addRow("KeepDecreasingBelowZero") << -0.5f << -0.6f << -0.6f << true;
+ } else {
+ QTest::addRow("DecreaseBelowZero") << 0.5f << -0.5f << 0.0f << true;
+ QTest::addRow("KeepDecreasingBelowZero") << -0.5f << -0.6f << 0.0f << false;
+ }
+}
+
+void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackRateAndEmitsSignal()
+{
+ QFETCH(const float, initialPlaybackRate);
+ QFETCH(const float, targetPlaybackRate);
+ QFETCH(const float, expectedPlaybackRate);
+ QFETCH(const bool, signalExpected);
+
+ // Arrange
+ m_fixture->player.setPlaybackRate(initialPlaybackRate);
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.setPlaybackRate(targetPlaybackRate);
+
+ // Assert
+ if (signalExpected)
+ QCOMPARE_EQ(m_fixture->playbackRateChanged, SignalList({ { expectedPlaybackRate } }));
+ else
+ QVERIFY(m_fixture->playbackRateChanged.empty());
+
+ QCOMPARE_EQ(m_fixture->player.playbackRate(), expectedPlaybackRate);
+}
+
+void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackDuration()
+{
+ using namespace std::chrono;
+ using namespace std::chrono_literals;
+
+ CHECK_SELECTED_URL(m_15sVideo);
+
+ // speeding up a 15s file by 3 should result in a duration of 5s
+ // auto minDuration = 3s;
+ // auto maxDuration = 7s;
+ // auto playbackRate = 3.0;
+
+ // speeding up a 15s file by 5 should result in a duration of 3s
+ auto minDuration = 2s;
+ auto maxDuration = 4s;
+ auto playbackRate = 5.0;
+
+ QFETCH(const QLatin1String, testMode);
+
+ QMediaPlayer &player = m_fixture->player;
+
+ if (testMode == "SetRateBeforeSetSource"_L1)
+ player.setPlaybackRate(playbackRate);
+
+ player.setSource(*m_15sVideo);
+
+ QTRY_COMPARE_EQ(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ auto begin = steady_clock::now();
+
+ if (testMode == "SetRateBeforePlay"_L1) {
+ QSKIP_GSTREAMER("FIXME: SetRateBeforeSetSource is currently broken");
+ player.setPlaybackRate(playbackRate);
+ }
+
+ player.play();
+
+ if (testMode == "SetRateAfterPlay"_L1)
+ player.setPlaybackRate(playbackRate);
+
+ if (testMode == "SetRateAfterPlaybackStarted"_L1) {
+ QTRY_COMPARE_GT(player.position(), 50);
+ player.setPlaybackRate(playbackRate);
+ }
+
+ QCOMPARE(player.playbackRate(), playbackRate);
+
+ QTRY_COMPARE_EQ_WITH_TIMEOUT(player.playbackState(), QMediaPlayer::StoppedState, 20s);
+
+ auto end = steady_clock::now();
+ auto duration = end - begin;
+
+ if (false)
+ qDebug() << round<milliseconds>(duration);
+
+ QCOMPARE_LT(duration, maxDuration);
+ QCOMPARE_GT(duration, minDuration);
+}
+
+void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackDuration_data()
+{
+ QTest::addColumn<QLatin1String>("testMode");
+
+ QTest::addRow("SetRateBeforeSetSource") << "SetRateBeforeSetSource"_L1;
+ QTest::addRow("SetRateBeforePlay") << "SetRateBeforePlay"_L1;
+ QTest::addRow("SetRateAfterPlay") << "SetRateAfterPlay"_L1;
+ QTest::addRow("SetRateAfterPlaybackStarted") << "SetRateAfterPlaybackStarted"_L1;
+}
+
+void tst_QMediaPlayerBackend::setVolume_changesVolume_whenVolumeIsInRange()
+{
+ m_fixture->output.setVolume(0.0f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.0f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f } }));
+
+ m_fixture->output.setVolume(0.5f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.5f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f }, { 0.5f } }));
+
+ m_fixture->output.setVolume(1.0f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 1.0f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f }, { 0.5f }, { 1.0f } }));
+}
+
+void tst_QMediaPlayerBackend::setVolume_clampsToRange_whenVolumeIsOutsideRange()
+{
+ m_fixture->output.setVolume(-0.1f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.0f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f } }));
+
+ m_fixture->output.setVolume(1.1f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 1.0f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f }, { 1.0f } }));
+}
+
+void tst_QMediaPlayerBackend::setVolume_doesNotChangeMutedState()
+{
+ m_fixture->output.setMuted(true);
+ m_fixture->output.setVolume(0.5f);
+ QVERIFY(m_fixture->output.isMuted());
+
+ m_fixture->output.setMuted(false);
+ m_fixture->output.setVolume(0.0f);
+ QVERIFY(!m_fixture->output.isMuted());
+}
+
+void tst_QMediaPlayerBackend::setMuted_changesMutedState_whenMutedStateChanged()
+{
+ m_fixture->output.setMuted(true);
+ QVERIFY(m_fixture->output.isMuted());
+ QCOMPARE(m_fixture->mutedChanged, SignalList({ { true } }));
+
+ // No new events emitted when muted state did not change
+ m_fixture->output.setMuted(true);
+ QCOMPARE(m_fixture->mutedChanged, SignalList({ { true } }));
+
+ m_fixture->output.setMuted(false);
+ QVERIFY(!m_fixture->output.isMuted());
+ QCOMPARE(m_fixture->mutedChanged, SignalList({ { true }, { false } }));
+
+ // No new events emitted when muted state did not change
+ m_fixture->output.setMuted(false);
+ QCOMPARE(m_fixture->mutedChanged, SignalList({ { true }, { false } }));
+}
+
+void tst_QMediaPlayerBackend::setMuted_doesNotChangeVolume()
+{
+ m_fixture->output.setVolume(0.5f);
+
+ m_fixture->output.setMuted(true);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.5f);
+
+ m_fixture->output.setMuted(false);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.5f);
+}
+
+void tst_QMediaPlayerBackend::processEOS()
+{
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failure with gstreamer");
+
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124517: for some media types, including wav files, gstreamer does not emit buffer
+ // progress messages
+ CHECK_SELECTED_URL(m_localWavFile);
+ m_fixture->player.setSource(*m_localWavFile);
+ } else {
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ }
+
+ m_fixture->player.play();
+ m_fixture->player.setPosition(900);
- player.setPosition(900);
//wait up to 5 seconds for EOS
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 2);
+ QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState);
+
+ //at EOS the position stays at the end of file
+ QCOMPARE(m_fixture->player.position(), m_fixture->player.duration());
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QTRY_COMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), m_fixture->player.duration());
- //position stays at the end of file
- QCOMPARE(player.position(), player.duration());
- QVERIFY(positionSpy.count() > 0);
- QCOMPARE(positionSpy.last()[0].value<qint64>(), player.duration());
+ m_fixture->playbackStateChanged.clear();
+ m_fixture->mediaStatusChanged.clear();
+ m_fixture->positionChanged.clear();
+
+ m_fixture->player.play();
+
+ //position is reset to start
+ QTRY_COMPARE_LT(m_fixture->player.position(), 500);
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QCOMPARE(m_fixture->positionChanged.first()[0].value<qint64>(), 0);
+
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 1);
+ QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PlayingState);
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
+
+ m_fixture->positionChanged.clear();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0 && m_fixture->positionChanged.last()[0].value<qint64>() > 100);
+ m_fixture->player.setPosition(900);
+ //wait up to 5 seconds for EOS
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 2);
+ QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState);
+
+ QCOMPARE_GT(m_fixture->bufferProgressChanged.size(), 1);
+ QCOMPARE(m_fixture->bufferProgressChanged.back().front(), 0.f);
+
+ // position stays at the end of file
+ QCOMPARE(m_fixture->player.position(), m_fixture->player.duration());
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QTRY_COMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), m_fixture->player.duration());
//after setPosition EndOfMedia status should be reset to Loaded
- stateSpy.clear();
- statusSpy.clear();
- player.setPosition(500);
+ m_fixture->playbackStateChanged.clear();
+ m_fixture->mediaStatusChanged.clear();
+ m_fixture->player.setPosition(500);
//this transition can be async, so allow backend to perform it
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(stateSpy.count(), 0);
- QTRY_VERIFY(statusSpy.count() > 0 &&
- statusSpy.last()[0].value<QMediaPlayer::MediaStatus>() == QMediaPlayer::LoadedMedia);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 0);
+ QTRY_VERIFY(m_fixture->mediaStatusChanged.size() > 0 &&
+ m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>() == QMediaPlayer::LoadedMedia);
- player.play();
- player.setPosition(900);
+ m_fixture->player.play();
+ m_fixture->player.setPosition(900);
//wait up to 5 seconds for EOS
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.position(), player.duration());
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->player.position(), m_fixture->player.duration());
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ m_fixture->playbackStateChanged.clear();
+ m_fixture->mediaStatusChanged.clear();
+ m_fixture->positionChanged.clear();
// pause() should reset position to beginning and status to Buffered
- player.pause();
+ m_fixture->player.pause();
- QTRY_COMPARE(player.position(), 0);
- QTRY_VERIFY(positionSpy.count() > 0);
- QCOMPARE(positionSpy.first()[0].value<qint64>(), 0);
+ QTRY_COMPARE(m_fixture->player.position(), 0);
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QTRY_COMPARE(m_fixture->positionChanged.first()[0].value<qint64>(), 0);
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PausedState);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PausedState);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 1);
+ QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PausedState);
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
}
// Helper class for tst_QMediaPlayerBackend::deleteLaterAtEOS()
@@ -600,8 +2087,8 @@ private slots:
void onMediaStatusChanged(QMediaPlayer::MediaStatus status)
{
if (status == QMediaPlayer::EndOfMedia) {
- player-> deleteLater();
- player = 0;
+ player->deleteLater();
+ player = nullptr;
}
}
@@ -613,73 +2100,28 @@ private:
// QTBUG-24927 - deleteLater() called to QMediaPlayer from its signal handler does not work as expected
void tst_QMediaPlayerBackend::deleteLaterAtEOS()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
QPointer<QMediaPlayer> player(new QMediaPlayer);
+ QAudioOutput output;
+ player->setAudioOutput(&output);
+ player->setPosition(800); // don't wait as long for EOS
DeleteLaterAtEos deleter(player);
- player->setMedia(localWavFile);
+ player->setSource(*m_localWavFile);
// Create an event loop for verifying deleteLater behavior instead of using
// QTRY_VERIFY or QTest::qWait. QTest::qWait makes extra effort to process
// DeferredDelete events during the wait, which interferes with this test.
QEventLoop loop;
- QTimer::singleShot(0, &deleter, SLOT(play()));
- QTimer::singleShot(5000, &loop, SLOT(quit()));
- connect(player.data(), SIGNAL(destroyed()), &loop, SLOT(quit()));
+ QTimer::singleShot(0, &deleter, &DeleteLaterAtEos::play);
+ QTimer::singleShot(5000, &loop, &QEventLoop::quit);
+ connect(player.data(), &QObject::destroyed, &loop, &QEventLoop::quit);
loop.exec();
// Verify that the player was destroyed within the event loop.
// This check will fail without the fix for QTBUG-24927.
QVERIFY(player.isNull());
}
-void tst_QMediaPlayerBackend::volumeAndMuted()
-{
- //volume and muted properties should be independent
- QMediaPlayer player;
- QVERIFY(player.volume() > 0);
- QVERIFY(!player.isMuted());
-
- player.setMedia(localWavFile);
- player.pause();
-
- QVERIFY(player.volume() > 0);
- QVERIFY(!player.isMuted());
-
- QSignalSpy volumeSpy(&player, SIGNAL(volumeChanged(int)));
- QSignalSpy mutedSpy(&player, SIGNAL(mutedChanged(bool)));
-
- //setting volume to 0 should not trigger muted
- player.setVolume(0);
- QTRY_COMPARE(player.volume(), 0);
- QVERIFY(!player.isMuted());
- QCOMPARE(volumeSpy.count(), 1);
- QCOMPARE(volumeSpy.last()[0].toInt(), player.volume());
- QCOMPARE(mutedSpy.count(), 0);
-
- player.setVolume(50);
- QTRY_COMPARE(player.volume(), 50);
- QVERIFY(!player.isMuted());
- QCOMPARE(volumeSpy.count(), 2);
- QCOMPARE(volumeSpy.last()[0].toInt(), player.volume());
- QCOMPARE(mutedSpy.count(), 0);
-
- player.setMuted(true);
- QTRY_VERIFY(player.isMuted());
- QVERIFY(player.volume() > 0);
- QCOMPARE(volumeSpy.count(), 2);
- QCOMPARE(mutedSpy.count(), 1);
- QCOMPARE(mutedSpy.last()[0].toBool(), player.isMuted());
-
- player.setMuted(false);
- QTRY_VERIFY(!player.isMuted());
- QVERIFY(player.volume() > 0);
- QCOMPARE(volumeSpy.count(), 2);
- QCOMPARE(mutedSpy.count(), 2);
- QCOMPARE(mutedSpy.last()[0].toBool(), player.isMuted());
-
-}
-
void tst_QMediaPlayerBackend::volumeAcrossFiles_data()
{
QTest::addColumn<int>("volume");
@@ -695,166 +2137,173 @@ void tst_QMediaPlayerBackend::volumeAcrossFiles_data()
void tst_QMediaPlayerBackend::volumeAcrossFiles()
{
-#ifdef Q_OS_LINUX
- if (m_inCISystem)
- QSKIP("QTBUG-26577 Fails with gstreamer backend on ubuntu 10.4");
-#endif
+ CHECK_SELECTED_URL(m_localWavFile);
QFETCH(int, volume);
QFETCH(bool, muted);
+ float vol = volume/100.;
+ QAudioOutput output;
QMediaPlayer player;
+ player.setAudioOutput(&output);
+
//volume and muted should not be preserved between player instances
- QVERIFY(player.volume() > 0);
- QVERIFY(!player.isMuted());
+ QVERIFY(output.volume() > 0);
+ QVERIFY(!output.isMuted());
- player.setVolume(volume);
- player.setMuted(muted);
+ output.setVolume(vol);
+ output.setMuted(muted);
- QTRY_COMPARE(player.volume(), volume);
- QTRY_COMPARE(player.isMuted(), muted);
+ QTRY_COMPARE(output.volume(), vol);
+ QTRY_COMPARE(output.isMuted(), muted);
- player.setMedia(localWavFile);
- QCOMPARE(player.volume(), volume);
- QCOMPARE(player.isMuted(), muted);
+ player.setSource(*m_localWavFile);
+ QCOMPARE(output.volume(), vol);
+ QCOMPARE(output.isMuted(), muted);
player.pause();
//to ensure the backend doesn't change volume/muted
//async during file loading.
- QTRY_COMPARE(player.volume(), volume);
- QCOMPARE(player.isMuted(), muted);
+ QTRY_COMPARE(output.volume(), vol);
+ QCOMPARE(output.isMuted(), muted);
- player.setMedia(QMediaContent());
- QTRY_COMPARE(player.volume(), volume);
- QCOMPARE(player.isMuted(), muted);
+ player.setSource(QUrl());
+ QTRY_COMPARE(output.volume(), vol);
+ QCOMPARE(output.isMuted(), muted);
- player.setMedia(localWavFile);
+ player.setSource(*m_localWavFile);
player.pause();
- QTRY_COMPARE(player.volume(), volume);
- QCOMPARE(player.isMuted(), muted);
+ QTRY_COMPARE(output.volume(), vol);
+ QCOMPARE(output.isMuted(), muted);
}
void tst_QMediaPlayerBackend::initialVolume()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
{
+ QAudioOutput output;
QMediaPlayer player;
- player.setVolume(1);
- player.setMedia(localWavFile);
- QCOMPARE(player.volume(), 1);
+ player.setAudioOutput(&output);
+ output.setVolume(1);
+ player.setSource(*m_localWavFile);
+ QCOMPARE(output.volume(), 1);
player.play();
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.volume(), 1);
+ QCOMPARE(output.volume(), 1);
}
{
+ QAudioOutput output;
QMediaPlayer player;
- player.setMedia(localWavFile);
- QCOMPARE(player.volume(), 100);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localWavFile);
+ QCOMPARE(output.volume(), 1);
player.play();
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.volume(), 100);
+ QCOMPARE(output.volume(), 1);
}
}
void tst_QMediaPlayerBackend::seekPauseSeek()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
+ CHECK_SELECTED_URL(m_localVideoFile);
+ TestVideoSink surface(true);
+ QAudioOutput output;
QMediaPlayer player;
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ player.setAudioOutput(&output);
+
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
- TestVideoSurface *surface = new TestVideoSurface;
- player.setVideoOutput(surface);
+ player.setVideoOutput(&surface);
- player.setMedia(localVideoFile);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QVERIFY(surface->m_frameList.isEmpty()); // frame must not appear until we call pause() or play()
+ player.setSource(*m_localVideoFile);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QVERIFY(surface.m_frameList.isEmpty()); // frame must not appear until we call pause() or play()
positionSpy.clear();
qint64 position = 7000;
player.setPosition(position);
- QTRY_VERIFY(!positionSpy.isEmpty() && qAbs(player.position() - position) < (qint64)500);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QTRY_VERIFY(!positionSpy.isEmpty());
+ QTRY_COMPARE(player.position(), position);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
QTest::qWait(250); // wait a bit to ensure the frame is not rendered
- QVERIFY(surface->m_frameList.isEmpty()); // still no frame, we must call pause() or play() to see a frame
+ QVERIFY(surface.m_frameList
+ .isEmpty()); // still no frame, we must call pause() or play() to see a frame
player.pause();
- QTRY_COMPARE(player.state(), QMediaPlayer::PausedState); // it might take some time for the operation to be completed
- QTRY_VERIFY_WITH_TIMEOUT(!surface->m_frameList.isEmpty(), 10000); // we must see a frame at position 7000 here
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PausedState); // it might take some time for the operation to be completed
+ QTRY_VERIFY(!surface.m_frameList.isEmpty()); // we must see a frame at position 7000 here
// Make sure that the frame has a timestamp before testing - not all backends provides this
- if (!surface->m_frameList.back().isValid() || surface->m_frameList.back().startTime() < 0)
+ if (!surface.m_frameList.back().isValid() || surface.m_frameList.back().startTime() < 0)
QSKIP("No timestamp");
{
- QVideoFrame frame = surface->m_frameList.back();
-#if !QT_CONFIG(directshow)
+ QVideoFrame frame = surface.m_frameList.back();
const qint64 elapsed = (frame.startTime() / 1000) - position; // frame.startTime() is microsecond, position is milliseconds.
QVERIFY2(qAbs(elapsed) < (qint64)500, QByteArray::number(elapsed).constData());
-#endif
- QCOMPARE(frame.width(), 160);
+ QCOMPARE(frame.width(), 213);
QCOMPARE(frame.height(), 120);
// create QImage for QVideoFrame to verify RGB pixel colors
- QVERIFY(frame.map(QAbstractVideoBuffer::ReadOnly));
- QImage image(frame.bits(), frame.width(), frame.height(), QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
+ QImage image = frame.toImage();
QVERIFY(!image.isNull());
QVERIFY(qRed(image.pixel(0, 0)) >= 230); // conversion from YUV => RGB, that's why it's not 255
QVERIFY(qGreen(image.pixel(0, 0)) < 20);
QVERIFY(qBlue(image.pixel(0, 0)) < 20);
- frame.unmap();
}
- surface->m_frameList.clear();
+ surface.m_frameList.clear();
positionSpy.clear();
position = 12000;
player.setPosition(position);
QTRY_VERIFY(!positionSpy.isEmpty() && qAbs(player.position() - position) < (qint64)500);
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
- QVERIFY(!surface->m_frameList.isEmpty());
+ QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
+ QTRY_VERIFY(!surface.m_frameList.isEmpty());
{
- QVideoFrame frame = surface->m_frameList.back();
-#if !QT_CONFIG(directshow)
+ QVideoFrame frame = surface.m_frameList.back();
const qint64 elapsed = (frame.startTime() / 1000) - position;
QVERIFY2(qAbs(elapsed) < (qint64)500, QByteArray::number(elapsed).constData());
-#endif
- QCOMPARE(frame.width(), 160);
+ QCOMPARE(frame.width(), 213);
QCOMPARE(frame.height(), 120);
- QVERIFY(frame.map(QAbstractVideoBuffer::ReadOnly));
- QImage image(frame.bits(), frame.width(), frame.height(), QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
+ QImage image = frame.toImage();
QVERIFY(!image.isNull());
QVERIFY(qRed(image.pixel(0, 0)) < 20);
QVERIFY(qGreen(image.pixel(0, 0)) >= 230);
QVERIFY(qBlue(image.pixel(0, 0)) < 20);
- frame.unmap();
}
}
void tst_QMediaPlayerBackend::seekInStoppedState()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ CHECK_SELECTED_URL(m_localVideoFile);
+ TestVideoSink surface(false);
+ QAudioOutput output;
QMediaPlayer player;
- player.setNotifyInterval(500);
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ player.setAudioOutput(&output);
+ player.setVideoOutput(&surface);
+
+ QSignalSpy stateSpy(&player, &QMediaPlayer::playbackStateChanged);
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
- player.setMedia(localVideoFile);
+ player.setSource(*m_localVideoFile);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
QCOMPARE(player.position(), 0);
QVERIFY(player.isSeekable());
@@ -864,12 +2313,12 @@ void tst_QMediaPlayerBackend::seekInStoppedState()
qint64 position = 5000;
player.setPosition(position);
- QTRY_VERIFY(qAbs(player.position() - position) < qint64(500));
- QCOMPARE(positionSpy.count(), 1);
- QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(500));
+ QTRY_VERIFY(qAbs(player.position() - position) < qint64(200));
+ QTRY_VERIFY(positionSpy.size() > 0);
+ QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200));
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 0);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(stateSpy.size(), 0);
QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
@@ -877,35 +2326,34 @@ void tst_QMediaPlayerBackend::seekInStoppedState()
player.play();
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QVERIFY(qAbs(player.position() - position) < qint64(500));
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(player.position() > position);
- QTest::qWait(2000);
+ QTest::qWait(100);
// Check that it never played from the beginning
- QVERIFY(player.position() > (position - 500));
- for (int i = 0; i < positionSpy.count(); ++i)
- QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 500));
+ QVERIFY(player.position() > position);
+ for (int i = 0; i < positionSpy.size(); ++i)
+ QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200));
// ------
// Same tests but after play() --> stop()
player.stop();
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(player.position(), 0);
+ QTRY_COMPARE(player.position(), 0);
stateSpy.clear();
positionSpy.clear();
player.setPosition(position);
- QTRY_VERIFY(qAbs(player.position() - position) < qint64(500));
- QCOMPARE(positionSpy.count(), 1);
- QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(500));
+ QTRY_VERIFY(qAbs(player.position() - position) < qint64(200));
+ QTRY_VERIFY(positionSpy.size() > 0);
+ QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200));
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 0);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(stateSpy.size(), 0);
QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
@@ -913,560 +2361,562 @@ void tst_QMediaPlayerBackend::seekInStoppedState()
player.play();
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QVERIFY(qAbs(player.position() - position) < qint64(500));
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QVERIFY(qAbs(player.position() - position) < qint64(200));
- QTest::qWait(2000);
+ QTest::qWait(500);
// Check that it never played from the beginning
- QVERIFY(player.position() > (position - 500));
- for (int i = 0; i < positionSpy.count(); ++i)
- QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 500));
+ QVERIFY(player.position() > (position - 200));
+ for (int i = 0; i < positionSpy.size(); ++i)
+ QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200));
// ------
// Same tests but after reaching the end of the media
- player.setPosition(player.duration() - 500);
+ player.setPosition(player.duration() - 100);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.position(), player.duration());
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QVERIFY(qAbs(player.position() - player.duration()) < 10);
stateSpy.clear();
positionSpy.clear();
player.setPosition(position);
- QTRY_VERIFY(qAbs(player.position() - position) < qint64(500));
- QCOMPARE(positionSpy.count(), 1);
- QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(500));
-
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 0);
+ QTRY_VERIFY(qAbs(player.position() - position) < qint64(200));
+ QTRY_VERIFY(positionSpy.size() > 0);
+ QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200));
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(stateSpy.size(), 0);
QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- positionSpy.clear();
-
player.play();
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || player.mediaStatus() == QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QVERIFY(qAbs(player.position() - position) < qint64(500));
+ positionSpy.clear();
+ QTRY_VERIFY(player.position() > (position - 200));
- QTest::qWait(2000);
+ QTest::qWait(500);
// Check that it never played from the beginning
- QVERIFY(player.position() > (position - 500));
- for (int i = 0; i < positionSpy.count(); ++i)
- QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 500));
+ QVERIFY(player.position() > (position - 200));
+ for (int i = 0; i < positionSpy.size(); ++i)
+ QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200));
}
void tst_QMediaPlayerBackend::subsequentPlayback()
{
-#ifdef Q_OS_LINUX
- if (m_inCISystem)
- QSKIP("QTBUG-26769 Fails with gstreamer backend on ubuntu 10.4, setPosition(0)");
-#endif
+ QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures with gstreamer");
- if (localCompressedSoundFile.isNull())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localCompressedSoundFile);
+ QAudioOutput output;
QMediaPlayer player;
- player.setMedia(localCompressedSoundFile);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localCompressedSoundFile);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QTRY_VERIFY(player.isSeekable());
+ player.setPosition(5000);
player.play();
QCOMPARE(player.error(), QMediaPlayer::NoError);
- QTRY_COMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::EndOfMedia, 15000);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::EndOfMedia, 10s);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
// Could differ by up to 1 compressed frame length
QVERIFY(qAbs(player.position() - player.duration()) < 100);
QVERIFY(player.position() > 0);
player.play();
- QTRY_COMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_VERIFY_WITH_TIMEOUT(player.position() > 2000 && player.position() < 5000, 10000);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_COMPARE_GT(player.position(), 1000);
player.pause();
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
// make sure position does not "jump" closer to the end of the file
- QVERIFY(player.position() > 2000 && player.position() < 5000);
+ QVERIFY(player.position() > 1000);
// try to seek back to zero
player.setPosition(0);
QTRY_COMPARE(player.position(), qint64(0));
player.play();
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_VERIFY_WITH_TIMEOUT(player.position() > 2000 && player.position() < 5000, 10000);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_COMPARE_GT(player.position(), 1000);
player.pause();
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
- QVERIFY(player.position() > 2000 && player.position() < 5000);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
+ QCOMPARE_GT(player.position(), 1000);
}
-void tst_QMediaPlayerBackend::probes()
+void tst_QMediaPlayerBackend::multipleMediaPlayback()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ CHECK_SELECTED_URL(m_localVideoFile);
+ CHECK_SELECTED_URL(m_localVideoFile2);
- QMediaPlayer *player = new QMediaPlayer;
+ QAudioOutput output;
+ TestVideoSink surface(false);
+ QMediaPlayer player;
- TestVideoSurface *surface = new TestVideoSurface;
- player->setVideoOutput(surface);
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localVideoFile);
- QVideoProbe *videoProbe = new QVideoProbe;
- QAudioProbe *audioProbe = new QAudioProbe;
+ QCOMPARE(player.source(), *m_localVideoFile);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- ProbeDataHandler probeHandler;
- connect(videoProbe, SIGNAL(videoFrameProbed(QVideoFrame)), &probeHandler, SLOT(processFrame(QVideoFrame)));
- connect(videoProbe, SIGNAL(flush()), &probeHandler, SLOT(flushVideo()));
- connect(audioProbe, SIGNAL(audioBufferProbed(QAudioBuffer)), &probeHandler, SLOT(processBuffer(QAudioBuffer)));
- connect(audioProbe, SIGNAL(flush()), &probeHandler, SLOT(flushAudio()));
+ player.setPosition(0);
+ player.play();
- if (!videoProbe->setSource(player))
- QSKIP("QVideoProbe is not supported");
- audioProbe->setSource(player);
+ QCOMPARE(player.error(), QMediaPlayer::NoError);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QVERIFY(player.isSeekable());
+ QTRY_VERIFY(player.position() > 0);
+ QCOMPARE(player.source(), *m_localVideoFile);
- player->setMedia(localVideoFile);
- QTRY_COMPARE(player->mediaStatus(), QMediaPlayer::LoadedMedia);
+ player.stop();
+
+ player.setSource(*m_localVideoFile2);
- player->pause();
- QTRY_COMPARE(surface->m_frameList.size(), 1);
- QVERIFY(!probeHandler.m_frameList.isEmpty());
- QTRY_VERIFY(!probeHandler.m_bufferList.isEmpty());
+ QCOMPARE(player.source(), *m_localVideoFile2);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QTRY_VERIFY(player.isSeekable());
- delete player;
- QTRY_VERIFY(probeHandler.isVideoFlushCalled);
- delete videoProbe;
- delete audioProbe;
+ player.setPosition(0);
+ player.play();
+
+ QCOMPARE(player.error(), QMediaPlayer::NoError);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(player.position() > 0);
+ QCOMPARE(player.source(), *m_localVideoFile2);
+
+ player.stop();
+
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState);
}
-void tst_QMediaPlayerBackend::playlist()
+void tst_QMediaPlayerBackend::multiplePlaybackRateChangingStressTest()
{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ if (isCI()) {
+ if (isDarwinPlatform())
+ QSKIP("SKIP on macOS CI since multiple fake drawing on macOS CI platform causes UB. To "
+ "be investigated.");
+
+ if (isGStreamerPlatform())
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer");
+ }
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
QMediaPlayer player;
- QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent)));
- QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent)));
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy mediaStatusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ player.setAudioOutput(&output);
+ player.setVideoOutput(&surface);
- QFileInfo fileInfo(QFINDTESTDATA("testdata/sample.m3u"));
- player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ player.setSource(*m_localVideoFile3ColorsWithSound);
player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
-
- if (player.mediaStatus() == QMediaPlayer::InvalidMedia || mediaSpy.count() == 1)
- QSKIP("QMediaPlayer does not support loading M3U playlists as QMediaPlaylist");
-
- QCOMPARE(mediaSpy.count(), 2);
- // sample.m3u -> sample.m3u resolved -> test.wav ->
- // nested1.m3u -> nested1.m3u resolved -> test.wav ->
- // nested2.m3u -> nested2.m3u resolved ->
- // test.wav -> _test.wav
- // currentMediaChanged signals not emmitted for
- // nested1.m3u\_test.wav and nested2.m3u\_test.wav
- // because current media stays the same
- QCOMPARE(currentMediaSpy.count(), 11);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 19); // 6 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
- QCOMPARE(mediaSpy.count(), 0);
- QCOMPARE(currentMediaSpy.count(), 8);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 19); // 6 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ surface.waitForFrame();
- // <<< Invalid - 1st pass >>>
- fileInfo.setFile(QFINDTESTDATA("testdata/invalid_media.m3u"));
- player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ QSignalSpy spy(&player, &QMediaPlayer::playbackStateChanged);
- player.play();
- QTRY_COMPARE(player.state(), QMediaPlayer::StoppedState);
- // playlist -> resolved playlist
- QCOMPARE(mediaSpy.count(), 2);
- // playlist -> resolved playlist -> invalid -> ""
- QCOMPARE(currentMediaSpy.count(), 4);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 3); // LoadingMedia -> InvalidMedia -> NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ using namespace std::chrono_literals;
+ using namespace std::chrono;
- // <<< Invalid - 2nd pass >>>
- player.play();
- QTRY_COMPARE(player.state(), QMediaPlayer::StoppedState);
- // media is not changed
- QCOMPARE(mediaSpy.count(), 0);
- // resolved playlist -> invalid -> ""
- QCOMPARE(currentMediaSpy.count(), 3);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 3); // LoadingMedia -> InvalidMedia -> NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ constexpr milliseconds expectedVideoDuration = 3000ms;
+ constexpr milliseconds waitingInterval = 200ms;
+ constexpr milliseconds maxDuration = expectedVideoDuration + 2000ms;
+ constexpr milliseconds minDuration = expectedVideoDuration - 100ms;
+ constexpr milliseconds maxFrameDelay = 2000ms;
- // <<< Invalid2 - 1st pass >>>
- fileInfo.setFile(QFINDTESTDATA("/testdata/invalid_media2.m3u"));
- player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ surface.m_elapsedTimer.start();
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 20000);
- // playlist -> resolved playlist
- QCOMPARE(mediaSpy.count(), 2);
- // playlist -> resolved playlist -> test.wav -> invalid -> test.wav -> ""
- QCOMPARE(currentMediaSpy.count(), 6);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 9); // 3 x LoadingMedia + 2 x (BufferedMedia -> EndOfMedia) + InvalidMedia + NoMedia (not in this order)
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ nanoseconds duration = 0ns;
- // <<< Invalid2 - 2nd pass >>>
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 20000);
- // playlist -> resolved playlist
- QCOMPARE(mediaSpy.count(), 0);
- // playlist -> test.wav -> invalid -> test.wav -> ""
- QCOMPARE(currentMediaSpy.count(), 5);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 9); // 3 x LoadingMedia + 2 x (BufferedMedia -> EndOfMedia) + InvalidMedia + NoMedia (not in this order)
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ auto waitForPlaybackStateChange = [&]() {
+ QElapsedTimer timer;
+ timer.start();
- // <<< Recursive - 1st pass >>>
- fileInfo.setFile(QFINDTESTDATA("testdata/recursive_master.m3u"));
- player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ QScopeGuard addDuration([&]() {
+ duration += duration_cast<nanoseconds>(timer.durationElapsed() * player.playbackRate());
+ });
+ return spy.wait(waitingInterval);
+ };
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 20000);
- // master playlist -> resolved master playlist
- QCOMPARE(mediaSpy.count(), 2);
- // master playlist -> resolved master playlist ->
- // recursive playlist -> resolved recursive playlist ->
- // recursive playlist (this URL is already in the chain of playlists, so the playlist is not resolved) ->
- // invalid -> test.wav -> ""
- QCOMPARE(currentMediaSpy.count(), 8);
- QCOMPARE(stateSpy.count(), 2);
- // there is one invalid media in the master playlist
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 6); // LoadingMedia -> InvalidMedia -> LoadingMedia -> BufferedMedia
- // -> EndOfMedia -> NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ for (int i = 0; !waitForPlaybackStateChange(); ++i) {
+ player.setPlaybackRate(0.5 * (i % 4 + 1));
- // <<< Recursive - 2nd pass >>>
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 20000);
- QCOMPARE(mediaSpy.count(), 0);
- // resolved master playlist ->
- // resolved recursive playlist ->
- // recursive playlist (this URL is already in the chain of playlists, so the playlist is not resolved) ->
- // invalid -> test.wav -> ""
- QCOMPARE(currentMediaSpy.count(), 6);
- QCOMPARE(stateSpy.count(), 2);
- // there is one invalid media in the master playlist
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 6); // LoadingMedia -> InvalidMedia -> LoadingMedia -> BufferedMedia
- // -> EndOfMedia -> NoMedia
+ QCOMPARE_LE(duration, maxDuration);
+
+ QVERIFY2(surface.m_elapsedTimer.durationElapsed() < maxFrameDelay,
+ "If the delay is more than 2s, we consider the video playing is hanging.");
+
+ /* Some debug code for windows. Use the code instead of the check above to debug the bug.
+ * https://bugreports.qt.io/browse/QTBUG-105940.
+ * TODO: fix hanging on windows and remove.
+ if ( surface.m_elapsedTimer.elapsed() > maxFrameDelay ) {
+ qDebug() << "pause/play";
+ player.pause();
+ player.play();
+ surface.m_elapsedTimer.restart();
+ spy.clear();
+ }*/
+ }
+
+ QCOMPARE_GT(duration, minDuration);
+
+ QCOMPARE(spy.size(), 1);
+ QCOMPARE(spy.at(0).size(), 1);
+ QCOMPARE(spy.at(0).at(0).value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState);
+
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
}
-void tst_QMediaPlayerBackend::playlistObject()
+void tst_QMediaPlayerBackend::multipleSeekStressTest()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ QSKIP_GSTREAMER("QTBUG-124005: spurious test failures with gstreamer");
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
QMediaPlayer player;
- QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent)));
- QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent)));
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy mediaStatusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ player.setAudioOutput(&output);
+ player.setVideoOutput(&surface);
- // --- empty playlist
- QMediaPlaylist emptyPlaylist;
- player.setPlaylist(&emptyPlaylist);
+ player.setSource(*m_localVideoFile3ColorsWithSound);
player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 1); // Empty media
- QCOMPARE(stateSpy.count(), 0);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 0);
+ auto waitAndCheckFrame = [&](qint64 pos, QString checkInfo) {
+ auto errorPrintingGuard = qScopeGuard([&]() {
+ qDebug() << "Error:" << checkInfo;
+ qDebug() << "Position:" << pos;
+ });
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ auto frame = surface.waitForFrame();
+ QVERIFY(frame.isValid());
- // --- Valid playlist
- QMediaPlaylist playlist;
- playlist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/test.wav")).absoluteFilePath()));
- playlist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/_test.wav")).absoluteFilePath()));
- player.setPlaylist(&playlist);
+ const auto trackTime = pos * 1000;
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+ // in theory, previous frame might be received, in this case we wait for a new one that is
+ // expected to be relevant
+ if (frame.endTime() < trackTime || frame.startTime() > trackTime) {
+ frame = surface.waitForFrame();
+ QVERIFY(frame.isValid());
+ }
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 3); // test.wav -> _test.wav -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 7); // 2 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
+ QCOMPARE_GE(frame.startTime(), trackTime - 200'000);
+ QCOMPARE_LE(frame.endTime(), trackTime + 200'000);
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ auto frameImage = frame.toImage();
+ const auto actualColor = frameImage.pixel(1, 1);
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+ const auto actualColorIndex = findSimilarColorIndex(m_video3Colors, actualColor);
- QCOMPARE(mediaSpy.count(), 0);
- QCOMPARE(currentMediaSpy.count(), 4); // playlist -> test.wav -> _test.wav -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 7); // 2 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
+ const auto expectedColorIndex = pos / 1000;
- player.setPlaylist(nullptr);
+ QCOMPARE(actualColorIndex, expectedColorIndex);
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ errorPrintingGuard.dismiss();
+ };
- // --- Nested playlist
- QMediaPlaylist nestedPlaylist;
- nestedPlaylist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/_test.wav")).absoluteFilePath()));
- nestedPlaylist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/test.wav")).absoluteFilePath()));
- nestedPlaylist.addMedia(&playlist);
- player.setPlaylist(&nestedPlaylist);
+ auto seekAndCheck = [&](qint64 pos) {
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
+ player.setPosition(pos);
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+ QTRY_VERIFY(positionSpy.size() >= 1);
+ int setPosition = positionSpy.first().first().toInt();
+ QCOMPARE_GT(setPosition, pos - 100);
+ QCOMPARE_LT(setPosition, pos + 100);
+ };
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 6); // _test.wav -> test.wav -> nested playlist
- // -> test.wav -> _test.wav -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 13); // 4 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
+ constexpr qint64 posInterval = 10;
- player.setPlaylist(nullptr);
+ {
+ for (qint64 pos = posInterval; pos <= 2200; pos += posInterval)
+ seekAndCheck(pos);
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ waitAndCheckFrame(2200, "emulate fast moving of a seek slider forward");
- // --- playlist with invalid media
- QMediaPlaylist invalidPlaylist;
- invalidPlaylist.addMedia(QUrl("invalid"));
- invalidPlaylist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/test.wav")).absoluteFilePath()));
+ QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ }
- player.setPlaylist(&invalidPlaylist);
+ {
+ for (qint64 pos = 2100; pos >= 800; pos -= posInterval)
+ seekAndCheck(pos);
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+ waitAndCheckFrame(800, "emulate fast moving of a seek slider backward");
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 3); // invalid -> test.wav -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 6); // Loading -> Invalid -> Loading -> Buffered -> EndOfMedia -> NoMedia
+ QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ }
- player.setPlaylist(nullptr);
+ {
+ player.pause();
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ for (qint64 pos = 500; pos <= 1100; pos += posInterval)
+ seekAndCheck(pos);
- // --- playlist with only invalid media
- QMediaPlaylist invalidPlaylist2;
- invalidPlaylist2.addMedia(QUrl("invalid"));
- invalidPlaylist2.addMedia(QUrl("invalid2"));
+ waitAndCheckFrame(1100, "emulate fast moving of a seek slider forward on paused state");
- player.setPlaylist(&invalidPlaylist2);
+ QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
+ }
+}
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderingTime_data()
+{
+ QTest::addColumn<bool>("withAudio");
+ QTest::addColumn<int>("positionDeviationMs");
+
+ QTest::newRow("Without audio") << false << 170;
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 3); // invalid -> invalid2 -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 2);
- QCOMPARE(mediaStatusSpy.count(), 5); // Loading -> Invalid -> Loading -> Invalid -> NoMedia
+ // set greater positionDeviationMs for case with audio due to possible synchronization.
+ QTest::newRow("With audio") << true << 200;
}
-void tst_QMediaPlayerBackend::surfaceTest_data()
+void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderingTime()
{
- QTest::addColumn< QList<QVideoFrame::PixelFormat> >("formatsList");
+ QSKIP_GSTREAMER("QTBUG-124005: timing issues");
- QList<QVideoFrame::PixelFormat> formatsRGB;
- formatsRGB << QVideoFrame::Format_RGB32
- << QVideoFrame::Format_ARGB32
- << QVideoFrame::Format_RGB565
- << QVideoFrame::Format_BGRA32;
+ QFETCH(bool, withAudio);
+ QFETCH(int, positionDeviationMs);
- QList<QVideoFrame::PixelFormat> formatsYUV;
- formatsYUV << QVideoFrame::Format_YUV420P
- << QVideoFrame::Format_YUV422P
- << QVideoFrame::Format_YV12
- << QVideoFrame::Format_UYVY
- << QVideoFrame::Format_YUYV
- << QVideoFrame::Format_NV12
- << QVideoFrame::Format_NV21;
-
- QTest::newRow("RGB formats")
- << formatsRGB;
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
-#if !QT_CONFIG(directshow)
- QTest::newRow("YVU formats")
- << formatsYUV;
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("SKIP on macOS CI since multiple fake drawing on macOS CI platform causes UB. To be "
+ "investigated: QTBUG-111744");
#endif
+ m_fixture->player.setAudioOutput(
+ withAudio ? &m_fixture->output
+ : nullptr); // TODO: mock audio output and check sound by frequency
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
- QTest::newRow("RGB & YUV formats")
- << formatsRGB + formatsYUV;
+ auto checkColorAndPosition = [&](qint64 expectedPosition, QString errorTag) {
+ constexpr qint64 intervalTime = 1000;
+
+ const int colorIndex = expectedPosition / intervalTime;
+ const auto expectedColor = m_video3Colors[colorIndex];
+ const auto actualPosition = m_fixture->player.position();
+
+ auto frame = m_fixture->surface.videoFrame();
+ auto image = frame.toImage();
+ QVERIFY(!image.isNull());
+
+ const auto actualColor = image.pixel(1, 1);
+
+ auto errorPrintingGuard = qScopeGuard([&]() {
+ qDebug() << "Error Tag:" << errorTag;
+ qDebug() << " Actual Color:" << QColor(actualColor)
+ << " Expected Color:" << QColor(expectedColor);
+ qDebug() << " Most probable actual color index:"
+ << findSimilarColorIndex(m_video3Colors, actualColor)
+ << "Expected color index:" << colorIndex;
+ qDebug() << " Actual position:" << actualPosition;
+ qDebug() << " Frame start time:" << frame.startTime();
+ });
+
+ // TODO: investigate why frames sometimes are not delivered in time on windows
+ constexpr qreal maxColorDifference = 0.18;
+ QVERIFY(m_fixture->player.isPlaying());
+ QCOMPARE_LE(colorDifference(actualColor, expectedColor), maxColorDifference);
+ QCOMPARE_GT(actualPosition, expectedPosition - positionDeviationMs);
+ QCOMPARE_LT(actualPosition, expectedPosition + positionDeviationMs);
+
+ const auto framePosition = frame.startTime() / 1000;
+
+ QCOMPARE_GT(framePosition, expectedPosition - positionDeviationMs);
+ QCOMPARE_LT(framePosition, expectedPosition + positionDeviationMs);
+ QCOMPARE_LT(qAbs(framePosition - actualPosition), positionDeviationMs);
+
+ errorPrintingGuard.dismiss();
+ };
+
+ m_fixture->player.play();
+
+ m_fixture->surface.waitForFrame();
+
+ auto waitUntil = [&](qint64 targetPosition) {
+ const auto position = m_fixture->player.position();
+
+ const auto waitingIntervalMs =
+ static_cast<int>((targetPosition - position) / m_fixture->player.playbackRate());
+
+ if (targetPosition > position)
+ QTest::qWait(waitingIntervalMs);
+
+ qDebug() << "Test waiting:" << waitingIntervalMs << "ms, Position:" << position << "=>"
+ << m_fixture->player.position() << "Expected target position:" << targetPosition
+ << "playbackRate:" << m_fixture->player.playbackRate();
+ };
+
+ waitUntil(400);
+ checkColorAndPosition(400, "Check default playback rate");
+
+ m_fixture->player.setPlaybackRate(2.);
+
+ waitUntil(1400);
+ checkColorAndPosition(1400, "Check 2.0 playback rate");
+
+ m_fixture->player.setPlaybackRate(0.5);
+
+ waitUntil(1800);
+ checkColorAndPosition(1800, "Check 0.5 playback rate");
+
+ m_fixture->player.setPlaybackRate(0.321);
+
+ m_fixture->player.stop();
}
void tst_QMediaPlayerBackend::surfaceTest()
{
- // 25 fps video file
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failure, probably asynchronous event delivery");
- QFETCH(QList<QVideoFrame::PixelFormat>, formatsList);
+ CHECK_SELECTED_URL(m_localVideoFile);
+ // 25 fps video file
- TestVideoSurface surface(false);
- surface.setSupportedFormats(formatsList);
+ QAudioOutput output;
+ TestVideoSink surface(false);
QMediaPlayer player;
+ player.setAudioOutput(&output);
player.setVideoOutput(&surface);
- player.setMedia(localVideoFile);
+ player.setSource(*m_localVideoFile);
player.play();
QTRY_VERIFY(player.position() >= 1000);
- if (surface.error() == QAbstractVideoSurface::UnsupportedFormatError)
- QSKIP("None of the pixel formats is supported by the backend");
- QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface.m_totalFrames)));
+ QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QStringLiteral("Expected >= 25, got %1").arg(surface.m_totalFrames)));
}
-void tst_QMediaPlayerBackend::multipleSurfaces()
+void tst_QMediaPlayerBackend::metadata()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ // QTBUG-124380: gstreamer reports CoverArtImage instead of ThumbnailImage
+ QMediaMetaData::Key thumbnailKey =
+ isGStreamerPlatform() ? QMediaMetaData::CoverArtImage : QMediaMetaData::ThumbnailImage;
- QList<QVideoFrame::PixelFormat> formats1;
- formats1 << QVideoFrame::Format_RGB32
- << QVideoFrame::Format_ARGB32;
- QList<QVideoFrame::PixelFormat> formats2;
- formats2 << QVideoFrame::Format_YUV420P
- << QVideoFrame::Format_RGB32;
+ CHECK_SELECTED_URL(m_localFileWithMetadata);
- TestVideoSurface surface1(false);
- surface1.setSupportedFormats(formats1);
- TestVideoSurface surface2(false);
- surface2.setSupportedFormats(formats2);
+ m_fixture->player.setSource(*m_localFileWithMetadata);
- QMediaPlayer player;
- player.setVideoOutput(QList<QAbstractVideoSurface *>() << &surface1 << &surface2);
- player.setMedia(localVideoFile);
- player.play();
- QTRY_VERIFY(player.position() >= 1000);
- QVERIFY2(surface1.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface1.m_totalFrames)));
- QVERIFY2(surface2.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface2.m_totalFrames)));
- QCOMPARE(surface1.m_totalFrames, surface2.m_totalFrames);
+ QTRY_VERIFY(m_fixture->metadataChanged.size() > 0);
+
+ const QMediaMetaData metadata = m_fixture->player.metaData();
+ QCOMPARE(metadata.value(QMediaMetaData::Title).toString(), QStringLiteral("Nokia Tune"));
+ QCOMPARE(metadata.value(QMediaMetaData::ContributingArtist).toString(), QStringLiteral("TestArtist"));
+ QCOMPARE(metadata.value(QMediaMetaData::AlbumTitle).toString(), QStringLiteral("TestAlbum"));
+ QCOMPARE(metadata.value(QMediaMetaData::Duration), QVariant(7704));
+ QVERIFY(!metadata.value(thumbnailKey).value<QImage>().isNull());
+ m_fixture->clearSpies();
+
+ m_fixture->player.setSource(QUrl());
+
+ QCOMPARE(m_fixture->metadataChanged.size(), 1);
+ QVERIFY(m_fixture->player.metaData().isEmpty());
}
-void tst_QMediaPlayerBackend::metadata()
+void tst_QMediaPlayerBackend::metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail_data()
{
- if (localFileWithMetadata.isNull())
- QSKIP("No supported media file");
+ QTest::addColumn<MaybeUrl>("mediaUrl");
+ QTest::addColumn<bool>("hasThumbnail");
+ QTest::addColumn<QSize>("expectedSize");
+ QTest::addColumn<QColor>("expectedColor");
+
+ QTest::addRow("jpeg thumbnail") << m_videoFileWithJpegThumbnail << true << QSize{ 20, 28 } << QColor(35, 177, 77);
+ QTest::addRow("png thumbnail") << m_videoFileWithPngThumbnail << true << QSize{ 20, 28 } << QColor(35, 177, 77);
+ QTest::addRow("no thumbnail") << m_localVideoFile3ColorsWithSound << false << QSize{ 0, 0 } << QColor(0, 0, 0);
+}
- QMediaPlayer player;
+void tst_QMediaPlayerBackend::metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail()
+{
+ // QTBUG-124380: gstreamer reports CoverArtImage instead of ThumbnailImage
+ QMediaMetaData::Key key =
+ isGStreamerPlatform() ? QMediaMetaData::CoverArtImage : QMediaMetaData::ThumbnailImage;
- QSignalSpy metadataAvailableSpy(&player, SIGNAL(metaDataAvailableChanged(bool)));
- QSignalSpy metadataChangedSpy(&player, SIGNAL(metaDataChanged()));
+ // Arrange
+ QFETCH(const MaybeUrl, mediaUrl);
+ QFETCH(const bool, hasThumbnail);
+ QFETCH(const QSize, expectedSize);
+ QFETCH(const QColor, expectedColor);
- player.setMedia(localFileWithMetadata);
+ CHECK_SELECTED_URL(mediaUrl);
- QTRY_VERIFY(player.isMetaDataAvailable());
- QCOMPARE(metadataAvailableSpy.count(), 1);
- QVERIFY(metadataAvailableSpy.last()[0].toBool());
- QVERIFY(metadataChangedSpy.count() > 0);
+ m_fixture->player.setSource(*mediaUrl);
+ QTRY_VERIFY(!m_fixture->metadataChanged.empty());
- QCOMPARE(player.metaData(QMediaMetaData::Title).toString(), QStringLiteral("Nokia Tune"));
- QCOMPARE(player.metaData(QMediaMetaData::ContributingArtist).toString(), QStringLiteral("TestArtist"));
- QCOMPARE(player.metaData(QMediaMetaData::AlbumTitle).toString(), QStringLiteral("TestAlbum"));
+ // Act
+ const QMediaMetaData metadata = m_fixture->player.metaData();
+ const QImage thumbnail = metadata.value(key).value<QImage>();
- metadataAvailableSpy.clear();
- metadataChangedSpy.clear();
+ // Assert
+ QCOMPARE_EQ(!thumbnail.isNull(), hasThumbnail);
+ QCOMPARE_EQ(thumbnail.size(), expectedSize);
- player.setMedia(QMediaContent());
+ if (hasThumbnail) {
+ const QPoint center{ expectedSize.width() / 2, expectedSize.height() / 2 };
+ const auto centerColor = thumbnail.pixelColor(center);
- QVERIFY(!player.isMetaDataAvailable());
- QCOMPARE(metadataAvailableSpy.count(), 1);
- QVERIFY(!metadataAvailableSpy.last()[0].toBool());
- QCOMPARE(metadataChangedSpy.count(), 1);
- QVERIFY(player.availableMetaData().isEmpty());
+ constexpr int maxChannelDiff = 5;
+ QCOMPARE_LT(std::abs(centerColor.red() - expectedColor.red()), maxChannelDiff);
+ QCOMPARE_LT(std::abs(centerColor.green() - expectedColor.green()), maxChannelDiff);
+ QCOMPARE_LT(std::abs(centerColor.blue() - expectedColor.blue()), maxChannelDiff);
+ }
+}
+
+void tst_QMediaPlayerBackend::metadata_returnsMetadataWithHasHdrContent_whenMediaHasHdrContent_data()
+{
+ QTest::addColumn<MaybeUrl>("mediaUrl");
+ QTest::addColumn<bool>("hasHdrContent");
+
+ QTest::addRow("SDR Video") << m_localVideoFile << false;
+ QTest::addRow("HDR Video") << m_hdrVideo << true;
+}
+
+void tst_QMediaPlayerBackend::metadata_returnsMetadataWithHasHdrContent_whenMediaHasHdrContent()
+{
+ QFETCH(const MaybeUrl, mediaUrl);
+ QFETCH(const bool, hasHdrContent);
+
+ if (!isFFMPEGPlatform() && !isDarwinPlatform())
+ QSKIP("This test is only for FFmpeg and Darwin backends");
+
+ m_fixture->player.setSource(*mediaUrl);
+ QTRY_VERIFY(!m_fixture->metadataChanged.empty());
+
+ const QMediaMetaData metadata = m_fixture->player.videoTracks().front();
+ const bool hdrContent = metadata.value(QMediaMetaData::HasHdrContent).value<bool>();
+
+ QCOMPARE_EQ(hasHdrContent, hdrContent);
}
void tst_QMediaPlayerBackend::playerStateAtEOS()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
+ QAudioOutput output;
QMediaPlayer player;
+ player.setAudioOutput(&output);
bool endOfMediaReceived = false;
- connect(&player, &QMediaPlayer::mediaStatusChanged, [&](QMediaPlayer::MediaStatus status) {
+ connect(&player, &QMediaPlayer::mediaStatusChanged,
+ this, [&](QMediaPlayer::MediaStatus status) {
if (status == QMediaPlayer::EndOfMedia) {
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
endOfMediaReceived = true;
}
});
- player.setMedia(localWavFile);
+ player.setSource(*m_localWavFile);
player.play();
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
@@ -1475,89 +2925,1325 @@ void tst_QMediaPlayerBackend::playerStateAtEOS()
void tst_QMediaPlayerBackend::playFromBuffer()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failure, probably asynchronous event delivery");
+
+ CHECK_SELECTED_URL(m_localVideoFile);
- TestVideoSurface surface(false);
+ TestVideoSink surface(false);
QMediaPlayer player;
player.setVideoOutput(&surface);
- QFile file(localVideoFile.request().url().toLocalFile());
- if (!file.open(QIODevice::ReadOnly))
- QSKIP("Could not open file");
- player.setMedia(localVideoFile, &file);
+ QFile file(u":"_s + m_localVideoFile->toEncoded(QUrl::RemoveScheme));
+ QVERIFY(file.open(QIODevice::ReadOnly));
+
+ player.setSourceDevice(&file, *m_localVideoFile);
player.play();
QTRY_VERIFY(player.position() >= 1000);
- if (surface.error() == QAbstractVideoSurface::UnsupportedFormatError)
- QSKIP("None of the pixel formats is supported by the backend");
- QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface.m_totalFrames)));
+ QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QStringLiteral("Expected >= 25, got %1").arg(surface.m_totalFrames)));
}
-TestVideoSurface::TestVideoSurface(bool storeFrames):
- m_totalFrames(0),
- m_storeFrames(storeFrames)
+void tst_QMediaPlayerBackend::audioVideoAvailable()
{
- // set default formats
- m_supported << QVideoFrame::Format_RGB32
- << QVideoFrame::Format_ARGB32
- << QVideoFrame::Format_ARGB32_Premultiplied
- << QVideoFrame::Format_RGB565
- << QVideoFrame::Format_RGB555;
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
+ QMediaPlayer player;
+ QSignalSpy hasVideoSpy(&player, &QMediaPlayer::hasVideoChanged);
+ QSignalSpy hasAudioSpy(&player, &QMediaPlayer::hasAudioChanged);
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localVideoFile);
+ QTRY_VERIFY(player.hasVideo());
+ QTRY_VERIFY(player.hasAudio());
+ QCOMPARE(hasVideoSpy.size(), 1);
+ QCOMPARE(hasAudioSpy.size(), 1);
+ player.setSource(QUrl());
+ QTRY_VERIFY(!player.hasVideo());
+ QTRY_VERIFY(!player.hasAudio());
+ QCOMPARE(hasVideoSpy.size(), 2);
+ QCOMPARE(hasAudioSpy.size(), 2);
}
-QList<QVideoFrame::PixelFormat> TestVideoSurface::supportedPixelFormats(
- QAbstractVideoBuffer::HandleType handleType) const
+void tst_QMediaPlayerBackend::audioVideoAvailable_updatedOnNewMedia()
{
- if (handleType == QAbstractVideoBuffer::NoHandle) {
- return m_supported;
+ CHECK_SELECTED_URL(m_localVideoFile);
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
+ QMediaPlayer player;
+ QSignalSpy hasVideoSpy(&player, &QMediaPlayer::hasVideoChanged);
+ QSignalSpy hasAudioSpy(&player, &QMediaPlayer::hasAudioChanged);
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localVideoFile);
+ QTRY_VERIFY(player.hasVideo());
+ QTRY_VERIFY(player.hasAudio());
+ QCOMPARE(hasVideoSpy.size(), 1);
+ QCOMPARE(hasAudioSpy.size(), 1);
+
+ hasVideoSpy.clear();
+ hasAudioSpy.clear();
+
+ player.setSource(*m_localWavFile);
+
+ auto expectedHasVideoSignals = SignalList{
+ { false },
+ };
+ QTRY_COMPARE(hasVideoSpy, expectedHasVideoSignals);
+
+ if (isGStreamerPlatform()) {
+ // GStreamer unsets hasAudio/hasVideo on new URIs
+ auto expectedHasAudioSignals = SignalList{
+ { false },
+ { true },
+ };
+ QTRY_COMPARE(hasAudioSpy, expectedHasAudioSignals);
} else {
- return QList<QVideoFrame::PixelFormat>();
+ QCOMPARE(hasAudioSpy.size(), 0);
}
}
-bool TestVideoSurface::start(const QVideoSurfaceFormat &format)
+void tst_QMediaPlayerBackend::isSeekable()
{
- if (!isFormatSupported(format)) {
- setError(UnsupportedFormatError);
- return false;
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(false);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(*m_localVideoFile);
+ QTRY_VERIFY(player.isSeekable());
+}
+
+void tst_QMediaPlayerBackend::positionAfterSeek()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(false);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(*m_localVideoFile);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ player.pause();
+ player.setPosition(500);
+ QTRY_VERIFY(player.position() == 500);
+ player.setPosition(700);
+ QVERIFY(player.position() != 0);
+ QTRY_VERIFY(player.position() == 700);
+ player.play();
+ QTRY_VERIFY(player.position() > 700);
+ player.setPosition(200);
+ QVERIFY(player.position() != 0);
+ QTRY_VERIFY(player.position() < 700);
+}
+
+void tst_QMediaPlayerBackend::pause_rendersVideoAtCorrectResolution_data()
+{
+ QTest::addColumn<MaybeUrl>("mediaFile");
+ QTest::addColumn<int>("width");
+ QTest::addColumn<int>("height");
+
+ QTest::addRow("mp4") << m_videoDimensionTestFile << 540 << 320;
+ QTest::addRow("av1") << m_av1File << 160 * 143 / 80 << 160;
+}
+
+void tst_QMediaPlayerBackend::pause_rendersVideoAtCorrectResolution()
+{
+#ifdef Q_OS_ANDROID
+ QSKIP("SKIP initTestCase on CI, because of QTBUG-126428");
+#endif
+ QFETCH(const MaybeUrl, mediaFile);
+ QFETCH(const int, width);
+ QFETCH(const int, height);
+ CHECK_SELECTED_URL(mediaFile);
+
+ // Arrange
+ TestVideoSink surface(true);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(*mediaFile);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Act
+ player.pause();
+
+ if (isCI() && isFFMPEGPlatform())
+ QEXPECT_FAIL("av1", "QTBUG-119711: AV1 decoding requires HW support in the FFMPEG backend",
+ Abort);
+
+ QTRY_COMPARE(surface.m_totalFrames, 1);
+
+ // Assert
+ QCOMPARE(surface.m_frameList.last().width(), width);
+ QCOMPARE(surface.videoSize().width(), width);
+ QCOMPARE(surface.m_frameList.last().height(), height);
+ QCOMPARE(surface.videoSize().height(), height);
+}
+
+void tst_QMediaPlayerBackend::position()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(true);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(*m_localVideoFile);
+ QTRY_VERIFY(player.isSeekable());
+
+ player.play();
+ player.setPosition(1000);
+ QVERIFY(player.position() > 950);
+ QVERIFY(player.position() < 1050);
+ QTRY_VERIFY(player.position() > 1050);
+
+ player.pause();
+ player.setPosition(500);
+ QVERIFY(player.position() > 450);
+ QVERIFY(player.position() < 550);
+ QTest::qWait(200);
+ QVERIFY(player.position() > 450);
+ QVERIFY(player.position() < 550);
+}
+
+void tst_QMediaPlayerBackend::durationDetectionIssues_data()
+{
+ QTest::addColumn<QString>("mediaFile");
+ QTest::addColumn<qint64>("expectedDuration");
+ QTest::addColumn<int>("expectedVideoTrackCount");
+ QTest::addColumn<qint64>("expectedVideoTrackDuration");
+ QTest::addColumn<int>("expectedAudioTrackCount");
+ QTest::addColumn<QVariant>("expectedAudioTrackDuration");
+
+ // clang-format off
+
+ QTest::newRow("stream-duration-in-metadata")
+ << QString{ "qrc:/testdata/duration_issues.webm" }
+ << 400ll // Total media duration
+ << 1 // Number of video tracks in file
+ << 400ll // Video stream duration
+ << 0 // Number of audio tracks in file
+ << QVariant{}; // Audio stream duration (unused)
+
+ QTest::newRow("no-stream-duration-in-metadata")
+ << QString{ "qrc:/testdata/nokia-tune.mkv" }
+ << 7531ll // Total media duration
+ << 0 // Number of video tracks in file
+ << 0ll // Video stream duration (unused)
+ << 1 // Number of audio tracks in file
+ << QVariant{}; // Audio stream duration (not present on file)
+
+ // clang-format on
+}
+
+void tst_QMediaPlayerBackend::durationDetectionIssues()
+{
+ if (isGStreamerPlatform() && isCI())
+ QSKIP("QTBUG-124005: Fails with gstreamer on CI");
+
+ QFETCH(QString, mediaFile);
+ QFETCH(qint64, expectedDuration);
+ QFETCH(int, expectedVideoTrackCount);
+ QFETCH(qint64, expectedVideoTrackDuration);
+ QFETCH(int, expectedAudioTrackCount);
+ QFETCH(QVariant, expectedAudioTrackDuration);
+
+ // ffmpeg detects stream an incorrect stream duration, so we take
+ // the correct duration from the metadata
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
+ QMediaPlayer player;
+
+ QSignalSpy durationSpy(&player, &QMediaPlayer::durationChanged);
+
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&output);
+ player.setSource(mediaFile);
+
+ QTRY_COMPARE_EQ(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Duration event received
+ QCOMPARE(durationSpy.size(), 1);
+ QCOMPARE(durationSpy.front().front(), expectedDuration);
+
+ // Duration property
+ QCOMPARE(player.duration(), expectedDuration);
+ QCOMPARE(player.metaData().value(QMediaMetaData::Duration), expectedDuration);
+
+ // Track duration properties
+ const auto videoTracks = player.videoTracks();
+ QCOMPARE(videoTracks.size(), expectedVideoTrackCount);
+
+ if (expectedVideoTrackCount != 0)
+ QCOMPARE(videoTracks.front().value(QMediaMetaData::Duration), expectedVideoTrackDuration);
+
+ const auto audioTracks = player.audioTracks();
+ QCOMPARE(audioTracks.size(), expectedAudioTrackCount);
+
+ if (expectedAudioTrackCount != 0)
+ QCOMPARE(audioTracks.front().value(QMediaMetaData::Duration), expectedAudioTrackDuration);
+}
+
+struct LoopIteration {
+ qint64 startPos;
+ qint64 endPos;
+ qint64 posCount;
+};
+// Creates a vector of LoopIterations, containing start- and end position
+// and the number of position changes per video loop iteration.
+static std::vector<LoopIteration> loopIterations(const QSignalSpy &positionSpy)
+{
+ std::vector<LoopIteration> result;
+ // Loops through all positions emitted by QMediaPlayer::positionChanged
+ for (auto &params : positionSpy) {
+ const auto pos = params.front().value<qint64>();
+
+ // Adds new LoopIteration struct to result if position is lower than previous position
+ if (result.empty() || pos < result.back().endPos) {
+ result.push_back(LoopIteration{pos, pos, 1});
+ }
+ // Updates end position of the current LoopIteration if position is higher than previous position
+ else {
+ result.back().posCount++;
+ result.back().endPos = pos;
+ }
+ }
+ return result;
+}
+
+void tst_QMediaPlayerBackend::finiteLoops()
+{
+ QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend");
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ QCOMPARE(m_fixture->player.loops(), 1);
+ m_fixture->player.setLoops(3);
+ QCOMPARE(m_fixture->player.loops(), 3);
+
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.setPlaybackRate(5);
+ QCOMPARE(m_fixture->player.loops(), 3);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ // check pause doesn't affect looping
+ {
+ QTest::qWait(static_cast<int>(m_fixture->player.duration() * 3
+ * 0.6 /*relative pos*/ / m_fixture->player.playbackRate()));
+ m_fixture->player.pause();
+ m_fixture->player.play();
+ }
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ // Check for expected number of loop iterations and startPos, endPos and posCount per iteration
+ std::vector<LoopIteration> iterations = loopIterations(m_fixture->positionChanged);
+ QCOMPARE(iterations.size(), 3u);
+ QCOMPARE_GT(iterations[0].startPos, 0);
+ QCOMPARE(iterations[0].endPos, m_fixture->player.duration());
+ QCOMPARE(iterations[1].startPos, 0);
+ QCOMPARE(iterations[1].endPos, m_fixture->player.duration());
+ QCOMPARE(iterations[2].startPos, 0);
+ QCOMPARE(iterations[2].endPos, m_fixture->player.duration());
+ if (isFFMPEGPlatform()) {
+ QCOMPARE_GT(iterations[0].posCount, 10);
+ QCOMPARE_GT(iterations[1].posCount, 10);
+ QCOMPARE_GT(iterations[2].posCount, 10);
}
- return QAbstractVideoSurface::start(format);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+
+ // Check that loop counter is reset when playback is restarted.
+ {
+ m_fixture->positionChanged.clear();
+ m_fixture->player.play();
+ m_fixture->player.setPlaybackRate(10);
+ m_fixture->surface.waitForFrame();
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(loopIterations(m_fixture->positionChanged).size(), 3u);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ }
}
-void TestVideoSurface::stop()
+void tst_QMediaPlayerBackend::infiniteLoops()
{
- QAbstractVideoSurface::stop();
+ QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend");
+
+ CHECK_SELECTED_URL(m_localVideoFile2);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ m_fixture->player.setLoops(QMediaPlayer::Infinite);
+ QCOMPARE(m_fixture->player.loops(), QMediaPlayer::Infinite);
+
+ // select some small file
+ m_fixture->player.setSource(*m_localVideoFile2);
+ m_fixture->player.setPlaybackRate(20);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ for (int i = 0; i < 2; ++i) {
+ m_fixture->positionChanged.clear();
+
+ QTest::qWait(
+ std::max(static_cast<int>(m_fixture->player.duration()
+ / m_fixture->player.playbackRate() * 4),
+ 300 /*ensure some minimum waiting time to reduce threading flakiness*/));
+ QVERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferingMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+
+ const auto iterations = loopIterations(m_fixture->positionChanged);
+ QVERIFY(!iterations.empty());
+ QCOMPARE(iterations.front().endPos, m_fixture->player.duration());
+ }
+
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ m_fixture->player.stop(); // QMediaPlayer::stop stops whether or not looping is infinite
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ QCOMPARE(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::LoadedMedia } }));
}
-bool TestVideoSurface::present(const QVideoFrame &frame)
+void tst_QMediaPlayerBackend::seekOnLoops()
{
- if (m_storeFrames)
- m_frameList.push_back(frame);
- m_totalFrames++;
- return true;
+ QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend");
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ m_fixture->player.setLoops(3);
+ m_fixture->player.setPlaybackRate(2);
+
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ // seek in the 1st loop
+ m_fixture->player.setPosition(m_fixture->player.duration() * 4 / 5);
+
+ // wait for the 2nd loop and seek
+ m_fixture->surface.waitForFrame();
+ QTRY_VERIFY(m_fixture->player.position() < m_fixture->player.duration() / 2);
+ m_fixture->player.setPosition(m_fixture->player.duration() * 8 / 9);
+
+ // wait for the 3rd loop and seek
+ m_fixture->surface.waitForFrame();
+ QTRY_VERIFY(m_fixture->player.position() < m_fixture->player.duration() / 2);
+ m_fixture->player.setPosition(m_fixture->player.duration() * 4 / 5);
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ auto iterations = loopIterations(m_fixture->positionChanged);
+
+ QCOMPARE(iterations.size(), 3u);
+ QCOMPARE_GT(iterations[0].startPos, 0);
+ QCOMPARE(iterations[0].endPos, m_fixture->player.duration());
+ QCOMPARE_GT(iterations[0].posCount, 2);
+ QCOMPARE(iterations[1].startPos, 0);
+ QCOMPARE(iterations[1].endPos, m_fixture->player.duration());
+ QCOMPARE_GT(iterations[1].posCount, 2);
+ QCOMPARE(iterations[2].startPos, 0);
+ QCOMPARE(iterations[2].endPos, m_fixture->player.duration());
+ QCOMPARE_GT(iterations[2].posCount, 2);
+
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
}
+void tst_QMediaPlayerBackend::changeLoopsOnTheFly()
+{
+ QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend");
-void ProbeDataHandler::processFrame(const QVideoFrame &frame)
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ m_fixture->player.setLoops(4);
+ m_fixture->player.setPlaybackRate(5);
+
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ m_fixture->player.setPosition(m_fixture->player.duration() * 4 / 5);
+
+ // wait for the 2nd loop
+ m_fixture->surface.waitForFrame();
+ QTRY_VERIFY(m_fixture->player.position() < m_fixture->player.duration() / 2);
+ m_fixture->player.setPosition(m_fixture->player.duration() * 8 / 9);
+
+ m_fixture->player.setLoops(1);
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+
+ auto iterations = loopIterations(m_fixture->positionChanged);
+ QCOMPARE(iterations.size(), 2u);
+
+ QCOMPARE(iterations[1].startPos, 0);
+ QCOMPARE(iterations[1].endPos, m_fixture->player.duration());
+ QCOMPARE_GT(iterations[1].posCount, 2);
+}
+
+void tst_QMediaPlayerBackend::seekAfterLoopReset()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ m_fixture->surface.setStoreFrames(false);
+
+ m_fixture->player.setLoops(QMediaPlayer::Infinite);
+ m_fixture->player.setPlaybackRate(2);
+
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ // seek in the 1st loop
+ m_fixture->player.setPosition(m_fixture->player.duration() * 4 / 5);
+
+ // wait for the 2nd loop
+ m_fixture->surface.waitForFrame();
+ QTRY_VERIFY(m_fixture->player.position() < m_fixture->player.duration() / 2);
+
+ // reset loops and seek
+ m_fixture->player.setLoops(1);
+ m_fixture->player.setPosition(m_fixture->player.duration() * 8 / 9);
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+}
+
+void tst_QMediaPlayerBackend::changeVideoOutputNoFramesLost()
+{
+ QSKIP_GSTREAMER("QTBUG-124005: gstreamer will lose frames, possibly due to buffering");
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ QVideoSink sinks[4];
+ std::atomic_int framesCount[4] = {
+ 0,
+ };
+ for (int i = 0; i < 4; ++i)
+ setVideoSinkAsyncFramesCounter(sinks[i], framesCount[i]);
+
+ QMediaPlayer player;
+
+ player.setPlaybackRate(10);
+
+ player.setVideoOutput(&sinks[0]);
+ player.setSource(*m_localVideoFile3ColorsWithSound);
+ player.play();
+ QTRY_VERIFY(!player.isPlaying());
+
+ player.setPlaybackRate(4);
+ player.setVideoOutput(&sinks[1]);
+ player.play();
+
+ QTRY_COMPARE_GE(framesCount[1], framesCount[0] / 4);
+ player.setVideoOutput(&sinks[2]);
+ const int savedFrameNumber1 = framesCount[1];
+
+ QTRY_COMPARE_GE(framesCount[2], (framesCount[0] - savedFrameNumber1) / 2);
+ player.setVideoOutput(&sinks[3]);
+ const int savedFrameNumber2 = framesCount[2];
+
+ QTRY_VERIFY(!player.isPlaying());
+
+ // check if no frames sent to old sinks
+ QCOMPARE(framesCount[1], savedFrameNumber1);
+ QCOMPARE(framesCount[2], savedFrameNumber2);
+
+ // no frames lost
+ QCOMPARE(framesCount[1] + framesCount[2] + framesCount[3], framesCount[0]);
+}
+
+void tst_QMediaPlayerBackend::cleanSinkAndNoMoreFramesAfterStop()
+{
+ QSKIP_GSTREAMER(
+ "QTBUG-124005: spurious failures on gstreamer, probably due to asynchronous play()");
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ QVideoSink sink;
+ std::atomic_int framesCount = 0;
+ setVideoSinkAsyncFramesCounter(sink, framesCount);
+ QMediaPlayer player;
+
+ player.setPlaybackRate(10);
+ player.setVideoOutput(&sink);
+
+ player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ // Run a few time to have more chances to detect race conditions
+ for (int i = 0; i < 8; ++i) {
+ player.play();
+ QTRY_VERIFY(framesCount > 0);
+ QVERIFY(sink.videoFrame().isValid());
+
+ player.stop();
+
+ if (isGStreamerPlatform())
+ // QTBUG-124005: stop() is asynchronous in gstreamer
+ QTRY_VERIFY(!sink.videoFrame().isValid());
+ else
+ QVERIFY(!sink.videoFrame().isValid());
+
+ QCOMPARE_NE(framesCount, 0);
+ framesCount = 0;
+
+ QTest::qWait(30);
+
+ if (isGStreamerPlatform())
+ continue; // QTBUG-124005: stop() is asynchronous in gstreamer
+
+ // check if nothing changed after short waiting
+ QCOMPARE(framesCount, 0);
+ }
+}
+
+void tst_QMediaPlayerBackend::lazyLoadVideo()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine);
+ component.loadUrl(QUrl("qrc:/LazyLoad.qml"));
+ QScopedPointer<QObject> root(component.create());
+ QQuickItem *rootItem = qobject_cast<QQuickItem *>(root.get());
+ QVERIFY(rootItem);
+
+ QQuickView view;
+ rootItem->setParentItem(view.contentItem());
+ view.resize(600, 800);
+ view.show();
+
+ QQuickLoader *loader = qobject_cast<QQuickLoader *>(rootItem->findChild<QQuickItem *>("loader"));
+ QVERIFY(loader);
+ QCOMPARE(QQmlProperty::read(loader, "active").toBool(), false);
+ loader->setProperty("active", true);
+ QCOMPARE(QQmlProperty::read(loader, "active").toBool(), true);
+
+ QQuickItem *videoPlayer = qobject_cast<QQuickItem *>(loader->findChild<QQuickItem *>("videoPlayer"));
+ QVERIFY(videoPlayer);
+
+ QTRY_COMPARE_EQ(QQmlProperty::read(videoPlayer, "playbackState").value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PlayingState);
+ QCOMPARE(QQmlProperty::read(videoPlayer, "error").value<QMediaPlayer::Error>(), QMediaPlayer::NoError);
+
+ QVideoSink *videoSink = QQmlProperty::read(videoPlayer, "videoSink").value<QVideoSink *>();
+ QVERIFY(videoSink);
+
+ QSignalSpy spy(videoSink, &QVideoSink::videoFrameChanged);
+ QVERIFY(spy.wait());
+
+ QVideoFrame frame = spy.at(0).at(0).value<QVideoFrame>();
+ QVERIFY(frame.isValid());
+}
+
+void tst_QMediaPlayerBackend::videoSinkSignals()
+{
+#ifdef Q_OS_ANDROID
+ QSKIP("SKIP initTestCase on CI, because of QTBUG-126428");
+#endif
+ std::atomic<int> videoFrameCounter = 0;
+ std::atomic<int> videoSizeCounter = 0;
+
+ // TODO: come up with custom frames source,
+ // create the test target tst_QVideoSinkBackend,
+ // and move the test there
+
+ CHECK_SELECTED_URL(m_localVideoFile2);
+
+ QVideoSink sink;
+ QMediaPlayer player;
+ player.setVideoSink(&sink);
+
+ player.setSource(*m_localVideoFile2);
+
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
+
+ sink.platformVideoSink()->setNativeSize({}); // reset size to be able to check the size update
+
+ connect(&sink, &QVideoSink::videoFrameChanged, this, [&](const QVideoFrame &frame) {
+ QCOMPARE(sink.videoFrame(), frame);
+ QCOMPARE(sink.videoSize(), frame.size());
+ ++videoFrameCounter;
+ }, Qt::DirectConnection);
+
+ connect(&sink, &QVideoSink::videoSizeChanged, this, [&]() {
+ QCOMPARE(sink.videoSize(), sink.videoFrame().size());
+ if (sink.videoSize().isValid()) // filter end frame
+ ++videoSizeCounter;
+ }, Qt::DirectConnection);
+
+ player.play();
+
+ QTRY_COMPARE_GE(videoFrameCounter, 2);
+ QCOMPARE(videoSizeCounter, 1);
+}
+
+void tst_QMediaPlayerBackend::nonAsciiFileName()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ auto temporaryFile =
+ copyResourceToTemporaryFile(":/testdata/test.wav", "äöüØøÆ中文.XXXXXX.wav");
+ QVERIFY(temporaryFile);
+
+ m_fixture->player.setSource(temporaryFile->fileName());
+ m_fixture->player.play();
+
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+}
+
+void tst_QMediaPlayerBackend::setMedia_setsVideoSinkSize_beforePlaying()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ QVideoSink sink1;
+ QVideoSink sink2;
+ QMediaPlayer player;
+
+ QSignalSpy spy1(&sink1, &QVideoSink::videoSizeChanged);
+ QSignalSpy spy2(&sink2, &QVideoSink::videoSizeChanged);
+
+ player.setVideoOutput(&sink1);
+ QCOMPARE(sink1.videoSize(), QSize());
+
+ player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
+
+ QCOMPARE(sink1.videoSize(), QSize(684, 384));
+
+ player.setVideoOutput(&sink2);
+ QCOMPARE(sink2.videoSize(), QSize(684, 384));
+
+ QCOMPARE(spy1.size(), 1);
+ QCOMPARE(spy2.size(), 1);
+}
+
+#if QT_CONFIG(process)
+std::unique_ptr<QProcess> tst_QMediaPlayerBackend::createRtpStreamProcess(QString fileName,
+ QString sdpUrl)
+{
+ Q_ASSERT(!m_vlcCommand.isEmpty());
+
+ auto process = std::make_unique<QProcess>();
+#if defined(Q_OS_WINDOWS)
+ fileName.replace('/', '\\');
+#endif
+
+ QStringList vlcParams = { "-vvv", fileName,
+ "--sout", QStringLiteral("#rtp{dst=localhost,sdp=%1}").arg(sdpUrl),
+ "--intf", "dummy" };
+
+ process->start(m_vlcCommand, vlcParams);
+ if (!process->waitForStarted())
+ return nullptr;
+
+ // rtp stream might be with started some delay after the vlc process starts.
+ // Ideally, we should wait for open connections, it requires some extra work + QNetwork
+ // dependency.
+ int timeout = 500;
+#ifdef Q_OS_MACOS
+ timeout = 2000;
+#endif
+ QTest::qWait(timeout);
+
+ return process;
+}
+#endif //QT_CONFIG(process)
+
+void tst_QMediaPlayerBackend::play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata_data()
+{
+ QTest::addColumn<MaybeUrl>("fileURL");
+ QTest::addColumn<QRgb>("expectedColor");
+ QTest::addColumn<QtVideo::Rotation>("expectedRotationAngle");
+ QTest::addColumn<QSize>("videoSize");
+
+ // clang-format off
+ QTest::addRow("without rotation") << m_colorMatrixVideo
+ << QRgb(0xff0000)
+ << QtVideo::Rotation::None
+ << QSize(960, 540);
+
+ QTest::addRow("90 deg clockwise") << m_colorMatrix90degClockwiseVideo
+ << QRgb(0x0000FF)
+ << QtVideo::Rotation::Clockwise90
+ << QSize(540, 960);
+
+ QTest::addRow("180 deg clockwise") << m_colorMatrix180degClockwiseVideo
+ << QRgb(0xFFFF00)
+ << QtVideo::Rotation::Clockwise180
+ << QSize(960, 540);
+
+ QTest::addRow("270 deg clockwise") << m_colorMatrix270degClockwiseVideo
+ << QRgb(0x00FF00)
+ << QtVideo::Rotation::Clockwise270
+ << QSize(540, 960);
+ // clang-format on
+}
+
+void tst_QMediaPlayerBackend::play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata()
+{
+ if (isGStreamerPlatform() && isCI())
+ QSKIP("QTBUG-124005: Fails with gstreamer on CI");
+
+ // This test uses 4 video files with a 2x2 color matrix consisting of
+ // red (upper left), blue (lower left), yellow (lower right) and green (upper right).
+ // The files are identical, except that three of them contain
+ // orientation (rotation) metadata specifying that they should be
+ // viewed with a 90, 180 and 270 degree clockwise rotation respectively.
+
+ // Fetch path and expected color of upper left area of each file
+ QFETCH(const MaybeUrl, fileURL);
+ QFETCH(const QRgb, expectedColor);
+ QFETCH(const QtVideo::Rotation, expectedRotationAngle);
+ QFETCH(const QSize, videoSize);
+
+ CHECK_SELECTED_URL(fileURL);
+
+ // Load video file
+ m_fixture->player.setSource(*fileURL);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Compare videoSize of the output video sink with the expected value before starting playing
+ QCOMPARE(m_fixture->surface.videoSize(), videoSize);
+
+ // Compare orientation metadata of QMediaPlayer with expected value
+ const auto metaData = m_fixture->player.metaData();
+ const auto playerOrientation = metaData.value(QMediaMetaData::Orientation).value<QtVideo::Rotation>();
+ QCOMPARE(playerOrientation, expectedRotationAngle);
+
+ // Compare orientation metadata of active video stream with expected value
+ const int activeVideoTrack = m_fixture->player.activeVideoTrack();
+ const auto videoTrackMetaData = m_fixture->player.videoTracks().at(activeVideoTrack);
+ const auto videoTrackOrientation = videoTrackMetaData.value(QMediaMetaData::Orientation).value<QtVideo::Rotation>();
+ QCOMPARE(videoTrackOrientation, expectedRotationAngle);
+
+ // Play video file, sample upper left area, compare with expected color
+ m_fixture->player.play();
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QVideoFrame videoFrame = m_fixture->surface.waitForFrame();
+ QVERIFY(videoFrame.isValid());
+ QCOMPARE(videoFrame.rotation(), expectedRotationAngle);
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
+ QImage image = videoFrame.toImage();
+ QVERIFY(!image.isNull());
+ QRgb upperLeftColor = image.pixel(5, 5);
+ QCOMPARE_LT(colorDifference(upperLeftColor, expectedColor), 0.004);
+
+ QSKIP_GSTREAMER("QTBUG-124005: surface.videoSize() not updated with rotation");
+
+ // Compare videoSize of the output video sink with the expected value after getting a frame
+ QCOMPARE(m_fixture->surface.videoSize(), videoSize);
+}
+
+void tst_QMediaPlayerBackend::setVideoOutput_doesNotStopPlayback()
+{
+ using namespace std::chrono_literals;
+
+ CHECK_SELECTED_URL(m_15sVideo);
+
+ QFETCH(QMediaPlayer::PlaybackState, playbackState);
+
+ TestVideoSink surface(false);
+ QAudioOutput audioOut;
+
+ QMediaPlayer player;
+ player.setAudioOutput(&audioOut);
+ player.setSource(*m_15sVideo);
+
+ switch (playbackState) {
+ case QMediaPlayer::StoppedState:
+ break;
+ case QMediaPlayer::PausedState:
+ QSKIP_FFMPEG("QTBUG-126014: Test failure with the ffmpeg backend");
+ player.pause();
+ break;
+ case QMediaPlayer::PlayingState:
+ QSKIP_FFMPEG("QTBUG-126014: Test failure with the ffmpeg backend");
+ QSKIP_GSTREAMER("QTBUG-124005: Test failure with the gstreamer backend");
+ player.play();
+ break;
+ }
+
+ // set video output
+ QTest::qWait(1s);
+ player.setVideoOutput(&surface);
+
+ if (playbackState == QMediaPlayer::PlayingState) {
+ QVideoFrame frame = surface.waitForFrame();
+ QCOMPARE(frame.size(), QSize(20, 20));
+ }
+
+ // unset video output
+ QTest::qWait(1s);
+ player.setVideoOutput(nullptr);
+
+ // wait for play until end
+ if (playbackState != QMediaPlayer::PlayingState)
+ player.play();
+
+ player.setPlaybackRate(5);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+}
+
+void tst_QMediaPlayerBackend::setVideoOutput_doesNotStopPlayback_data()
{
- m_frameList.append(frame);
+ QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState");
+ QTest::newRow("StoppedState") << QMediaPlayer::StoppedState;
+ QTest::newRow("PausedState") << QMediaPlayer::PausedState;
+ QTest::newRow("PlayingState") << QMediaPlayer::PlayingState;
}
-void ProbeDataHandler::processBuffer(const QAudioBuffer &buffer)
+void tst_QMediaPlayerBackend::setAudioOutput_doesNotStopPlayback()
{
- m_bufferList.append(buffer);
+ QSKIP_FFMPEG("QTBUG-126014: Test failure with the ffmpeg backend");
+
+ using namespace std::chrono_literals;
+
+ CHECK_SELECTED_URL(m_15sVideo);
+ QFETCH(QMediaPlayer::PlaybackState, playbackState);
+
+ TestVideoSink surface(false);
+ QAudioOutput audioOut;
+
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ player.setSource(*m_15sVideo);
+
+ switch (playbackState) {
+ case QMediaPlayer::StoppedState:
+ break;
+ case QMediaPlayer::PausedState:
+ player.pause();
+ break;
+ case QMediaPlayer::PlayingState:
+ player.play();
+ break;
+ }
+
+ // set audio output
+ QTest::qWait(1s);
+ player.setAudioOutput(&audioOut);
+
+ // unset audio output
+ QTest::qWait(1s);
+ player.setAudioOutput(nullptr);
+
+ // wait for play until end
+ if (playbackState != QMediaPlayer::PlayingState)
+ player.play();
+ player.setPlaybackRate(5);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState);
}
-void ProbeDataHandler::flushVideo()
+void tst_QMediaPlayerBackend::setAudioOutput_doesNotStopPlayback_data()
{
- isVideoFlushCalled = true;
+ QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState");
+ QTest::newRow("StoppedState") << QMediaPlayer::StoppedState;
+ QTest::newRow("PausedState") << QMediaPlayer::PausedState;
+ QTest::newRow("PlayingState") << QMediaPlayer::PlayingState;
}
-void ProbeDataHandler::flushAudio()
+void tst_QMediaPlayerBackend::swapAudioDevice_doesNotStopPlayback()
{
+ using namespace std::chrono_literals;
+ const QList<QAudioDevice> outputDevices = QMediaDevices::audioOutputs();
+
+ if (outputDevices.size() < 2)
+ QSKIP("swapAudioDevice_doesNotStopPlayback requires two audio output devices");
+
+ CHECK_SELECTED_URL(m_15sVideo);
+ QFETCH(QMediaPlayer::PlaybackState, playbackState);
+
+ TestVideoSink surface(false);
+ QAudioOutput audioOut;
+
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&audioOut);
+ player.setSource(*m_15sVideo);
+ switch (playbackState) {
+ case QMediaPlayer::StoppedState:
+ break;
+ case QMediaPlayer::PausedState:
+ player.pause();
+ break;
+ case QMediaPlayer::PlayingState:
+ player.play();
+ break;
+ }
+
+ // swap output device
+ QTest::qWait(1s);
+ audioOut.setDevice(outputDevices[0]);
+
+ QTest::qWait(1s);
+ audioOut.setDevice(outputDevices[1]);
+
+ QTest::qWait(1s);
+ audioOut.setDevice(outputDevices[0]);
+
+ // wait for play until end
+ if (playbackState != QMediaPlayer::PlayingState)
+ player.play();
+ player.setPlaybackRate(5);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+}
+
+void tst_QMediaPlayerBackend::swapAudioDevice_doesNotStopPlayback_data()
+{
+ QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState");
+ QTest::newRow("StoppedState") << QMediaPlayer::StoppedState;
+ QTest::newRow("PausedState") << QMediaPlayer::PausedState;
+ QTest::newRow("PlayingState") << QMediaPlayer::PlayingState;
+}
+
+void tst_QMediaPlayerBackend::play_readsSubtitle()
+{
+ using namespace std::chrono_literals;
+ CHECK_SELECTED_URL(m_subtitleVideo);
+
+ QVideoSink &sink = m_fixture->surface;
+ QMediaPlayer &player = m_fixture->player;
+
+ TestSubtitleSink subtitleSink;
+ QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink,
+ &TestSubtitleSink::addSubtitle);
+
+ player.setSource(*m_subtitleVideo);
+ QTRY_COMPARE(player.subtitleTracks().size(), 1);
+ QCOMPARE_EQ(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 3000);
+
+ player.setActiveSubtitleTrack(0);
+
+ if (!isGStreamerPlatform()) // FIXME: spurious deadlocks
+ player.setPlaybackRate(5.f);
+
+ player.play();
+
+ QStringList expectedSubtitleList = {
+ u"Hello"_s,
+ u""_s,
+ u"World"_s,
+ u""_s,
+ };
+
+ QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitleList);
+}
+
+void tst_QMediaPlayerBackend::multiTrack_validateMetadata()
+{
+ CHECK_SELECTED_URL(m_multitrackVideo);
+ QMediaPlayer &player = m_fixture->player;
+
+ player.setSource(*m_multitrackVideo);
+
+ QTRY_COMPARE(player.videoTracks().size(), 2);
+ QTRY_COMPARE(player.audioTracks().size(), 2);
+ QTRY_COMPARE(player.subtitleTracks().size(), 2);
+
+ QSKIP_GSTREAMER("GStreamer does not provide correct track order");
+
+ QCOMPARE(player.videoTracks()[0][QMediaMetaData::Title], u"One"_s);
+ QCOMPARE(player.videoTracks()[1][QMediaMetaData::Title], u"Two"_s);
+
+ QCOMPARE(player.audioTracks()[0][QMediaMetaData::Language], QLocale::Language::English);
+ QCOMPARE(player.audioTracks()[1][QMediaMetaData::Language], QLocale::Language::Spanish);
+ QCOMPARE(player.subtitleTracks()[0][QMediaMetaData::Language], QLocale::Language::English);
+ QCOMPARE(player.subtitleTracks()[1][QMediaMetaData::Language], QLocale::Language::Spanish);
+}
+
+void tst_QMediaPlayerBackend::play_readsSubtitle_fromMultiTrack()
+{
+ using namespace std::chrono_literals;
+ CHECK_SELECTED_URL(m_multitrackVideo);
+
+ QFETCH(int, track);
+ QFETCH(const QStringList, expectedSubtitles);
+
+ QVideoSink &sink = m_fixture->surface;
+ QMediaPlayer &player = m_fixture->player;
+
+ TestSubtitleSink subtitleSink;
+ QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink,
+ &TestSubtitleSink::addSubtitle);
+
+ player.setSource(*m_multitrackVideo);
+
+ QTRY_COMPARE(player.subtitleTracks().size(), 2);
+
+ if (track != -1) {
+ if (isGStreamerPlatform())
+ QCOMPARE(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 4000);
+ if (isFFMPEGPlatform())
+ QCOMPARE(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 15046);
+ }
+
+ if (isGStreamerPlatform()) {
+ bool swapTracks =
+ player.subtitleTracks()[0][QMediaMetaData::Language] == QLocale::Language::Spanish;
+
+ if (swapTracks && track == 1)
+ track = 0;
+ if (swapTracks && track == 0)
+ track = 1;
+ }
+
+ player.setActiveSubtitleTrack(track);
+ if (!isGStreamerPlatform())
+ player.setPlaybackRate(5.f);
+ player.play();
+
+ if (expectedSubtitles.isEmpty())
+ QTRY_COMPARE_GT(player.position(), 2000);
+
+ QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitles);
+}
+
+void tst_QMediaPlayerBackend::play_readsSubtitle_fromMultiTrack_data()
+{
+ QSKIP_GSTREAMER("GStreamer does not provide consistent track order");
+
+ QTest::addColumn<int>("track");
+ QTest::addColumn<QStringList>("expectedSubtitles");
+
+ QTest::addRow("track 0") << 0
+ << QStringList{
+ u"1s track 1"_s,
+ u""_s,
+ u"3s track 1"_s,
+ u""_s,
+ };
+ QTest::addRow("track 1") << 1
+ << QStringList{
+ u"1s track 2"_s,
+ u""_s,
+ u"3s track 2"_s,
+ u""_s,
+ };
+
+ QTest::addRow("no subtitles") << -1 << QStringList{};
+}
+
+void tst_QMediaPlayerBackend::setActiveSubtitleTrack_switchesSubtitles()
+{
+ QVideoSink &sink = m_fixture->surface;
+ QMediaPlayer &player = m_fixture->player;
+
+ QFETCH(const QUrl, media);
+ QFETCH(const int, positionToSwapTrack);
+ QFETCH(const QLatin1String, testMode);
+ QFETCH(const QStringList, expectedSubtitles);
+
+ TestSubtitleSink subtitleSink;
+ QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink,
+ &TestSubtitleSink::addSubtitle);
+
+ player.setSource(media);
+
+ QTRY_COMPARE(player.subtitleTracks().size(), 2);
+
+ int track0 = 0;
+ int track1 = 1;
+ if (isGStreamerPlatform()) {
+ bool swapTracks =
+ player.subtitleTracks()[0][QMediaMetaData::Language] == QLocale::Language::Spanish;
+
+ if (swapTracks) {
+ track1 = 0;
+ track0 = 1;
+ }
+ }
+
+ player.setActiveSubtitleTrack(track0);
+
+ player.play();
+ QTRY_COMPARE_GT(player.position(), positionToSwapTrack);
+
+ if (testMode == "setWhilePaused"_L1) {
+ player.pause();
+ player.setActiveSubtitleTrack(track1);
+ player.play();
+ } else if (testMode == "setWhilePlaying"_L1) {
+ player.setActiveSubtitleTrack(track1);
+ } else {
+ QFAIL("should not reach");
+ }
+
+ QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitles);
+}
+
+void tst_QMediaPlayerBackend::setActiveSubtitleTrack_switchesSubtitles_data()
+{
+ QSKIP_GSTREAMER("GStreamer does not provide consistent track order");
+
+ QTest::addColumn<QUrl>("media");
+ QTest::addColumn<QLatin1String>("testMode");
+ QTest::addColumn<int>("positionToSwapTrack");
+ QTest::addColumn<QStringList>("expectedSubtitles");
+
+ QTest::addRow("while paused") << *m_multitrackVideo << "setWhilePaused"_L1 << 2100
+ << QStringList{
+ u"1s track 1"_s,
+ u""_s,
+ u"3s track 2"_s,
+ u""_s,
+ };
+ QTest::addRow("while playing") << *m_multitrackVideo << "setWhilePlaying"_L1 << 2100
+ << QStringList{
+ u"1s track 1"_s,
+ u""_s,
+ u"3s track 2"_s,
+ u""_s,
+ };
+
+ QTest::addRow("while paused, subtitles start at zero")
+ << *m_multitrackSubtitleStartsAtZeroVideo << "setWhilePaused"_L1 << 1100
+ << QStringList{
+ u"0s track 1"_s,
+ u""_s,
+ u"2s track 2"_s,
+ u""_s,
+ };
+ QTest::addRow("while playing, subtitles start at zero")
+ << *m_multitrackSubtitleStartsAtZeroVideo << "setWhilePlaying"_L1 << 1100
+ << QStringList{
+ u"0s track 1"_s,
+ u""_s,
+ u"2s track 2"_s,
+ u""_s,
+ };
+}
+
+void tst_QMediaPlayerBackend::setActiveVideoTrack_switchesVideoTrack()
+{
+ using namespace std::chrono_literals;
+ QSKIP_GSTREAMER("GStreamer does not provide consistent track order");
+
+ TestVideoSink &sink = m_fixture->surface;
+ sink.setStoreFrames();
+ QMediaPlayer &player = m_fixture->player;
+
+ player.setSource(*m_multitrackVideo);
+
+ QTRY_COMPARE(player.videoTracks().size(), 2);
+
+ int track0 = 0;
+ int track1 = 1;
+ if (isGStreamerPlatform()) {
+ bool swapTracks = player.subtitleTracks()[0][QMediaMetaData::Title] != u"One"_s;
+
+ if (swapTracks) {
+ track0 = 1;
+ track1 = 0;
+ }
+ }
+
+ player.setActiveVideoTrack(track0);
+ player.play();
+
+ sink.waitForFrame();
+
+ QTest::qWait(500ms);
+ sink.waitForFrame();
+ QCOMPARE(sink.m_frameList.back().toImage().pixel(10, 10), QColor(0xff, 0x80, 0x7f).rgb());
+
+ player.setActiveVideoTrack(track1);
+
+ QTest::qWait(500ms);
+ sink.waitForFrame();
+ QCOMPARE(sink.m_frameList.back().toImage().pixel(10, 10), QColor(0x80, 0x80, 0xff).rgb());
+}
+
+void tst_QMediaPlayerBackend::disablingAllTracks_doesNotStopPlayback()
+{
+ QSKIP_GSTREAMER("position does not advance in GStreamer");
+
+ QMediaPlayer &player = m_fixture->player;
+
+ player.setSource(*m_multitrackVideo);
+
+ // CAVEAT: we cannot set active tracks before tracksChanged is emitted
+ QTRY_COMPARE(player.videoTracks().size(), 2);
+
+ player.setActiveVideoTrack(-1);
+ player.setActiveAudioTrack(-1);
+
+ player.play();
+ QTRY_VERIFY(player.position() > 1000);
+
+ QCOMPARE(m_fixture->surface.m_totalFrames, 0);
+}
+
+void tst_QMediaPlayerBackend::disablingAllTracks_beforeTracksChanged_doesNotStopPlayback()
+{
+ QSKIP_GSTREAMER("position does not advance in GStreamer");
+ QSKIP_FFMPEG("setActiveXXXTrack(-1) only works after tracksChanged");
+
+ QMediaPlayer &player = m_fixture->player;
+
+ player.setSource(*m_multitrackVideo);
+
+ player.setActiveVideoTrack(-1);
+ player.setActiveAudioTrack(-1);
+
+ player.play();
+ QTRY_VERIFY(player.position() > 1000);
+
+ QCOMPARE(m_fixture->surface.m_totalFrames, 0);
}
QTEST_MAIN(tst_QMediaPlayerBackend)
+
#include "tst_qmediaplayerbackend.moc"