/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (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 Technology Preview License Agreement accompanying
** this package.
**
** 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.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@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
#if defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(Q_OS_SYMBIAN)
#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 volumeSliderMuteVisibility();
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()
{
#ifdef Q_OS_SYMBIAN
QSKIP("Not implemented yet.", SkipAll);
return;
#endif
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_stateChangedSignalSpy = 0;
m_media = 0;
#ifdef Q_OS_WINCE
QString pluginsPath = QLibraryInfo::location(QLibraryInfo::PluginsPath);
#ifdef DEBUG
QVERIFY(QFile::exists(pluginsPath + "/phonon_backend/phonon_waveoutd4.dll") || QFile::exists(pluginsPath + "/phonon_backend/phonon_phonon_ds9d4.dll"));
#else
QVERIFY(QFile::exists(pluginsPath + "/phonon_backend/phonon_waveout4.dll") || QFile::exists(pluginsPath + "/phonon_backend/phonon_phonon_ds94.dll"));
#endif
#endif
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 || Phonon::PausedState);
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()
{
if (m_stateChangedSignalSpy)
delete m_stateChangedSignalSpy;
if (m_media)
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;
}
void tst_MediaObject::volumeSliderMuteVisibility()
{
//this test doesn't really belong to mediaobject
// ### see if we should create a realy Phonon::VolumeSlider autotest
Phonon::VolumeSlider slider;
QVERIFY(slider.isMuteVisible()); // that is the default value
slider.setMuteVisible(true);
QVERIFY(slider.isMuteVisible());
//let's check that changing the visibility of the slider itself
//doesn't change what the slider reports
slider.setVisible(false);
QVERIFY(slider.isMuteVisible());
slider.setVisible(true);
slider.setMuteVisible(false);
QVERIFY(!slider.isMuteVisible());
slider.setMuteVisible(true);
QVERIFY(slider.isMuteVisible());
}
#endif //QT_NO_PHONON
QTEST_MAIN(tst_MediaObject)
#include "tst_mediaobject.moc"
// vim: sw=4 ts=4