/**************************************************************************** ** ** 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$ ** ****************************************************************************/ #include #include #include "qmediaplayer.h" #include #include #include #include #include #include "../shared/mediafileselector.h" //TESTED_COMPONENT=src/multimedia #include QT_USE_NAMESPACE /* This is the backend conformance test. Since it relies on platform media framework and sound hardware it may be less stable. */ class tst_QMediaPlayerBackend : public QObject { Q_OBJECT public slots: void init(); void cleanup(); void initTestCase(); private slots: void construction(); void loadMedia(); void unloadMedia(); void loadMediaInLoadingState(); void playPauseStop(); void processEOS(); void deleteLaterAtEOS(); void volumeAndMuted(); void volumeAcrossFiles_data(); void volumeAcrossFiles(); void initialVolume(); void seekPauseSeek(); void seekInStoppedState(); void subsequentPlayback(); void surfaceTest(); // void multipleSurfaces(); void metadata(); void playerStateAtEOS(); void playFromBuffer(); void audioVideoAvailable(); void isSeekable(); void positionAfterSeek(); void videoDimensions(); void position(); private: QUrl selectVideoFile(const QStringList& mediaCandidates); bool isWavSupported(); //one second local wav file QUrl localWavFile; QUrl localWavFile2; QUrl localVideoFile; QUrl localCompressedSoundFile; QUrl localFileWithMetadata; bool m_inCISystem; }; /* This is a simple video surface which records all presented frames. */ class TestVideoSink : public QVideoSink { Q_OBJECT public: explicit TestVideoSink(bool storeFrames = true) : m_storeFrames(storeFrames) { connect(this, &QVideoSink::videoFrameChanged, this, &TestVideoSink::addVideoFrame); } public Q_SLOTS: void addVideoFrame(const QVideoFrame &frame) { if (m_storeFrames) m_frameList.append(frame); ++m_totalFrames; } public: QList m_frameList; int m_totalFrames = 0; // used instead of the list when frames are not stored private: bool m_storeFrames; }; void tst_QMediaPlayerBackend::init() { } QUrl tst_QMediaPlayerBackend::selectVideoFile(const QStringList& mediaCandidates) { // select supported video format QMediaPlayer player; TestVideoSink *surface = new TestVideoSink; player.setVideoOutput(surface); QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error))); for (const QString &s : mediaCandidates) { QFileInfo videoFile(s); if (!videoFile.exists()) continue; QUrl media = QUrl(QUrl::fromLocalFile(videoFile.absoluteFilePath())); player.setSource(media); player.pause(); for (int i = 0; i < 2000 && surface->m_frameList.isEmpty() && errorSpy.isEmpty(); i+=50) { QTest::qWait(50); } if (!surface->m_frameList.isEmpty() && errorSpy.isEmpty()) { return media; } errorSpy.clear(); } return QUrl(); } bool tst_QMediaPlayerBackend::isWavSupported() { return !localWavFile.isEmpty(); } void tst_QMediaPlayerBackend::initTestCase() { QMediaPlayer player; if (!player.isAvailable()) QSKIP("Media player service is not available"); localWavFile = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/test.wav")); localWavFile2 = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/_test.wav"));; QStringList mediaCandidates; mediaCandidates << QFINDTESTDATA("testdata/colors.mp4"); mediaCandidates << QFINDTESTDATA("testdata/colors.ogv"); localVideoFile = MediaFileSelector::selectMediaFile(mediaCandidates); mediaCandidates.clear(); mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mp3"); mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mkv"); localCompressedSoundFile = MediaFileSelector::selectMediaFile(mediaCandidates); localFileWithMetadata = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/nokia-tune.mp3")); qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10); } void tst_QMediaPlayerBackend::cleanup() { } void tst_QMediaPlayerBackend::construction() { QMediaPlayer player; QTRY_VERIFY(player.isAvailable()); } void tst_QMediaPlayerBackend::loadMedia() { if (!isWavSupported()) QSKIP("Sound format is not supported"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia); QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); QSignalSpy mediaSpy(&player, SIGNAL(sourceChanged(QUrl))); player.setSource(localWavFile); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QVERIFY(player.mediaStatus() != QMediaPlayer::NoMedia); QVERIFY(player.mediaStatus() != QMediaPlayer::InvalidMedia); QVERIFY(player.source() == localWavFile); QCOMPARE(stateSpy.count(), 0); QVERIFY(statusSpy.count() > 0); QCOMPARE(mediaSpy.count(), 1); QCOMPARE(mediaSpy.last()[0].value(), localWavFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QVERIFY(player.hasAudio()); QVERIFY(!player.hasVideo()); } void tst_QMediaPlayerBackend::unloadMedia() { if (!isWavSupported()) QSKIP("Sound format is not supported"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); QSignalSpy mediaSpy(&player, SIGNAL(sourceChanged(QUrl))); QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); QSignalSpy durationSpy(&player, SIGNAL(durationChanged(qint64))); player.setSource(localWavFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QVERIFY(player.position() == 0); QVERIFY(player.duration() > 0); player.play(); QTRY_VERIFY(player.position() > 0); QVERIFY(player.duration() > 0); stateSpy.clear(); statusSpy.clear(); mediaSpy.clear(); positionSpy.clear(); durationSpy.clear(); player.setSource(QUrl()); QVERIFY(player.position() <= 0); QVERIFY(player.duration() <= 0); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia); QCOMPARE(player.source(), QUrl()); QVERIFY(!stateSpy.isEmpty()); QVERIFY(!statusSpy.isEmpty()); QVERIFY(!mediaSpy.isEmpty()); QVERIFY(!positionSpy.isEmpty()); } void tst_QMediaPlayerBackend::loadMediaInLoadingState() { if (!isWavSupported()) QSKIP("Sound format is not supported"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); player.setSource(localWavFile2); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); // Sets new media while old has not been finished. player.setSource(localWavFile); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); player.play(); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); player.setSource(localWavFile2); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia); } void tst_QMediaPlayerBackend::playPauseStop() { if (!isWavSupported()) QSKIP("Sound format is not supported"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); QSignalSpy errorSpy(&player, SIGNAL(errorOccurred(QMediaPlayer::Error, const QString&))); // Check play() without a media player.play(); QCOMPARE(player.playbackState(), 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); // Check pause() without a media player.pause(); QCOMPARE(player.playbackState(), 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); // The rest is with a valid media player.setSource(localWavFile); QCOMPARE(player.position(), qint64(0)); player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QCOMPARE(stateSpy.count(), 1); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::PlayingState); QTRY_VERIFY(statusSpy.count() > 0 && statusSpy.last()[0].value() == QMediaPlayer::BufferedMedia); QTRY_VERIFY(player.position() > 100); QVERIFY(player.duration() > 0); QVERIFY(positionSpy.count() > 0); QVERIFY(positionSpy.last()[0].value() > 0); stateSpy.clear(); statusSpy.clear(); positionSpy.clear(); qint64 positionBeforePause = player.position(); player.pause(); QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); QCOMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QCOMPARE(stateSpy.count(), 1); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::PausedState); QTest::qWait(500); QVERIFY(qAbs(player.position() - positionBeforePause) < 150); QCOMPARE(positionSpy.count(), 1); stateSpy.clear(); statusSpy.clear(); player.stop(); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QCOMPARE(stateSpy.count(), 1); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); //it's allowed to emit statusChanged() signal async QTRY_COMPARE(statusSpy.count(), 1); QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::LoadedMedia); //ensure the position is reset to 0 at stop and positionChanged(0) is emitted QTRY_COMPARE(player.position(), qint64(0)); QCOMPARE(positionSpy.last()[0].value(), qint64(0)); QVERIFY(player.duration() > 0); stateSpy.clear(); statusSpy.clear(); positionSpy.clear(); player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QCOMPARE(stateSpy.count(), 1); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::PlayingState); QCOMPARE(statusSpy.count(), 1); // Should not go through Loading again when play -> stop -> play QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::BufferedMedia); player.stop(); stateSpy.clear(); statusSpy.clear(); positionSpy.clear(); player.setSource(localWavFile2); QTRY_VERIFY(statusSpy.count() > 0); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::LoadedMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(stateSpy.count(), 0); player.play(); QTRY_VERIFY(player.position() > 100); player.setSource(localWavFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::LoadedMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); QCOMPARE(player.position(), 0); QCOMPARE(positionSpy.last()[0].value(), 0); stateSpy.clear(); statusSpy.clear(); positionSpy.clear(); player.play(); QTRY_VERIFY(player.position() > 100); player.setSource(QUrl()); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::NoMedia); QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::NoMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); QCOMPARE(player.position(), 0); QCOMPARE(positionSpy.last()[0].value(), 0); QCOMPARE(player.duration(), 0); } void tst_QMediaPlayerBackend::processEOS() { if (!isWavSupported()) QSKIP("Sound format is not supported"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); player.setSource(localWavFile); player.play(); 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::EndOfMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(stateSpy.count(), 2); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); //at EOS the position stays at the end of file QCOMPARE(player.position(), player.duration()); QVERIFY(positionSpy.count() > 0); QCOMPARE(positionSpy.last()[0].value(), player.duration()); stateSpy.clear(); statusSpy.clear(); positionSpy.clear(); player.play(); //position is reset to start QTRY_VERIFY(player.position() < 100); QTRY_VERIFY(positionSpy.count() > 0); QCOMPARE(positionSpy.first()[0].value(), 0); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QCOMPARE(stateSpy.count(), 1); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::PlayingState); QVERIFY(statusSpy.count() > 0); QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::BufferedMedia); 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::EndOfMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(stateSpy.count(), 2); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); //position stays at the end of file QCOMPARE(player.position(), player.duration()); QVERIFY(positionSpy.count() > 0); QCOMPARE(positionSpy.last()[0].value(), player.duration()); //after setPosition EndOfMedia status should be reset to Loaded stateSpy.clear(); statusSpy.clear(); player.setPosition(500); //this transition can be async, so allow backend to perform it QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QCOMPARE(stateSpy.count(), 0); QTRY_VERIFY(statusSpy.count() > 0 && statusSpy.last()[0].value() == QMediaPlayer::LoadedMedia); player.play(); player.setPosition(900); //wait up to 5 seconds for EOS QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(player.position(), player.duration()); stateSpy.clear(); statusSpy.clear(); positionSpy.clear(); // pause() should reset position to beginning and status to Buffered player.pause(); QTRY_COMPARE(player.position(), 0); QTRY_VERIFY(positionSpy.count() > 0); QCOMPARE(positionSpy.first()[0].value(), 0); QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QCOMPARE(stateSpy.count(), 1); QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::PausedState); QVERIFY(statusSpy.count() > 0); QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::BufferedMedia); } // Helper class for tst_QMediaPlayerBackend::deleteLaterAtEOS() class DeleteLaterAtEos : public QObject { Q_OBJECT public: DeleteLaterAtEos(QMediaPlayer* p) : player(p) { } public slots: void play() { QVERIFY(connect(player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(onMediaStatusChanged(QMediaPlayer::MediaStatus)))); player->play(); } private slots: void onMediaStatusChanged(QMediaPlayer::MediaStatus status) { if (status == QMediaPlayer::EndOfMedia) { player-> deleteLater(); player = nullptr; } } private: QMediaPlayer* player; }; // Regression test for // 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"); QPointer player(new QMediaPlayer); QAudioOutput output; player->setAudioOutput(&output); DeleteLaterAtEos deleter(player); player->setSource(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(1500, &loop, SLOT(quit())); connect(player.data(), SIGNAL(destroyed()), &loop, SLOT(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; QAudioOutput output; player.setAudioOutput(&output); QCOMPARE(output.volume(), 1.); QVERIFY(!output.isMuted()); player.setSource(localWavFile); player.pause(); QCOMPARE(output.volume(), 1.); QVERIFY(!output.isMuted()); QSignalSpy volumeSpy(&output, SIGNAL(volumeChanged(float))); QSignalSpy mutedSpy(&output, SIGNAL(mutedChanged(bool))); //setting volume to 0 should not trigger muted output.setVolume(0); QTRY_COMPARE(output.volume(), 0); QVERIFY(!output.isMuted()); QCOMPARE(volumeSpy.count(), 1); QCOMPARE(volumeSpy.last()[0].toFloat(), output.volume()); QCOMPARE(mutedSpy.count(), 0); output.setVolume(0.5); QTRY_COMPARE(output.volume(), 0.5); QVERIFY(!output.isMuted()); QCOMPARE(volumeSpy.count(), 2); QCOMPARE(volumeSpy.last()[0].toFloat(), output.volume()); QCOMPARE(mutedSpy.count(), 0); output.setMuted(true); QTRY_VERIFY(output.isMuted()); QVERIFY(output.volume() > 0); QCOMPARE(volumeSpy.count(), 2); QCOMPARE(mutedSpy.count(), 1); QCOMPARE(mutedSpy.last()[0].toBool(), output.isMuted()); output.setMuted(false); QTRY_VERIFY(!output.isMuted()); QVERIFY(output.volume() > 0); QCOMPARE(volumeSpy.count(), 2); QCOMPARE(mutedSpy.count(), 2); QCOMPARE(mutedSpy.last()[0].toBool(), output.isMuted()); } void tst_QMediaPlayerBackend::volumeAcrossFiles_data() { QTest::addColumn("volume"); QTest::addColumn("muted"); QTest::newRow("100 unmuted") << 100 << false; QTest::newRow("50 unmuted") << 50 << false; QTest::newRow("0 unmuted") << 0 << false; QTest::newRow("100 muted") << 100 << true; QTest::newRow("50 muted") << 50 << true; QTest::newRow("0 muted") << 0 << true; } void tst_QMediaPlayerBackend::volumeAcrossFiles() { QFETCH(int, volume); QFETCH(bool, muted); float vol = volume/100.; QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); //volume and muted should not be preserved between player instances QVERIFY(output.volume() > 0); QVERIFY(!output.isMuted()); output.setVolume(vol); output.setMuted(muted); QTRY_COMPARE(output.volume(), vol); QTRY_COMPARE(output.isMuted(), muted); player.setSource(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(output.volume(), vol); QCOMPARE(output.isMuted(), muted); player.setSource(QUrl()); QTRY_COMPARE(output.volume(), vol); QCOMPARE(output.isMuted(), muted); player.setSource(localWavFile); player.pause(); QTRY_COMPARE(output.volume(), vol); QCOMPARE(output.isMuted(), muted); } void tst_QMediaPlayerBackend::initialVolume() { if (!isWavSupported()) QSKIP("Sound format is not supported"); { QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); output.setVolume(1); player.setSource(localWavFile); QCOMPARE(output.volume(), 1); player.play(); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); QCOMPARE(output.volume(), 1); } { QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); player.setSource(localWavFile); QCOMPARE(output.volume(), 1); player.play(); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); QCOMPARE(output.volume(), 1); } } void tst_QMediaPlayerBackend::seekPauseSeek() { if (localVideoFile.isEmpty()) QSKIP("No supported video file"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); TestVideoSink *surface = new TestVideoSink; player.setVideoOutput(surface); player.setSource(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()); 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 player.pause(); 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) QSKIP("No timestamp"); { 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()); QCOMPARE(frame.width(), 160); QCOMPARE(frame.height(), 120); // create QImage for QVideoFrame to verify RGB pixel colors 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); } surface->m_frameList.clear(); positionSpy.clear(); position = 12000; player.setPosition(position); QTRY_VERIFY(!positionSpy.isEmpty() && qAbs(player.position() - position) < (qint64)500); QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); QTRY_VERIFY(!surface->m_frameList.isEmpty()); { QVideoFrame frame = surface->m_frameList.back(); const qint64 elapsed = (frame.startTime() / 1000) - position; QVERIFY2(qAbs(elapsed) < (qint64)500, QByteArray::number(elapsed).constData()); QCOMPARE(frame.width(), 160); QCOMPARE(frame.height(), 120); 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); } } void tst_QMediaPlayerBackend::seekInStoppedState() { if (localVideoFile.isEmpty()) QSKIP("No supported video file"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); player.setSource(localVideoFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(player.position(), 0); QVERIFY(player.isSeekable()); stateSpy.clear(); positionSpy.clear(); qint64 position = 5000; player.setPosition(position); QTRY_VERIFY(qAbs(player.position() - position) < qint64(200)); QCOMPARE(positionSpy.count(), 1); QVERIFY(qAbs(positionSpy.last()[0].value() - position) < qint64(200)); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(stateSpy.count(), 0); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); positionSpy.clear(); player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_VERIFY(player.position() > position); QCOMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QTest::qWait(100); // Check that it never played from the beginning QVERIFY(player.position() > position); for (int i = 0; i < positionSpy.count(); ++i) QVERIFY(positionSpy.at(i)[0].value() > (position - 200)); // ------ // Same tests but after play() --> stop() player.stop(); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QTRY_COMPARE(player.position(), 0); stateSpy.clear(); positionSpy.clear(); player.setPosition(position); QTRY_VERIFY(qAbs(player.position() - position) < qint64(200)); QCOMPARE(positionSpy.count(), 1); QVERIFY(qAbs(positionSpy.last()[0].value() - position) < qint64(200)); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(stateSpy.count(), 0); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); positionSpy.clear(); player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QVERIFY(qAbs(player.position() - position) < qint64(200)); QTest::qWait(500); // Check that it never played from the beginning QVERIFY(player.position() > (position - 200)); for (int i = 0; i < positionSpy.count(); ++i) QVERIFY(positionSpy.at(i)[0].value() > (position - 200)); // ------ // Same tests but after reaching the end of the media player.setPosition(player.duration() - 100); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); 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(200)); QCOMPARE(positionSpy.count(), 1); QVERIFY(qAbs(positionSpy.last()[0].value() - position) < qint64(200)); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(stateSpy.count(), 0); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); positionSpy.clear(); player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QVERIFY(player.position() >= position - 200); QCOMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QTest::qWait(500); // Check that it never played from the beginning QVERIFY(player.position() > (position - 200)); for (int i = 0; i < positionSpy.count(); ++i) QVERIFY(positionSpy.at(i)[0].value() > (position - 200)); } void tst_QMediaPlayerBackend::subsequentPlayback() { if (localCompressedSoundFile.isEmpty()) QSKIP("Sound format is not supported"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); player.setSource(localCompressedSoundFile); player.setPosition(5000); player.play(); QCOMPARE(player.error(), QMediaPlayer::NoError); QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); 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.playbackState(), QMediaPlayer::PlayingState); QTRY_VERIFY(player.position() > 1000); player.pause(); QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); // make sure position does not "jump" closer to the end of the file QVERIFY(player.position() > 1000); // try to seek back to zero player.setPosition(0); QTRY_COMPARE(player.position(), qint64(0)); player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_VERIFY(player.position() > 1000); player.pause(); QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); QVERIFY(player.position() > 1000); } void tst_QMediaPlayerBackend::surfaceTest() { // 25 fps video file if (localVideoFile.isEmpty()) QSKIP("No supported video file"); TestVideoSink surface(false); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); player.setVideoOutput(&surface); player.setSource(localVideoFile); player.play(); QTRY_VERIFY(player.position() >= 1000); QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface.m_totalFrames))); } #if 0 void tst_QMediaPlayerBackend::multipleSurfaces() { if (localVideoFile.isEmpty()) QSKIP("No supported video file"); QVideoSink surface1; QVideoSink surface2; QMediaPlayer player; player.setVideoOutput(QList() << &surface1 << &surface2); player.setSource(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); } #endif void tst_QMediaPlayerBackend::metadata() { if (localFileWithMetadata.isEmpty()) QSKIP("No supported media file"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); QSignalSpy metadataChangedSpy(&player, SIGNAL(metaDataChanged())); player.setSource(localFileWithMetadata); QTRY_VERIFY(metadataChangedSpy.count() > 0); QCOMPARE(player.metaData().value(QMediaMetaData::Title).toString(), QStringLiteral("Nokia Tune")); QCOMPARE(player.metaData().value(QMediaMetaData::ContributingArtist).toString(), QStringLiteral("TestArtist")); QCOMPARE(player.metaData().value(QMediaMetaData::AlbumTitle).toString(), QStringLiteral("TestAlbum")); metadataChangedSpy.clear(); player.setSource(QUrl()); QCOMPARE(metadataChangedSpy.count(), 1); QVERIFY(player.metaData().isEmpty()); } void tst_QMediaPlayerBackend::playerStateAtEOS() { if (!isWavSupported()) QSKIP("Sound format is not supported"); QMediaPlayer player; QAudioOutput output; player.setAudioOutput(&output); bool endOfMediaReceived = false; connect(&player, &QMediaPlayer::mediaStatusChanged, [&](QMediaPlayer::MediaStatus status) { if (status == QMediaPlayer::EndOfMedia) { QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); endOfMediaReceived = true; } }); player.setSource(localWavFile); player.play(); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); QVERIFY(endOfMediaReceived); } void tst_QMediaPlayerBackend::playFromBuffer() { if (localVideoFile.isEmpty()) QSKIP("No supported video file"); TestVideoSink surface(false); QMediaPlayer player; player.setVideoOutput(&surface); QFile file(localVideoFile.toLocalFile()); if (!file.open(QIODevice::ReadOnly)) QSKIP("Could not open file"); player.setSourceDevice(&file, localVideoFile); player.play(); QTRY_VERIFY(player.position() >= 1000); QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface.m_totalFrames))); } void tst_QMediaPlayerBackend::audioVideoAvailable() { if (localVideoFile.isEmpty()) QSKIP("No supported video file"); TestVideoSink surface(false); QMediaPlayer player; QSignalSpy hasVideoSpy(&player, SIGNAL(hasVideoChanged(bool))); QSignalSpy hasAudioSpy(&player, SIGNAL(hasAudioChanged(bool))); player.setVideoOutput(&surface); player.setSource(localVideoFile); QTRY_VERIFY(player.hasVideo()); QTRY_VERIFY(player.hasAudio()); QCOMPARE(hasVideoSpy.count(), 1); QCOMPARE(hasAudioSpy.count(), 1); player.setSource(QUrl()); QTRY_VERIFY(!player.hasVideo()); QTRY_VERIFY(!player.hasAudio()); QCOMPARE(hasVideoSpy.count(), 2); QCOMPARE(hasAudioSpy.count(), 2); } void tst_QMediaPlayerBackend::isSeekable() { if (localVideoFile.isEmpty()) QSKIP("No supported video file"); TestVideoSink surface(false); QMediaPlayer player; player.setVideoOutput(&surface); QVERIFY(!player.isSeekable()); player.setSource(localVideoFile); QTRY_VERIFY(player.isSeekable()); } void tst_QMediaPlayerBackend::positionAfterSeek() { if (localVideoFile.isEmpty()) QSKIP("No supported video file"); TestVideoSink surface(false); QMediaPlayer player; player.setVideoOutput(&surface); QVERIFY(!player.isSeekable()); player.setSource(localVideoFile); 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::videoDimensions() { if (localVideoFile.isEmpty()) QSKIP("No supported video file"); TestVideoSink surface(true); QMediaPlayer player; player.setVideoOutput(&surface); QVERIFY(!player.isSeekable()); player.setSource(localVideoFile); player.pause(); QTRY_COMPARE(surface.m_totalFrames, 1); QCOMPARE(surface.m_frameList.last().height(), 120); QCOMPARE(surface.videoSize().height(), 120); } void tst_QMediaPlayerBackend::position() { TestVideoSink surface(true); QMediaPlayer player; player.setVideoOutput(&surface); #ifdef Q_OS_ANDROID QEXPECT_FAIL("", "On Android isSeekable() is always set to true due to QTBUG-96952", Continue); #endif QVERIFY(!player.isSeekable()); player.setSource(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); } QTEST_MAIN(tst_QMediaPlayerBackend) #include "tst_qmediaplayerbackend.moc"