1
/* This file is part of the KDE project.
3
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
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.
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.
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/>.
18
#include <QtCore/QEvent>
19
#include "mediaobject.h"
20
#include "backendheader.h"
21
#include "videowidget.h"
22
#include "videoframe.h"
23
#include "audiooutput.h"
24
#include "quicktimevideoplayer.h"
25
#include "quicktimemetadata.h"
26
#include "audiograph.h"
27
#include "mediaobjectaudionode.h"
28
#include "quicktimeaudioplayer.h"
37
MediaObject::MediaObject(QObject *parent) : MediaNode(AudioSource | VideoSource, parent)
39
m_owningMediaObject = this;
40
m_state = Phonon::LoadingState;
42
m_videoPlayer = new QuickTimeVideoPlayer();
43
m_audioPlayer = new QuickTimeAudioPlayer();
44
m_nextVideoPlayer = new QuickTimeVideoPlayer();
45
m_nextAudioPlayer = new QuickTimeAudioPlayer();
46
m_mediaObjectAudioNode = new MediaObjectAudioNode(m_audioPlayer, m_nextAudioPlayer);
47
setAudioNode(m_mediaObjectAudioNode);
49
m_metaData = new QuickTimeMetaData();
50
m_audioGraph = new AudioGraph(this);
56
m_percentageLoaded = 0;
57
m_waitNextSwap = false;
58
m_audioEffectCount = 0;
59
m_audioOutputCount = 0;
60
m_videoEffectCount = 0;
61
m_videoOutputCount = 0;
62
m_audioSystem = AS_Unset;
63
m_errorType = Phonon::NoError;
72
MediaObject::~MediaObject()
74
// m_mediaObjectAudioNode is owned by super class.
75
m_audioPlayer->unsetVideoPlayer();
76
m_nextAudioPlayer->unsetVideoPlayer();
78
delete m_nextVideoPlayer;
83
bool MediaObject::setState(Phonon::State state)
85
Phonon::State prevState = m_state;
87
if (prevState != m_state){
88
emit stateChanged(m_state, prevState);
89
if (m_state != state){
90
// End-application did something
91
// upon receiving the signal.
98
void MediaObject::inspectAudioGraphRecursive(AudioConnection *connection, int &effectCount, int &outputCount)
100
if ((connection->m_sink->m_description & (AudioSource | AudioSink)) == (AudioSource | AudioSink))
102
else if (connection->m_sink->m_description & AudioSink)
105
for (int i=0; i<connection->m_sink->m_audioSinkList.size(); ++i)
106
inspectAudioGraphRecursive(connection->m_sink->m_audioSinkList[i], effectCount, outputCount);
109
void MediaObject::inspectVideoGraphRecursive(MediaNode *node, int &effectCount, int &outputCount)
111
if ((node->m_description & (VideoSource | VideoSink)) == (VideoSource | VideoSink))
113
else if (node->m_description & VideoSink)
116
for (int i=0; i<node->m_videoSinkList.size(); ++i)
117
inspectVideoGraphRecursive(node->m_videoSinkList[i], effectCount, outputCount);
120
void MediaObject::inspectGraph()
122
// Inspect the graph to check wether there are any
123
// effects or outputs connected. This will have
124
// influence on the audio system and video system that ends up beeing used:
125
int prevVideoOutputCount = m_videoOutputCount;
126
m_audioEffectCount = 0;
127
m_audioOutputCount = 0;
128
m_videoEffectCount = 0;
129
m_videoOutputCount = 0;
130
AudioConnection rootConnection(this);
131
inspectAudioGraphRecursive(&rootConnection, m_audioEffectCount, m_audioOutputCount);
132
inspectVideoGraphRecursive(this, m_videoEffectCount, m_videoOutputCount);
134
if (m_videoOutputCount != prevVideoOutputCount){
135
MediaNodeEvent e1(MediaNodeEvent::VideoOutputCountChanged, &m_videoOutputCount);
140
void MediaObject::setupAudioSystem()
142
// Select which audio system to use:
143
AudioSystem newAudioSystem = AS_Unset;
144
if (!m_audioOutputCount || !m_videoPlayer->canPlayMedia()){
145
newAudioSystem = AS_Silent;
146
} else if (m_audioEffectCount == 0){
147
newAudioSystem = AS_Video;
148
} else if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_4){
149
newAudioSystem = AS_Video;
150
SET_ERROR("Audio effects are not supported for Mac OS 10.3 and below", NORMAL_ERROR);
151
} else if (m_videoPlayer->isDrmProtected()){
152
newAudioSystem = AS_Video;
153
SET_ERROR("Audio effects are not supported for DRM protected media", NORMAL_ERROR);
154
} else if (m_audioGraph->graphCannotPlay()){
155
newAudioSystem = AS_Video;
156
SET_ERROR("Audio effects are not supported for the current codec", NORMAL_ERROR);
157
#ifdef QUICKTIME_C_API_AVAILABLE
159
newAudioSystem = AS_Graph;
163
newAudioSystem = AS_Video;
164
SET_ERROR("Audio effects are not supported for the 64-bit version of the Phonon QT7 backend", NORMAL_ERROR);
168
if (newAudioSystem == m_audioSystem)
171
// Enable selected audio system:
172
m_audioSystem = newAudioSystem;
173
switch (newAudioSystem){
175
m_audioGraph->stop();
176
m_videoPlayer->enableAudio(false);
177
m_nextVideoPlayer->enableAudio(false);
178
m_audioPlayer->enableAudio(false);
179
m_nextAudioPlayer->enableAudio(false);
182
if (m_state == Phonon::PausedState)
183
m_audioGraph->prepare();
185
m_audioGraph->start();
186
// Starting the graph can lead to a recursive call
187
// telling us that we must direct audio through
188
// video. If that has happened, we must not proceed:
189
if (m_audioSystem != AS_Graph)
191
m_videoPlayer->enableAudio(false);
192
m_nextVideoPlayer->enableAudio(false);
193
m_audioPlayer->enableAudio(true);
194
m_audioPlayer->seek(m_videoPlayer->currentTime());
195
m_nextAudioPlayer->enableAudio(true);
196
m_audioPlayer->seek(m_videoPlayer->currentTime());
197
m_nextAudioPlayer->seek(m_nextVideoPlayer->currentTime());
201
m_audioGraph->stop();
202
m_videoPlayer->enableAudio(true);
203
m_nextVideoPlayer->enableAudio(true);
204
m_audioPlayer->enableAudio(false);
205
m_nextAudioPlayer->enableAudio(false);
206
m_videoPlayer->seek(m_audioPlayer->currentTime());
207
m_nextVideoPlayer->seek(m_nextAudioPlayer->currentTime());
212
void MediaObject::setSource(const MediaSource &source)
215
PhononAutoReleasePool pool;
216
setState(Phonon::LoadingState);
218
// Save current state for event/signal handling below:
219
bool prevHasVideo = m_videoPlayer->hasVideo();
220
qint64 prevTotalTime = totalTime();
221
m_waitNextSwap = false;
223
// Cancel cross-fade if any:
224
m_nextVideoPlayer->pause();
225
m_nextAudioPlayer->pause();
226
m_mediaObjectAudioNode->cancelCrossFade();
229
m_audioPlayer->unsetVideoPlayer();
230
m_videoPlayer->setMediaSource(source);
231
m_audioPlayer->setVideoPlayer(m_videoPlayer);
232
m_metaData->setVideo(m_videoPlayer);
234
m_audioGraph->updateStreamSpecifications();
235
m_nextAudioPlayer->unsetVideoPlayer();
236
m_nextVideoPlayer->unsetVideo();
239
// Emit/notify information about the new source:
240
QRect videoRect = m_videoPlayer->videoRect();
241
MediaNodeEvent e1(MediaNodeEvent::VideoFrameSizeChanged, &videoRect);
244
// Clear video widgets:
245
VideoFrame emptyFrame;
246
updateVideo(emptyFrame);
248
emit currentSourceChanged(source);
249
emit metaDataChanged(m_metaData->metaData());
251
if (prevHasVideo != m_videoPlayer->hasVideo())
252
emit hasVideoChanged(m_videoPlayer->hasVideo());
253
if (prevTotalTime != totalTime())
254
emit totalTimeChanged(totalTime());
257
if (!m_videoPlayer->isDrmAuthorized())
258
SET_ERROR("This computer is not authorized to play current media (DRM protected).", FATAL_ERROR)
261
if (!m_videoPlayer->canPlayMedia())
262
SET_ERROR("Cannot play media.", FATAL_ERROR)
264
// The state might have changed from LoadingState
265
// as a response to an error state change. So we
266
// need to check it before stopping:
267
if (m_state == Phonon::LoadingState)
274
void MediaObject::setNextSource(const MediaSource &source)
277
m_nextAudioPlayer->unsetVideoPlayer();
278
m_nextVideoPlayer->setMediaSource(source);
279
m_nextAudioPlayer->setVideoPlayer(m_nextVideoPlayer);
283
void MediaObject::swapCurrentWithNext(qint32 transitionTime)
285
PhononAutoReleasePool pool;
286
setState(Phonon::LoadingState);
287
// Save current state for event/signal handling below:
288
bool prevHasVideo = m_videoPlayer->hasVideo();
289
qint64 prevTotalTime = totalTime();
291
qSwap(m_audioPlayer, m_nextAudioPlayer);
292
qSwap(m_videoPlayer, m_nextVideoPlayer);
293
m_mediaObjectAudioNode->startCrossFade(transitionTime);
294
m_audioGraph->updateStreamSpecifications();
295
m_metaData->setVideo(m_videoPlayer);
297
m_waitNextSwap = false;
300
// Emit/notify information about the new source:
301
QRect videoRect = m_videoPlayer->videoRect();
302
MediaNodeEvent e1(MediaNodeEvent::VideoFrameSizeChanged, &videoRect);
305
emit currentSourceChanged(m_videoPlayer->mediaSource());
306
emit metaDataChanged(m_metaData->metaData());
308
if (prevHasVideo != m_videoPlayer->hasVideo())
309
emit hasVideoChanged(m_videoPlayer->hasVideo());
310
if (prevTotalTime != totalTime())
311
emit totalTimeChanged(totalTime());
314
if (!m_videoPlayer->isDrmAuthorized())
315
SET_ERROR("This computer is not authorized to play current media (DRM protected).", FATAL_ERROR)
318
if (!m_videoPlayer->canPlayMedia())
319
SET_ERROR("Cannot play next media.", FATAL_ERROR)
323
if (m_state == Phonon::LoadingState){
324
if (setState(Phonon::PlayingState))
330
void MediaObject::updateTimer(int &timer, int interval)
336
timer = startTimer(interval);
339
void MediaObject::play_internal()
341
// Play main audio/video:
342
m_videoPlayer->play();
343
m_audioPlayer->play();
345
// Play old audio/video to finish cross-fade:
346
if (m_nextVideoPlayer->currentTime() > 0){
347
m_nextVideoPlayer->play();
348
m_nextAudioPlayer->play();
351
updateTimer(m_rapidTimer, 100);
354
void MediaObject::pause_internal()
356
m_audioGraph->stop();
357
m_audioPlayer->pause();
358
m_nextAudioPlayer->pause();
359
m_videoPlayer->pause();
360
m_nextVideoPlayer->pause();
361
updateTimer(m_rapidTimer, -1);
362
updateTimer(m_bufferTimer, -1);
365
m_swapTimeLeft = m_swapTime.msecsTo(QTime::currentTime());
368
void MediaObject::play()
371
if (m_state == Phonon::PlayingState)
374
// update swap time after pause:
375
m_swapTime = QTime::currentTime();
376
m_swapTime.addMSecs(m_swapTimeLeft);
377
setState(Phonon::PlayingState);
380
if (m_currentTime == m_videoPlayer->duration())
382
if (!m_videoPlayer->canPlayMedia())
384
if (!setState(Phonon::PlayingState))
386
if (m_audioSystem == AS_Graph){
387
m_audioGraph->start();
388
m_mediaObjectAudioNode->setMute(true);
390
// Inform the graph that we are about to play:
392
MediaNodeEvent e1(MediaNodeEvent::MediaPlaying, &playing);
396
m_mediaObjectAudioNode->setMute(false);
400
void MediaObject::pause()
403
if (m_state == Phonon::PausedState)
405
if (!setState(Phonon::PausedState))
408
// Inform the graph that we are no longer playing:
409
bool playing = false;
410
MediaNodeEvent e1(MediaNodeEvent::MediaPlaying, &playing);
413
if (m_audioSystem == AS_Graph)
414
m_audioGraph->prepare();
418
void MediaObject::stop()
421
if (m_state == Phonon::StoppedState)
423
if (!setState(Phonon::StoppedState))
425
m_waitNextSwap = false;
426
m_nextVideoPlayer->unsetVideo();
427
m_nextAudioPlayer->unsetVideoPlayer();
433
void MediaObject::seek(qint64 milliseconds)
436
if (m_state == Phonon::ErrorState)
439
// Stop cross-fade if any:
440
m_nextVideoPlayer->unsetVideo();
441
m_nextAudioPlayer->unsetVideoPlayer();
442
m_mediaObjectAudioNode->cancelCrossFade();
444
// Seek to new position:
445
m_mediaObjectAudioNode->setMute(true);
446
m_videoPlayer->seek(milliseconds);
447
m_audioPlayer->seek(m_videoPlayer->currentTime());
448
m_mediaObjectAudioNode->setMute(false);
450
// Update time and cancel pending swap:
451
if (m_currentTime < m_videoPlayer->duration())
452
m_waitNextSwap = false;
455
if (m_state != Phonon::PlayingState)
460
QStringList MediaObject::availableAudioStreams() const
463
return QStringList();
466
QStringList MediaObject::availableVideoStreams() const
469
return QStringList();
472
QStringList MediaObject::availableSubtitleStreams() const
475
return QStringList();
478
QString MediaObject::currentAudioStream(const QObject */*audioPath*/) const
484
QString MediaObject::currentVideoStream(const QObject */*videoPath*/) const
490
QString MediaObject::currentSubtitleStream(const QObject */*videoPath*/) const
496
void MediaObject::setCurrentAudioStream(const QString &/*streamName*/,const QObject */*audioPath*/)
501
void MediaObject::setCurrentVideoStream(const QString &/*streamName*/,const QObject */*videoPath*/)
506
void MediaObject::setCurrentSubtitleStream(const QString &/*streamName*/,const QObject */*videoPath*/)
511
int MediaObject::videoOutputCount()
513
return m_videoOutputCount;
516
void MediaObject::synchAudioVideo()
518
if (m_state != Phonon::PlayingState)
520
if (m_videoSinkList.isEmpty() || m_audioSinkList.isEmpty())
527
qint32 MediaObject::tickInterval() const
530
return m_tickInterval;
533
void MediaObject::setTickInterval(qint32 interval)
536
m_tickInterval = interval;
537
if (m_tickInterval > 0)
538
m_tickTimer = startTimer(m_tickInterval);
540
killTimer(m_tickTimer);
545
bool MediaObject::hasVideo() const
548
return m_videoPlayer ? m_videoPlayer->hasVideo() : false;
551
bool MediaObject::isSeekable() const
554
return m_videoPlayer ? m_videoPlayer->isSeekable() : false;
557
qint64 MediaObject::currentTime() const
560
const_cast<MediaObject *>(this)->updateCurrentTime();
561
return m_currentTime;
564
void MediaObject::updateCurrentTime()
566
quint64 lastUpdateTime = m_currentTime;
567
m_currentTime = (m_audioSystem == AS_Graph) ? m_audioPlayer->currentTime() : m_videoPlayer->currentTime();
568
quint64 total = m_videoPlayer->duration();
570
// Check if it's time to emit aboutToFinish:
571
quint32 mark = qMax(quint64(0), qMin(total, total + m_transitionTime - 2000));
572
if (lastUpdateTime < mark && mark <= m_currentTime)
573
emit aboutToFinish();
575
// Check if it's time to emit prefinishMarkReached:
576
mark = qMax(quint64(0), total - m_prefinishMark);
577
if (lastUpdateTime < mark && mark <= m_currentTime)
578
emit prefinishMarkReached(total - m_currentTime);
580
if (m_nextVideoPlayer->state() == QuickTimeVideoPlayer::NoMedia){
581
// There is no next source in que.
582
// Check if it's time to emit finished:
583
if (lastUpdateTime < m_currentTime && m_currentTime == total){
585
m_currentTime = (m_audioSystem == AS_Graph) ? m_audioPlayer->currentTime() : m_videoPlayer->currentTime();
586
if (m_state == Phonon::PlayingState && m_currentTime == total)
590
// We have a next source.
591
// Check if it's time to swap to next source:
592
mark = qMax(quint64(0), total + m_transitionTime);
593
if (m_waitNextSwap && m_state == Phonon::PlayingState &&
594
m_transitionTime < m_swapTime.msecsTo(QTime::currentTime())){
595
swapCurrentWithNext(0);
596
} else if (mark >= total){
597
if (lastUpdateTime < total && total == m_currentTime){
598
m_swapTime = QTime::currentTime();
599
m_swapTime.addMSecs(mark - total);
600
m_waitNextSwap = true;
602
} else if (lastUpdateTime < mark && mark <= m_currentTime){
603
swapCurrentWithNext(total - m_currentTime);
608
qint64 MediaObject::totalTime() const
611
return m_videoPlayer->duration();
614
Phonon::State MediaObject::state() const
620
QString MediaObject::errorString() const
623
return m_errorString;
626
Phonon::ErrorType MediaObject::errorType() const
632
bool MediaObject::checkForError()
634
int type = gGetErrorType();
635
if (type == NO_ERROR)
638
m_errorType = (type == NORMAL_ERROR) ? Phonon::NormalError : Phonon::FatalError;
639
m_errorString = gGetErrorString();
642
setState(Phonon::ErrorState);
646
QuickTimeVideoPlayer* MediaObject::videoPlayer() const
648
return m_videoPlayer;
651
MediaSource MediaObject::source() const
654
return m_videoPlayer->mediaSource();
657
qint32 MediaObject::prefinishMark() const
660
return m_prefinishMark;
663
void MediaObject::setPrefinishMark(qint32 mark)
666
m_prefinishMark = mark;
669
qint32 MediaObject::transitionTime() const
672
return m_transitionTime;
675
void MediaObject::setTransitionTime(qint32 transitionTime)
678
m_transitionTime = transitionTime;
681
void MediaObject::setVolumeOnMovie(float volume)
683
m_videoPlayer->setMasterVolume(volume);
684
m_nextVideoPlayer->setMasterVolume(volume);
687
bool MediaObject::setAudioDeviceOnMovie(int id)
689
m_nextVideoPlayer->setAudioDevice(id);
690
return m_videoPlayer->setAudioDevice(id);
693
void MediaObject::updateCrossFade()
695
m_mediaObjectAudioNode->updateCrossFade(m_currentTime);
696
// Clean-up previous movie if done fading:
697
if (m_mediaObjectAudioNode->m_fadeDuration == 0){
698
if (m_nextVideoPlayer->isPlaying() || m_nextAudioPlayer->isPlaying()){
699
m_nextVideoPlayer->unsetVideo();
700
m_nextAudioPlayer->unsetVideoPlayer();
705
void MediaObject::updateBufferStatus()
707
float percent = m_videoPlayer->percentageLoaded();
708
if (percent != m_percentageLoaded){
709
m_percentageLoaded = percent;
710
emit bufferStatus(m_percentageLoaded * 100);
714
void MediaObject::updateAudioBuffers()
716
// Schedule audio slices:
717
m_audioPlayer->scheduleAudioToGraph();
718
m_nextAudioPlayer->scheduleAudioToGraph();
721
bool MediaObject::isCrossFading()
723
return m_mediaObjectAudioNode->isCrossFading();
726
void MediaObject::updateVideoFrames()
728
// Draw next frame if awailable:
729
if (m_videoPlayer->videoFrameChanged()){
731
VideoFrame frame(m_videoPlayer);
732
if (m_nextVideoPlayer->isPlaying()
733
&& m_nextVideoPlayer->hasVideo()
735
VideoFrame bgFrame(m_nextVideoPlayer);
736
frame.setBackgroundFrame(bgFrame);
737
frame.setBaseOpacity(m_mediaObjectAudioNode->m_volume1);
740
// Send the frame through the graph:
746
void MediaObject::updateLipSynch(int allowedOffset)
748
if (m_audioSystem != AS_Graph || !m_audioGraph->isRunning())
750
if (m_videoSinkList.isEmpty() || m_audioSinkList.isEmpty())
753
if (m_videoPlayer->hasVideo()){
754
qint64 diff = m_audioPlayer->currentTime() - m_videoPlayer->currentTime();
755
if (-allowedOffset > diff || diff > allowedOffset)
756
m_audioPlayer->seek(m_videoPlayer->currentTime());
759
if (isCrossFading() && m_nextVideoPlayer->hasVideo()){
760
qint64 diff = m_nextAudioPlayer->currentTime() - m_nextVideoPlayer->currentTime();
761
if (-(allowedOffset*2) > diff || diff > (allowedOffset*2))
762
m_nextAudioPlayer->seek(m_nextVideoPlayer->currentTime());
766
void MediaObject::bufferAudioVideo()
768
long nextVideoUpdate = m_videoPlayer->hasVideo() ? 30 : INT_MAX;
769
long nextAudioUpdate = m_audioPlayer->regularTaskFrequency();
770
updateAudioBuffers();
772
if (m_state == Phonon::PlayingState)
773
updateTimer(m_bufferTimer, qMin(nextVideoUpdate, nextAudioUpdate));
776
void MediaObject::updateRapidly()
780
updateBufferStatus();
783
void MediaObject::setMute(bool mute)
785
m_mediaObjectAudioNode->setMute(mute);
786
m_videoPlayer->setMute(mute);
787
m_nextVideoPlayer->setMute(mute);
790
void MediaObject::mediaNodeEvent(const MediaNodeEvent *event)
792
switch (event->type()){
793
case MediaNodeEvent::EndConnectionChange:
794
m_mediaObjectAudioNode->setMute(true);
799
m_mediaObjectAudioNode->setMute(false);
800
if (m_state == Phonon::PlayingState)
803
case MediaNodeEvent::AudioGraphCannotPlay:
804
case MediaNodeEvent::AudioGraphInitialized:
805
if (m_state != Phonon::LoadingState){
806
m_mediaObjectAudioNode->setMute(true);
810
m_mediaObjectAudioNode->setMute(false);
818
bool MediaObject::event(QEvent *event)
820
switch (event->type()){
821
case QEvent::Timer: {
822
QTimerEvent *timerEvent = static_cast<QTimerEvent *>(event);
823
if (timerEvent->timerId() == m_rapidTimer)
825
else if (timerEvent->timerId() == m_tickTimer)
826
emit tick(currentTime());
827
else if (timerEvent->timerId() == m_bufferTimer)
834
return QObject::event(event);
837
bool MediaObject::hasInterface(Interface /*interface*/) const
842
QVariant MediaObject::interfaceCall(Interface /*interface*/, int /*command*/, const QList<QVariant> &/*arguments*/)
847
}} // namespace Phonon::QT7
851
#include "moc_mediaobject.cpp"