|
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 |