diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:34:13 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:34:13 (GMT) |
commit | 67ad0519fd165acee4a4d2a94fa502e9e4847bd0 (patch) | |
tree | 1dbf50b3dff8d5ca7e9344733968c72704eb15ff /tests/auto/mediaobject | |
download | Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.zip Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.gz Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.bz2 |
Long live Qt!
Diffstat (limited to 'tests/auto/mediaobject')
-rw-r--r-- | tests/auto/mediaobject/.gitignore | 1 | ||||
-rw-r--r-- | tests/auto/mediaobject/media/sax.mp3 | bin | 0 -> 417844 bytes | |||
-rw-r--r-- | tests/auto/mediaobject/media/sax.ogg | bin | 0 -> 358374 bytes | |||
-rw-r--r-- | tests/auto/mediaobject/media/sax.wav | bin | 0 -> 756236 bytes | |||
-rwxr-xr-x | tests/auto/mediaobject/mediaobject.pro | 16 | ||||
-rw-r--r-- | tests/auto/mediaobject/mediaobject.qrc | 7 | ||||
-rw-r--r-- | tests/auto/mediaobject/qtesthelper.h | 223 | ||||
-rw-r--r-- | tests/auto/mediaobject/tst_mediaobject.cpp | 932 |
8 files changed, 1179 insertions, 0 deletions
diff --git a/tests/auto/mediaobject/.gitignore b/tests/auto/mediaobject/.gitignore new file mode 100644 index 0000000..2b4d356 --- /dev/null +++ b/tests/auto/mediaobject/.gitignore @@ -0,0 +1 @@ +tst_mediaobject diff --git a/tests/auto/mediaobject/media/sax.mp3 b/tests/auto/mediaobject/media/sax.mp3 Binary files differnew file mode 100644 index 0000000..0a078b1 --- /dev/null +++ b/tests/auto/mediaobject/media/sax.mp3 diff --git a/tests/auto/mediaobject/media/sax.ogg b/tests/auto/mediaobject/media/sax.ogg Binary files differnew file mode 100644 index 0000000..12be04a --- /dev/null +++ b/tests/auto/mediaobject/media/sax.ogg diff --git a/tests/auto/mediaobject/media/sax.wav b/tests/auto/mediaobject/media/sax.wav Binary files differnew file mode 100644 index 0000000..f3e814c --- /dev/null +++ b/tests/auto/mediaobject/media/sax.wav diff --git a/tests/auto/mediaobject/mediaobject.pro b/tests/auto/mediaobject/mediaobject.pro new file mode 100755 index 0000000..5d4a098 --- /dev/null +++ b/tests/auto/mediaobject/mediaobject.pro @@ -0,0 +1,16 @@ +############################################################ +# Project file for autotest for file mediaobject.h +############################################################ + +load(qttest_p4) + +contains(QT_CONFIG, phonon):QT += phonon +SOURCES += tst_mediaobject.cpp +HEADERS += qtesthelper.h +RESOURCES += mediaobject.qrc + +wince*{ + DEPLOYMENT_PLUGIN += phonon_waveout + DEFINES += tst_MediaObject=tst_MediaObject_waveout +} + diff --git a/tests/auto/mediaobject/mediaobject.qrc b/tests/auto/mediaobject/mediaobject.qrc new file mode 100644 index 0000000..4f46213 --- /dev/null +++ b/tests/auto/mediaobject/mediaobject.qrc @@ -0,0 +1,7 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> +<file>media/sax.wav</file> +<file>media/sax.ogg</file> +<file>media/sax.mp3</file> +</qresource> +</RCC> diff --git a/tests/auto/mediaobject/qtesthelper.h b/tests/auto/mediaobject/qtesthelper.h new file mode 100644 index 0000000..a7f3989 --- /dev/null +++ b/tests/auto/mediaobject/qtesthelper.h @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +/* This file is part of the KDE project + Copyright (C) 2007 Matthias Kretz <kretz@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#ifndef QTESTHELPER_H +#define QTESTHELPER_H + +#include <QtCore/QEventLoop> +#include <QtCore/QTimer> +#include <QtTest/QSignalSpy> +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE +namespace QTest +{ + + /** + * Starts an event loop that runs until the given signal is received. Optionally the event loop + * can return earlier on a timeout. + * + * \return \p true if the requested signal was received + * \p false on timeout + */ + bool waitForSignal(QObject *obj, const char *signal, int timeout = 0) + { + QEventLoop loop; + QObject::connect(obj, signal, &loop, SLOT(quit())); + QTimer timer; + QSignalSpy timeoutSpy(&timer, SIGNAL(timeout())); + if (timeout > 0) { + QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.setSingleShot(true); + timer.start(timeout); + } + loop.exec(); + return timeoutSpy.isEmpty(); + } + + +// template<> + char *toString(const Phonon::State &state) + { + switch (state) { + case Phonon::LoadingState: + return qstrdup("LoadingState"); + case Phonon::StoppedState: + return qstrdup("StoppedState"); + case Phonon::PlayingState: + return qstrdup("PlayingState"); + case Phonon::BufferingState: + return qstrdup("BufferingState"); + case Phonon::PausedState: + return qstrdup("PausedState"); + case Phonon::ErrorState: + return qstrdup("ErrorState"); + } + return 0; + } + +// template<> + char *toString(const QVariant::Type &type) + { + switch (type) { + case QVariant::Invalid: + return qstrdup("QVariant::Invalid"); + case QVariant::BitArray: + return qstrdup("QVariant::BitArray"); + case QVariant::Bitmap: + return qstrdup("QVariant::Bitmap"); + case QVariant::Bool: + return qstrdup("QVariant::Bool"); + case QVariant::Brush: + return qstrdup("QVariant::Brush"); + case QVariant::ByteArray: + return qstrdup("QVariant::ByteArray"); + case QVariant::Char: + return qstrdup("QVariant::Char"); + case QVariant::Color: + return qstrdup("QVariant::Color"); + case QVariant::Cursor: + return qstrdup("QVariant::Cursor"); + case QVariant::Date: + return qstrdup("QVariant::Date"); + case QVariant::DateTime: + return qstrdup("QVariant::DateTime"); + case QVariant::Double: + return qstrdup("QVariant::Double"); + case QVariant::Font: + return qstrdup("QVariant::Font"); + case QVariant::Icon: + return qstrdup("QVariant::Icon"); + case QVariant::Image: + return qstrdup("QVariant::Image"); + case QVariant::Int: + return qstrdup("QVariant::Int"); + case QVariant::KeySequence: + return qstrdup("QVariant::KeySequence"); + case QVariant::Line: + return qstrdup("QVariant::Line"); + case QVariant::LineF: + return qstrdup("QVariant::LineF"); + case QVariant::List: + return qstrdup("QVariant::List"); + case QVariant::Locale: + return qstrdup("QVariant::Locale"); + case QVariant::LongLong: + return qstrdup("QVariant::LongLong"); + case QVariant::Map: + return qstrdup("QVariant::Map"); + case QVariant::Matrix: + return qstrdup("QVariant::Matrix"); + case QVariant::Transform: + return qstrdup("QVariant::Transform"); + case QVariant::Palette: + return qstrdup("QVariant::Palette"); + case QVariant::Pen: + return qstrdup("QVariant::Pen"); + case QVariant::Pixmap: + return qstrdup("QVariant::Pixmap"); + case QVariant::Point: + return qstrdup("QVariant::Point"); + case QVariant::PointF: + return qstrdup("QVariant::PointF"); + case QVariant::Polygon: + return qstrdup("QVariant::Polygon"); + case QVariant::Rect: + return qstrdup("QVariant::Rect"); + case QVariant::RectF: + return qstrdup("QVariant::RectF"); + case QVariant::RegExp: + return qstrdup("QVariant::RegExp"); + case QVariant::Region: + return qstrdup("QVariant::Region"); + case QVariant::Size: + return qstrdup("QVariant::Size"); + case QVariant::SizeF: + return qstrdup("QVariant::SizeF"); + case QVariant::SizePolicy: + return qstrdup("QVariant::SizePolicy"); + case QVariant::String: + return qstrdup("QVariant::String"); + case QVariant::StringList: + return qstrdup("QVariant::StringList"); + case QVariant::TextFormat: + return qstrdup("QVariant::TextFormat"); + case QVariant::TextLength: + return qstrdup("QVariant::TextLength"); + case QVariant::Time: + return qstrdup("QVariant::Time"); + case QVariant::UInt: + return qstrdup("QVariant::UInt"); + case QVariant::ULongLong: + return qstrdup("QVariant::ULongLong"); + case QVariant::Url: + return qstrdup("QVariant::Url"); + case QVariant::UserType: + return qstrdup("QVariant::UserType"); + case QVariant::LastType: + return qstrdup("QVariant::LastType"); + default: + return 0; + } + return 0; + } +} // namespace QTest +QT_END_NAMESPACE + +#endif // QTESTHELPER_H diff --git a/tests/auto/mediaobject/tst_mediaobject.cpp b/tests/auto/mediaobject/tst_mediaobject.cpp new file mode 100644 index 0000000..96589b7 --- /dev/null +++ b/tests/auto/mediaobject/tst_mediaobject.cpp @@ -0,0 +1,932 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Matthias Kretz <kretz@kde.org> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include <QtTest/QtTest> +#include <QtCore/QDate> +#include <QtCore/QDebug> +#include <QtCore/QObject> +#include <QtCore/QUrl> + +#ifndef QT_NO_PHONON +#include <phonon/path.h> +#include <phonon/audiooutput.h> +#include <phonon/mediaobject.h> +#include <phonon/phononnamespace.h> +#include <phonon/audiooutput.h> +#include <phonon/seekslider.h> +#include <phonon/mediaobject.h> +#include <phonon/volumeslider.h> +#include <phonon/videowidget.h> +#include <phonon/backendcapabilities.h> + +#include "qtesthelper.h" +#include <cstdlib> +#endif + +#ifndef Q_WS_WIN +#include <unistd.h> +#endif + +#ifdef Q_OS_WINCE +#define MEDIA_FILE "/sax.wav" +#define MEDIA_FILEPATH ":/media/sax.wav" +const qint64 SEEK_BACKWARDS = 2000; +const qint64 ALLOWED_TIME_FOR_SEEKING = 1500; // 1.5s +const qint64 SEEKING_TOLERANCE = 250; +#else +#ifdef Q_OS_WIN +#define MEDIA_FILE "/sax.mp3" +#define MEDIA_FILEPATH ":/media/sax.mp3" +#else +#define MEDIA_FILE "/sax.ogg" +#define MEDIA_FILEPATH ":/media/sax.ogg" +#endif +const qint64 SEEK_BACKWARDS = 4000; +const qint64 ALLOWED_TIME_FOR_SEEKING = 1000; // 1s +const qint64 SEEKING_TOLERANCE = 0; +#endif //Q_OS_WINCE + +class tst_MediaObject : public QObject +{ + Q_OBJECT + public: + tst_MediaObject() + : m_success(false) + { + qputenv("PHONON_GST_AUDIOSINK", "fake"); + } + +#ifndef QT_NO_PHONON + + Q_SIGNALS: + void continueTestPlayOnFinish(); + + protected Q_SLOTS: + void enqueueMedia(); + void setMediaAndPlay(); + void stateChanged(Phonon::State, Phonon::State); + private Q_SLOTS: + void init(); + void cleanup(); + + void testPlayFromResource(); + void testPlayIllegalFile(); + void initTestCase(); + void checkForDefaults(); + + // state change tests + void stopToStop(); + void stopToPause(); + void stopToPlay(); + void playToPlay(); + void playToPause(); + void playToStop(); + void pauseToPause(); + void pauseToPlay(); + void pauseToStop(); + + void testPrefinishMark(); + void testSeek(); + void testTickSignal(); + void testJustInTimeQueuing(); + void testPlayOnFinish(); + void testPlayBeforeFinish(); + void testPauseOnFinish(); + void testReconnectBetweenTwoMediaObjects(); + void cleanupTestCase(); + private: + void _startPlayback(Phonon::State currentState = Phonon::StoppedState); + void _stopPlayback(Phonon::State currentState); + void _pausePlayback(); + void _testOneSeek(qint64 seekTo); + + QUrl m_url; + Phonon::MediaObject *m_media; + QSignalSpy *m_stateChangedSignalSpy; + QString m_tmpFileName; +#endif //QT_NO_PHONON + bool m_success; +}; + +#ifndef QT_NO_PHONON + +#define startPlayback() _startPlayback(); if (!m_success) return; m_success = false; +#define startPlayback2(currentState) _startPlayback(currentState); if (!m_success) return; m_success = false; +#define stopPlayback(currentState) _stopPlayback(currentState); if (!m_success) return; m_success = false; +#define pausePlayback() _pausePlayback(); if (!m_success) return; m_success = false; +#define testOneSeek(seekTo) _testOneSeek(seekTo); if (!m_success) return; m_success = false; + +const qint64 ALLOWED_SEEK_INACCURACY = 300; // 0.3s +const qint64 ALLOWED_TICK_INACCURACY = 350; // allow +/- 350 ms inaccuracy + +using namespace Phonon; + +static qint64 castQVariantToInt64(const QVariant &variant) +{ + return *reinterpret_cast<const qint64 *>(variant.constData()); +} + +static qint32 castQVariantToInt32(const QVariant &variant) +{ + return *reinterpret_cast<const qint32 *>(variant.constData()); +} + +static const char *const red = "\033[0;31m"; +static const char *const green = "\033[0;32m"; +static const char *const yellow = "\033[0;33m"; +static const char *const blue = "\033[0;34m"; +static const char *const purple = "\033[0;35m"; +static const char *const cyan = "\033[0;36m"; +static const char *const white = "\033[0;37m"; +static const char *const normal = "\033[0m"; + +void tst_MediaObject::stateChanged(Phonon::State newstate, Phonon::State oldstate) +{ + if (newstate == Phonon::ErrorState) { + QWARN(QByteArray(QByteArray(red) + ".......................................................... ") + QByteArray(QTest::toString(oldstate)) + " to " + QByteArray(QTest::toString(newstate)) + normal); + } else { + //qDebug() << ".........................................................." << cyan << QTest::toString(oldstate) << "to" << QTest::toString(newstate) << normal; + } +} + +void tst_MediaObject::testPlayFromResource() +{ + QFile file(MEDIA_FILEPATH); + MediaObject media; + media.setCurrentSource(&file); + QVERIFY(media.state() != Phonon::ErrorState); + if (media.state() != Phonon::StoppedState) + QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000); + QCOMPARE(media.state(), Phonon::StoppedState); + media.play(); + if (media.state() != Phonon::PlayingState) + QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000); + QCOMPARE(media.state(), Phonon::PlayingState); +} + +void tst_MediaObject::testPlayIllegalFile() +{ + QString filename = QDir::tempPath() + QString("/test.wav"); + QFile::remove(filename); + QFile file(filename); + file.open(QIODevice::WriteOnly); + for (int i=0;i<0xffff;i++) { + int r = qrand(); + file.write((const char*)&r, 2); + } + file.close(); + MediaObject media; + media.setCurrentSource(filename); + QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000); + QCOMPARE(media.state(), Phonon::ErrorState); + media.play(); + QCOMPARE(media.state(), Phonon::ErrorState); + QFile::remove(filename); +} + +void tst_MediaObject::init() +{ + QCOMPARE(m_media->outputPaths().size(), 1); + if (m_media->state() == Phonon::ErrorState) { + m_media->setCurrentSource(m_url); + if (m_media->state() == Phonon::ErrorState) { + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State))); + } + if (m_media->state() == Phonon::LoadingState) { + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State))); + } + m_stateChangedSignalSpy->clear(); + } +} + +void tst_MediaObject::cleanup() +{ + switch (m_media->state()) { + case Phonon::PlayingState: + case Phonon::BufferingState: + case Phonon::PausedState: + stopPlayback(m_media->state()); + break; + default: + break; + } + m_stateChangedSignalSpy->clear(); +} + +void tst_MediaObject::_startPlayback(Phonon::State currentState) +{ + m_stateChangedSignalSpy->clear(); + Phonon::State s = m_media->state(); + QCOMPARE(s, currentState); + m_media->play(); + while (s != Phonon::PlayingState) { + if (m_stateChangedSignalSpy->isEmpty()) { + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 3000); + QApplication::processEvents(); + } + while (!m_stateChangedSignalSpy->isEmpty()) { + QList<QVariant> args = m_stateChangedSignalSpy->takeFirst(); + Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1)); + QCOMPARE(laststate, s); + s = qvariant_cast<Phonon::State>(args.at(0)); + QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState); + } + } + QCOMPARE(s, Phonon::PlayingState); + QCOMPARE(m_media->state(), Phonon::PlayingState); + m_success = true; +} + +void tst_MediaObject::_stopPlayback(Phonon::State currentState) +{ + QVERIFY(currentState != Phonon::ErrorState); + m_stateChangedSignalSpy->clear(); + Phonon::State s = m_media->state(); + QCOMPARE(s, currentState); + m_media->stop(); + while (s != Phonon::StoppedState) { + if (m_stateChangedSignalSpy->isEmpty()) { + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000)); + } + while (!m_stateChangedSignalSpy->isEmpty()) { + QList<QVariant> args = m_stateChangedSignalSpy->takeFirst(); + Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1)); + QCOMPARE(laststate, s); + s = qvariant_cast<Phonon::State>(args.at(0)); + if (s == Phonon::StoppedState) { + QVERIFY(m_stateChangedSignalSpy->isEmpty()); + break; + } + QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState); + } + } + QCOMPARE(s, Phonon::StoppedState); + QCOMPARE(m_media->state(), Phonon::StoppedState); + m_success = true; +} + +void tst_MediaObject::_pausePlayback() +{ + m_stateChangedSignalSpy->clear(); + Phonon::State s = m_media->state(); + m_media->pause(); + while (s != Phonon::PausedState) { + if (m_stateChangedSignalSpy->isEmpty()) { + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State))); + } + while (!m_stateChangedSignalSpy->isEmpty()) { + QList<QVariant> args = m_stateChangedSignalSpy->takeFirst(); + Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1)); + QCOMPARE(laststate, s); + s = qvariant_cast<Phonon::State>(args.at(0)); + if (s == Phonon::PausedState) { + QVERIFY(m_stateChangedSignalSpy->isEmpty()); + break; + } + QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState); + } + } + QCOMPARE(s, Phonon::PausedState); + QCOMPARE(m_media->state(), Phonon::PausedState); + m_success = true; +} + +void tst_MediaObject::initTestCase() +{ + QCoreApplication::setApplicationName("tst_MediaObject"); + + m_url = qgetenv("PHONON_TESTURL"); + m_media = new MediaObject(this); + connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(stateChanged(Phonon::State, Phonon::State))); + m_stateChangedSignalSpy = new QSignalSpy(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State))); + QVERIFY(m_stateChangedSignalSpy->isValid()); + m_stateChangedSignalSpy->clear(); + + if (m_url.isEmpty()) { + m_tmpFileName = QDir::toNativeSeparators(QDir::tempPath() + MEDIA_FILE); + QFile::remove(m_tmpFileName); + QVERIFY(QFile::copy(MEDIA_FILEPATH, m_tmpFileName)); + QFile::Permissions p = QFile::permissions(m_tmpFileName); + QFile::setPermissions(m_tmpFileName, p | QFile::WriteOther); + m_url = QUrl::fromLocalFile(m_tmpFileName); + } + + qDebug() << "Using url:" << m_url.toString(); + + // AudioOutput is needed else the backend might have no time source + AudioOutput *audioOutput = new AudioOutput(Phonon::MusicCategory, this); + //audioOutput->setVolume(0.0f); + + QSignalSpy totalTimeChangedSignalSpy(m_media, SIGNAL(totalTimeChanged(qint64))); + QVERIFY(m_media->queue().isEmpty()); + QCOMPARE(m_media->currentSource().type(), MediaSource::Empty); + QCOMPARE(m_media->state(), Phonon::LoadingState); + QCOMPARE(m_stateChangedSignalSpy->count(), 0); + + m_media->setCurrentSource(m_url); + QCOMPARE(m_media->currentSource().type(), MediaSource::Url); + QCOMPARE(m_media->currentSource().url(), m_url); + + int emits = m_stateChangedSignalSpy->count(); + Phonon::State s = m_media->state(); + if (s == Phonon::LoadingState) { + // still in LoadingState, there should be no state change + QCOMPARE(emits, 0); + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 6000); + emits = m_stateChangedSignalSpy->count(); + s = m_media->state(); + } + if (s != Phonon::LoadingState) { + // there should exactly be one state change + QCOMPARE(emits, 1); + QList<QVariant> args = m_stateChangedSignalSpy->takeFirst(); + Phonon::State newstate = qvariant_cast<Phonon::State>(args.at(0)); + Phonon::State oldstate = qvariant_cast<Phonon::State>(args.at(1)); + + QCOMPARE(Phonon::LoadingState, oldstate); + QCOMPARE(s, newstate); + if (Phonon::ErrorState == s) { +#ifdef Q_WS_WIN + if (m_media->errorString().contains(QLatin1String("no audio hardware is available"))) + QSKIP("On Windows we need an audio devide to perform the MediaObject tests", SkipAll); + else +#endif + QFAIL("Loading the URL put the MediaObject into the ErrorState. Check that PHONON_TESTURL is set to a valid URL."); + } + QCOMPARE(Phonon::StoppedState, s); + QCOMPARE(m_stateChangedSignalSpy->count(), 0); + + // check for totalTimeChanged signal + QVERIFY(totalTimeChangedSignalSpy.count() > 0); + args = totalTimeChangedSignalSpy.takeLast(); + QCOMPARE(m_media->totalTime(), castQVariantToInt64(args.at(0))); + } else { + QFAIL("Still in LoadingState after a stateChange signal was emitted. Cannot go on."); + } + + Path path = createPath(m_media, audioOutput); + QVERIFY(path.isValid()); + + + QCOMPARE(m_media->outputPaths().size(), 1); + QCOMPARE(audioOutput->inputPaths().size(), 1); + +} + +void tst_MediaObject::checkForDefaults() +{ + QCOMPARE(m_media->tickInterval(), qint32(0)); + QCOMPARE(m_media->prefinishMark(), qint32(0)); +} + +void tst_MediaObject::stopToStop() +{ + QCOMPARE(m_stateChangedSignalSpy->count(), 0); + QCOMPARE(m_media->state(), Phonon::StoppedState); + m_media->stop(); + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 2000); + QCOMPARE(m_stateChangedSignalSpy->count(), 0); + QCOMPARE(m_media->state(), Phonon::StoppedState); +} + +void tst_MediaObject::stopToPause() +{ + QCOMPARE(m_stateChangedSignalSpy->count(), 0); + QCOMPARE(m_media->state(), Phonon::StoppedState); + m_media->pause(); + if (m_stateChangedSignalSpy->isEmpty()) { + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 6000)); + } + QCOMPARE(m_stateChangedSignalSpy->count(), 1); + QCOMPARE(m_media->state(), Phonon::PausedState); +} + +void tst_MediaObject::stopToPlay() +{ + startPlayback(); + QTest::waitForSignal(m_media, SIGNAL(finished()), 1000); + stopPlayback(Phonon::PlayingState); +} + +void tst_MediaObject::playToPlay() +{ + startPlayback(); + + m_media->play(); + QCOMPARE(m_stateChangedSignalSpy->count(), 0); + QCOMPARE(m_media->state(), Phonon::PlayingState); + + stopPlayback(Phonon::PlayingState); +} + +void tst_MediaObject::playToPause() +{ + startPlayback(); + QCOMPARE(m_media->state(), Phonon::PlayingState); + pausePlayback(); + stopPlayback(Phonon::PausedState); +} + +void tst_MediaObject::playToStop() +{ + startPlayback(); + stopPlayback(Phonon::PlayingState); +} + +void tst_MediaObject::pauseToPause() +{ + startPlayback(); + pausePlayback(); + + m_media->pause(); + QCOMPARE(m_stateChangedSignalSpy->count(), 0); + QCOMPARE(m_media->state(), Phonon::PausedState); + + stopPlayback(Phonon::PausedState); +} + +void tst_MediaObject::pauseToPlay() +{ + startPlayback(); + pausePlayback(); + startPlayback2(Phonon::PausedState); + stopPlayback(Phonon::PlayingState); +} + +void tst_MediaObject::pauseToStop() +{ + startPlayback(); + pausePlayback(); + stopPlayback(Phonon::PausedState); +} + +void tst_MediaObject::testPrefinishMark() +{ + const qint32 requestedPrefinishMarkTime = 2000; + m_media->setPrefinishMark(requestedPrefinishMarkTime); + QCOMPARE(m_media->prefinishMark(), requestedPrefinishMarkTime); + QSignalSpy prefinishMarkReachedSpy(m_media, SIGNAL(prefinishMarkReached(qint32))); + QSignalSpy finishSpy(m_media, SIGNAL(finished())); + startPlayback(); + if (m_media->isSeekable()) { + m_media->seek(m_media->totalTime() - SEEK_BACKWARDS - requestedPrefinishMarkTime); // give it 4 seconds + } + int wait = 10000; + int total = 0; + while (prefinishMarkReachedSpy.count() == 0 && (m_media->state() == Phonon::PlayingState || + m_media->state() == Phonon::BufferingState)) { + wait = qMax(1000, wait / 2); + QTest::waitForSignal(m_media, SIGNAL(prefinishMarkReached(qint32)), wait); + total += wait; + if (total >= 60*1000) // we wait 1 minute + QFAIL("Timeout failure waiting for signal"); + } + // at this point the media should be about to finish playing + qint64 r = m_media->remainingTime(); + Phonon::State state = m_media->state(); + QCOMPARE(prefinishMarkReachedSpy.count(), 1); + const qint32 prefinishMark = castQVariantToInt32(prefinishMarkReachedSpy.first().at(0)); + QVERIFY(prefinishMark <= requestedPrefinishMarkTime + 150); // allow it to be up to 150ms too early + if (state == Phonon::PlayingState || state == Phonon::BufferingState) { + if (r > prefinishMark) { + qDebug() << "remainingTime =" << r; + QFAIL("remainingTime needs to be less than or equal to prefinishMark"); + } + QVERIFY(r <= prefinishMark); + QTest::waitForSignal(m_media, SIGNAL(finished()), 10000); + } else { + QVERIFY(prefinishMark >= 0); + } + QCOMPARE(finishSpy.count(), 1); +} + +void tst_MediaObject::enqueueMedia() +{ + m_media->enqueue(m_url); +} + +Q_DECLARE_METATYPE(Phonon::MediaSource) +void tst_MediaObject::testJustInTimeQueuing() +{ +#ifdef Q_OS_WINCE + QSKIP("crashes on Windows CE", SkipAll); +#endif + qRegisterMetaType<Phonon::MediaSource>("Phonon::MediaSource"); + QSignalSpy currentSourceChanged(m_media, SIGNAL(currentSourceChanged(const Phonon::MediaSource &))); + QSignalSpy finished(m_media, SIGNAL(finished())); + connect(m_media, SIGNAL(aboutToFinish()), SLOT(enqueueMedia())); + + startPlayback(); + if (m_media->isSeekable()) { + m_media->seek(m_media->totalTime() - SEEK_BACKWARDS); + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(aboutToFinish()), 6000)); + } else { + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(aboutToFinish()), 3000 + m_media->remainingTime())); + } + disconnect(m_media, SIGNAL(aboutToFinish()), this, SLOT(enqueueMedia())); + if (currentSourceChanged.isEmpty()) { + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(currentSourceChanged(const Phonon::MediaSource &)), 3000)); + } + QCOMPARE(currentSourceChanged.size(), 1); + QCOMPARE(finished.size(), 0); + QVERIFY(m_media->queue().isEmpty()); + stopPlayback(m_media->state()); +} + +void tst_MediaObject::testPauseOnFinish() +{ + startPlayback(); + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000); + QCOMPARE(m_media->state(), Phonon::PlayingState); + if (m_media->isSeekable() && m_media->totalTime() > 2000) + m_media->seek(m_media->totalTime() - 2000); + QTest::waitForSignal(m_media, SIGNAL(finished()), 4000); + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000); + + QCOMPARE(m_media->state(), Phonon::PausedState); + connect(m_media, SIGNAL(finished()), m_media, SLOT(stop())); + m_media->seek(m_media->totalTime() - 2000); + m_media->play(); + + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000); + QCOMPARE(m_media->state(), Phonon::PlayingState); + QTest::waitForSignal(m_media, SIGNAL(finished()), 4000); + QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000); + stopPlayback(Phonon::StoppedState); +} + +void tst_MediaObject::testReconnectBetweenTwoMediaObjects(){ + // Purpose: Test that phonon can handle swithing the same sink + // between different media objects. + + Phonon::MediaObject obj1; + Phonon::MediaObject obj2; + Phonon::AudioOutput audio1; + Phonon::Path p1 = Phonon::createPath(&obj1, &audio1); + QVERIFY(p1.isValid()); + + QVERIFY(p1.reconnect(&obj1, &audio1)); + QVERIFY(p1.isValid()); + QVERIFY(p1.reconnect(&obj2, &audio1)); + QVERIFY(p1.isValid()); + QVERIFY(p1.reconnect(&obj1, &audio1)); + QVERIFY(p1.isValid()); + + // Repeat the same test while playing: + QFile file(MEDIA_FILEPATH); + obj1.setCurrentSource(&file); + QFile file2(MEDIA_FILEPATH); + obj2.setCurrentSource(&file2); + obj1.play(); + obj2.play(); + + QVERIFY(p1.reconnect(&obj1, &audio1)); + QVERIFY(p1.isValid()); + QVERIFY(p1.reconnect(&obj2, &audio1)); + QVERIFY(p1.isValid()); + QVERIFY(p1.reconnect(&obj1, &audio1)); + QVERIFY(p1.isValid()); +} + +void tst_MediaObject::testPlayOnFinish() +{ + connect(m_media, SIGNAL(finished()), SLOT(setMediaAndPlay())); + startPlayback(); + if (m_media->isSeekable()) { + m_media->seek(m_media->totalTime() - SEEK_BACKWARDS); + QVERIFY(QTest::waitForSignal(this, SIGNAL(continueTestPlayOnFinish()), 6000)); + } else { + QVERIFY(QTest::waitForSignal(this, SIGNAL(continueTestPlayOnFinish()), 3000 + m_media->remainingTime())); + } + QTest::waitForSignal(m_media, SIGNAL(finished()), 1000); + stopPlayback(m_media->state()); +} + +void tst_MediaObject::testTickSignal() +{ + QTime start1; + QTime start2; +#ifdef Q_OS_WINCE //On Windows CE we only provide ticks above 400ms + for (qint32 tickInterval = 400; tickInterval <= 1000; tickInterval *= 2) +#else + for (qint32 tickInterval = 80; tickInterval <= 500; tickInterval *= 2) +#endif + { + QSignalSpy tickSpy(m_media, SIGNAL(tick(qint64))); + //qDebug() << "Test 20 ticks with an interval of" << tickInterval << "ms"; + m_media->setTickInterval(tickInterval); + QVERIFY(m_media->tickInterval() <= tickInterval); + QVERIFY(m_media->tickInterval() >= tickInterval/2); + QVERIFY(tickSpy.isEmpty()); + m_media->seek(0); //let's go back to the beginning + start1.start(); + startPlayback(); + start2.start(); + int lastCount = 0; + qint64 s1, s2 = start2.elapsed(); + while (tickSpy.count() < 20 && (m_media->state() == Phonon::PlayingState || m_media->state() == Phonon::BufferingState)) + { + if (tickSpy.count() > lastCount) + { + s1 = start1.elapsed(); + qint64 tickTime = castQVariantToInt64(tickSpy.last().at(0)); + lastCount = tickSpy.count(); + // s1 is the time from before the beginning of the playback to + // after the tick signal + // s2 is the time from after the beginning of the playback to + // before the tick signal + // so: s2 <= s1 + QVERIFY(tickTime <= m_media->currentTime()); + if (s1 + ALLOWED_TICK_INACCURACY < tickTime || s2 - ALLOWED_TICK_INACCURACY > tickTime) { + qDebug() + << "\n" << lastCount << "ticks have been received" + << "\ntime from before playback was started to after the tick signal was received:" << s1 << "ms" + << "\ntime from after playback was started to before the tick signal was received:" << s2 << "ms" + << "\nreported tick time:" << tickTime << "ms" + << "\nallowed inaccuracy: +/-" << ALLOWED_TICK_INACCURACY << "ms"; + for (int i = 0; i < tickSpy.count(); ++i) { + qDebug() << castQVariantToInt64(tickSpy[i].at(0)); + } + } + QVERIFY(s1 + ALLOWED_TICK_INACCURACY >= tickTime); + QVERIFY(s2 - ALLOWED_TICK_INACCURACY <= tickTime); +#ifndef Q_OS_WINCE + QVERIFY(s1 >= lastCount * m_media->tickInterval()); +#else + QVERIFY(s1 >= lastCount * m_media->tickInterval() - ALLOWED_TICK_INACCURACY); +#endif + if (s2 > (lastCount + 1) * m_media->tickInterval()) + QWARN(qPrintable(QString("%1. tick came too late: %2ms elapsed while this tick should have come before %3ms") + .arg(lastCount).arg(s2).arg((lastCount + 1) * m_media->tickInterval()))); + } else if (lastCount == 0 && s2 > 20 * m_media->tickInterval()) { + QFAIL("no tick signals are being received"); + } + s2 = start2.elapsed(); + QTest::waitForSignal(m_media, SIGNAL(tick(qint64)), 2000); + } +#ifndef Q_OS_WINCE //the shorter wave file is finished on Windows CE... + stopPlayback(Phonon::PlayingState); +#else + stopPlayback(m_media->state()); +#endif + } +} + +void tst_MediaObject::testSeek() +{ + m_media->seek(0); // let's seek back to the beginning + startPlayback(); + QTime timer; + timer.start(); + qint64 t = m_media->totalTime(); + qint64 c = m_media->currentTime(); + qint64 r = m_media->remainingTime(); + int elapsed = timer.elapsed(); + if (c + r > t + elapsed || c + r < t - elapsed) { + // qDebug() << "currentTime:" << c + // << "remainingTime:" << r + // << "totalTime:" << t; + QFAIL("currentTime + remainingTime doesn't come close enough to totalTime"); + } + + QVERIFY(c + r <= t + elapsed); + QVERIFY(c + r >= t - elapsed); + if (m_media->isSeekable()) + if (r > 0) + { + m_media->setTickInterval(20); + qint64 s = c + r / 2; + testOneSeek(s); + + s /= 2; + testOneSeek(s); + s = s * 3 / 2; + testOneSeek(s); + + pausePlayback(); + + s = s * 3 / 2; + testOneSeek(s); + s /= 2; + testOneSeek(s); + + m_media->setTickInterval(0); + + + stopPlayback(Phonon::PausedState); + return; + } + else + QWARN("didn't test seeking as the MediaObject reported a remaining size <= 0"); + else + QWARN("didn't test seeking as the MediaObject is not seekable"); + stopPlayback(Phonon::PlayingState); +} + + +void tst_MediaObject::setMediaAndPlay() +{ + m_stateChangedSignalSpy->clear(); + QCOMPARE(m_stateChangedSignalSpy->count(), 0); + + QSignalSpy totalTimeChangedSignalSpy(m_media, SIGNAL(totalTimeChanged(qint64))); + QVERIFY(m_media->currentSource().type() != MediaSource::Invalid); + Phonon::State state = m_media->state(); + QVERIFY(state == Phonon::StoppedState || state == Phonon::PlayingState); + m_media->setCurrentSource(m_url); + // before calling play() we better make sure that if play() finishes very fast that we don't get + // called again + disconnect(m_media, SIGNAL(finished()), this, SLOT(setMediaAndPlay())); + state = m_media->state(); + startPlayback2(state); + + emit continueTestPlayOnFinish(); +} + +void tst_MediaObject::testPlayBeforeFinish() +{ + startPlayback(); + QCOMPARE(m_stateChangedSignalSpy->size(), 0); + Phonon::State state = m_media->state(); + QCOMPARE(state, Phonon::PlayingState); + m_media->setCurrentSource(m_url); + m_media->play(); + if (m_stateChangedSignalSpy->isEmpty()) { + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000)); + } + // first (optional) state to reach is StoppedState + QList<QVariant> args = m_stateChangedSignalSpy->takeFirst(); + Phonon::State oldstate = qvariant_cast<Phonon::State>(args.at(1)); + QCOMPARE(oldstate, state); + state = qvariant_cast<Phonon::State>(args.at(0)); + if (state == Phonon::StoppedState) { + if (m_stateChangedSignalSpy->isEmpty()) { + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000)); + } + args = m_stateChangedSignalSpy->takeFirst(); + oldstate = qvariant_cast<Phonon::State>(args.at(1)); + QCOMPARE(oldstate, state); + state = qvariant_cast<Phonon::State>(args.at(0)); + } + // next LoadingState + QCOMPARE(state, Phonon::LoadingState); + if (m_stateChangedSignalSpy->isEmpty()) { + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000)); + } + // next either BufferingState or PlayingState + args = m_stateChangedSignalSpy->takeFirst(); + oldstate = qvariant_cast<Phonon::State>(args.at(1)); + QCOMPARE(oldstate, state); + state = qvariant_cast<Phonon::State>(args.at(0)); + if (state == Phonon::BufferingState) { + if (m_stateChangedSignalSpy->isEmpty()) { + QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000)); // buffering can take a while + } + args = m_stateChangedSignalSpy->takeFirst(); + oldstate = qvariant_cast<Phonon::State>(args.at(1)); + QCOMPARE(oldstate, state); + state = qvariant_cast<Phonon::State>(args.at(0)); + } +#ifdef Q_WS_MAC + // m_media->setCurrentSource(m_url) in phonon frontend will always call + // 'stop' on the backend before calling 'setSource'. So the QT7 backend + // goes into stop, and naturally remains there after setting the new source. + // So going into playing state cannot happend when the backend is synchronized. + // Thats the reason for the ifdef. + QCOMPARE(state, Phonon::StoppedState); +#else + stopPlayback(Phonon::PlayingState); +#endif +} + +void tst_MediaObject::cleanupTestCase() +{ + delete m_stateChangedSignalSpy; + delete m_media; +#ifdef Q_OS_WINCE + QTest::qWait(200); +#endif + if (!m_tmpFileName.isNull()) { + QVERIFY(QFile::remove(m_tmpFileName)); + } +} + +void tst_MediaObject::_testOneSeek(qint64 seekTo) +{ + qint64 t = m_media->totalTime(); + qint64 oldTime = m_media->currentTime(); + if (oldTime == seekTo) { + return; + } + + QTime seekDuration; + seekDuration.start(); + m_media->seek(seekTo); + + QVERIFY(oldTime == 0 || seekTo == 0 || m_media->currentTime() != 0); + + int bufferingTime = 0; + Phonon::State s = m_media->state(); + QTime timer; + if (s == Phonon::BufferingState) { + timer.start(); + } + QEventLoop loop; + connect(m_media, SIGNAL(tick(qint64)), &loop, SLOT(quit())); + connect(m_media, SIGNAL(stateChanged(Phonon::State,Phonon::State)), &loop, SLOT(quit())); + + qint64 c = m_media->currentTime(); + qint64 r = m_media->remainingTime(); + int elapsed = 0; + while (qAbs(c - seekTo) > ALLOWED_SEEK_INACCURACY){ + QTimer::singleShot(ALLOWED_TIME_FOR_SEEKING, &loop, SLOT(quit())); + + loop.exec(); + c = m_media->currentTime(); + r = m_media->remainingTime(); + if (s == Phonon::BufferingState) { + bufferingTime += timer.restart(); + } else { + timer.start(); + } + s = m_media->state(); + elapsed = seekDuration.elapsed(); + QVERIFY(elapsed - bufferingTime < (ALLOWED_TIME_FOR_SEEKING + SEEKING_TOLERANCE)); + } + + QVERIFY(c >= seekTo - ALLOWED_SEEK_INACCURACY); + if (s == Phonon::PausedState) { + QVERIFY(bufferingTime == 0); + elapsed = 0; + } + if (c > seekTo + ALLOWED_SEEK_INACCURACY + elapsed - bufferingTime) { + QFAIL("currentTime is greater than the requested time + the time that elapsed since the seek started."); + } + if (c + r > t + 200 || c + r < t - 200) { + QFAIL("currentTime + remainingTime doesn't come close enough to totalTime"); + } + m_success = true; +} + +#endif //QT_NO_PHONON + + +QTEST_MAIN(tst_MediaObject) + +#include "tst_mediaobject.moc" +// vim: sw=4 ts=4 |