0
|
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 <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"
|
|
29 |
|
|
30 |
QT_BEGIN_NAMESPACE
|
|
31 |
|
|
32 |
namespace Phonon
|
|
33 |
{
|
|
34 |
namespace QT7
|
|
35 |
{
|
|
36 |
|
|
37 |
MediaObject::MediaObject(QObject *parent) : MediaNode(AudioSource | VideoSource, parent)
|
|
38 |
{
|
|
39 |
m_owningMediaObject = this;
|
|
40 |
m_state = Phonon::LoadingState;
|
|
41 |
|
|
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);
|
|
48 |
|
|
49 |
m_audioGraph = new AudioGraph(this);
|
|
50 |
|
|
51 |
m_tickInterval = 0;
|
|
52 |
m_prefinishMark = 0;
|
|
53 |
m_currentTime = 0;
|
|
54 |
m_transitionTime = 0;
|
|
55 |
m_percentageLoaded = 0;
|
|
56 |
m_waitNextSwap = false;
|
|
57 |
m_autoplayTitles = true;
|
|
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;
|
|
64 |
|
|
65 |
m_tickTimer = 0;
|
|
66 |
m_videoTimer = 0;
|
|
67 |
m_audioTimer = 0;
|
|
68 |
m_rapidTimer = 0;
|
|
69 |
|
|
70 |
#if QT_ALLOW_QUICKTIME
|
|
71 |
m_displayLink = 0;
|
|
72 |
m_pendingDisplayLinkEvent = false;
|
|
73 |
#endif
|
|
74 |
|
|
75 |
checkForError();
|
|
76 |
}
|
|
77 |
|
|
78 |
MediaObject::~MediaObject()
|
|
79 |
{
|
|
80 |
// m_mediaObjectAudioNode is owned by super class.
|
|
81 |
#if QT_ALLOW_QUICKTIME
|
|
82 |
stopDisplayLink();
|
|
83 |
#endif
|
|
84 |
m_audioPlayer->unsetVideoPlayer();
|
|
85 |
m_nextAudioPlayer->unsetVideoPlayer();
|
|
86 |
delete m_videoPlayer;
|
|
87 |
delete m_nextVideoPlayer;
|
|
88 |
checkForError();
|
|
89 |
}
|
|
90 |
|
|
91 |
bool MediaObject::setState(Phonon::State state)
|
|
92 |
{
|
|
93 |
Phonon::State prevState = m_state;
|
|
94 |
m_state = state;
|
|
95 |
if (prevState != m_state){
|
|
96 |
emit stateChanged(m_state, prevState);
|
|
97 |
if (m_state != state){
|
|
98 |
// End-application did something
|
|
99 |
// upon receiving the signal.
|
|
100 |
return false;
|
|
101 |
}
|
|
102 |
}
|
|
103 |
return true;
|
|
104 |
}
|
|
105 |
|
|
106 |
void MediaObject::inspectAudioGraphRecursive(AudioConnection *connection, int &effectCount, int &outputCount)
|
|
107 |
{
|
|
108 |
if ((connection->m_sink->m_description & (AudioSource | AudioSink)) == (AudioSource | AudioSink))
|
|
109 |
++effectCount;
|
|
110 |
else if (connection->m_sink->m_description & AudioSink)
|
|
111 |
++outputCount;
|
|
112 |
|
|
113 |
for (int i=0; i<connection->m_sink->m_audioSinkList.size(); ++i)
|
|
114 |
inspectAudioGraphRecursive(connection->m_sink->m_audioSinkList[i], effectCount, outputCount);
|
|
115 |
}
|
|
116 |
|
|
117 |
void MediaObject::inspectVideoGraphRecursive(MediaNode *node, int &effectCount, int &outputCount)
|
|
118 |
{
|
|
119 |
if ((node->m_description & (VideoSource | VideoSink)) == (VideoSource | VideoSink))
|
|
120 |
++effectCount;
|
|
121 |
else if (node->m_description & VideoSink)
|
|
122 |
++outputCount;
|
|
123 |
|
|
124 |
for (int i=0; i<node->m_videoSinkList.size(); ++i)
|
|
125 |
inspectVideoGraphRecursive(node->m_videoSinkList[i], effectCount, outputCount);
|
|
126 |
}
|
|
127 |
|
|
128 |
void MediaObject::inspectGraph()
|
|
129 |
{
|
|
130 |
// Inspect the graph to check wether there are any
|
|
131 |
// effects or outputs connected. This will have
|
|
132 |
// influence on the audio system and video system that ends up beeing used:
|
|
133 |
int prevVideoOutputCount = m_videoOutputCount;
|
|
134 |
m_audioEffectCount = 0;
|
|
135 |
m_audioOutputCount = 0;
|
|
136 |
m_videoEffectCount = 0;
|
|
137 |
m_videoOutputCount = 0;
|
|
138 |
AudioConnection rootConnection(this);
|
|
139 |
inspectAudioGraphRecursive(&rootConnection, m_audioEffectCount, m_audioOutputCount);
|
|
140 |
inspectVideoGraphRecursive(this, m_videoEffectCount, m_videoOutputCount);
|
|
141 |
|
|
142 |
if (m_videoOutputCount != prevVideoOutputCount){
|
|
143 |
MediaNodeEvent e1(MediaNodeEvent::VideoOutputCountChanged, &m_videoOutputCount);
|
|
144 |
notify(&e1);
|
|
145 |
}
|
|
146 |
}
|
|
147 |
|
|
148 |
void MediaObject::setupAudioSystem()
|
|
149 |
{
|
|
150 |
// Select which audio system to use:
|
|
151 |
AudioSystem newAudioSystem = AS_Unset;
|
|
152 |
if (!m_audioOutputCount || !m_videoPlayer->canPlayMedia()){
|
|
153 |
newAudioSystem = AS_Silent;
|
|
154 |
} else if (m_audioEffectCount == 0){
|
|
155 |
newAudioSystem = AS_Video;
|
|
156 |
} else if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_4){
|
|
157 |
newAudioSystem = AS_Video;
|
|
158 |
SET_ERROR("Audio effects are not supported for Mac OS 10.3 and below", NORMAL_ERROR);
|
|
159 |
} else if (m_videoPlayer->isDrmProtected()){
|
|
160 |
newAudioSystem = AS_Video;
|
|
161 |
SET_ERROR("Audio effects are not supported for DRM protected media", NORMAL_ERROR);
|
|
162 |
} else if (m_audioGraph->graphCannotPlay()){
|
|
163 |
newAudioSystem = AS_Video;
|
|
164 |
SET_ERROR("Audio effects are not supported for the current codec", NORMAL_ERROR);
|
|
165 |
#ifdef QUICKTIME_C_API_AVAILABLE
|
|
166 |
} else {
|
|
167 |
newAudioSystem = AS_Graph;
|
|
168 |
}
|
|
169 |
#else
|
|
170 |
} else {
|
|
171 |
newAudioSystem = AS_Video;
|
|
172 |
SET_ERROR("Audio effects are not supported for the 64-bit version of the Phonon QT7 backend", NORMAL_ERROR);
|
|
173 |
}
|
|
174 |
#endif
|
|
175 |
|
|
176 |
if (newAudioSystem == m_audioSystem)
|
|
177 |
return;
|
|
178 |
|
|
179 |
// Enable selected audio system:
|
|
180 |
m_audioSystem = newAudioSystem;
|
|
181 |
switch (newAudioSystem){
|
|
182 |
case AS_Silent:
|
|
183 |
m_audioGraph->stop();
|
|
184 |
m_videoPlayer->enableAudio(false);
|
|
185 |
m_nextVideoPlayer->enableAudio(false);
|
|
186 |
m_audioPlayer->enableAudio(false);
|
|
187 |
m_nextAudioPlayer->enableAudio(false);
|
|
188 |
break;
|
|
189 |
case AS_Graph:
|
|
190 |
if (m_state == Phonon::PausedState)
|
|
191 |
m_audioGraph->prepare();
|
|
192 |
else
|
|
193 |
m_audioGraph->start();
|
|
194 |
// Starting the graph can lead to a recursive call
|
|
195 |
// telling us that we must direct audio through
|
|
196 |
// video. If that has happened, we must not proceed:
|
|
197 |
if (m_audioSystem != AS_Graph)
|
|
198 |
return;
|
|
199 |
m_videoPlayer->enableAudio(false);
|
|
200 |
m_nextVideoPlayer->enableAudio(false);
|
|
201 |
m_audioPlayer->enableAudio(true);
|
|
202 |
m_audioPlayer->seek(m_videoPlayer->currentTime());
|
|
203 |
m_nextAudioPlayer->enableAudio(true);
|
|
204 |
m_audioPlayer->seek(m_videoPlayer->currentTime());
|
|
205 |
m_nextAudioPlayer->seek(m_nextVideoPlayer->currentTime());
|
|
206 |
break;
|
|
207 |
case AS_Video:
|
|
208 |
case AS_Unset:
|
|
209 |
m_audioGraph->stop();
|
|
210 |
m_videoPlayer->enableAudio(true);
|
|
211 |
m_nextVideoPlayer->enableAudio(true);
|
|
212 |
m_audioPlayer->enableAudio(false);
|
|
213 |
m_nextAudioPlayer->enableAudio(false);
|
|
214 |
m_videoPlayer->seek(m_audioPlayer->currentTime());
|
|
215 |
m_nextVideoPlayer->seek(m_nextAudioPlayer->currentTime());
|
|
216 |
break;
|
|
217 |
}
|
|
218 |
}
|
|
219 |
|
|
220 |
void MediaObject::setSource(const MediaSource &source)
|
|
221 |
{
|
|
222 |
IMPLEMENTED;
|
|
223 |
PhononAutoReleasePool pool;
|
|
224 |
setState(Phonon::LoadingState);
|
|
225 |
|
|
226 |
// Save current state for event/signal handling below:
|
|
227 |
bool prevHasVideo = m_videoPlayer->hasVideo();
|
|
228 |
qint64 prevTotalTime = totalTime();
|
|
229 |
int prevTrackCount = m_videoPlayer->trackCount();
|
|
230 |
m_waitNextSwap = false;
|
|
231 |
|
|
232 |
// Cancel cross-fade if any:
|
|
233 |
m_nextVideoPlayer->pause();
|
|
234 |
m_nextAudioPlayer->pause();
|
|
235 |
m_mediaObjectAudioNode->cancelCrossFade();
|
|
236 |
|
|
237 |
// Set new source:
|
|
238 |
m_audioPlayer->unsetVideoPlayer();
|
|
239 |
m_videoPlayer->setMediaSource(source);
|
|
240 |
m_audioPlayer->setVideoPlayer(m_videoPlayer);
|
|
241 |
|
|
242 |
m_audioGraph->updateStreamSpecifications();
|
|
243 |
m_nextAudioPlayer->unsetVideoPlayer();
|
|
244 |
m_nextVideoPlayer->unsetCurrentMediaSource();
|
|
245 |
m_currentTime = 0;
|
|
246 |
|
|
247 |
// Emit/notify information about the new source:
|
|
248 |
QRect videoRect = m_videoPlayer->videoRect();
|
|
249 |
MediaNodeEvent e1(MediaNodeEvent::VideoFrameSizeChanged, &videoRect);
|
|
250 |
notify(&e1);
|
|
251 |
|
|
252 |
// Clear video widgets:
|
|
253 |
VideoFrame emptyFrame;
|
|
254 |
updateVideo(emptyFrame);
|
|
255 |
|
|
256 |
emit currentSourceChanged(source);
|
|
257 |
emit metaDataChanged(m_videoPlayer->metaData());
|
|
258 |
|
|
259 |
if (prevHasVideo != m_videoPlayer->hasVideo())
|
|
260 |
emit hasVideoChanged(m_videoPlayer->hasVideo());
|
|
261 |
if (prevTotalTime != totalTime())
|
|
262 |
emit totalTimeChanged(totalTime());
|
|
263 |
if (prevTrackCount != m_videoPlayer->trackCount())
|
|
264 |
emit availableTitlesChanged(m_videoPlayer->trackCount());
|
|
265 |
if (checkForError())
|
|
266 |
return;
|
|
267 |
if (!m_videoPlayer->isDrmAuthorized())
|
|
268 |
SET_ERROR("This computer is not authorized to play current media (DRM protected).", FATAL_ERROR)
|
|
269 |
if (checkForError())
|
|
270 |
return;
|
|
271 |
if (!m_videoPlayer->canPlayMedia())
|
|
272 |
SET_ERROR("Cannot play media.", FATAL_ERROR)
|
|
273 |
|
|
274 |
// The state might have changed from LoadingState
|
|
275 |
// as a response to an error state change. So we
|
|
276 |
// need to check it before stopping:
|
|
277 |
if (m_state == Phonon::LoadingState)
|
|
278 |
stop();
|
|
279 |
|
|
280 |
setupAudioSystem();
|
|
281 |
checkForError();
|
|
282 |
}
|
|
283 |
|
|
284 |
void MediaObject::setNextSource(const MediaSource &source)
|
|
285 |
{
|
|
286 |
IMPLEMENTED;
|
|
287 |
m_nextAudioPlayer->unsetVideoPlayer();
|
|
288 |
m_nextVideoPlayer->setMediaSource(source);
|
|
289 |
m_nextAudioPlayer->setVideoPlayer(m_nextVideoPlayer);
|
|
290 |
checkForError();
|
|
291 |
}
|
|
292 |
|
|
293 |
void MediaObject::swapCurrentWithNext(qint32 transitionTime)
|
|
294 |
{
|
|
295 |
PhononAutoReleasePool pool;
|
|
296 |
setState(Phonon::LoadingState);
|
|
297 |
// Save current state for event/signal handling below:
|
|
298 |
bool prevHasVideo = m_videoPlayer->hasVideo();
|
|
299 |
qint64 prevTotalTime = totalTime();
|
|
300 |
int prevTrackCount = m_videoPlayer->trackCount();
|
|
301 |
|
|
302 |
qSwap(m_audioPlayer, m_nextAudioPlayer);
|
|
303 |
qSwap(m_videoPlayer, m_nextVideoPlayer);
|
|
304 |
m_mediaObjectAudioNode->startCrossFade(transitionTime);
|
|
305 |
m_audioGraph->updateStreamSpecifications();
|
|
306 |
|
|
307 |
m_waitNextSwap = false;
|
|
308 |
m_currentTime = 0;
|
|
309 |
|
|
310 |
// Emit/notify information about the new source:
|
|
311 |
QRect videoRect = m_videoPlayer->videoRect();
|
|
312 |
MediaNodeEvent e1(MediaNodeEvent::VideoFrameSizeChanged, &videoRect);
|
|
313 |
notify(&e1);
|
|
314 |
|
|
315 |
emit currentSourceChanged(m_videoPlayer->mediaSource());
|
|
316 |
emit metaDataChanged(m_videoPlayer->metaData());
|
|
317 |
|
|
318 |
if (prevHasVideo != m_videoPlayer->hasVideo())
|
|
319 |
emit hasVideoChanged(m_videoPlayer->hasVideo());
|
|
320 |
if (prevTotalTime != totalTime())
|
|
321 |
emit totalTimeChanged(totalTime());
|
|
322 |
if (prevTrackCount != m_videoPlayer->trackCount())
|
|
323 |
emit availableTitlesChanged(m_videoPlayer->trackCount());
|
|
324 |
if (checkForError())
|
|
325 |
return;
|
|
326 |
if (!m_videoPlayer->isDrmAuthorized())
|
|
327 |
SET_ERROR("This computer is not authorized to play current media (DRM protected).", FATAL_ERROR)
|
|
328 |
if (checkForError())
|
|
329 |
return;
|
|
330 |
if (!m_videoPlayer->canPlayMedia())
|
|
331 |
SET_ERROR("Cannot play next media.", FATAL_ERROR)
|
|
332 |
|
|
333 |
setupAudioSystem();
|
|
334 |
checkForError();
|
|
335 |
if (m_state == Phonon::LoadingState){
|
|
336 |
if (setState(Phonon::PlayingState))
|
|
337 |
play_internal();
|
|
338 |
checkForError();
|
|
339 |
}
|
|
340 |
}
|
|
341 |
|
|
342 |
#if QT_ALLOW_QUICKTIME
|
|
343 |
static CVReturn displayLinkCallback(CVDisplayLinkRef /*displayLink*/,
|
|
344 |
const CVTimeStamp */*inNow*/,
|
|
345 |
const CVTimeStamp */*inOutputTime*/,
|
|
346 |
CVOptionFlags /*flagsIn*/,
|
|
347 |
CVOptionFlags */*flagsOut*/,
|
|
348 |
void *userData)
|
|
349 |
{
|
|
350 |
MediaObject *mediaObject = static_cast<MediaObject *>(userData);
|
|
351 |
mediaObject->displayLinkEvent();
|
|
352 |
return kCVReturnSuccess;
|
|
353 |
}
|
|
354 |
|
|
355 |
void MediaObject::displayLinkEvent()
|
|
356 |
{
|
|
357 |
// This function is called from a
|
|
358 |
// thread != gui thread. So we post the event.
|
|
359 |
// But we need to make sure that we don't post faster
|
|
360 |
// than the event loop can eat:
|
|
361 |
m_displayLinkMutex.lock();
|
|
362 |
bool pending = m_pendingDisplayLinkEvent;
|
|
363 |
m_pendingDisplayLinkEvent = true;
|
|
364 |
m_displayLinkMutex.unlock();
|
|
365 |
|
|
366 |
if (!pending)
|
|
367 |
qApp->postEvent(this, new QEvent(QEvent::User), Qt::HighEventPriority);
|
|
368 |
}
|
|
369 |
|
|
370 |
void MediaObject::startDisplayLink()
|
|
371 |
{
|
|
372 |
if (m_displayLink)
|
|
373 |
return;
|
|
374 |
OSStatus err = CVDisplayLinkCreateWithCGDisplay(kCGDirectMainDisplay, &m_displayLink);
|
|
375 |
if (err != noErr)
|
|
376 |
goto fail;
|
|
377 |
err = CVDisplayLinkSetCurrentCGDisplay(m_displayLink, kCGDirectMainDisplay);
|
|
378 |
if (err != noErr)
|
|
379 |
goto fail;
|
|
380 |
err = CVDisplayLinkSetOutputCallback(m_displayLink, displayLinkCallback, this);
|
|
381 |
if (err != noErr)
|
|
382 |
goto fail;
|
|
383 |
err = CVDisplayLinkStart(m_displayLink);
|
|
384 |
if (err != noErr)
|
|
385 |
goto fail;
|
|
386 |
return;
|
|
387 |
fail:
|
|
388 |
stopDisplayLink();
|
|
389 |
}
|
|
390 |
|
|
391 |
void MediaObject::stopDisplayLink()
|
|
392 |
{
|
|
393 |
if (!m_displayLink)
|
|
394 |
return;
|
|
395 |
CVDisplayLinkStop(m_displayLink);
|
|
396 |
CFRelease(m_displayLink);
|
|
397 |
m_displayLink = 0;
|
|
398 |
}
|
|
399 |
#endif
|
|
400 |
|
|
401 |
void MediaObject::restartAudioVideoTimers()
|
|
402 |
{
|
|
403 |
if (m_videoTimer)
|
|
404 |
killTimer(m_videoTimer);
|
|
405 |
if (m_audioTimer)
|
|
406 |
killTimer(m_audioTimer);
|
|
407 |
|
|
408 |
#if QT_ALLOW_QUICKTIME
|
|
409 |
// We prefer to use a display link as timer if available, since
|
|
410 |
// it is more steady, and results in better and smoother frame drawing:
|
|
411 |
startDisplayLink();
|
|
412 |
if (!m_displayLink){
|
|
413 |
float fps = m_videoPlayer->staticFps();
|
|
414 |
long videoUpdateFrequency = fps ? long(1000.0f / fps) : 0.001;
|
|
415 |
m_videoTimer = startTimer(videoUpdateFrequency);
|
|
416 |
}
|
|
417 |
#else
|
|
418 |
float fps = m_videoPlayer->staticFps();
|
|
419 |
long videoUpdateFrequency = fps ? long(1000.0f / fps) : 0.001;
|
|
420 |
m_videoTimer = startTimer(videoUpdateFrequency);
|
|
421 |
#endif
|
|
422 |
|
|
423 |
long audioUpdateFrequency = m_audioPlayer->regularTaskFrequency();
|
|
424 |
m_audioTimer = startTimer(audioUpdateFrequency);
|
|
425 |
updateVideoFrames();
|
|
426 |
updateAudioBuffers();
|
|
427 |
}
|
|
428 |
|
|
429 |
void MediaObject::play_internal()
|
|
430 |
{
|
|
431 |
// Play main audio/video:
|
|
432 |
m_videoPlayer->play();
|
|
433 |
m_audioPlayer->play();
|
|
434 |
updateLipSynch(0);
|
|
435 |
// Play old audio/video to finish cross-fade:
|
|
436 |
if (m_nextVideoPlayer->currentTime() > 0){
|
|
437 |
m_nextVideoPlayer->play();
|
|
438 |
m_nextAudioPlayer->play();
|
|
439 |
}
|
|
440 |
restartAudioVideoTimers();
|
|
441 |
if (!m_rapidTimer)
|
|
442 |
m_rapidTimer = startTimer(100);
|
|
443 |
}
|
|
444 |
|
|
445 |
void MediaObject::pause_internal()
|
|
446 |
{
|
|
447 |
m_audioGraph->stop();
|
|
448 |
m_audioPlayer->pause();
|
|
449 |
m_nextAudioPlayer->pause();
|
|
450 |
m_videoPlayer->pause();
|
|
451 |
m_nextVideoPlayer->pause();
|
|
452 |
killTimer(m_rapidTimer);
|
|
453 |
killTimer(m_videoTimer);
|
|
454 |
killTimer(m_audioTimer);
|
|
455 |
m_rapidTimer = 0;
|
|
456 |
m_videoTimer = 0;
|
|
457 |
m_audioTimer = 0;
|
|
458 |
#if QT_ALLOW_QUICKTIME
|
|
459 |
stopDisplayLink();
|
|
460 |
#endif
|
|
461 |
if (m_waitNextSwap)
|
|
462 |
m_swapTimeLeft = m_swapTime.msecsTo(QTime::currentTime());
|
|
463 |
}
|
|
464 |
|
|
465 |
void MediaObject::play()
|
|
466 |
{
|
|
467 |
IMPLEMENTED;
|
|
468 |
if (m_state == Phonon::PlayingState)
|
|
469 |
return;
|
|
470 |
if (m_waitNextSwap){
|
|
471 |
// update swap time after pause:
|
|
472 |
m_swapTime = QTime::currentTime();
|
|
473 |
m_swapTime.addMSecs(m_swapTimeLeft);
|
|
474 |
setState(Phonon::PlayingState);
|
|
475 |
return;
|
|
476 |
}
|
|
477 |
if (m_currentTime == m_videoPlayer->duration())
|
|
478 |
return;
|
|
479 |
if (!m_videoPlayer->canPlayMedia())
|
|
480 |
return;
|
|
481 |
if (!setState(Phonon::PlayingState))
|
|
482 |
return;
|
|
483 |
if (m_audioSystem == AS_Graph){
|
|
484 |
m_audioGraph->start();
|
|
485 |
m_mediaObjectAudioNode->setMute(true);
|
|
486 |
}
|
|
487 |
// Inform the graph that we are about to play:
|
|
488 |
bool playing = true;
|
|
489 |
MediaNodeEvent e1(MediaNodeEvent::MediaPlaying, &playing);
|
|
490 |
notify(&e1);
|
|
491 |
// Start to play:
|
|
492 |
play_internal();
|
|
493 |
m_mediaObjectAudioNode->setMute(false);
|
|
494 |
checkForError();
|
|
495 |
}
|
|
496 |
|
|
497 |
void MediaObject::pause()
|
|
498 |
{
|
|
499 |
IMPLEMENTED;
|
|
500 |
if (m_state == Phonon::PausedState)
|
|
501 |
return;
|
|
502 |
if (!setState(Phonon::PausedState))
|
|
503 |
return;
|
|
504 |
pause_internal();
|
|
505 |
// Inform the graph that we are no longer playing:
|
|
506 |
bool playing = false;
|
|
507 |
MediaNodeEvent e1(MediaNodeEvent::MediaPlaying, &playing);
|
|
508 |
notify(&e1);
|
|
509 |
// But be prepared:
|
|
510 |
if (m_audioSystem == AS_Graph)
|
|
511 |
m_audioGraph->prepare();
|
|
512 |
checkForError();
|
|
513 |
}
|
|
514 |
|
|
515 |
void MediaObject::stop()
|
|
516 |
{
|
|
517 |
IMPLEMENTED;
|
|
518 |
if (m_state == Phonon::StoppedState)
|
|
519 |
return;
|
|
520 |
if (!setState(Phonon::StoppedState))
|
|
521 |
return;
|
|
522 |
m_waitNextSwap = false;
|
|
523 |
m_nextVideoPlayer->unsetCurrentMediaSource();
|
|
524 |
m_nextAudioPlayer->unsetVideoPlayer();
|
|
525 |
pause_internal();
|
|
526 |
seek(0);
|
|
527 |
checkForError();
|
|
528 |
}
|
|
529 |
|
|
530 |
void MediaObject::seek(qint64 milliseconds)
|
|
531 |
{
|
|
532 |
IMPLEMENTED;
|
|
533 |
if (m_state == Phonon::ErrorState)
|
|
534 |
return;
|
|
535 |
|
|
536 |
// Stop cross-fade if any:
|
|
537 |
m_nextVideoPlayer->unsetCurrentMediaSource();
|
|
538 |
m_nextAudioPlayer->unsetVideoPlayer();
|
|
539 |
m_mediaObjectAudioNode->cancelCrossFade();
|
|
540 |
|
|
541 |
// Seek to new position:
|
|
542 |
m_mediaObjectAudioNode->setMute(true);
|
|
543 |
m_videoPlayer->seek(milliseconds);
|
|
544 |
m_audioPlayer->seek(m_videoPlayer->currentTime());
|
|
545 |
m_mediaObjectAudioNode->setMute(false);
|
|
546 |
|
|
547 |
// Update time and cancel pending swap:
|
|
548 |
if (m_currentTime < m_videoPlayer->duration())
|
|
549 |
m_waitNextSwap = false;
|
|
550 |
|
|
551 |
updateCurrentTime();
|
|
552 |
if (m_state != Phonon::PlayingState)
|
|
553 |
updateVideoFrames();
|
|
554 |
checkForError();
|
|
555 |
}
|
|
556 |
|
|
557 |
QStringList MediaObject::availableAudioStreams() const
|
|
558 |
{
|
|
559 |
NOT_IMPLEMENTED;
|
|
560 |
return QStringList();
|
|
561 |
}
|
|
562 |
|
|
563 |
QStringList MediaObject::availableVideoStreams() const
|
|
564 |
{
|
|
565 |
NOT_IMPLEMENTED;
|
|
566 |
return QStringList();
|
|
567 |
}
|
|
568 |
|
|
569 |
QStringList MediaObject::availableSubtitleStreams() const
|
|
570 |
{
|
|
571 |
NOT_IMPLEMENTED;
|
|
572 |
return QStringList();
|
|
573 |
}
|
|
574 |
|
|
575 |
QString MediaObject::currentAudioStream(const QObject */*audioPath*/) const
|
|
576 |
{
|
|
577 |
NOT_IMPLEMENTED;
|
|
578 |
return QString();
|
|
579 |
}
|
|
580 |
|
|
581 |
QString MediaObject::currentVideoStream(const QObject */*videoPath*/) const
|
|
582 |
{
|
|
583 |
NOT_IMPLEMENTED;
|
|
584 |
return QString();
|
|
585 |
}
|
|
586 |
|
|
587 |
QString MediaObject::currentSubtitleStream(const QObject */*videoPath*/) const
|
|
588 |
{
|
|
589 |
NOT_IMPLEMENTED;
|
|
590 |
return QString();
|
|
591 |
}
|
|
592 |
|
|
593 |
void MediaObject::setCurrentAudioStream(const QString &/*streamName*/,const QObject */*audioPath*/)
|
|
594 |
{
|
|
595 |
NOT_IMPLEMENTED;
|
|
596 |
}
|
|
597 |
|
|
598 |
void MediaObject::setCurrentVideoStream(const QString &/*streamName*/,const QObject */*videoPath*/)
|
|
599 |
{
|
|
600 |
NOT_IMPLEMENTED;
|
|
601 |
}
|
|
602 |
|
|
603 |
void MediaObject::setCurrentSubtitleStream(const QString &/*streamName*/,const QObject */*videoPath*/)
|
|
604 |
{
|
|
605 |
NOT_IMPLEMENTED;
|
|
606 |
}
|
|
607 |
|
|
608 |
int MediaObject::videoOutputCount()
|
|
609 |
{
|
|
610 |
return m_videoOutputCount;
|
|
611 |
}
|
|
612 |
|
|
613 |
void MediaObject::synchAudioVideo()
|
|
614 |
{
|
|
615 |
if (m_state != Phonon::PlayingState)
|
|
616 |
return;
|
|
617 |
if (m_videoSinkList.isEmpty() || m_audioSinkList.isEmpty())
|
|
618 |
return;
|
|
619 |
|
|
620 |
seek(m_currentTime);
|
|
621 |
checkForError();
|
|
622 |
}
|
|
623 |
|
|
624 |
qint32 MediaObject::tickInterval() const
|
|
625 |
{
|
|
626 |
IMPLEMENTED;
|
|
627 |
return m_tickInterval;
|
|
628 |
}
|
|
629 |
|
|
630 |
void MediaObject::setTickInterval(qint32 interval)
|
|
631 |
{
|
|
632 |
IMPLEMENTED;
|
|
633 |
m_tickInterval = interval;
|
|
634 |
if (m_tickInterval > 0)
|
|
635 |
m_tickTimer = startTimer(m_tickInterval);
|
|
636 |
else{
|
|
637 |
killTimer(m_tickTimer);
|
|
638 |
m_tickTimer = 0;
|
|
639 |
}
|
|
640 |
}
|
|
641 |
|
|
642 |
bool MediaObject::hasVideo() const
|
|
643 |
{
|
|
644 |
IMPLEMENTED;
|
|
645 |
return m_videoPlayer ? m_videoPlayer->hasVideo() : false;
|
|
646 |
}
|
|
647 |
|
|
648 |
bool MediaObject::isSeekable() const
|
|
649 |
{
|
|
650 |
IMPLEMENTED;
|
|
651 |
return m_videoPlayer ? m_videoPlayer->isSeekable() : false;
|
|
652 |
}
|
|
653 |
|
|
654 |
qint64 MediaObject::currentTime() const
|
|
655 |
{
|
|
656 |
IMPLEMENTED_SILENT;
|
|
657 |
const_cast<MediaObject *>(this)->updateCurrentTime();
|
|
658 |
return m_currentTime;
|
|
659 |
}
|
|
660 |
|
|
661 |
void MediaObject::updateCurrentTime()
|
|
662 |
{
|
|
663 |
quint64 lastUpdateTime = m_currentTime;
|
|
664 |
m_currentTime = (m_audioSystem == AS_Graph) ? m_audioPlayer->currentTime() : m_videoPlayer->currentTime();
|
|
665 |
quint64 total = m_videoPlayer->duration();
|
|
666 |
|
|
667 |
if (m_videoPlayer->currentTrack() < m_videoPlayer->trackCount() - 1){
|
|
668 |
// There are still more tracks to play after the current track.
|
|
669 |
if (m_autoplayTitles) {
|
|
670 |
if (lastUpdateTime < m_currentTime && m_currentTime == total)
|
|
671 |
setCurrentTrack(m_videoPlayer->currentTrack() + 1);
|
|
672 |
}
|
|
673 |
} else if (m_nextVideoPlayer->state() == QuickTimeVideoPlayer::NoMedia){
|
|
674 |
// There is no more sources or tracks to play after the current source.
|
|
675 |
// Check if it's time to emit aboutToFinish:
|
|
676 |
quint32 mark = qMax(quint64(0), qMin(total, total + m_transitionTime - 2000));
|
|
677 |
if (lastUpdateTime < mark && mark <= m_currentTime)
|
|
678 |
emit aboutToFinish();
|
|
679 |
|
|
680 |
// Check if it's time to emit prefinishMarkReached:
|
|
681 |
mark = qMax(quint64(0), total - m_prefinishMark);
|
|
682 |
if (lastUpdateTime < mark && mark <= m_currentTime)
|
|
683 |
emit prefinishMarkReached(total - m_currentTime);
|
|
684 |
|
|
685 |
if (lastUpdateTime < m_currentTime && m_currentTime == total){
|
|
686 |
emit finished();
|
|
687 |
m_currentTime = (m_audioSystem == AS_Graph) ? m_audioPlayer->currentTime() : m_videoPlayer->currentTime();
|
|
688 |
if (m_state == Phonon::PlayingState && m_currentTime == total)
|
|
689 |
pause();
|
|
690 |
}
|
|
691 |
} else {
|
|
692 |
// We have a next source.
|
|
693 |
// Check if it's time to swap to next source:
|
|
694 |
quint32 mark = qMax(quint64(0), total + m_transitionTime);
|
|
695 |
if (m_waitNextSwap && m_state == Phonon::PlayingState &&
|
|
696 |
m_transitionTime < m_swapTime.msecsTo(QTime::currentTime())){
|
|
697 |
swapCurrentWithNext(0);
|
|
698 |
} else if (mark >= total){
|
|
699 |
if (lastUpdateTime < total && total == m_currentTime){
|
|
700 |
m_swapTime = QTime::currentTime();
|
|
701 |
m_swapTime.addMSecs(mark - total);
|
|
702 |
m_waitNextSwap = true;
|
|
703 |
}
|
|
704 |
} else if (lastUpdateTime < mark && mark <= m_currentTime){
|
|
705 |
swapCurrentWithNext(total - m_currentTime);
|
|
706 |
}
|
|
707 |
}
|
|
708 |
}
|
|
709 |
|
|
710 |
qint64 MediaObject::totalTime() const
|
|
711 |
{
|
|
712 |
IMPLEMENTED_SILENT;
|
|
713 |
return m_videoPlayer->duration();
|
|
714 |
}
|
|
715 |
|
|
716 |
Phonon::State MediaObject::state() const
|
|
717 |
{
|
|
718 |
IMPLEMENTED;
|
|
719 |
return m_state;
|
|
720 |
}
|
|
721 |
|
|
722 |
QString MediaObject::errorString() const
|
|
723 |
{
|
|
724 |
IMPLEMENTED;
|
|
725 |
return m_errorString;
|
|
726 |
}
|
|
727 |
|
|
728 |
Phonon::ErrorType MediaObject::errorType() const
|
|
729 |
{
|
|
730 |
IMPLEMENTED;
|
|
731 |
return m_errorType;
|
|
732 |
}
|
|
733 |
|
|
734 |
bool MediaObject::checkForError()
|
|
735 |
{
|
|
736 |
int type = gGetErrorType();
|
|
737 |
if (type == NO_ERROR)
|
|
738 |
return false;
|
|
739 |
|
|
740 |
m_errorType = (type == NORMAL_ERROR) ? Phonon::NormalError : Phonon::FatalError;
|
|
741 |
m_errorString = gGetErrorString();
|
|
742 |
pause_internal();
|
|
743 |
gClearError();
|
|
744 |
setState(Phonon::ErrorState);
|
|
745 |
return true;
|
|
746 |
}
|
|
747 |
|
|
748 |
QuickTimeVideoPlayer* MediaObject::videoPlayer() const
|
|
749 |
{
|
|
750 |
return m_videoPlayer;
|
|
751 |
}
|
|
752 |
|
|
753 |
MediaSource MediaObject::source() const
|
|
754 |
{
|
|
755 |
IMPLEMENTED;
|
|
756 |
return m_videoPlayer->mediaSource();
|
|
757 |
}
|
|
758 |
|
|
759 |
qint32 MediaObject::prefinishMark() const
|
|
760 |
{
|
|
761 |
IMPLEMENTED;
|
|
762 |
return m_prefinishMark;
|
|
763 |
}
|
|
764 |
|
|
765 |
void MediaObject::setPrefinishMark(qint32 mark)
|
|
766 |
{
|
|
767 |
IMPLEMENTED;
|
|
768 |
m_prefinishMark = mark;
|
|
769 |
}
|
|
770 |
|
|
771 |
qint32 MediaObject::transitionTime() const
|
|
772 |
{
|
|
773 |
IMPLEMENTED;
|
|
774 |
return m_transitionTime;
|
|
775 |
}
|
|
776 |
|
|
777 |
void MediaObject::setTransitionTime(qint32 transitionTime)
|
|
778 |
{
|
|
779 |
IMPLEMENTED;
|
|
780 |
m_transitionTime = transitionTime;
|
|
781 |
}
|
|
782 |
|
|
783 |
void MediaObject::setVolumeOnMovie(float volume)
|
|
784 |
{
|
|
785 |
m_videoPlayer->setMasterVolume(volume);
|
|
786 |
m_nextVideoPlayer->setMasterVolume(volume);
|
|
787 |
}
|
|
788 |
|
|
789 |
bool MediaObject::setAudioDeviceOnMovie(int id)
|
|
790 |
{
|
|
791 |
m_nextVideoPlayer->setAudioDevice(id);
|
|
792 |
return m_videoPlayer->setAudioDevice(id);
|
|
793 |
}
|
|
794 |
|
|
795 |
void MediaObject::updateCrossFade()
|
|
796 |
{
|
|
797 |
m_mediaObjectAudioNode->updateCrossFade(m_currentTime);
|
|
798 |
// Clean-up previous movie if done fading:
|
|
799 |
if (m_mediaObjectAudioNode->m_fadeDuration == 0){
|
|
800 |
if (m_nextVideoPlayer->isPlaying() || m_nextAudioPlayer->isPlaying()){
|
|
801 |
m_nextVideoPlayer->unsetCurrentMediaSource();
|
|
802 |
m_nextAudioPlayer->unsetVideoPlayer();
|
|
803 |
}
|
|
804 |
}
|
|
805 |
}
|
|
806 |
|
|
807 |
void MediaObject::updateBufferStatus()
|
|
808 |
{
|
|
809 |
float percent = m_videoPlayer->percentageLoaded();
|
|
810 |
if (percent != m_percentageLoaded){
|
|
811 |
m_percentageLoaded = percent;
|
|
812 |
emit bufferStatus(m_percentageLoaded * 100);
|
|
813 |
}
|
|
814 |
}
|
|
815 |
|
|
816 |
void MediaObject::updateAudioBuffers()
|
|
817 |
{
|
|
818 |
// Schedule audio slices:
|
|
819 |
m_audioPlayer->scheduleAudioToGraph();
|
|
820 |
m_nextAudioPlayer->scheduleAudioToGraph();
|
|
821 |
}
|
|
822 |
|
|
823 |
bool MediaObject::isCrossFading()
|
|
824 |
{
|
|
825 |
return m_mediaObjectAudioNode->isCrossFading();
|
|
826 |
}
|
|
827 |
|
|
828 |
void MediaObject::updateVideoFrames()
|
|
829 |
{
|
|
830 |
// Draw next frame if awailable:
|
|
831 |
if (m_videoPlayer->videoFrameChanged()){
|
|
832 |
updateLipSynch(50);
|
|
833 |
VideoFrame frame(m_videoPlayer);
|
|
834 |
if (m_nextVideoPlayer->isPlaying()
|
|
835 |
&& m_nextVideoPlayer->hasVideo()
|
|
836 |
&& isCrossFading()){
|
|
837 |
VideoFrame bgFrame(m_nextVideoPlayer);
|
|
838 |
frame.setBackgroundFrame(bgFrame);
|
|
839 |
frame.setBaseOpacity(m_mediaObjectAudioNode->m_volume1);
|
|
840 |
}
|
|
841 |
|
|
842 |
// Send the frame through the graph:
|
|
843 |
updateVideo(frame);
|
|
844 |
checkForError();
|
|
845 |
}
|
|
846 |
}
|
|
847 |
|
|
848 |
void MediaObject::updateLipSynch(int allowedOffset)
|
|
849 |
{
|
|
850 |
if (m_audioSystem != AS_Graph || !m_audioGraph->isRunning())
|
|
851 |
return;
|
|
852 |
if (m_videoSinkList.isEmpty() || m_audioSinkList.isEmpty())
|
|
853 |
return;
|
|
854 |
|
|
855 |
if (m_videoPlayer->hasVideo()){
|
|
856 |
qint64 diff = m_audioPlayer->currentTime() - m_videoPlayer->currentTime();
|
|
857 |
if (-allowedOffset > diff || diff > allowedOffset)
|
|
858 |
m_audioPlayer->seek(m_videoPlayer->currentTime());
|
|
859 |
}
|
|
860 |
|
|
861 |
if (isCrossFading() && m_nextVideoPlayer->hasVideo()){
|
|
862 |
qint64 diff = m_nextAudioPlayer->currentTime() - m_nextVideoPlayer->currentTime();
|
|
863 |
if (-(allowedOffset*2) > diff || diff > (allowedOffset*2))
|
|
864 |
m_nextAudioPlayer->seek(m_nextVideoPlayer->currentTime());
|
|
865 |
}
|
|
866 |
}
|
|
867 |
|
|
868 |
void MediaObject::updateRapidly()
|
|
869 |
{
|
|
870 |
updateCurrentTime();
|
|
871 |
updateCrossFade();
|
|
872 |
updateBufferStatus();
|
|
873 |
}
|
|
874 |
|
|
875 |
void MediaObject::setMute(bool mute)
|
|
876 |
{
|
|
877 |
m_mediaObjectAudioNode->setMute(mute);
|
|
878 |
m_videoPlayer->setMute(mute);
|
|
879 |
m_nextVideoPlayer->setMute(mute);
|
|
880 |
}
|
|
881 |
|
|
882 |
void MediaObject::mediaNodeEvent(const MediaNodeEvent *event)
|
|
883 |
{
|
|
884 |
switch (event->type()){
|
|
885 |
case MediaNodeEvent::EndConnectionChange:
|
|
886 |
m_mediaObjectAudioNode->setMute(true);
|
|
887 |
inspectGraph();
|
|
888 |
setupAudioSystem();
|
|
889 |
synchAudioVideo();
|
|
890 |
checkForError();
|
|
891 |
m_mediaObjectAudioNode->setMute(false);
|
|
892 |
if (m_state == Phonon::PlayingState)
|
|
893 |
restartAudioVideoTimers();
|
|
894 |
break;
|
|
895 |
case MediaNodeEvent::AudioGraphCannotPlay:
|
|
896 |
case MediaNodeEvent::AudioGraphInitialized:
|
|
897 |
if (m_state != Phonon::LoadingState){
|
|
898 |
m_mediaObjectAudioNode->setMute(true);
|
|
899 |
setupAudioSystem();
|
|
900 |
updateLipSynch(0);
|
|
901 |
checkForError();
|
|
902 |
m_mediaObjectAudioNode->setMute(false);
|
|
903 |
}
|
|
904 |
break;
|
|
905 |
default:
|
|
906 |
break;
|
|
907 |
}
|
|
908 |
}
|
|
909 |
|
|
910 |
bool MediaObject::event(QEvent *event)
|
|
911 |
{
|
|
912 |
switch (event->type()){
|
|
913 |
#if QT_ALLOW_QUICKTIME
|
|
914 |
case QEvent::User:{
|
|
915 |
m_displayLinkMutex.lock();
|
|
916 |
m_pendingDisplayLinkEvent = false;
|
|
917 |
m_displayLinkMutex.unlock();
|
|
918 |
updateVideoFrames();
|
|
919 |
break; }
|
|
920 |
#endif
|
|
921 |
case QEvent::Timer:{
|
|
922 |
int timerId = static_cast<QTimerEvent *>(event)->timerId();
|
|
923 |
if (timerId == m_rapidTimer)
|
|
924 |
updateRapidly();
|
|
925 |
else if (timerId == m_tickTimer)
|
|
926 |
emit tick(currentTime());
|
|
927 |
else if (timerId == m_videoTimer)
|
|
928 |
updateVideoFrames();
|
|
929 |
else if (timerId == m_audioTimer)
|
|
930 |
updateAudioBuffers();
|
|
931 |
break; }
|
|
932 |
default:
|
|
933 |
break;
|
|
934 |
}
|
|
935 |
return QObject::event(event);
|
|
936 |
}
|
|
937 |
|
|
938 |
void MediaObject::setCurrentTrack(int track)
|
|
939 |
{
|
|
940 |
if (track == m_videoPlayer->currentTrack() || track < 0 || track >= m_videoPlayer->trackCount())
|
|
941 |
return;
|
|
942 |
|
|
943 |
m_videoPlayer->setCurrentTrack(track);
|
|
944 |
emit titleChanged(track);
|
|
945 |
emit metaDataChanged(m_videoPlayer->metaData());
|
|
946 |
}
|
|
947 |
|
|
948 |
bool MediaObject::hasInterface(Interface iface) const
|
|
949 |
{
|
|
950 |
return iface == AddonInterface::TitleInterface;
|
|
951 |
}
|
|
952 |
|
|
953 |
QVariant MediaObject::interfaceCall(Interface iface, int command, const QList<QVariant> ¶ms)
|
|
954 |
{
|
|
955 |
switch (iface) {
|
|
956 |
case TitleInterface:
|
|
957 |
switch (command) {
|
|
958 |
case availableTitles:
|
|
959 |
return m_videoPlayer->trackCount();
|
|
960 |
case title:
|
|
961 |
return m_videoPlayer->currentTrack();
|
|
962 |
case setTitle:
|
|
963 |
setCurrentTrack(params.first().toInt());
|
|
964 |
break;
|
|
965 |
case autoplayTitles:
|
|
966 |
return m_autoplayTitles;
|
|
967 |
case setAutoplayTitles:
|
|
968 |
m_autoplayTitles = params.first().toBool();
|
|
969 |
break;
|
|
970 |
}
|
|
971 |
default:
|
|
972 |
break;
|
|
973 |
}
|
|
974 |
return QVariant();
|
|
975 |
}
|
|
976 |
|
|
977 |
}} // namespace Phonon::QT7
|
|
978 |
|
|
979 |
QT_END_NAMESPACE
|
|
980 |
|
|
981 |
#include "moc_mediaobject.cpp"
|
|
982 |
|