| 1 | /*  This file is part of the KDE project.
 | 
|---|
| 2 | 
 | 
|---|
| 3 | Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
 | 
|---|
| 4 | 
 | 
|---|
| 5 | This library is free software: you can redistribute it and/or modify
 | 
|---|
| 6 | it under the terms of the GNU Lesser General Public License as published by
 | 
|---|
| 7 | the Free Software Foundation, either version 2.1 or 3 of the License.
 | 
|---|
| 8 | 
 | 
|---|
| 9 | This library is distributed in the hope that it will be useful,
 | 
|---|
| 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
|---|
| 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
|---|
| 12 | GNU Lesser General Public License for more details.
 | 
|---|
| 13 | 
 | 
|---|
| 14 | You should have received a copy of the GNU Lesser General Public License
 | 
|---|
| 15 | along with this library.  If not, see <http://www.gnu.org/licenses/>.
 | 
|---|
| 16 | */
 | 
|---|
| 17 | 
 | 
|---|
| 18 | #include "mediaobject.h"
 | 
|---|
| 19 | #include "audiooutput.h"
 | 
|---|
| 20 | 
 | 
|---|
| 21 | #include <QtCore/QVector>
 | 
|---|
| 22 | #include <QtCore/QTimerEvent>
 | 
|---|
| 23 | #include <QtCore/QTimer>
 | 
|---|
| 24 | #include <QtCore/QTime>
 | 
|---|
| 25 | #include <QtCore/QLibrary>
 | 
|---|
| 26 | #include <QtCore/QUrl>
 | 
|---|
| 27 | #include <QtCore/QWriteLocker>
 | 
|---|
| 28 | 
 | 
|---|
| 29 | #include <phonon/streaminterface.h>
 | 
|---|
| 30 | 
 | 
|---|
| 31 | 
 | 
|---|
| 32 | #define WAVEHEADER_OFFSET_FORMATTAG        20
 | 
|---|
| 33 | #define WAVEHEADER_OFFSET_CHANNELS         22
 | 
|---|
| 34 | #define WAVEHEADER_OFFSET_SAMPLESPERSEC    24
 | 
|---|
| 35 | #define WAVEHEADER_OFFSET_AVGBYTESPERSEC   28
 | 
|---|
| 36 | #define WAVEHEADER_OFFSET_BLOCKALIGN       32
 | 
|---|
| 37 | #define WAVEHEADER_OFFSET_BITSPERSAMPLE    34
 | 
|---|
| 38 | #define WAVEHEADER_OFFSET_DATA             44
 | 
|---|
| 39 | #define WAVEHEADER_SIZE                    WAVEHEADER_OFFSET_DATA
 | 
|---|
| 40 | 
 | 
|---|
| 41 | QT_BEGIN_NAMESPACE
 | 
|---|
| 42 | 
 | 
|---|
| 43 | namespace Phonon
 | 
|---|
| 44 | {
 | 
|---|
| 45 |     namespace WaveOut
 | 
|---|
| 46 |     {        
 | 
|---|
| 47 |         static unsigned int buffer_size = (16 * 1024 * 4);
 | 
|---|
| 48 | 
 | 
|---|
| 49 |         QString getErrorText(MMRESULT error)
 | 
|---|
| 50 |         {
 | 
|---|
| 51 |             ushort b[256];
 | 
|---|
| 52 |             waveOutGetErrorText(error, (LPWSTR)b, 256);
 | 
|---|
| 53 |             return QString((const QChar *)b);
 | 
|---|
| 54 |         }
 | 
|---|
| 55 | 
 | 
|---|
| 56 |         class WorkerThread : public QThread
 | 
|---|
| 57 |         {
 | 
|---|
| 58 |          Q_OBJECT
 | 
|---|
| 59 |          public slots:
 | 
|---|
| 60 |               void stream(QIODevice *file, QByteArray *buffer, bool *finished);
 | 
|---|
| 61 |         };
 | 
|---|
| 62 | 
 | 
|---|
| 63 |         void WorkerThread::stream(QIODevice *ioStream, QByteArray *buffer, bool *finished)
 | 
|---|
| 64 |         {
 | 
|---|
| 65 |             (*finished) = false;
 | 
|---|
| 66 |             memset((void*) buffer->data(), 0, buffer->size());
 | 
|---|
| 67 |             qint64 i = ioStream->read(buffer->data(), buffer_size);
 | 
|---|
| 68 |             buffer->resize(i);
 | 
|---|
| 69 |             (*finished) = true;
 | 
|---|
| 70 |         }
 | 
|---|
| 71 | 
 | 
|---|
| 72 | 
 | 
|---|
| 73 |         void QT_WIN_CALLBACK MediaObject::WaveOutCallBack(HWAVEOUT m_hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
 | 
|---|
| 74 |         {
 | 
|---|
| 75 |             Q_UNUSED(m_hWaveOut);
 | 
|---|
| 76 |             Q_UNUSED(dwInstance);
 | 
|---|
| 77 |             Q_UNUSED(dwParam2);
 | 
|---|
| 78 | 
 | 
|---|
| 79 |             switch(uMsg)
 | 
|---|
| 80 |             {
 | 
|---|
| 81 |             case WOM_OPEN:
 | 
|---|
| 82 |                 break;
 | 
|---|
| 83 |             case WOM_DONE:
 | 
|---|
| 84 |                 {
 | 
|---|
| 85 |                     WAVEHDR *waveHeader = (WAVEHDR*)dwParam1;
 | 
|---|
| 86 |                     MediaObject* mediaObject = reinterpret_cast<MediaObject *>(waveHeader->dwUser);
 | 
|---|
| 87 |                     if (mediaObject) {
 | 
|---|
| 88 |                         mediaObject->swapBuffers();
 | 
|---|
| 89 |                     }
 | 
|---|
| 90 |                 }
 | 
|---|
| 91 |                 break;
 | 
|---|
| 92 |             case WOM_CLOSE:
 | 
|---|
| 93 |                 break;
 | 
|---|
| 94 |             }
 | 
|---|
| 95 |         }
 | 
|---|
| 96 | 
 | 
|---|
| 97 |         class StreamReader : public Phonon::StreamInterface
 | 
|---|
| 98 |         {
 | 
|---|
| 99 |         public:
 | 
|---|
| 100 |               StreamReader(QObject *parent, const Phonon::MediaSource &source) :
 | 
|---|
| 101 |                   m_seekable(false), m_pos(0), m_size(-1)
 | 
|---|
| 102 |               {
 | 
|---|
| 103 |                   Q_UNUSED(parent);
 | 
|---|
| 104 |                   connectToSource(source);
 | 
|---|
| 105 |               }
 | 
|---|
| 106 | 
 | 
|---|
| 107 |               //for Phonon::StreamInterface
 | 
|---|
| 108 |               void writeData(const QByteArray &data)
 | 
|---|
| 109 |               {
 | 
|---|
| 110 |                   QWriteLocker locker(&m_lock);
 | 
|---|
| 111 |                   m_pos += data.size();
 | 
|---|
| 112 |                   m_buffer += data;
 | 
|---|
| 113 |               }
 | 
|---|
| 114 | 
 | 
|---|
| 115 |               void endOfData()
 | 
|---|
| 116 |               {
 | 
|---|
| 117 |               }
 | 
|---|
| 118 | 
 | 
|---|
| 119 |               void setStreamSize(qint64 newSize)
 | 
|---|
| 120 |               {
 | 
|---|
| 121 |                   QWriteLocker locker(&m_lock);
 | 
|---|
| 122 |                   m_size = newSize;
 | 
|---|
| 123 |               }
 | 
|---|
| 124 | 
 | 
|---|
| 125 |               qint64 streamSize() const
 | 
|---|
| 126 |               {
 | 
|---|
| 127 |                   QReadLocker locker(&m_lock);
 | 
|---|
| 128 |                   return m_size;
 | 
|---|
| 129 |               }
 | 
|---|
| 130 | 
 | 
|---|
| 131 |               void setStreamSeekable(bool s)
 | 
|---|
| 132 |               {
 | 
|---|
| 133 |                   QWriteLocker locker(&m_lock);
 | 
|---|
| 134 |                   m_seekable = s;
 | 
|---|
| 135 |               }
 | 
|---|
| 136 | 
 | 
|---|
| 137 |               bool streamSeekable() const
 | 
|---|
| 138 |               {
 | 
|---|
| 139 |                   QReadLocker locker(&m_lock);
 | 
|---|
| 140 |                   return m_seekable;
 | 
|---|
| 141 |               }
 | 
|---|
| 142 | 
 | 
|---|
| 143 |               void setCurrentPos(qint64 pos)
 | 
|---|
| 144 |               {
 | 
|---|
| 145 |                   QWriteLocker locker(&m_lock);
 | 
|---|
| 146 |                   m_pos = pos;
 | 
|---|
| 147 |                   seekStream(pos);
 | 
|---|
| 148 |                   m_buffer.clear();
 | 
|---|
| 149 |               }
 | 
|---|
| 150 | 
 | 
|---|
| 151 |               qint64 currentPos() const
 | 
|---|
| 152 |               {
 | 
|---|
| 153 |                   QReadLocker locker(&m_lock);
 | 
|---|
| 154 |                   return m_pos;
 | 
|---|
| 155 |               }
 | 
|---|
| 156 | 
 | 
|---|
| 157 |               int currentBufferSize() const
 | 
|---|
| 158 |               {
 | 
|---|
| 159 |                   QReadLocker locker(&m_lock);
 | 
|---|
| 160 |                   return m_buffer.size();
 | 
|---|
| 161 |               }
 | 
|---|
| 162 | 
 | 
|---|
| 163 |             //for Phonon::StreamInterface
 | 
|---|
| 164 |             QByteArray m_buffer;
 | 
|---|
| 165 |             bool m_seekable;
 | 
|---|
| 166 |             qint64 m_pos;
 | 
|---|
| 167 |             qint64 m_size;
 | 
|---|
| 168 |             mutable QReadWriteLock m_lock;
 | 
|---|
| 169 |         };
 | 
|---|
| 170 | 
 | 
|---|
| 171 |         class IOWrapper : public QIODevice {
 | 
|---|
| 172 |         public:
 | 
|---|
| 173 |             IOWrapper(QObject *parent, const Phonon::MediaSource &source) : m_streamReader(this, source)
 | 
|---|
| 174 |             {
 | 
|---|
| 175 |                 Q_UNUSED(parent);
 | 
|---|
| 176 |                 setOpenMode(QIODevice::ReadOnly);
 | 
|---|
| 177 |             }
 | 
|---|
| 178 |             bool seek(qint64 pos);
 | 
|---|
| 179 |             qint64 size() const;
 | 
|---|
| 180 |             qint64 pos();
 | 
|---|
| 181 |             bool isReadable() const;
 | 
|---|
| 182 |          protected:
 | 
|---|
| 183 |             qint64 readData (char * data, qint64 maxSize);
 | 
|---|
| 184 |             qint64 writeData(const char *,qint64);
 | 
|---|
| 185 |         private:
 | 
|---|
| 186 |             StreamReader m_streamReader;
 | 
|---|
| 187 |         };
 | 
|---|
| 188 | 
 | 
|---|
| 189 |         bool IOWrapper::isReadable () const
 | 
|---|
| 190 |         {
 | 
|---|
| 191 |             return true;
 | 
|---|
| 192 |         }
 | 
|---|
| 193 | 
 | 
|---|
| 194 |         qint64 IOWrapper::pos()
 | 
|---|
| 195 |         {
 | 
|---|
| 196 |             return (m_streamReader.streamSeekable() ? m_streamReader.currentPos() : 0);
 | 
|---|
| 197 |         }
 | 
|---|
| 198 | 
 | 
|---|
| 199 |         bool IOWrapper::seek( qint64 pos)
 | 
|---|
| 200 |         {
 | 
|---|
| 201 |             if (!m_streamReader.streamSeekable())
 | 
|---|
| 202 |                 return false;
 | 
|---|
| 203 |             m_streamReader.setCurrentPos(pos);
 | 
|---|
| 204 |             return true;
 | 
|---|
| 205 |         }
 | 
|---|
| 206 | 
 | 
|---|
| 207 |         qint64 IOWrapper::size() const
 | 
|---|
| 208 |         {
 | 
|---|
| 209 |             return m_streamReader.streamSize();
 | 
|---|
| 210 |         }
 | 
|---|
| 211 | 
 | 
|---|
| 212 |         qint64 IOWrapper::readData(char * data, qint64 maxSize)
 | 
|---|
| 213 |         {
 | 
|---|
| 214 |             int oldSize = m_streamReader.currentBufferSize();
 | 
|---|
| 215 |             while (m_streamReader.currentBufferSize() < maxSize) {
 | 
|---|
| 216 |                 m_streamReader.needData();
 | 
|---|
| 217 |                 if (oldSize == m_streamReader.currentBufferSize()) {
 | 
|---|
| 218 |                     break; //we didn't get any data
 | 
|---|
| 219 |                 }
 | 
|---|
| 220 |                 oldSize = m_streamReader.currentBufferSize();
 | 
|---|
| 221 |             }
 | 
|---|
| 222 | 
 | 
|---|
| 223 |             qint64 bytesRead = qMin(qint64(m_streamReader.currentBufferSize()), maxSize);
 | 
|---|
| 224 |             {
 | 
|---|
| 225 |                 QWriteLocker locker(&m_streamReader.m_lock);
 | 
|---|
| 226 |                 qMemCopy(data, m_streamReader.m_buffer.data(), bytesRead);
 | 
|---|
| 227 |                 //truncate the buffer
 | 
|---|
| 228 |                 m_streamReader.m_buffer = m_streamReader.m_buffer.mid(bytesRead);
 | 
|---|
| 229 |             }
 | 
|---|
| 230 |             return bytesRead;
 | 
|---|
| 231 |         }
 | 
|---|
| 232 | 
 | 
|---|
| 233 |         qint64 IOWrapper::writeData(const char *,qint64)
 | 
|---|
| 234 |         {
 | 
|---|
| 235 |             return 0;
 | 
|---|
| 236 |         }
 | 
|---|
| 237 | 
 | 
|---|
| 238 |         MediaObject::MediaObject(QObject *parent) : m_file(0), m_stream(0),
 | 
|---|
| 239 |                                                     m_hWaveOut(0), m_nextBufferIndex(1), 
 | 
|---|
| 240 |                                                     m_mediaSize(-1), m_bufferingFinished(0),
 | 
|---|
| 241 |                                                     m_paused(0), m_tickInterval(0),
 | 
|---|
| 242 |                                                     m_hasNextSource(0), m_hasSource(0),
 | 
|---|
| 243 |                                                     m_sourceIsValid(0), m_errorType(Phonon::NoError),
 | 
|---|
| 244 |                                                     m_currentTime(0), m_transitionTime(0),
 | 
|---|
| 245 |                                                     m_tick(0), m_volume(100), m_prefinishMark(0),
 | 
|---|
| 246 |                                                     m_tickIntervalResolution(0), m_bufferPrepared(0),
 | 
|---|
| 247 |                                                     m_stopped(0)
 | 
|---|
| 248 |         {
 | 
|---|
| 249 |             m_thread = new WorkerThread();
 | 
|---|
| 250 |             connect(this, SIGNAL(outOfData(QIODevice*,QByteArray*,bool*)), m_thread, SLOT(stream(QIODevice*,QByteArray*,bool*)));
 | 
|---|
| 251 |             m_thread->start();
 | 
|---|
| 252 |             m_soundBuffer1.waveHeader = new WAVEHDR;
 | 
|---|
| 253 |             m_soundBuffer2.waveHeader = new WAVEHDR;
 | 
|---|
| 254 |             setParent(parent);
 | 
|---|
| 255 |             setState(Phonon::LoadingState);            
 | 
|---|
| 256 |         }
 | 
|---|
| 257 | 
 | 
|---|
| 258 |         MediaObject::~MediaObject()
 | 
|---|
| 259 |         {
 | 
|---|
| 260 |             stop();
 | 
|---|
| 261 |             disconnect(this, SIGNAL(outOfData(QIODevice*,QByteArray*,bool*)), m_thread, SLOT(stream(QIODevice*,QByteArray*,bool*)));
 | 
|---|
| 262 |             do { //The event loop of m_thread might not be started, yet
 | 
|---|
| 263 |                 m_thread->quit(); //If the event loop is not started yet quit() does nothing
 | 
|---|
| 264 |                 m_thread->wait(100);
 | 
|---|
| 265 |             } while (m_thread->isRunning());
 | 
|---|
| 266 |             delete m_thread;
 | 
|---|
| 267 |             deleteValidWaveOutDevice();
 | 
|---|
| 268 |             delete m_soundBuffer1.waveHeader;
 | 
|---|
| 269 |             delete m_soundBuffer2.waveHeader;
 | 
|---|
| 270 |         }
 | 
|---|
| 271 | 
 | 
|---|
| 272 |         Phonon::State MediaObject::state() const
 | 
|---|
| 273 |         {
 | 
|---|
| 274 |            return m_state;
 | 
|---|
| 275 |         }
 | 
|---|
| 276 | 
 | 
|---|
| 277 |         bool MediaObject::hasVideo() const
 | 
|---|
| 278 |         {
 | 
|---|
| 279 |             return false;
 | 
|---|
| 280 |         }
 | 
|---|
| 281 | 
 | 
|---|
| 282 |         bool MediaObject::isSeekable() const
 | 
|---|
| 283 |         {
 | 
|---|
| 284 |             if (!m_stream) 
 | 
|---|
| 285 |               return false; 
 | 
|---|
| 286 |             return !m_stream->isSequential();
 | 
|---|
| 287 |         }
 | 
|---|
| 288 | 
 | 
|---|
| 289 |         qint64 MediaObject::totalTime() const
 | 
|---|
| 290 |         {
 | 
|---|
| 291 |             return m_totalTime;
 | 
|---|
| 292 |         }
 | 
|---|
| 293 | 
 | 
|---|
| 294 |         qint64 MediaObject::currentTime() const
 | 
|---|
| 295 |         {
 | 
|---|
| 296 |             //this handles inaccuracy when stopping on a title
 | 
|---|
| 297 |             return m_currentTime;
 | 
|---|
| 298 |         }
 | 
|---|
| 299 | 
 | 
|---|
| 300 |         qint32 MediaObject::tickInterval() const
 | 
|---|
| 301 |         {
 | 
|---|
| 302 |             return m_tickInterval * m_tickIntervalResolution;
 | 
|---|
| 303 |         }
 | 
|---|
| 304 | 
 | 
|---|
| 305 |         void MediaObject::setTickInterval(qint32 newTickInterval)
 | 
|---|
| 306 |         {
 | 
|---|
| 307 |             if ((m_tickIntervalResolution == 0) || (newTickInterval == 0))
 | 
|---|
| 308 |                 return;
 | 
|---|
| 309 |             m_tickInterval = newTickInterval / m_tickIntervalResolution;
 | 
|---|
| 310 |             if ((newTickInterval > 0) && (m_tickInterval == 0))
 | 
|---|
| 311 |                 m_tickInterval = 1;
 | 
|---|
| 312 |         }
 | 
|---|
| 313 | 
 | 
|---|
| 314 |         void MediaObject::pause()
 | 
|---|
| 315 |         {
 | 
|---|
| 316 |             if (!m_paused) {
 | 
|---|
| 317 |                 m_paused = true;
 | 
|---|
| 318 |                 setState(Phonon::PausedState);
 | 
|---|
| 319 |                 if (!(waveOutPause(m_hWaveOut) == MMSYSERR_NOERROR))
 | 
|---|
| 320 |                 {
 | 
|---|
| 321 |                     setError(Phonon::NormalError, QLatin1String("cannot pause (system error)"));
 | 
|---|
| 322 |                 }
 | 
|---|
| 323 |             }
 | 
|---|
| 324 |         }
 | 
|---|
| 325 | 
 | 
|---|
| 326 |         void MediaObject::stop()
 | 
|---|
| 327 |         {
 | 
|---|
| 328 |             setState(Phonon::StoppedState);
 | 
|---|
| 329 |             m_stopped = true;
 | 
|---|
| 330 |             m_paused = false;
 | 
|---|
| 331 |             seek(0);
 | 
|---|
| 332 |             if (!(waveOutReset(m_hWaveOut) == MMSYSERR_NOERROR))
 | 
|---|
| 333 |                 setError(Phonon::NormalError, QLatin1String("cannot stop (system error)"));
 | 
|---|
| 334 |         }
 | 
|---|
| 335 | 
 | 
|---|
| 336 |         void MediaObject::play()
 | 
|---|
| 337 |         {
 | 
|---|
| 338 |             if ((m_state == Phonon::PlayingState) && !m_paused && !m_stopped)
 | 
|---|
| 339 |                 return;
 | 
|---|
| 340 |             if  ((m_state == Phonon::LoadingState) ||
 | 
|---|
| 341 |                  (m_state == Phonon::BufferingState) ||
 | 
|---|
| 342 |                  (m_state == Phonon::ErrorState)) {
 | 
|---|
| 343 |                     setError(Phonon::FatalError, QLatin1String("illegale state for playback"));
 | 
|---|
| 344 |                     return;
 | 
|---|
| 345 |             }
 | 
|---|
| 346 | 
 | 
|---|
| 347 |             if (m_state == Phonon::StoppedState)
 | 
|---|
| 348 |                 stop();
 | 
|---|
| 349 |             if (m_sourceIsValid) {
 | 
|---|
| 350 |                 setState(Phonon::PlayingState);
 | 
|---|
| 351 |                 if (!m_paused) {
 | 
|---|
| 352 |                     m_nextBufferIndex = true;
 | 
|---|
| 353 |                     m_stopped = false;
 | 
|---|
| 354 |                     playBuffer(m_soundBuffer1.waveHeader);
 | 
|---|
| 355 |                     playBuffer(m_soundBuffer2.waveHeader);
 | 
|---|
| 356 |                 } else {
 | 
|---|
| 357 |                     if (!(waveOutRestart(m_hWaveOut) == MMSYSERR_NOERROR))
 | 
|---|
| 358 |                         setError(Phonon::NormalError, QLatin1String("cannot resume (system)"));
 | 
|---|
| 359 |                 }
 | 
|---|
| 360 |             } else {
 | 
|---|
| 361 |                 setError(Phonon::FatalError, QLatin1String("cannot playback invalid source"));
 | 
|---|
| 362 |             }
 | 
|---|
| 363 |             m_paused = false;
 | 
|---|
| 364 |         }
 | 
|---|
| 365 | 
 | 
|---|
| 366 |         QString MediaObject::errorString() const
 | 
|---|
| 367 |         {
 | 
|---|
| 368 |             
 | 
|---|
| 369 |             return m_errorString;
 | 
|---|
| 370 |         }
 | 
|---|
| 371 | 
 | 
|---|
| 372 |         Phonon::ErrorType MediaObject::errorType() const
 | 
|---|
| 373 |         {
 | 
|---|
| 374 |             return Phonon::ErrorType();
 | 
|---|
| 375 |         }
 | 
|---|
| 376 | 
 | 
|---|
| 377 |         qint32 MediaObject::prefinishMark() const
 | 
|---|
| 378 |         {
 | 
|---|
| 379 |             return m_prefinishMark;
 | 
|---|
| 380 |         }
 | 
|---|
| 381 | 
 | 
|---|
| 382 |         void MediaObject::setPrefinishMark(qint32 newPrefinishMark)
 | 
|---|
| 383 |         {
 | 
|---|
| 384 |             m_prefinishMark = newPrefinishMark;
 | 
|---|
| 385 |         }
 | 
|---|
| 386 | 
 | 
|---|
| 387 |         qint32 MediaObject::transitionTime() const
 | 
|---|
| 388 |         {
 | 
|---|
| 389 |             return m_transitionTime;
 | 
|---|
| 390 |         }
 | 
|---|
| 391 | 
 | 
|---|
| 392 |         void MediaObject::setTransitionTime(qint32 time)
 | 
|---|
| 393 |         {
 | 
|---|
| 394 |            m_transitionTime = time;
 | 
|---|
| 395 |         }
 | 
|---|
| 396 | 
 | 
|---|
| 397 |         qint64 MediaObject::remainingTime() const
 | 
|---|
| 398 |         {
 | 
|---|
| 399 |             return m_totalTime - m_currentTime;
 | 
|---|
| 400 |         }
 | 
|---|
| 401 | 
 | 
|---|
| 402 |         Phonon::MediaSource MediaObject::source() const
 | 
|---|
| 403 |         {
 | 
|---|
| 404 |             return Phonon::MediaSource();
 | 
|---|
| 405 |         }
 | 
|---|
| 406 | 
 | 
|---|
| 407 |         void MediaObject::setNextSource(const Phonon::MediaSource &source)
 | 
|---|
| 408 |         {
 | 
|---|
| 409 |             m_nextSource = source;
 | 
|---|
| 410 |             m_hasNextSource = true;
 | 
|---|
| 411 |         }
 | 
|---|
| 412 | 
 | 
|---|
| 413 |         void MediaObject::setSource(const Phonon::MediaSource &source)
 | 
|---|
| 414 |         {
 | 
|---|
| 415 |             if (m_state == Phonon::PlayingState)
 | 
|---|
| 416 |             {
 | 
|---|
| 417 |                 setError(Phonon::NormalError, QLatin1String("source changed while playing"));
 | 
|---|
| 418 |                 stop();
 | 
|---|
| 419 |             }
 | 
|---|
| 420 | 
 | 
|---|
| 421 |             m_source = source;
 | 
|---|
| 422 |             m_hasSource = true;
 | 
|---|
| 423 |             m_sourceIsValid = false;
 | 
|---|
| 424 | 
 | 
|---|
| 425 |             emit currentSourceChanged(source);
 | 
|---|
| 426 | 
 | 
|---|
| 427 |             if (source.type() == Phonon::MediaSource::LocalFile) {
 | 
|---|
| 428 |                 if (!openWaveFile(source.fileName())) {
 | 
|---|
| 429 |                   setError(Phonon::FatalError, QLatin1String("cannot open media file"));
 | 
|---|
| 430 |                   return ;
 | 
|---|
| 431 |                 }
 | 
|---|
| 432 |             } else if (source.type() == Phonon::MediaSource::Stream) {
 | 
|---|
| 433 |                 if (m_stream)
 | 
|---|
| 434 |                    delete m_stream;
 | 
|---|
| 435 |                 m_stream = new IOWrapper(this, source);
 | 
|---|
| 436 |                 m_mediaSize = m_stream->size();
 | 
|---|
| 437 |             } else if (source.type() == Phonon::MediaSource::Url) {
 | 
|---|
| 438 |                 if (!openWaveFile(source.url().toLocalFile())) {
 | 
|---|
| 439 |                     setError(Phonon::FatalError, QLatin1String("cannot open media file"));
 | 
|---|
| 440 |                     return ;
 | 
|---|
| 441 |                 }
 | 
|---|
| 442 |             } else {
 | 
|---|
| 443 |                 setError(Phonon::FatalError, QLatin1String("type of source not supported"));
 | 
|---|
| 444 |                 return ;
 | 
|---|
| 445 |             }
 | 
|---|
| 446 |             setState(Phonon::LoadingState);
 | 
|---|
| 447 | 
 | 
|---|
| 448 |             if (!readHeader())
 | 
|---|
| 449 |                 setError(Phonon::FatalError, QLatin1String("invalid header"));
 | 
|---|
| 450 |             else if (!getWaveOutDevice())
 | 
|---|
| 451 |                 setError(Phonon::FatalError, QLatin1String("No waveOut device available"));
 | 
|---|
| 452 |             else if (!fillBuffers())
 | 
|---|
| 453 |                 setError(Phonon::FatalError, QLatin1String("no data for buffering"));
 | 
|---|
| 454 |             else if (!prepareBuffers())
 | 
|---|
| 455 |                 setError(Phonon::FatalError, QLatin1String("cannot prepare buffers"));
 | 
|---|
| 456 |             else
 | 
|---|
| 457 |                 m_sourceIsValid = true;
 | 
|---|
| 458 | 
 | 
|---|
| 459 |             if (m_sourceIsValid)
 | 
|---|
| 460 |                 setState(Phonon::StoppedState);
 | 
|---|
| 461 |         }
 | 
|---|
| 462 | 
 | 
|---|
| 463 |         void MediaObject::seek(qint64 time)
 | 
|---|
| 464 |         {
 | 
|---|
| 465 |             if (!m_sourceIsValid) {
 | 
|---|
| 466 |                 setError(Phonon::NormalError, QLatin1String("source is not valid"));
 | 
|---|
| 467 |                 return;
 | 
|---|
| 468 |             }
 | 
|---|
| 469 |             if ((time >= 0) && (time < m_totalTime)) {
 | 
|---|
| 470 |                 int counter = 0;
 | 
|---|
| 471 |                 while (!m_bufferingFinished && (counter < 200)) {
 | 
|---|
| 472 |                   Sleep(20);
 | 
|---|
| 473 |                   counter ++;
 | 
|---|
| 474 |                 }
 | 
|---|
| 475 |                 if (counter >= 200) {
 | 
|---|
| 476 |                    setError(Phonon::NormalError, QLatin1String("buffering timed out"));
 | 
|---|
| 477 |                    return;
 | 
|---|
| 478 |                 }
 | 
|---|
| 479 | 
 | 
|---|
| 480 |                 m_stream->seek(WAVEHEADER_SIZE + time * m_waveFormatEx.nSamplesPerSec * m_waveFormatEx.wBitsPerSample * m_waveFormatEx.nChannels / 8 / 1000);
 | 
|---|
| 481 |                 m_currentTime = time;
 | 
|---|
| 482 |                 if (m_state == Phonon::PlayingState)
 | 
|---|
| 483 |                   play();
 | 
|---|
| 484 |             } else {
 | 
|---|
| 485 |                 setError(Phonon::NormalError, QLatin1String("seeking out of range"));
 | 
|---|
| 486 |             }
 | 
|---|
| 487 |         }
 | 
|---|
| 488 | 
 | 
|---|
| 489 |         void MediaObject::unPrepareBuffers()
 | 
|---|
| 490 |         {
 | 
|---|
| 491 |             if (m_bufferPrepared) {
 | 
|---|
| 492 |             DWORD err1 = waveOutUnprepareHeader(m_hWaveOut, m_soundBuffer1.waveHeader, sizeof(WAVEHDR));
 | 
|---|
| 493 |             DWORD err2 = waveOutUnprepareHeader(m_hWaveOut, m_soundBuffer2.waveHeader, sizeof(WAVEHDR));
 | 
|---|
| 494 |             if (!(err1 == MMSYSERR_NOERROR) || !(err2 == MMSYSERR_NOERROR))
 | 
|---|
| 495 |                 setError(Phonon::NormalError, QLatin1String("cannot unprepare buffer") + getErrorText(err1) + getErrorText(err2));
 | 
|---|
| 496 |             }
 | 
|---|
| 497 |             m_bufferPrepared = false;
 | 
|---|
| 498 |         }
 | 
|---|
| 499 | 
 | 
|---|
| 500 |         bool MediaObject::prepareBuffers()
 | 
|---|
| 501 |         {
 | 
|---|
| 502 |             memset((void*)m_soundBuffer1.waveHeader, 0, sizeof(WAVEHDR));
 | 
|---|
| 503 |             m_soundBuffer1.waveHeader->lpData = m_soundBuffer1.data.data();
 | 
|---|
| 504 |             m_soundBuffer1.waveHeader->dwBufferLength = m_soundBuffer1.data.size();
 | 
|---|
| 505 |             m_soundBuffer1.waveHeader->dwUser = (DWORD_PTR) this;
 | 
|---|
| 506 | 
 | 
|---|
| 507 |             ZeroMemory((void*)m_soundBuffer2.waveHeader, sizeof(WAVEHDR));
 | 
|---|
| 508 |             m_soundBuffer2.waveHeader->lpData = m_soundBuffer2.data.data();
 | 
|---|
| 509 |             m_soundBuffer2.waveHeader->dwBufferLength = m_soundBuffer1.data.size();
 | 
|---|
| 510 |             m_soundBuffer2.waveHeader->dwUser = (DWORD_PTR) this;
 | 
|---|
| 511 | 
 | 
|---|
| 512 |             m_bufferPrepared = (waveOutPrepareHeader(m_hWaveOut, m_soundBuffer1.waveHeader, sizeof(WAVEHDR)) == MMSYSERR_NOERROR)
 | 
|---|
| 513 |                 && (waveOutPrepareHeader(m_hWaveOut, m_soundBuffer2.waveHeader, sizeof(WAVEHDR)) == MMSYSERR_NOERROR);
 | 
|---|
| 514 |              return m_bufferPrepared;
 | 
|---|
| 515 |         }
 | 
|---|
| 516 | 
 | 
|---|
| 517 |         void MediaObject::deleteValidWaveOutDevice()
 | 
|---|
| 518 |         {
 | 
|---|
| 519 |             if (m_hWaveOut) {
 | 
|---|
| 520 |                 unPrepareBuffers();
 | 
|---|
| 521 |                 if (!(waveOutClose(m_hWaveOut)  == MMSYSERR_NOERROR))
 | 
|---|
| 522 |                     setError(Phonon::NormalError, QLatin1String("cannot close wave device"));
 | 
|---|
| 523 |             }
 | 
|---|
| 524 |         }
 | 
|---|
| 525 | 
 | 
|---|
| 526 |         bool MediaObject::getWaveOutDevice()
 | 
|---|
| 527 |         {
 | 
|---|
| 528 |             deleteValidWaveOutDevice();
 | 
|---|
| 529 | 
 | 
|---|
| 530 |             for(UINT deviceId = 0; deviceId < waveOutGetNumDevs(); deviceId++)
 | 
|---|
| 531 |             {
 | 
|---|
| 532 |                 if(deviceId == waveOutGetNumDevs())
 | 
|---|
| 533 |                     return false;
 | 
|---|
| 534 |                 if(waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveFormatEx, (DWORD)WaveOutCallBack, 0, CALLBACK_FUNCTION) == MMSYSERR_NOERROR)
 | 
|---|
| 535 |                     return m_hWaveOut; //m_hWaveOut !=0;
 | 
|---|
| 536 |             }
 | 
|---|
| 537 |             return false;
 | 
|---|
| 538 |         }
 | 
|---|
| 539 | 
 | 
|---|
| 540 |         bool  MediaObject::openWaveFile(QString fileName)
 | 
|---|
| 541 |         {
 | 
|---|
| 542 |             if (m_file)
 | 
|---|
| 543 |                 delete m_file;
 | 
|---|
| 544 |             m_file = new QFile(fileName);
 | 
|---|
| 545 |             m_file->setParent(this);
 | 
|---|
| 546 |             m_stream = m_file;
 | 
|---|
| 547 |             m_mediaSize = m_file->size();
 | 
|---|
| 548 |             return (m_file->open(QIODevice::ReadOnly));
 | 
|---|
| 549 |         }
 | 
|---|
| 550 | 
 | 
|---|
| 551 |         bool MediaObject::readHeader()
 | 
|---|
| 552 |         {
 | 
|---|
| 553 |             QByteArray header = m_stream->read(WAVEHEADER_SIZE);
 | 
|---|
| 554 | 
 | 
|---|
| 555 |             if (header.size() == WAVEHEADER_SIZE) {
 | 
|---|
| 556 | 
 | 
|---|
| 557 |                 m_waveFormatEx.wFormatTag         = *((WORD* )(header.data() + WAVEHEADER_OFFSET_FORMATTAG     ));
 | 
|---|
| 558 |                 m_waveFormatEx.nChannels          = *((WORD* )(header.data() + WAVEHEADER_OFFSET_CHANNELS      ));
 | 
|---|
| 559 |                 m_waveFormatEx.nSamplesPerSec     = *((DWORD*)(header.data() + WAVEHEADER_OFFSET_SAMPLESPERSEC ));
 | 
|---|
| 560 |                 m_waveFormatEx.nAvgBytesPerSec    = *((DWORD*)(header.data() + WAVEHEADER_OFFSET_AVGBYTESPERSEC));
 | 
|---|
| 561 |                 m_waveFormatEx.nBlockAlign        = *((WORD* )(header.data() + WAVEHEADER_OFFSET_BLOCKALIGN    ));
 | 
|---|
| 562 |                 m_waveFormatEx.wBitsPerSample     = *((WORD* )(header.data() + WAVEHEADER_OFFSET_BITSPERSAMPLE ));
 | 
|---|
| 563 | 
 | 
|---|
| 564 |                 m_tickIntervalResolution = (qint64(buffer_size) * 8 * 1000) / m_waveFormatEx.nSamplesPerSec / m_waveFormatEx.wBitsPerSample / m_waveFormatEx.nChannels;
 | 
|---|
| 565 |                 if (m_mediaSize > 0)
 | 
|---|
| 566 |                    m_totalTime = ((m_mediaSize - WAVEHEADER_SIZE) * 8 * 1000) / m_waveFormatEx.nSamplesPerSec / m_waveFormatEx.wBitsPerSample / m_waveFormatEx.nChannels;
 | 
|---|
| 567 |                 else
 | 
|---|
| 568 |                   m_totalTime = -1;
 | 
|---|
| 569 |                   emit totalTimeChanged(m_totalTime);
 | 
|---|
| 570 |                 return true;
 | 
|---|
| 571 |             } else {
 | 
|---|
| 572 |                 return false;
 | 
|---|
| 573 |             }
 | 
|---|
| 574 |         }
 | 
|---|
| 575 |         
 | 
|---|
| 576 |         bool MediaObject::fillBuffers()
 | 
|---|
| 577 |         {
 | 
|---|
| 578 |             
 | 
|---|
| 579 |             m_soundBuffer1.data = m_stream->read(buffer_size);
 | 
|---|
| 580 |             m_soundBuffer2.data = m_stream->read(buffer_size);
 | 
|---|
| 581 | 
 | 
|---|
| 582 |             m_bufferingFinished = true;
 | 
|---|
| 583 | 
 | 
|---|
| 584 |             if (!(m_soundBuffer1.data.size() > 0))
 | 
|---|
| 585 |                 setError(Phonon::NormalError, QLatin1String("cannot read source"));
 | 
|---|
| 586 |             return true;
 | 
|---|
| 587 |         }
 | 
|---|
| 588 | 
 | 
|---|
| 589 |         void MediaObject::setState(Phonon::State newState)
 | 
|---|
| 590 |         {
 | 
|---|
| 591 |             if (m_state == newState)
 | 
|---|
| 592 |                 return;
 | 
|---|
| 593 |             emit stateChanged(newState, m_state);
 | 
|---|
| 594 |             m_state = newState;
 | 
|---|
| 595 |         }
 | 
|---|
| 596 | 
 | 
|---|
| 597 |         void MediaObject::setError(ErrorType errorType, QString errorMessage)
 | 
|---|
| 598 |         {
 | 
|---|
| 599 |             m_errorType = errorType;
 | 
|---|
| 600 |             setState(Phonon::ErrorState);
 | 
|---|
| 601 |             m_errorString = errorMessage;
 | 
|---|
| 602 |         }
 | 
|---|
| 603 | 
 | 
|---|
| 604 |         void MediaObject::setAudioOutput(QObject *audioOutput)
 | 
|---|
| 605 |         {
 | 
|---|
| 606 |             m_audioOutput = qobject_cast<AudioOutput*>(audioOutput);
 | 
|---|
| 607 | 
 | 
|---|
| 608 |             if (m_audioOutput) {
 | 
|---|
| 609 |                 m_volume = m_audioOutput->volume();
 | 
|---|
| 610 |                 connect(m_audioOutput, SIGNAL(volumeChanged(qreal)), this, SLOT(setVolume(qreal)));
 | 
|---|
| 611 |             }
 | 
|---|
| 612 |         }
 | 
|---|
| 613 | 
 | 
|---|
| 614 |         void MediaObject::setVolume(qreal newVolume)
 | 
|---|
| 615 |         {
 | 
|---|
| 616 |             m_volume = newVolume;
 | 
|---|
| 617 |         }
 | 
|---|
| 618 | 
 | 
|---|
| 619 |         void MediaObject::swapBuffers()
 | 
|---|
| 620 |         {
 | 
|---|
| 621 |             if (m_stopped || m_paused)
 | 
|---|
| 622 |                 return;
 | 
|---|
| 623 | 
 | 
|---|
| 624 |             m_currentTime += m_tickIntervalResolution;
 | 
|---|
| 625 |             if (m_tickInterval) {
 | 
|---|
| 626 |                 m_tick ++;
 | 
|---|
| 627 |                 if (m_tick > (m_tickInterval - 1)) {
 | 
|---|
| 628 |                     emit tick(m_currentTime);
 | 
|---|
| 629 |                     m_tick = 0;
 | 
|---|
| 630 |                 }
 | 
|---|
| 631 |             }
 | 
|---|
| 632 |             if ((m_prefinishMark > 0)&& (m_prefinishMark < m_currentTime))
 | 
|---|
| 633 |                 emit prefinishMarkReached(m_totalTime - m_currentTime);
 | 
|---|
| 634 | 
 | 
|---|
| 635 |             while (!m_bufferingFinished) {
 | 
|---|
| 636 |                 setState(Phonon::BufferingState);
 | 
|---|
| 637 |                 qWarning() << QLatin1String("buffer underun");
 | 
|---|
| 638 |                 Sleep(20);
 | 
|---|
| 639 |             }
 | 
|---|
| 640 | 
 | 
|---|
| 641 |             setState(Phonon::PlayingState);
 | 
|---|
| 642 | 
 | 
|---|
| 643 |             //if size == o then stop...
 | 
|---|
| 644 |             if (m_nextBufferIndex) {
 | 
|---|
| 645 |                 int size = m_soundBuffer1.waveHeader->dwBufferLength = m_soundBuffer1.data.size();
 | 
|---|
| 646 |                 if (size == buffer_size) {
 | 
|---|
| 647 |                     playBuffer(m_soundBuffer1.waveHeader);
 | 
|---|
| 648 |                     emit outOfData(m_stream, &m_soundBuffer1.data, &m_bufferingFinished);
 | 
|---|
| 649 |                 } else {
 | 
|---|
| 650 |                     playBuffer(m_soundBuffer1.waveHeader);
 | 
|---|
| 651 |                     m_stopped = true;
 | 
|---|
| 652 |                     setState(Phonon::StoppedState);
 | 
|---|
| 653 |                     emit finished();
 | 
|---|
| 654 |                     seek(0);
 | 
|---|
| 655 |                 }
 | 
|---|
| 656 |             } else {
 | 
|---|
| 657 |                 int size = m_soundBuffer2.waveHeader->dwBufferLength = m_soundBuffer2.data.size();
 | 
|---|
| 658 |                 if (size == buffer_size) {
 | 
|---|
| 659 |                     playBuffer(m_soundBuffer2.waveHeader);
 | 
|---|
| 660 |                     emit outOfData(m_stream, &m_soundBuffer2.data, &m_bufferingFinished);
 | 
|---|
| 661 |                 } else {
 | 
|---|
| 662 |                     playBuffer(m_soundBuffer2.waveHeader);
 | 
|---|
| 663 |                     m_stopped = true;
 | 
|---|
| 664 |                     setState(Phonon::StoppedState);
 | 
|---|
| 665 |                     emit finished();
 | 
|---|
| 666 |                     seek(0);
 | 
|---|
| 667 |                 }
 | 
|---|
| 668 |             }
 | 
|---|
| 669 |             m_nextBufferIndex =! m_nextBufferIndex;
 | 
|---|
| 670 |         }
 | 
|---|
| 671 | 
 | 
|---|
| 672 | 
 | 
|---|
| 673 |         void MediaObject::playBuffer(WAVEHDR *waveHeader)
 | 
|---|
| 674 |         {
 | 
|---|
| 675 |             DWORD err = waveOutWrite(m_hWaveOut, waveHeader, sizeof(WAVEHDR));
 | 
|---|
| 676 |             if (!err == MMSYSERR_NOERROR) {
 | 
|---|
| 677 |                 setError(Phonon::FatalError, QLatin1String("cannot play sound buffer (system) ") + getErrorText(err));
 | 
|---|
| 678 |                 m_stopped = true;
 | 
|---|
| 679 |             }
 | 
|---|
| 680 |         }
 | 
|---|
| 681 |     }
 | 
|---|
| 682 | }
 | 
|---|
| 683 | 
 | 
|---|
| 684 | QT_END_NAMESPACE
 | 
|---|
| 685 | 
 | 
|---|
| 686 | #include "mediaobject.moc"
 | 
|---|