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
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
30
QT_BEGIN_NAMESPACE
31
32
using namespace Phonon;
33
using namespace Phonon::MMF;
34
35
/*! \class MMF::AbstractMediaPlayer
36
  \internal
37
*/
38
39
//-----------------------------------------------------------------------------
40
// Constants
41
//-----------------------------------------------------------------------------
42
43
const int       NullMaxVolume = -1;
44
const int       BufferStatusTimerInterval = 100; // ms
45
46
47
//-----------------------------------------------------------------------------
48
// Constructor / destructor
49
//-----------------------------------------------------------------------------
50
51
MMF::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
75
void 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
108
void 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
142
void 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
173
void 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
214
bool MMF::AbstractMediaPlayer::isSeekable() const
215
{
216
    return true;
217
}
218
219
qint64 MMF::AbstractMediaPlayer::currentTime() const
220
{
221
    return m_position;
222
}
223
224
void 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
234
void 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
321
void 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
331
void 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
346
void MMF::AbstractMediaPlayer::startPositionTimer()
347
{
348
    m_positionTimer->start(tickInterval());
349
}
350
351
void MMF::AbstractMediaPlayer::stopPositionTimer()
352
{
353
    m_positionTimer->stop();
354
}
355
356
void MMF::AbstractMediaPlayer::startBufferStatusTimer()
357
{
358
    m_bufferStatusTimer->start(BufferStatusTimerInterval);
359
}
360
361
void MMF::AbstractMediaPlayer::stopBufferStatusTimer()
362
{
363
    m_bufferStatusTimer->stop();
364
}
365
366
void MMF::AbstractMediaPlayer::stopTimers()
367
{
368
    stopPositionTimer();
369
    stopBufferStatusTimer();
370
}
371
372
void 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
405
void MMF::AbstractMediaPlayer::bufferingStarted()
406
{
407
    m_stateBeforeBuffering = privateState();
408
    changeState(BufferingState);
409
    bufferStatusTick();
410
    startBufferStatusTimer();
411
}
412
413
void MMF::AbstractMediaPlayer::bufferingComplete()
414
{
415
    stopBufferStatusTimer();
416
    emit MMF::AbstractPlayer::bufferStatus(100);
417
    if (!progressiveDownloadStalled())
418
        changeState(m_stateBeforeBuffering);
419
}
420
421
void MMF::AbstractMediaPlayer::maxVolumeChanged(int mmfMaxVolume)
422
{
423
    m_mmfMaxVolume = mmfMaxVolume;
424
    doVolumeChanged();
425
}
426
427
void 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
456
void 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
485
qint64 MMF::AbstractMediaPlayer::toMilliSeconds(const TTimeIntervalMicroSeconds &in)
486
{
487
    return in.Int64() / 1000;
488
}
489
490
bool 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
499
bool 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
512
void 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
522
void 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
542
void 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
557
void 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
566
void MMF::AbstractMediaPlayer::startPlayback()
567
{
568
    doPlay();
569
    startPositionTimer();
570
    changeState(PlayingState);
571
}
572
573
void 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
588
void 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
598
void 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
613
void 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
642
Phonon::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
654
void 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
684
void 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
706
QT_END_NAMESPACE