source: trunk/src/3rdparty/phonon/mmf/abstractmediaplayer.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 18.9 KB
Line 
1/* This file is part of the KDE project.
2
3Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
5This library is free software: you can redistribute it and/or modify
6it under the terms of the GNU Lesser General Public License as published by
7the Free Software Foundation, either version 2.1 or 3 of the License.
8
9This library is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12GNU Lesser General Public License for more details.
13
14You should have received a copy of the GNU Lesser General Public License
15along with this library. If not, see <http://www.gnu.org/licenses/>.
16
17*/
18
19#include <QResource>
20#include <QUrl>
21
22#include "abstractmediaplayer.h"
23#include "defs.h"
24#include "mediaobject.h"
25#include "utils.h"
26#include <cdbcols.h>
27#include <cdblen.h>
28#include <commdb.h>
29
30QT_BEGIN_NAMESPACE
31
32using namespace Phonon;
33using namespace Phonon::MMF;
34
35/*! \class MMF::AbstractMediaPlayer
36 \internal
37*/
38
39//-----------------------------------------------------------------------------
40// Constants
41//-----------------------------------------------------------------------------
42
43const int NullMaxVolume = -1;
44const int BufferStatusTimerInterval = 100; // ms
45
46
47//-----------------------------------------------------------------------------
48// Constructor / destructor
49//-----------------------------------------------------------------------------
50
51MMF::AbstractMediaPlayer::AbstractMediaPlayer
52 (MediaObject *parent, const AbstractPlayer *player)
53 : AbstractPlayer(player)
54 , m_parent(parent)
55 , m_pending(NothingPending)
56 , m_positionTimer(new QTimer(this))
57 , m_position(0)
58 , m_bufferStatusTimer(new QTimer(this))
59 , m_mmfMaxVolume(NullMaxVolume)
60 , m_prefinishMarkSent(false)
61 , m_aboutToFinishSent(false)
62#ifdef PHONON_MMF_PROGRESSIVE_DOWNLOAD
63 , m_download(0)
64 , m_downloadStalled(false)
65#endif
66{
67 connect(m_positionTimer.data(), SIGNAL(timeout()), this, SLOT(positionTick()));
68 connect(m_bufferStatusTimer.data(), SIGNAL(timeout()), this, SLOT(bufferStatusTick()));
69}
70
71//-----------------------------------------------------------------------------
72// MediaObjectInterface
73//-----------------------------------------------------------------------------
74
75void MMF::AbstractMediaPlayer::play()
76{
77 TRACE_CONTEXT(AbstractMediaPlayer::play, EAudioApi);
78 TRACE_ENTRY("state %d", privateState());
79
80 switch (privateState()) {
81 case GroundState:
82 setError(tr("Not ready to play"));
83 break;
84
85 case LoadingState:
86 setPending(PlayPending);
87 break;
88
89 case StoppedState:
90 case PausedState:
91 startPlayback();
92 break;
93
94 case PlayingState:
95 case BufferingState:
96 case ErrorState:
97 // Do nothing
98 break;
99
100 // Protection against adding new states and forgetting to update this switch
101 default:
102 TRACE_PANIC(InvalidStatePanic);
103 }
104
105 TRACE_EXIT("state %d", privateState());
106}
107
108void MMF::AbstractMediaPlayer::pause()
109{
110 TRACE_CONTEXT(AbstractMediaPlayer::pause, EAudioApi);
111 TRACE_ENTRY("state %d", privateState());
112
113 stopTimers();
114
115 switch (privateState()) {
116 case GroundState:
117 case LoadingState:
118 case StoppedState:
119 setPending(PausePending);
120 break;
121
122 case PausedState:
123 // Do nothing
124 break;
125
126 case PlayingState:
127 case BufferingState:
128 changeState(PausedState);
129 // Fall through
130 case ErrorState:
131 doPause();
132 break;
133
134 // Protection against adding new states and forgetting to update this switch
135 default:
136 TRACE_PANIC(InvalidStatePanic);
137 }
138
139 TRACE_EXIT("state %d", privateState());
140}
141
142void MMF::AbstractMediaPlayer::stop()
143{
144 TRACE_CONTEXT(AbstractMediaPlayer::stop, EAudioApi);
145 TRACE_ENTRY("state %d", privateState());
146
147 setPending(NothingPending);
148 stopTimers();
149
150 switch (privateState()) {
151 case GroundState:
152 case LoadingState:
153 case StoppedState:
154 case ErrorState:
155 // Do nothing
156 break;
157
158 case PlayingState:
159 case BufferingState:
160 case PausedState:
161 doStop();
162 changeState(StoppedState);
163 break;
164
165 // Protection against adding new states and forgetting to update this switch
166 default:
167 TRACE_PANIC(InvalidStatePanic);
168 }
169
170 TRACE_EXIT("state %d", privateState());
171}
172
173void MMF::AbstractMediaPlayer::seek(qint64 ms)
174{
175 TRACE_CONTEXT(AbstractMediaPlayer::seek, EAudioApi);
176 TRACE_ENTRY("state %d pos %Ld", state(), ms);
177
178 switch (privateState()) {
179 // Fallthrough all these
180 case GroundState:
181 case StoppedState:
182 case PausedState:
183 case PlayingState:
184 case LoadingState:
185 {
186 bool wasPlaying = false;
187 if (state() == PlayingState) {
188 stopPositionTimer();
189 doPause();
190 wasPlaying = true;
191 }
192
193 doSeek(ms);
194 m_position = ms;
195 resetMarksIfRewound();
196
197 if(wasPlaying && state() != ErrorState) {
198 doPlay();
199 startPositionTimer();
200 }
201
202 break;
203 }
204 case BufferingState:
205 // Fallthrough
206 case ErrorState:
207 // Do nothing
208 break;
209 }
210
211 TRACE_EXIT_0();
212}
213
214bool MMF::AbstractMediaPlayer::isSeekable() const
215{
216 return true;
217}
218
219qint64 MMF::AbstractMediaPlayer::currentTime() const
220{
221 return m_position;
222}
223
224void MMF::AbstractMediaPlayer::doSetTickInterval(qint32 interval)
225{
226 TRACE_CONTEXT(AbstractMediaPlayer::doSetTickInterval, EAudioApi);
227 TRACE_ENTRY("state %d m_interval %d interval %d", privateState(), tickInterval(), interval);
228
229 m_positionTimer->setInterval(interval);
230
231 TRACE_EXIT_0();
232}
233
234void MMF::AbstractMediaPlayer::open()
235{
236 TRACE_CONTEXT(AbstractMediaPlayer::open, EAudioApi);
237 const MediaSource source = m_parent->source();
238 TRACE_ENTRY("state %d source.type %d", privateState(), source.type());
239
240 close();
241 changeState(GroundState);
242
243 TInt symbianErr = KErrNone;
244 QString errorMessage;
245
246 switch (source.type()) {
247 case MediaSource::LocalFile: {
248 RFile *const file = m_parent->file();
249 Q_ASSERT(file);
250 symbianErr = openFile(*file);
251 if (KErrNone != symbianErr)
252 errorMessage = tr("Error opening file");
253 break;
254 }
255
256 case MediaSource::Url: {
257 const QUrl url(source.url());
258 if (url.scheme() == QLatin1String("file")) {
259 RFile *const file = m_parent->file();
260 Q_ASSERT(file);
261 symbianErr = openFile(*file);
262 if (KErrNone != symbianErr)
263 errorMessage = tr("Error opening file");
264 }
265#ifdef PHONON_MMF_PROGRESSIVE_DOWNLOAD
266 else if (url.scheme() == QLatin1String("http")) {
267 Q_ASSERT(!m_download);
268 m_download = new Download(url, this);
269 connect(m_download, SIGNAL(lengthChanged(qint64)),
270 this, SLOT(downloadLengthChanged(qint64)));
271 connect(m_download, SIGNAL(stateChanged(Download::State)),
272 this, SLOT(downloadStateChanged(Download::State)));
273 int iap = m_parent->currentIAP();
274 TRACE("HTTP Url: Using IAP %d", iap);
275 m_download->start(iap);
276 }
277#endif
278 else {
279 int iap = m_parent->currentIAP();
280 TRACE("Using IAP %d", iap);
281 symbianErr = openUrl(url.toString(), iap);
282 if (KErrNone != symbianErr)
283 errorMessage = tr("Error opening URL");
284 }
285
286 break;
287 }
288
289 case MediaSource::Stream: {
290 QResource *const resource = m_parent->resource();
291 if (resource) {
292 m_buffer.Set(resource->data(), resource->size());
293 symbianErr = openDescriptor(m_buffer);
294 if (KErrNone != symbianErr)
295 errorMessage = tr("Error opening resource");
296 } else {
297 errorMessage = tr("Error opening source: resource not opened");
298 }
299 break;
300 }
301
302 // Other source types are handled in MediaObject::createPlayer
303
304 // Protection against adding new media types and forgetting to update this switch
305 default:
306 TRACE_PANIC(InvalidMediaTypePanic);
307 }
308
309 if (errorMessage.isEmpty()) {
310 changeState(LoadingState);
311 } else {
312 if (symbianErr)
313 setError(errorMessage, symbianErr);
314 else
315 setError(errorMessage);
316 }
317
318 TRACE_EXIT_0();
319}
320
321void MMF::AbstractMediaPlayer::close()
322{
323 doClose();
324#ifdef PHONON_MMF_PROGRESSIVE_DOWNLOAD
325 delete m_download;
326 m_download = 0;
327#endif
328 m_position = 0;
329}
330
331void MMF::AbstractMediaPlayer::volumeChanged(qreal volume)
332{
333 TRACE_CONTEXT(AbstractMediaPlayer::volumeChanged, EAudioInternal);
334 TRACE_ENTRY("state %d", privateState());
335
336 AbstractPlayer::volumeChanged(volume);
337 doVolumeChanged();
338
339 TRACE_EXIT_0();
340}
341
342//-----------------------------------------------------------------------------
343// Private functions
344//-----------------------------------------------------------------------------
345
346void MMF::AbstractMediaPlayer::startPositionTimer()
347{
348 m_positionTimer->start(tickInterval());
349}
350
351void MMF::AbstractMediaPlayer::stopPositionTimer()
352{
353 m_positionTimer->stop();
354}
355
356void MMF::AbstractMediaPlayer::startBufferStatusTimer()
357{
358 m_bufferStatusTimer->start(BufferStatusTimerInterval);
359}
360
361void MMF::AbstractMediaPlayer::stopBufferStatusTimer()
362{
363 m_bufferStatusTimer->stop();
364}
365
366void MMF::AbstractMediaPlayer::stopTimers()
367{
368 stopPositionTimer();
369 stopBufferStatusTimer();
370}
371
372void MMF::AbstractMediaPlayer::doVolumeChanged()
373{
374 switch (privateState()) {
375 case GroundState:
376 case LoadingState:
377 case ErrorState:
378 // Do nothing
379 break;
380
381 case StoppedState:
382 case PausedState:
383 case PlayingState:
384 case BufferingState: {
385 const qreal volume = (m_volume * m_mmfMaxVolume) + 0.5;
386 const int err = setDeviceVolume(volume);
387
388 if (KErrNone != err) {
389 setError(tr("Setting volume failed"), err);
390 }
391 break;
392 }
393
394 // Protection against adding new states and forgetting to update this
395 // switch
396 default:
397 Utils::panic(InvalidStatePanic);
398 }
399}
400
401//-----------------------------------------------------------------------------
402// Protected functions
403//-----------------------------------------------------------------------------
404
405void MMF::AbstractMediaPlayer::bufferingStarted()
406{
407 m_stateBeforeBuffering = privateState();
408 changeState(BufferingState);
409 bufferStatusTick();
410 startBufferStatusTimer();
411}
412
413void MMF::AbstractMediaPlayer::bufferingComplete()
414{
415 stopBufferStatusTimer();
416 emit MMF::AbstractPlayer::bufferStatus(100);
417 if (!progressiveDownloadStalled())
418 changeState(m_stateBeforeBuffering);
419}
420
421void MMF::AbstractMediaPlayer::maxVolumeChanged(int mmfMaxVolume)
422{
423 m_mmfMaxVolume = mmfMaxVolume;
424 doVolumeChanged();
425}
426
427void MMF::AbstractMediaPlayer::loadingComplete(int error)
428{
429 TRACE_CONTEXT(AbstractMediaPlayer::loadingComplete, EAudioApi);
430 TRACE_ENTRY("state %d error %d", state(), error);
431 if (progressiveDownloadStalled()) {
432 Q_ASSERT(Phonon::BufferingState == state());
433 if (KErrNone == error) {
434 bufferingComplete();
435 doSeek(m_position);
436 startPlayback();
437#ifdef PHONON_MMF_PROGRESSIVE_DOWNLOAD
438 m_downloadStalled = false;
439#endif
440 }
441 } else {
442 Q_ASSERT(Phonon::LoadingState == state());
443 if (KErrNone == error) {
444 updateMetaData();
445 changeState(StoppedState);
446 } else {
447 if (isProgressiveDownload() && KErrCorrupt == error) {
448 setProgressiveDownloadStalled();
449 } else {
450 setError(tr("Loading clip failed"), error);
451 }
452 }
453 }
454}
455
456void MMF::AbstractMediaPlayer::playbackComplete(int error)
457{
458 stopTimers();
459
460 if (KErrNone == error && !m_aboutToFinishSent) {
461 const qint64 total = totalTime();
462 emit MMF::AbstractPlayer::tick(total);
463 m_aboutToFinishSent = true;
464 emit aboutToFinish();
465 }
466
467 if (KErrNone == error) {
468 changeState(PausedState);
469
470 // MediaObject::switchToNextSource deletes the current player, so we
471 // call it via delayed slot invokation to ensure that this object does
472 // not get deleted during execution of a member function.
473 QMetaObject::invokeMethod(m_parent, "switchToNextSource", Qt::QueuedConnection);
474 }
475 else {
476 if (isProgressiveDownload() && KErrCorrupt == error) {
477 setProgressiveDownloadStalled();
478 } else {
479 setError(tr("Playback complete"), error);
480 emit finished();
481 }
482 }
483}
484
485qint64 MMF::AbstractMediaPlayer::toMilliSeconds(const TTimeIntervalMicroSeconds &in)
486{
487 return in.Int64() / 1000;
488}
489
490bool MMF::AbstractMediaPlayer::isProgressiveDownload() const
491{
492#ifdef PHONON_MMF_PROGRESSIVE_DOWNLOAD
493 return (0 != m_download);
494#else
495 return false;
496#endif
497}
498
499bool MMF::AbstractMediaPlayer::progressiveDownloadStalled() const
500{
501#ifdef PHONON_MMF_PROGRESSIVE_DOWNLOAD
502 return m_downloadStalled;
503#else
504 return false;
505#endif
506}
507
508//-----------------------------------------------------------------------------
509// Slots
510//-----------------------------------------------------------------------------
511
512void MMF::AbstractMediaPlayer::positionTick()
513{
514 const qint64 pos = getCurrentTime();
515 if (pos > m_position) {
516 m_position = pos;
517 emitMarksIfReached(m_position);
518 emit MMF::AbstractPlayer::tick(m_position);
519 }
520}
521
522void MMF::AbstractMediaPlayer::emitMarksIfReached(qint64 current)
523{
524 const qint64 total = totalTime();
525 const qint64 remaining = total - current;
526
527 if (prefinishMark() && !m_prefinishMarkSent) {
528 if (remaining < (prefinishMark() + tickInterval()/2)) {
529 m_prefinishMarkSent = true;
530 emit prefinishMarkReached(remaining);
531 }
532 }
533
534 if (!m_aboutToFinishSent) {
535 if (remaining < tickInterval()) {
536 m_aboutToFinishSent = true;
537 emit aboutToFinish();
538 }
539 }
540}
541
542void MMF::AbstractMediaPlayer::resetMarksIfRewound()
543{
544 const qint64 current = getCurrentTime();
545 const qint64 total = totalTime();
546 const qint64 remaining = total - current;
547
548 if (prefinishMark() && m_prefinishMarkSent)
549 if (remaining >= (prefinishMark() + tickInterval()/2))
550 m_prefinishMarkSent = false;
551
552 if (m_aboutToFinishSent)
553 if (remaining >= tickInterval())
554 m_aboutToFinishSent = false;
555}
556
557void MMF::AbstractMediaPlayer::setPending(Pending pending)
558{
559 const Phonon::State oldState = state();
560 m_pending = pending;
561 const Phonon::State newState = state();
562 if (newState != oldState)
563 emit stateChanged(newState, oldState);
564}
565
566void MMF::AbstractMediaPlayer::startPlayback()
567{
568 doPlay();
569 startPositionTimer();
570 changeState(PlayingState);
571}
572
573void MMF::AbstractMediaPlayer::setProgressiveDownloadStalled()
574{
575#ifdef PHONON_MMF_PROGRESSIVE_DOWNLOAD
576 TRACE_CONTEXT(AbstractMediaPlayer::setProgressiveDownloadStalled, EAudioApi);
577 TRACE_ENTRY("state %d", state());
578 Q_ASSERT(isProgressiveDownload());
579 m_downloadStalled = true;
580 doClose();
581 bufferingStarted();
582 // Video player loses window handle when closed - need to reapply it here
583 videoOutputChanged();
584 m_download->resume();
585#endif
586}
587
588void MMF::AbstractMediaPlayer::bufferStatusTick()
589{
590 // During progressive download, there is no way to detect the buffering status.
591 // Phonon does not support a "buffering; amount unknown" signal, therefore we
592 // return a buffering status of zero.
593 const int status = progressiveDownloadStalled() ? 0 : bufferStatus();
594 emit MMF::AbstractPlayer::bufferStatus(status);
595}
596
597#ifdef PHONON_MMF_PROGRESSIVE_DOWNLOAD
598void MMF::AbstractMediaPlayer::downloadLengthChanged(qint64 length)
599{
600 TRACE_CONTEXT(AbstractMediaPlayer::downloadLengthChanged, EAudioApi);
601 TRACE_ENTRY("length %Ld", length);
602 Q_UNUSED(length)
603 if (m_downloadStalled) {
604 bufferingComplete();
605 int err = m_parent->openFileHandle(m_download->targetFileName());
606 if (KErrNone == err)
607 err = openFile(*m_parent->file());
608 if (KErrNone != err)
609 setError(tr("Error opening file"));
610 }
611}
612
613void MMF::AbstractMediaPlayer::downloadStateChanged(Download::State state)
614{
615 TRACE_CONTEXT(AbstractMediaPlayer::downloadStateChanged, EAudioApi);
616 TRACE_ENTRY("state %d", state);
617 switch (state) {
618 case Download::Idle:
619 case Download::Initializing:
620 break;
621 case Download::Downloading:
622 {
623 int err = m_parent->openFileHandle(m_download->targetFileName());
624 if (KErrNone == err)
625 err = openFile(*m_parent->file());
626 else if (KErrCorrupt == err)
627 // Insufficient data downloaded - enter Buffering state
628 setProgressiveDownloadStalled();
629 if (KErrNone != err)
630 setError(tr("Error opening file"));
631 }
632 break;
633 case Download::Complete:
634 break;
635 case Download::Error:
636 setError(tr("Download error"));
637 break;
638 }
639}
640#endif // PHONON_MMF_PROGRESSIVE_DOWNLOAD
641
642Phonon::State MMF::AbstractMediaPlayer::phononState(PrivateState state) const
643{
644 Phonon::State result = AbstractPlayer::phononState(state);
645
646 if (PausePending == m_pending) {
647 Q_ASSERT(Phonon::StoppedState == result || Phonon::LoadingState == result);
648 result = Phonon::PausedState;
649 }
650
651 return result;
652}
653
654void MMF::AbstractMediaPlayer::changeState(PrivateState newState)
655{
656 TRACE_CONTEXT(AbstractMediaPlayer::changeState, EAudioInternal);
657
658 const Phonon::State oldPhononState = phononState(privateState());
659 const Phonon::State newPhononState = phononState(newState);
660
661 if (LoadingState == oldPhononState && StoppedState == newPhononState) {
662 switch (m_pending) {
663 case NothingPending:
664 AbstractPlayer::changeState(newState);
665 break;
666
667 case PlayPending:
668 changeState(PlayingState); // necessary in order to apply initial volume
669 doVolumeChanged();
670 startPlayback();
671 break;
672
673 case PausePending:
674 AbstractPlayer::changeState(PausedState);
675 break;
676 }
677
678 setPending(NothingPending);
679 } else {
680 AbstractPlayer::changeState(newState);
681 }
682}
683
684void MMF::AbstractMediaPlayer::updateMetaData()
685{
686 TRACE_CONTEXT(AbstractMediaPlayer::updateMetaData, EAudioInternal);
687 TRACE_ENTRY_0();
688
689 m_metaData.clear();
690
691 const int numberOfEntries = numberOfMetaDataEntries();
692 for(int i=0; i<numberOfEntries; ++i) {
693 const QPair<QString, QString> entry = metaDataEntry(i);
694
695 // Note that we capitalize the key, as required by the Ogg Vorbis
696 // metadata standard to which Phonon adheres:
697 // http://xiph.org/vorbis/doc/v-comment.html
698 m_metaData.insert(entry.first.toUpper(), entry.second);
699 }
700
701 emit metaDataChanged(m_metaData);
702
703 TRACE_EXIT_0();
704}
705
706QT_END_NAMESPACE
707
Note: See TracBrowser for help on using the repository browser.