Merge branch 'mmfphonon' into 4.6
[qt:qt.git] / tests / auto / mediaobject / tst_mediaobject.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 /*  This file is part of the KDE project
42     Copyright (C) 2006-2007 Matthias Kretz <kretz@kde.org>
43
44     This program is free software; you can redistribute it and/or
45     modify it under the terms of the GNU General Public License as
46     published by the Free Software Foundation; either version 2 of
47     the License, or (at your option) version 3.
48
49     This program is distributed in the hope that it will be useful,
50     but WITHOUT ANY WARRANTY; without even the implied warranty of
51     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
52     GNU General Public License for more details.
53
54     You should have received a copy of the GNU General Public License
55     along with this program.  If not, see <http://www.gnu.org/licenses/>.
56
57 */
58
59 #include <QtTest/QtTest>
60 #include <QtCore/QDate>
61 #include <QtCore/QDebug>
62 #include <QtCore/QObject>
63 #include <QtCore/QUrl>
64
65 #ifndef QT_NO_PHONON
66 #include <phonon/path.h>
67 #include <phonon/audiooutput.h>
68 #include <phonon/mediaobject.h>
69 #include <phonon/phononnamespace.h>
70 #include <phonon/audiooutput.h>
71 #include <phonon/seekslider.h>
72 #include <phonon/mediaobject.h>
73 #include <phonon/volumeslider.h>
74 #include <phonon/videowidget.h>
75 #include <phonon/backendcapabilities.h>
76
77 #include "qtesthelper.h"
78 #include <cstdlib>
79 #endif
80
81 #ifndef Q_WS_WIN
82 #include <unistd.h>
83 #endif
84
85 #ifdef Q_OS_WINCE
86 #define MEDIA_FILE "/sax.wav"
87 #define MEDIA_FILEPATH ":/media/sax.wav"
88 const qint64 SEEK_BACKWARDS = 2000;
89 const qint64 ALLOWED_TIME_FOR_SEEKING = 1500; // 1.5s
90 const qint64 SEEKING_TOLERANCE = 250;
91 #else 
92 #if defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(Q_OS_SYMBIAN)
93 #define MEDIA_FILE "/sax.mp3"
94 #define MEDIA_FILEPATH ":/media/sax.mp3"
95 #else
96 #define MEDIA_FILE "/sax.ogg"
97 #define MEDIA_FILEPATH ":/media/sax.ogg"
98 #endif
99 const qint64 SEEK_BACKWARDS = 4000;
100 const qint64 ALLOWED_TIME_FOR_SEEKING = 1000; // 1s
101 const qint64 SEEKING_TOLERANCE = 0;
102 #endif //Q_OS_WINCE
103
104 class tst_MediaObject : public QObject
105 {
106     Q_OBJECT
107     public:
108         tst_MediaObject()
109             : m_success(false)
110     {
111         qputenv("PHONON_GST_AUDIOSINK", "fake");
112     }
113
114 #ifndef QT_NO_PHONON
115
116     Q_SIGNALS:
117         void continueTestPlayOnFinish();
118
119     protected Q_SLOTS:
120         void enqueueMedia();
121         void setMediaAndPlay();
122         void stateChanged(Phonon::State, Phonon::State);
123     private Q_SLOTS:
124         void init();
125         void cleanup();
126
127         void testPlayFromResource();
128         void testPlayIllegalFile();
129         void initTestCase();
130         void checkForDefaults();
131
132         // state change tests
133         void stopToStop();
134         void stopToPause();
135         void stopToPlay();
136         void playToPlay();
137         void playToPause();
138         void playToStop();
139         void pauseToPause();
140         void pauseToPlay();
141         void pauseToStop();
142         void playSDP();
143
144         void testPrefinishMark();
145         void testSeek();
146         void testTickSignal();
147         void testJustInTimeQueuing();
148         void testPlayOnFinish();
149         void testPlayBeforeFinish();
150         void testPauseOnFinish();
151         void testReconnectBetweenTwoMediaObjects();
152         void volumeSliderMuteVisibility();
153         void cleanupTestCase();
154     private:
155         void _startPlayback(Phonon::State currentState = Phonon::StoppedState);
156         void _stopPlayback(Phonon::State currentState);
157         void _pausePlayback();
158         void _testOneSeek(qint64 seekTo);
159
160         QUrl m_url;
161         Phonon::MediaObject *m_media;
162         QSignalSpy *m_stateChangedSignalSpy;
163         QString m_tmpFileName;
164
165         static void copyMediaFile(const QString &original,
166                                   const QString &name,
167                                   QString &resultFilePath,
168                                   QUrl *const asURL = 0);
169 #endif //QT_NO_PHONON
170         bool m_success;
171 };
172
173 #ifndef QT_NO_PHONON
174
175 #define startPlayback() _startPlayback(); if (!m_success) return; m_success = false;
176 #define startPlayback2(currentState) _startPlayback(currentState); if (!m_success) return; m_success = false;
177 #define stopPlayback(currentState) _stopPlayback(currentState); if (!m_success) return; m_success = false;
178 #define pausePlayback() _pausePlayback(); if (!m_success) return; m_success = false;
179 #define testOneSeek(seekTo) _testOneSeek(seekTo); if (!m_success) return; m_success = false;
180
181 const qint64 ALLOWED_SEEK_INACCURACY = 300; // 0.3s
182 const qint64 ALLOWED_TICK_INACCURACY = 350; // allow +/- 350 ms inaccuracy
183
184 using namespace Phonon;
185
186 static qint64 castQVariantToInt64(const QVariant &variant)
187 {
188     return *reinterpret_cast<const qint64 *>(variant.constData());
189 }
190
191 static qint32 castQVariantToInt32(const QVariant &variant)
192 {
193     return *reinterpret_cast<const qint32 *>(variant.constData());
194 }
195
196 static const char *const red    = "\033[0;31m";
197 static const char *const green  = "\033[0;32m";
198 static const char *const yellow = "\033[0;33m";
199 static const char *const blue   = "\033[0;34m";
200 static const char *const purple = "\033[0;35m";
201 static const char *const cyan   = "\033[0;36m";
202 static const char *const white  = "\033[0;37m";
203 static const char *const normal = "\033[0m";
204
205 void tst_MediaObject::stateChanged(Phonon::State newstate, Phonon::State oldstate)
206 {
207     if (newstate == Phonon::ErrorState) {
208         QWARN(QByteArray(QByteArray(red) + ".......................................................... ") + QByteArray(QTest::toString(oldstate)) + " to " + QByteArray(QTest::toString(newstate)) + normal);
209     } else {
210         //qDebug() << ".........................................................." << cyan << QTest::toString(oldstate) << "to" << QTest::toString(newstate) << normal;
211     }
212 }
213
214 void tst_MediaObject::testPlayFromResource()
215 {
216 #ifdef Q_OS_SYMBIAN
217     QSKIP("Not implemented yet.", SkipAll);
218     return;
219 #endif
220
221     QFile file(MEDIA_FILEPATH);
222     MediaObject media;
223     media.setCurrentSource(&file);
224     QVERIFY(media.state() != Phonon::ErrorState);
225     if (media.state() != Phonon::StoppedState)
226         QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000);
227     QCOMPARE(media.state(), Phonon::StoppedState);
228     media.play();
229     if (media.state() != Phonon::PlayingState)
230         QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000);
231     QCOMPARE(media.state(), Phonon::PlayingState);
232 }
233
234 void tst_MediaObject::testPlayIllegalFile()
235 {
236     QString filename = QDir::tempPath() + QString("/test.wav");
237     QFile::remove(filename);
238     QFile file(filename);
239     file.open(QIODevice::WriteOnly);
240     for (int i=0;i<0xffff;i++) {
241         int r = qrand();
242         file.write((const char*)&r, 2);
243     }
244     file.close();
245     MediaObject media;
246     media.setCurrentSource(filename);
247     QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000);
248     QCOMPARE(media.state(), Phonon::ErrorState);
249     media.play();
250     QCOMPARE(media.state(), Phonon::ErrorState);
251     QFile::remove(filename);
252 }
253
254 void tst_MediaObject::init()
255 {
256     QCOMPARE(m_media->outputPaths().size(), 1);
257     if (m_media->state() == Phonon::ErrorState) {
258         m_media->setCurrentSource(m_url);
259         if (m_media->state() == Phonon::ErrorState) {
260             QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
261         }
262         if (m_media->state() == Phonon::LoadingState) {
263             QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
264         }
265         m_stateChangedSignalSpy->clear();
266     }
267 }
268
269 void tst_MediaObject::cleanup()
270 {
271     switch (m_media->state()) {
272     case Phonon::PlayingState:
273     case Phonon::BufferingState:
274     case Phonon::PausedState:
275         stopPlayback(m_media->state());
276         break;
277     default:
278         break;
279     }
280     m_stateChangedSignalSpy->clear();
281 }
282
283 void tst_MediaObject::_startPlayback(Phonon::State currentState)
284 {
285     m_stateChangedSignalSpy->clear();
286     Phonon::State s = m_media->state();
287     QCOMPARE(s, currentState);
288     m_media->play();
289     while (s != Phonon::PlayingState) {
290         if (m_stateChangedSignalSpy->isEmpty()) {
291             QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 3000);
292             QApplication::processEvents();
293         }
294         while (!m_stateChangedSignalSpy->isEmpty()) {
295             QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
296             Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1));
297             QCOMPARE(laststate, s);
298             s = qvariant_cast<Phonon::State>(args.at(0));
299             QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState);
300         }
301     }
302     QCOMPARE(s, Phonon::PlayingState);
303     QCOMPARE(m_media->state(), Phonon::PlayingState);
304     m_success = true;
305 }
306
307 void tst_MediaObject::_stopPlayback(Phonon::State currentState)
308 {
309     QVERIFY(currentState != Phonon::ErrorState);
310     m_stateChangedSignalSpy->clear();
311     Phonon::State s = m_media->state();
312     QCOMPARE(s, currentState);
313     m_media->stop();
314     while (s != Phonon::StoppedState) {
315         if (m_stateChangedSignalSpy->isEmpty()) {
316             QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000));
317         }
318         while (!m_stateChangedSignalSpy->isEmpty()) {
319             QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
320             Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1));
321             QCOMPARE(laststate, s);
322             s = qvariant_cast<Phonon::State>(args.at(0));
323             if (s == Phonon::StoppedState) {
324                 QVERIFY(m_stateChangedSignalSpy->isEmpty());
325                 break;
326             }
327             QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState);
328         }
329     }
330     QCOMPARE(s, Phonon::StoppedState);
331     QCOMPARE(m_media->state(), Phonon::StoppedState);
332     m_success = true;
333 }
334
335 void tst_MediaObject::_pausePlayback()
336 {
337     m_stateChangedSignalSpy->clear();
338     Phonon::State s = m_media->state();
339     m_media->pause();
340     while (s != Phonon::PausedState) {
341         if (m_stateChangedSignalSpy->isEmpty()) {
342             QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
343         }
344         while (!m_stateChangedSignalSpy->isEmpty()) {
345             QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
346             Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1));
347             QCOMPARE(laststate, s);
348             s = qvariant_cast<Phonon::State>(args.at(0));
349             if (s == Phonon::PausedState) {
350                 QVERIFY(m_stateChangedSignalSpy->isEmpty());
351                 break;
352             }
353             QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState);
354         }
355     }
356     QCOMPARE(s, Phonon::PausedState);
357     QCOMPARE(m_media->state(), Phonon::PausedState);
358     m_success = true;
359 }
360
361 /*!
362   Copies the file \a name to the testing area. The resulting file name path is
363   returned in resultFilePath, and also set as a URL in \a asURL.
364  */
365 void tst_MediaObject::copyMediaFile(const QString &original,
366                                     const QString &name,
367                                     QString &resultFilePath,
368                                     QUrl *const asURL)
369 {
370     resultFilePath = QDir::toNativeSeparators(QDir::tempPath() + name);
371     if (asURL)
372         *asURL = QUrl::fromLocalFile(resultFilePath);
373
374     QFile::remove(resultFilePath);
375     QVERIFY(QFile::copy(original, resultFilePath));
376     QFile::setPermissions(resultFilePath, QFile::permissions(resultFilePath) | QFile::WriteOther);
377 }
378
379 void tst_MediaObject::initTestCase()
380 {
381     QCoreApplication::setApplicationName("tst_MediaObject");
382     m_stateChangedSignalSpy = 0;
383     m_media = 0;
384
385 #ifdef Q_OS_WINCE
386     QString pluginsPath = QLibraryInfo::location(QLibraryInfo::PluginsPath);
387 #ifdef DEBUG
388     QVERIFY(QFile::exists(pluginsPath + "/phonon_backend/phonon_waveoutd4.dll") || QFile::exists(pluginsPath + "/phonon_backend/phonon_phonon_ds9d4.dll"));
389 #else
390     QVERIFY(QFile::exists(pluginsPath + "/phonon_backend/phonon_waveout4.dll") || QFile::exists(pluginsPath + "/phonon_backend/phonon_phonon_ds94.dll"));
391 #endif
392 #endif
393
394
395     m_url = qgetenv("PHONON_TESTURL");
396     m_media = new MediaObject(this);
397     connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(stateChanged(Phonon::State, Phonon::State)));
398     m_stateChangedSignalSpy = new QSignalSpy(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
399     QVERIFY(m_stateChangedSignalSpy->isValid());
400     m_stateChangedSignalSpy->clear();
401
402     if (m_url.isEmpty())
403         copyMediaFile(MEDIA_FILEPATH, MEDIA_FILE, m_tmpFileName, &m_url);
404     
405     qDebug() << "Using url:" << m_url.toString();
406
407     // AudioOutput is needed else the backend might have no time source
408     AudioOutput *audioOutput = new AudioOutput(Phonon::MusicCategory, this);
409     //audioOutput->setVolume(0.0f);
410
411     QSignalSpy totalTimeChangedSignalSpy(m_media, SIGNAL(totalTimeChanged(qint64)));
412     QVERIFY(m_media->queue().isEmpty());
413     QCOMPARE(m_media->currentSource().type(), MediaSource::Empty);
414     QCOMPARE(m_media->state(), Phonon::LoadingState);
415     QCOMPARE(m_stateChangedSignalSpy->count(), 0);
416
417     m_media->setCurrentSource(m_url);
418     QCOMPARE(m_media->currentSource().type(), MediaSource::Url);
419     QCOMPARE(m_media->currentSource().url(), m_url);
420
421     int emits = m_stateChangedSignalSpy->count();
422     Phonon::State s = m_media->state();
423     if (s == Phonon::LoadingState) {
424         // still in LoadingState, there should be no state change
425         QCOMPARE(emits, 0);
426         QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 6000);
427         emits = m_stateChangedSignalSpy->count();
428         s = m_media->state();
429     }
430     if (s != Phonon::LoadingState) {
431         // there should exactly be one state change
432         QCOMPARE(emits, 1);
433         QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
434         Phonon::State newstate = qvariant_cast<Phonon::State>(args.at(0));
435         Phonon::State oldstate = qvariant_cast<Phonon::State>(args.at(1));
436
437         QCOMPARE(Phonon::LoadingState, oldstate);
438         QCOMPARE(s, newstate);
439         if (Phonon::ErrorState == s) {
440 #ifdef Q_WS_WIN
441             if (m_media->errorString().contains(QLatin1String("no audio hardware is available")))
442                 QSKIP("On Windows we need an audio devide to perform the MediaObject tests", SkipAll); 
443             else
444 #endif
445             QFAIL("Loading the URL put the MediaObject into the ErrorState. Check that PHONON_TESTURL is set to a valid URL.");
446         }
447         QCOMPARE(Phonon::StoppedState, s);
448         QCOMPARE(m_stateChangedSignalSpy->count(), 0);
449
450         // check for totalTimeChanged signal
451         QVERIFY(totalTimeChangedSignalSpy.count() > 0);
452         args = totalTimeChangedSignalSpy.takeLast();
453         QCOMPARE(m_media->totalTime(), castQVariantToInt64(args.at(0)));
454     } else {
455         QFAIL("Still in LoadingState after a stateChange signal was emitted. Cannot go on.");
456     }
457
458     Path path = createPath(m_media, audioOutput);
459     QVERIFY(path.isValid());
460
461
462     QCOMPARE(m_media->outputPaths().size(), 1);
463     QCOMPARE(audioOutput->inputPaths().size(), 1);
464
465 }
466
467 void tst_MediaObject::checkForDefaults()
468 {
469     QCOMPARE(m_media->tickInterval(), qint32(0));
470     QCOMPARE(m_media->prefinishMark(), qint32(0));
471 }
472
473 void tst_MediaObject::stopToStop()
474 {
475     QCOMPARE(m_stateChangedSignalSpy->count(), 0);
476     QCOMPARE(m_media->state(), Phonon::StoppedState);
477     m_media->stop();
478     QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 2000);
479     QCOMPARE(m_stateChangedSignalSpy->count(), 0);
480     QCOMPARE(m_media->state(), Phonon::StoppedState);
481 }
482
483 void tst_MediaObject::stopToPause()
484 {
485     QCOMPARE(m_stateChangedSignalSpy->count(), 0);
486     QCOMPARE(m_media->state(), Phonon::StoppedState);
487     m_media->pause();
488     if (m_stateChangedSignalSpy->isEmpty()) {
489         QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 6000));
490     }
491     QCOMPARE(m_stateChangedSignalSpy->count(), 1);
492     QCOMPARE(m_media->state(), Phonon::PausedState);
493 }
494
495 void tst_MediaObject::stopToPlay()
496 {
497     startPlayback();
498     QTest::waitForSignal(m_media, SIGNAL(finished()), 1000);
499     stopPlayback(Phonon::PlayingState);
500 }
501
502 void tst_MediaObject::playToPlay()
503 {
504     startPlayback();
505
506     m_media->play();
507     QCOMPARE(m_stateChangedSignalSpy->count(), 0);
508     QCOMPARE(m_media->state(), Phonon::PlayingState);
509
510     stopPlayback(Phonon::PlayingState);
511 }
512
513 void tst_MediaObject::playToPause()
514 {
515     startPlayback();
516     QCOMPARE(m_media->state(), Phonon::PlayingState);
517     pausePlayback();
518     stopPlayback(Phonon::PausedState);
519 }
520
521 void tst_MediaObject::playToStop()
522 {
523     startPlayback();
524     stopPlayback(Phonon::PlayingState);
525 }
526
527 void tst_MediaObject::pauseToPause()
528 {
529     startPlayback();
530     pausePlayback();
531
532     m_media->pause();
533     QCOMPARE(m_stateChangedSignalSpy->count(), 0);
534     QCOMPARE(m_media->state(), Phonon::PausedState);
535
536     stopPlayback(Phonon::PausedState);
537 }
538
539 void tst_MediaObject::pauseToPlay()
540 {
541     startPlayback();
542     pausePlayback();
543     startPlayback2(Phonon::PausedState);
544     stopPlayback(Phonon::PlayingState);
545 }
546
547 void tst_MediaObject::pauseToStop()
548 {
549     startPlayback();
550     pausePlayback();
551     stopPlayback(Phonon::PausedState);
552 }
553
554 /*!
555
556     We attempt to play a SDP file. An SDP file essentially describes different
557     media streams and is hence a layer in front of the actual media(s).
558     Sometimes the backend handles the SDP file, in other cases not.
559
560     Some Phonon backends doesn't support SDP at all, ifdef appropriately. Real
561     Player and Helix, the two backends for Symbian, are known to support SDP.
562  */
563 void tst_MediaObject::playSDP()
564 {
565 #ifdef Q_OS_SYMBIAN
566     QString sdpFile;
567     copyMediaFile(QLatin1String(":/media/test.sdp"), QLatin1String("test.sdp"), sdpFile);
568
569     // Let's verify our test setup.
570     QVERIFY(QFileInfo(sdpFile).isReadable());
571
572     // We need a window in order to setup the video.
573     QWidget widget;
574     widget.show();
575
576     const MediaSource oldSource(m_media->currentSource());
577     const MediaSource sdpSource(sdpFile);
578     m_media->setCurrentSource(sdpSource);
579     if (m_media->state() != Phonon::StoppedState)
580         QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000);
581
582     // At this point we're in error state due to absent media, but it has now loaded the SDP:
583     QCOMPARE(m_media->errorString(), QString::fromLatin1("Buffering clip failed: Unknown error (-39)"));
584
585     // We cannot play the SDP, we can neither attempt to play it, because we
586     // won't get a state change from ErrorState to ErrorState, and hence block
587     // on a never occuring signal.
588     m_media->setCurrentSource(oldSource);
589
590 #else
591     QSKIP("Unsupported on this platform.", SkipAll);
592 #endif
593 }
594
595 void tst_MediaObject::testPrefinishMark()
596 {
597     const qint32 requestedPrefinishMarkTime = 2000;
598     m_media->setPrefinishMark(requestedPrefinishMarkTime);
599     QCOMPARE(m_media->prefinishMark(), requestedPrefinishMarkTime);
600     QSignalSpy prefinishMarkReachedSpy(m_media, SIGNAL(prefinishMarkReached(qint32)));
601     QSignalSpy finishSpy(m_media, SIGNAL(finished()));
602     startPlayback();
603     if (m_media->isSeekable()) {
604         m_media->seek(m_media->totalTime() - SEEK_BACKWARDS - requestedPrefinishMarkTime); // give it 4 seconds
605     }
606     int wait = 10000;
607     int total = 0;
608     while (prefinishMarkReachedSpy.count() == 0 && (m_media->state() == Phonon::PlayingState ||
609                 m_media->state() == Phonon::BufferingState)) {
610         wait = qMax(1000, wait / 2);
611         QTest::waitForSignal(m_media, SIGNAL(prefinishMarkReached(qint32)), wait);
612         total += wait;
613         if (total >= 60*1000) // we wait 1 minute
614             QFAIL("Timeout failure waiting for signal");
615     }
616     // at this point the media should be about to finish playing
617     qint64 r = m_media->remainingTime();
618     Phonon::State state = m_media->state();
619     QCOMPARE(prefinishMarkReachedSpy.count(), 1);
620     const qint32 prefinishMark = castQVariantToInt32(prefinishMarkReachedSpy.first().at(0));
621     QVERIFY(prefinishMark <= requestedPrefinishMarkTime + 150); // allow it to be up to 150ms too early
622     if (state == Phonon::PlayingState || state == Phonon::BufferingState) {
623         if (r > prefinishMark) {
624             qDebug() << "remainingTime =" << r;
625             QFAIL("remainingTime needs to be less than or equal to prefinishMark");
626         }
627         QVERIFY(r <= prefinishMark);
628         QTest::waitForSignal(m_media, SIGNAL(finished()), 10000);
629     } else {
630         QVERIFY(prefinishMark >= 0);
631     }
632     QCOMPARE(finishSpy.count(), 1);
633 }
634
635 void tst_MediaObject::enqueueMedia()
636 {
637     m_media->enqueue(m_url);
638 }
639
640 Q_DECLARE_METATYPE(Phonon::MediaSource)
641 void tst_MediaObject::testJustInTimeQueuing()
642 {
643 #ifdef Q_OS_WINCE
644     QSKIP("crashes on Windows CE", SkipAll);
645 #endif
646     qRegisterMetaType<Phonon::MediaSource>("Phonon::MediaSource");
647     QSignalSpy currentSourceChanged(m_media, SIGNAL(currentSourceChanged(const Phonon::MediaSource &)));
648     QSignalSpy finished(m_media, SIGNAL(finished()));
649     connect(m_media, SIGNAL(aboutToFinish()), SLOT(enqueueMedia()));
650
651     startPlayback();
652     if (m_media->isSeekable()) {
653         m_media->seek(m_media->totalTime() - SEEK_BACKWARDS);
654         QVERIFY(QTest::waitForSignal(m_media, SIGNAL(aboutToFinish()), 6000));
655     } else {
656         QVERIFY(QTest::waitForSignal(m_media, SIGNAL(aboutToFinish()), 3000 + m_media->remainingTime()));
657     }
658     disconnect(m_media, SIGNAL(aboutToFinish()), this, SLOT(enqueueMedia()));
659     if (currentSourceChanged.isEmpty()) {
660         QVERIFY(QTest::waitForSignal(m_media, SIGNAL(currentSourceChanged(const Phonon::MediaSource &)), 3000));
661     }
662     QCOMPARE(currentSourceChanged.size(), 1);
663     QCOMPARE(finished.size(), 0);
664     QVERIFY(m_media->queue().isEmpty());
665     stopPlayback(m_media->state());
666 }
667
668 void tst_MediaObject::testPauseOnFinish()
669 {
670     startPlayback();
671     QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000);
672     QCOMPARE(m_media->state(), Phonon::PlayingState);
673     if (m_media->isSeekable() && m_media->totalTime() > 2000)
674         m_media->seek(m_media->totalTime() - 2000);
675     QTest::waitForSignal(m_media, SIGNAL(finished()), 4000);
676     QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000);
677
678     QCOMPARE(m_media->state(), Phonon::PausedState);
679     connect(m_media, SIGNAL(finished()), m_media, SLOT(stop()));
680     m_media->seek(m_media->totalTime() - 2000);
681     m_media->play();
682
683     QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000);
684     QCOMPARE(m_media->state(), Phonon::PlayingState);
685     QTest::waitForSignal(m_media, SIGNAL(finished()), 4000);
686     QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000);
687     stopPlayback(Phonon::StoppedState);
688 }
689
690 void tst_MediaObject::testReconnectBetweenTwoMediaObjects(){
691     // Purpose: Test that phonon can handle swithing the same sink
692     // between different media objects.
693
694     Phonon::MediaObject obj1;
695     Phonon::MediaObject obj2;
696     Phonon::AudioOutput audio1;
697     Phonon::Path p1 = Phonon::createPath(&obj1, &audio1);
698     QVERIFY(p1.isValid());
699
700     QVERIFY(p1.reconnect(&obj1, &audio1));
701     QVERIFY(p1.isValid());
702     QVERIFY(p1.reconnect(&obj2, &audio1));
703     QVERIFY(p1.isValid());
704     QVERIFY(p1.reconnect(&obj1, &audio1));
705     QVERIFY(p1.isValid());
706
707     // Repeat the same test while playing:
708     QFile file(MEDIA_FILEPATH);
709     obj1.setCurrentSource(&file);
710     QFile file2(MEDIA_FILEPATH);
711     obj2.setCurrentSource(&file2);
712     obj1.play();
713     obj2.play();
714     
715     QVERIFY(p1.reconnect(&obj1, &audio1));
716     QVERIFY(p1.isValid());
717     QVERIFY(p1.reconnect(&obj2, &audio1));
718     QVERIFY(p1.isValid());
719     QVERIFY(p1.reconnect(&obj1, &audio1));
720     QVERIFY(p1.isValid());
721 }
722
723 void tst_MediaObject::testPlayOnFinish()
724 {
725     connect(m_media, SIGNAL(finished()), SLOT(setMediaAndPlay()));
726     startPlayback();
727     if (m_media->isSeekable()) {
728         m_media->seek(m_media->totalTime() - SEEK_BACKWARDS);
729         QVERIFY(QTest::waitForSignal(this, SIGNAL(continueTestPlayOnFinish()), 6000));
730     } else {
731         QVERIFY(QTest::waitForSignal(this, SIGNAL(continueTestPlayOnFinish()), 3000 + m_media->remainingTime()));
732     }
733     QTest::waitForSignal(m_media, SIGNAL(finished()), 1000);
734     stopPlayback(m_media->state());
735 }
736
737 void tst_MediaObject::testTickSignal()
738 {
739     QTime start1;
740     QTime start2;
741 #ifdef Q_OS_WINCE //On Windows CE we only provide ticks above 400ms
742     for (qint32 tickInterval = 400; tickInterval <= 1000; tickInterval *= 2)
743 #else
744     for (qint32 tickInterval = 80; tickInterval <= 500; tickInterval *= 2)
745 #endif
746     {
747         QSignalSpy tickSpy(m_media, SIGNAL(tick(qint64)));
748         //qDebug() << "Test 20 ticks with an interval of" <<  tickInterval << "ms";
749         m_media->setTickInterval(tickInterval);
750         QVERIFY(m_media->tickInterval() <= tickInterval);
751         QVERIFY(m_media->tickInterval() >= tickInterval/2);
752         QVERIFY(tickSpy.isEmpty());
753         m_media->seek(0); //let's go back to the beginning
754         start1.start();
755         startPlayback();
756         start2.start();
757         int lastCount = 0;
758         qint64 s1, s2 = start2.elapsed();
759         while (tickSpy.count() < 20 && (m_media->state() == Phonon::PlayingState || m_media->state() == Phonon::BufferingState))
760         {
761             if (tickSpy.count() > lastCount)
762             {
763                 s1 = start1.elapsed();
764                 qint64 tickTime = castQVariantToInt64(tickSpy.last().at(0));
765                 lastCount = tickSpy.count();
766                 // s1 is the time from before the beginning of the playback to
767                 // after the tick signal
768                 // s2 is the time from after the beginning of the playback to
769                 // before the tick signal
770                 // so: s2 <= s1
771                 QVERIFY(tickTime <= m_media->currentTime());
772                 if (s1 + ALLOWED_TICK_INACCURACY < tickTime || s2 - ALLOWED_TICK_INACCURACY > tickTime) {
773                     qDebug()
774                         << "\n" << lastCount << "ticks have been received"
775                         << "\ntime from before playback was started to after the tick signal was received:" << s1 << "ms"
776                         << "\ntime from after playback was started to before the tick signal was received:" << s2 << "ms"
777                         << "\nreported tick time:" << tickTime << "ms"
778                         << "\nallowed inaccuracy: +/-" << ALLOWED_TICK_INACCURACY << "ms";
779                     for (int i = 0; i < tickSpy.count(); ++i) {
780                         qDebug() << castQVariantToInt64(tickSpy[i].at(0));
781                     }
782                 }
783                 QVERIFY(s1 + ALLOWED_TICK_INACCURACY >= tickTime);
784                 QVERIFY(s2 - ALLOWED_TICK_INACCURACY <= tickTime);
785 #ifndef Q_OS_WINCE
786                 QVERIFY(s1 >= lastCount * m_media->tickInterval());
787 #else
788                 QVERIFY(s1 >= lastCount * m_media->tickInterval() - ALLOWED_TICK_INACCURACY);
789 #endif
790                 if (s2 > (lastCount + 1) * m_media->tickInterval())
791                     QWARN(qPrintable(QString("%1. tick came too late: %2ms elapsed while this tick should have come before %3ms")
792                             .arg(lastCount).arg(s2).arg((lastCount + 1) * m_media->tickInterval())));
793             } else if (lastCount == 0 && s2 > 20 * m_media->tickInterval()) {
794                 QFAIL("no tick signals are being received");
795             }
796             s2 = start2.elapsed();
797             QTest::waitForSignal(m_media, SIGNAL(tick(qint64)), 2000);
798         }
799 #ifndef Q_OS_WINCE //the shorter wave file is finished on Windows CE...
800         stopPlayback(Phonon::PlayingState);
801 #else
802         stopPlayback(m_media->state());
803 #endif
804     }
805 }
806
807 void tst_MediaObject::testSeek()
808 {
809     m_media->seek(0); // let's seek back to the beginning
810     startPlayback();
811     QTime timer;
812     timer.start();
813     qint64 t = m_media->totalTime();
814     qint64 c = m_media->currentTime();
815     qint64 r = m_media->remainingTime();
816     int elapsed = timer.elapsed();
817     if (c + r > t + elapsed || c + r < t - elapsed) {
818     //  qDebug() << "currentTime:" << c
819     //      << "remainingTime:" << r
820     //      << "totalTime:" << t;
821         QFAIL("currentTime + remainingTime doesn't come close enough to totalTime");
822     }
823
824     QVERIFY(c + r <= t + elapsed);
825     QVERIFY(c + r >= t - elapsed);
826     if (m_media->isSeekable())
827         if (r > 0)
828         {
829             m_media->setTickInterval(20);
830             qint64 s = c + r / 2;
831             testOneSeek(s);
832
833             s /= 2;
834             testOneSeek(s);
835             s = s * 3 / 2;
836             testOneSeek(s);
837
838             pausePlayback();
839
840             s = s * 3 / 2;
841             testOneSeek(s);
842             s /= 2;
843             testOneSeek(s);
844
845             m_media->setTickInterval(0);
846
847
848             stopPlayback(Phonon::PausedState);
849             return;
850         }
851         else
852             QWARN("didn't test seeking as the MediaObject reported a remaining size <= 0");
853     else
854         QWARN("didn't test seeking as the MediaObject is not seekable");
855     stopPlayback(Phonon::PlayingState);
856 }
857
858
859 void tst_MediaObject::setMediaAndPlay()
860 {
861     m_stateChangedSignalSpy->clear();
862     QCOMPARE(m_stateChangedSignalSpy->count(), 0);
863
864     QSignalSpy totalTimeChangedSignalSpy(m_media, SIGNAL(totalTimeChanged(qint64)));
865     QVERIFY(m_media->currentSource().type() != MediaSource::Invalid);
866     Phonon::State state = m_media->state();
867     QVERIFY(state == Phonon::StoppedState || state == Phonon::PlayingState || Phonon::PausedState);
868     m_media->setCurrentSource(m_url);
869     // before calling play() we better make sure that if play() finishes very fast that we don't get
870     // called again
871     disconnect(m_media, SIGNAL(finished()), this, SLOT(setMediaAndPlay()));
872     state = m_media->state();
873     startPlayback2(state);
874
875     emit continueTestPlayOnFinish();
876 }
877
878 void tst_MediaObject::testPlayBeforeFinish()
879 {
880     startPlayback();
881     QCOMPARE(m_stateChangedSignalSpy->size(), 0);
882     Phonon::State state = m_media->state();
883     QCOMPARE(state, Phonon::PlayingState);
884     m_media->setCurrentSource(m_url);
885     m_media->play();
886     if (m_stateChangedSignalSpy->isEmpty()) {
887         QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000));
888     }
889     // first (optional) state to reach is StoppedState
890     QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
891     Phonon::State oldstate = qvariant_cast<Phonon::State>(args.at(1));
892     QCOMPARE(oldstate, state);
893     state = qvariant_cast<Phonon::State>(args.at(0));
894     if (state == Phonon::StoppedState) {
895         if (m_stateChangedSignalSpy->isEmpty()) {
896             QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000));
897         }
898         args = m_stateChangedSignalSpy->takeFirst();
899         oldstate = qvariant_cast<Phonon::State>(args.at(1));
900         QCOMPARE(oldstate, state);
901         state = qvariant_cast<Phonon::State>(args.at(0));
902     }
903     // next LoadingState
904     QCOMPARE(state, Phonon::LoadingState);
905     if (m_stateChangedSignalSpy->isEmpty()) {
906         QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000));
907     }
908     // next either BufferingState or PlayingState
909     args = m_stateChangedSignalSpy->takeFirst();
910     oldstate = qvariant_cast<Phonon::State>(args.at(1));
911     QCOMPARE(oldstate, state);
912     state = qvariant_cast<Phonon::State>(args.at(0));
913     if (state == Phonon::BufferingState) {
914         if (m_stateChangedSignalSpy->isEmpty()) {
915             QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000)); // buffering can take a while
916         }
917         args = m_stateChangedSignalSpy->takeFirst();
918         oldstate = qvariant_cast<Phonon::State>(args.at(1));
919         QCOMPARE(oldstate, state);
920         state = qvariant_cast<Phonon::State>(args.at(0));
921     }
922 #ifdef Q_WS_MAC
923     // m_media->setCurrentSource(m_url) in phonon frontend will always call
924     // 'stop' on the backend before calling 'setSource'. So the QT7 backend
925     // goes into stop, and naturally remains there after setting the new source.
926     // So going into playing state cannot happend when the backend is synchronized.
927     // Thats the reason for the ifdef.
928     QCOMPARE(state, Phonon::StoppedState);
929 #else
930     stopPlayback(Phonon::PlayingState);
931 #endif
932 }
933
934 void tst_MediaObject::cleanupTestCase()
935 {
936     if (m_stateChangedSignalSpy)
937       delete m_stateChangedSignalSpy;
938     if (m_media)
939       delete m_media;
940 #ifdef Q_OS_WINCE
941     QTest::qWait(200);
942 #endif
943     if (!m_tmpFileName.isNull()) {
944         QVERIFY(QFile::remove(m_tmpFileName));
945     }
946 }
947
948 void tst_MediaObject::_testOneSeek(qint64 seekTo)
949 {
950    qint64 t = m_media->totalTime();
951     qint64 oldTime = m_media->currentTime();
952     if (oldTime == seekTo) {
953         return;
954     }
955
956     QTime seekDuration;
957     seekDuration.start();
958     m_media->seek(seekTo);
959
960     QVERIFY(oldTime == 0 || seekTo == 0 || m_media->currentTime() != 0);
961
962     int bufferingTime = 0;
963     Phonon::State s = m_media->state();
964     QTime timer;
965     if (s == Phonon::BufferingState) {
966         timer.start();
967     }
968     QEventLoop loop;
969     connect(m_media, SIGNAL(tick(qint64)), &loop, SLOT(quit()));
970     connect(m_media, SIGNAL(stateChanged(Phonon::State,Phonon::State)), &loop, SLOT(quit()));
971
972     qint64 c = m_media->currentTime();
973     qint64 r = m_media->remainingTime();
974     int elapsed = 0;
975     while (qAbs(c - seekTo) > ALLOWED_SEEK_INACCURACY){
976         QTimer::singleShot(ALLOWED_TIME_FOR_SEEKING, &loop, SLOT(quit()));
977
978         loop.exec();
979         c = m_media->currentTime();
980         r = m_media->remainingTime();
981         if (s == Phonon::BufferingState) {
982             bufferingTime += timer.restart();
983         } else {
984             timer.start();
985         }
986         s = m_media->state();
987         elapsed = seekDuration.elapsed();
988         QVERIFY(elapsed - bufferingTime < (ALLOWED_TIME_FOR_SEEKING + SEEKING_TOLERANCE));
989     }
990
991     QVERIFY(c >= seekTo - ALLOWED_SEEK_INACCURACY);
992     if (s == Phonon::PausedState) {
993         QVERIFY(bufferingTime == 0);
994         elapsed = 0;
995     }
996     if (c > seekTo + ALLOWED_SEEK_INACCURACY + elapsed - bufferingTime) {
997         QFAIL("currentTime is greater than the requested time + the time that elapsed since the seek started.");
998     }
999     if (c + r > t + 200 || c + r < t - 200) {
1000         QFAIL("currentTime + remainingTime doesn't come close enough to totalTime");
1001     }
1002     m_success = true;
1003 }
1004
1005 void tst_MediaObject::volumeSliderMuteVisibility()
1006 {
1007     //this test doesn't really belong to mediaobject
1008     // ### see if we should create a realy Phonon::VolumeSlider autotest
1009     Phonon::VolumeSlider slider;
1010     QVERIFY(slider.isMuteVisible()); // that is the default value
1011     slider.setMuteVisible(true);
1012     QVERIFY(slider.isMuteVisible());
1013
1014     //let's check that changing the visibility of the slider itself
1015     //doesn't change what the slider reports
1016     slider.setVisible(false);
1017     QVERIFY(slider.isMuteVisible());
1018     slider.setVisible(true);
1019
1020     slider.setMuteVisible(false);
1021     QVERIFY(!slider.isMuteVisible());
1022     slider.setMuteVisible(true);
1023     QVERIFY(slider.isMuteVisible());
1024 }
1025
1026
1027 #endif //QT_NO_PHONON
1028
1029
1030 QTEST_MAIN(tst_MediaObject)
1031
1032 #include "tst_mediaobject.moc"
1033 // vim: sw=4 ts=4