1 /*
2 * Copyright (C) 2012 Sibi Antony (Phonon/QT4 implementation)
3 * Copyright (C) 2015 Georg Zotti (Reactivated with QT5 classes)
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 */
19
20 #include "StelVideoMgr.hpp"
21 #include "StelMainView.hpp"
22 #include <QDebug>
23 #include <QDir>
24 #ifdef ENABLE_MEDIA
25 #include <QGraphicsVideoItem>
26 #include <QMediaPlayer>
27 #include <QTimer>
28 #include <QApplication>
29 #include "StelApp.hpp"
30 #include "StelFader.hpp"
31 #endif
32
33
StelVideoMgr()34 StelVideoMgr::StelVideoMgr() : StelModule()
35 {
36 setObjectName("StelVideoMgr");
37 #ifdef ENABLE_MEDIA
38 // in case the property has not been set, getProperty() returns invalid.
39 verbose= (qApp->property("verbose") == true);
40 #endif
41 }
42
43 #ifdef ENABLE_MEDIA
~StelVideoMgr()44 StelVideoMgr::~StelVideoMgr()
45 {
46 for (const auto& id : videoObjects.keys())
47 {
48 dropVideo(id);
49 }
50 }
51
loadVideo(const QString & filename,const QString & id,const float x,const float y,const bool show,const float alpha)52 void StelVideoMgr::loadVideo(const QString& filename, const QString& id, const float x, const float y, const bool show, const float alpha)
53 {
54 if (videoObjects.contains(id))
55 {
56 qWarning() << "[StelVideoMgr] Video object with ID" << id << "already exists, dropping it";
57 dropVideo(id);
58 }
59
60 videoObjects[id] = new VideoPlayer;
61 videoObjects[id]->videoItem= new QGraphicsVideoItem();
62 // This sets a tiny size so that if window should appear before proper resize, it should not disturb.
63 videoObjects[id]->videoItem->setSize(QSizeF(1,1));
64
65 videoObjects[id]->player = new QMediaPlayer(Q_NULLPTR, QMediaPlayer::VideoSurface);
66 videoObjects[id]->duration=-1; // -1 to signal "unknown".
67 videoObjects[id]->resolution=QSize(); // initialize with "invalid" empty resolution, we must detect this when player is starting!
68 videoObjects[id]->keepVisible=false;
69 videoObjects[id]->needResize=true; // resolution and target frame not yet known.
70 videoObjects[id]->simplePlay=true;
71 videoObjects[id]->targetFrameSize=QSizeF(); // start with invalid, depends on parameters given in playVideo(), playPopoutVideo() and resolution detected only after playing started.
72 videoObjects[id]->popupOrigin=QPointF();
73 videoObjects[id]->popupTargetCenter=QPointF();
74 videoObjects[id]->player->setProperty("Stel_id", id); // allow tracking of log messages and access of members.
75 videoObjects[id]->player->setVideoOutput(videoObjects[id]->videoItem);
76 videoObjects[id]->videoItem->setOpacity(alpha);
77 #ifndef Q_OS_WIN
78 // There is a notable difference: on Windows this causes a crash. On Linux, it is required, else the movie frame is visible before proper resize.
79 videoObjects[id]->videoItem->setVisible(show);
80 #endif
81 videoObjects[id]->lastPos=-1;
82
83 // A few connections are not really needed, they are signals we don't use. TBD: Remove or keep commented out?
84 connect(videoObjects[id]->player, SIGNAL(bufferStatusChanged(int)), this, SLOT(handleBufferStatusChanged(int)));
85 connect(videoObjects[id]->player, SIGNAL(durationChanged(qint64)), this, SLOT(handleDurationChanged(qint64))); // (CRITICALLY IMPORTANT!)
86 connect(videoObjects[id]->player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(handleError(QMediaPlayer::Error)));
87 connect(videoObjects[id]->player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(handleMediaStatusChanged(QMediaPlayer::MediaStatus)));
88 //connect(videoObjects[id]->player, SIGNAL(positionChanged(qint64)), this, SLOT(handlePositionChanged(qint64)));
89 // we test isSeekable() where needed, so only debug log entry. --> And we may use the signal however now during blocking load below!
90 connect(videoObjects[id]->player, SIGNAL(seekableChanged(bool)), this, SLOT(handleSeekableChanged(bool)));
91 connect(videoObjects[id]->player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(handleStateChanged(QMediaPlayer::State)));
92 connect(videoObjects[id]->player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(handleVideoAvailableChanged(bool)));
93 connect(videoObjects[id]->player, SIGNAL(audioAvailableChanged(bool)), this, SLOT(handleAudioAvailableChanged(bool)));
94 connect(videoObjects[id]->player, SIGNAL(mutedChanged(bool)), this, SLOT(handleMutedChanged(bool)));
95 connect(videoObjects[id]->player, SIGNAL(volumeChanged(int)), this, SLOT(handleVolumeChanged(int)));
96 connect(videoObjects[id]->player, SIGNAL(availabilityChanged(bool)), this, SLOT(handleAvailabilityChanged(bool)));
97 connect(videoObjects[id]->player, SIGNAL(availabilityChanged(QMultimedia::AvailabilityStatus)), this, SLOT(handleAvailabilityChanged(QMultimedia::AvailabilityStatus)));
98
99 // Only this is triggered also on Windows. Lets us read resolution etc. (CRITICALLY IMPORTANT!)
100 connect(videoObjects[id]->player, SIGNAL(metaDataChanged()), this, SLOT(handleMetaDataChanged()));
101 // That signal would require less overhead, but is not triggered under Windows and apparently also not on MacOS X. (QTBUG-42034.)
102 // If this becomes available/bugfixed, it should be preferred as being more elegant.
103 // connect(videoObjects[id]->player, SIGNAL(metaDataChanged(QString,QVariant)), this, SLOT(handleMetaDataChanged(QString,QVariant)));
104
105 // We need an absolute pathname here.
106 QMediaContent content(QUrl::fromLocalFile(QFileInfo(filename).absoluteFilePath()));
107 videoObjects[id]->player->setMedia(content);
108 if (verbose)
109 {
110 qDebug() << "Loading " << content.canonicalUrl();
111 qDebug() << "Media Resources queried from player:";
112 qDebug() << "\tSTATUS: " << videoObjects[id]->player->mediaStatus();
113 qDebug() << "\tFile: " << videoObjects[id]->player->currentMedia().canonicalUrl();
114 }
115 // qDebug() << "scene->addItem...";
116 StelMainView::getInstance().scene()->addItem(videoObjects[id]->videoItem);
117 // qDebug() << "scene->addItem OK";
118
119 videoObjects[id]->videoItem->setPos(x, y);
120 // DEFAULT SIZE: show a tiny frame. This gets updated to native resolution as soon as resolution becomes known. Needed?
121 //videoObjects[id]->videoItem->setSize(QSizeF(1, 1));
122
123 // after many troubles with incompletely loaded files we attempt a blocking load from https://wiki.qt.io/Seek_in_Sound_File
124 // This may be no longer required. But please keep the block here for testing/reactivation if necessary.
125 // if (! videoObjects[id]->player->isSeekable())
126 // {
127 // qDebug() << "Not Seekable!";
128 // if (verbose)
129 // qDebug() << "Blocking load ...";
130 // QEventLoop loop;
131 // QTimer timer;
132 // qDebug() << "Not Seekable: setSingleShot";
133 // timer.setSingleShot(true);
134 // timer.setInterval(5000); // 5 seconds, may be too long?
135 // qDebug() << "Not Seekable: connect...";
136 // loop.connect(&timer, SIGNAL (timeout()), &loop, SLOT (quit()) );
137 // loop.connect(videoObjects[id]->player, SIGNAL (seekableChanged(bool)), &loop, SLOT (quit()));
138 // qDebug() << "Not Seekable: loop...";
139 // loop.exec();
140 // if (verbose)
141 // qDebug() << "Blocking load finished, should be seekable now or 5s are over.";
142 // }
143
144 if (verbose)
145 qDebug() << "Loaded video" << id << "for pos " << x << "/" << y << "Size" << videoObjects[id]->videoItem->size();
146 videoObjects[id]->player->setPosition(0); // This should force triggering a metadataAvailable() with resolution update.
147 if (show)
148 videoObjects[id]->player->play();
149 else
150 videoObjects[id]->player->pause();
151 }
152
playVideo(const QString & id,const bool keepVisibleAtEnd)153 void StelVideoMgr::playVideo(const QString& id, const bool keepVisibleAtEnd)
154 {
155 if (videoObjects.contains(id))
156 {
157 videoObjects[id]->keepVisible=keepVisibleAtEnd;
158 if (videoObjects[id]->player!=Q_NULLPTR)
159 {
160 // if already playing, stop and play from the start
161 if (videoObjects[id]->player->state() == QMediaPlayer::PlayingState)
162 {
163 videoObjects[id]->player->stop();
164 }
165 #ifndef Q_OS_WIN
166 // On Linux, we may have made movie frame invisible during loadVideo().
167 videoObjects[id]->videoItem->setVisible(true);
168 #endif
169
170 // otherwise just play it, or resume playing paused video.
171 if (videoObjects[id]->player->state() == QMediaPlayer::PausedState)
172 videoObjects[id]->lastPos=videoObjects[id]->player->position() - 1;
173 else
174 videoObjects[id]->lastPos=-1;
175
176 videoObjects[id]->simplePlay=true;
177 videoObjects[id]->player->play();
178 if (verbose)
179 qDebug() << "StelVideoMgr::playVideo(): playing " << id << videoObjects[id]->player->state() << " - media: " << videoObjects[id]->player->mediaStatus();
180 }
181 }
182 else qDebug() << "StelVideoMgr::playVideo(" << id << "): no such video";
183 }
184
185
186 //! Play a video which has previously been loaded with loadVideo with a complex start/end effect.
187 //! The video appears to grow out from @param fromX/ @param fromY
188 //! within @param popupDuration to size @param finalSizeX/@param finalSizeY, and
189 //! shrinks back towards @param fromX/@param fromY at the end during @param popdownDuration.
190 //! @param id the identifier used when @name loadVideo() was called
191 //! @param fromX X position of starting point, counted from left of window. May be absolute pixel coordinate (if >1) or relative to screen size (0<X<1)
192 //! @param fromY Y position of starting point, counted from top of window. May be absolute pixel coordinate (if >1) or relative to screen size (0<Y<1)
193 //! @param atCenterX X position of center of final video frame, counted from left of window. May be absolute pixel coordinate (if >1) or relative to screen size (0<X<1)
194 //! @param atCenterY Y position of center of final video frame, counted from top of window. May be absolute pixel coordinate (if >1) or relative to screen size (0<Y<1)
195 //! @param finalSizeX X size (width) of final video frame. May be absolute (if >1) or relative to window size (0<X<1). If -1, scale proportional from @param finalSizeY.
196 //! @param finalSizeY Y size (height) of final video frame. May be absolute (if >1) or relative to window size (0<Y<1). If -1, scale proportional from @param finalSizeX.
197 //! @param popupDuration duration of growing start transition (seconds)
198 //! @param popdownDuration duration of shrinking end transition (seconds)
199 //! @param frozenInTransition true if video should be paused during growing/shrinking transition.
playVideoPopout(const QString & id,float fromX,float fromY,float atCenterX,float atCenterY,float finalSizeX,float finalSizeY,float popupDuration,bool frozenInTransition)200 void StelVideoMgr::playVideoPopout(const QString& id, float fromX, float fromY, float atCenterX, float atCenterY,
201 float finalSizeX, float finalSizeY, float popupDuration, bool frozenInTransition)
202 {
203 if (verbose)
204 qDebug() << "\n\n====Configuring playVideoPopout(): " << id;
205 if (videoObjects.contains(id))
206 {
207 videoObjects[id]->keepVisible=frozenInTransition;
208 if (videoObjects[id]->player!=Q_NULLPTR)
209 {
210 // if already playing, stop and play from the start
211 if (videoObjects[id]->player->state() == QMediaPlayer::PlayingState)
212 {
213 qDebug() << "playVideoPopout(): stop the playing video";
214 videoObjects[id]->player->stop();
215 }
216
217 // prepare (1) target frame size, (2) XY start position, and (3) end position:
218 // (1) Target frame size.
219 // if finalSizeX or finalSizeY <= 1 we scale proportional to mainview!
220 // Note that finalSizeX or finalSizeY thus cannot be set to 1.0, i.e. single-pixel rows/columns!
221 // This is likely not tragic, else set to 1.0001
222 int viewportWidth=StelMainView::getInstance().size().width();
223 int viewportHeight=StelMainView::getInstance().size().height();
224
225 if (finalSizeX>0 && finalSizeX<=1)
226 finalSizeX*=viewportWidth;
227 if (finalSizeY>0 && finalSizeY<=1)
228 finalSizeY*=viewportHeight;
229
230 if (verbose)
231 qDebug() << "playVideoPopout() finalSize: "<< finalSizeX << "x" << finalSizeY;
232
233 QSize videoSize=videoObjects[id]->resolution;
234 if (verbose)
235 qDebug() << "playVideoPopout(): video resolution detected=" << videoSize;
236
237 if (!videoSize.isValid() && (finalSizeX==-1 || finalSizeY==-1))
238 {
239 // This should not happen, is a real problem.
240 qDebug() << "StelVideoMgr::playVideoPopout()" << id << ": size (resolution) not yet determined, cannot resize with -1 argument. Sorry, command stops here...";
241 return;
242 }
243 float aspectRatio=(float)videoSize.width()/(float)videoSize.height();
244 if (verbose)
245 qDebug() << "StelVideoMgr::playVideoPopout(): computed aspect ratio:" << aspectRatio;
246 if (finalSizeX!=-1.0f && finalSizeY!=-1.0f)
247 videoObjects[id]->videoItem->setAspectRatioMode(Qt::IgnoreAspectRatio);
248 else
249 {
250 videoObjects[id]->videoItem->setAspectRatioMode(Qt::KeepAspectRatio);
251 if (finalSizeX==-1.0f && finalSizeY==-1.0f)
252 {
253 finalSizeX=videoSize.height();
254 finalSizeY=videoSize.width();
255 }
256 else if (finalSizeY==-1.0f)
257 finalSizeY=finalSizeX/aspectRatio;
258 else if (finalSizeX==-1.0f)
259 finalSizeX=finalSizeY*aspectRatio;
260 }
261 if (verbose)
262 qDebug() << "StelVideoMgr::playVideoPopout(): Resetting target frame size to :" << finalSizeX << "x" << finalSizeY;
263 videoObjects[id]->targetFrameSize= QSizeF(finalSizeX, finalSizeY); // size in Pixels
264
265 // (2) start position:
266 if (fromX>0 && fromX<1)
267 fromX *= viewportWidth;
268 if (fromY>0 && fromY<1)
269 fromY *= viewportHeight;
270 videoObjects[id]->popupOrigin= QPointF(fromX, fromY); // Pixel coordinates of popout point.
271 if (verbose)
272 qDebug() << "StelVideoMgr::playVideoPopout(): Resetting start position to :" << fromX << "/" << fromY;
273
274
275 // (3) center of target frame
276 if (atCenterX>0 && atCenterX<1)
277 atCenterX *= viewportWidth;
278 if (atCenterY>0 && atCenterY<1)
279 atCenterY *= viewportHeight;
280 videoObjects[id]->popupTargetCenter= QPointF(atCenterX, atCenterY); // Pixel coordinates of frame center
281 if (verbose)
282 qDebug() << "StelVideoMgr::playVideoPopout(): Resetting target position to :" << atCenterX << "/" << atCenterY;
283
284 // (4) configure fader
285 videoObjects[id]->fader.setDuration(1000.0f*popupDuration);
286
287 // (5) TRIGGER!
288 videoObjects[id]->simplePlay=false;
289 videoObjects[id]->fader=true;
290 videoObjects[id]->videoItem->setVisible(true);
291 videoObjects[id]->lastPos=-1;
292 videoObjects[id]->player->setPosition(0);
293 videoObjects[id]->player->play();
294
295 if (verbose)
296 qDebug() << "StelVideoMgr::playVideoPopout(): fader triggered.";
297 }
298 }
299 else qDebug() << "StelVideoMgr::playVideoPopout(" << id << "): no such video";
300 }
301
302
pauseVideo(const QString & id)303 void StelVideoMgr::pauseVideo(const QString& id)
304 {
305 if (videoObjects.contains(id))
306 {
307 if (videoObjects[id]->player!=Q_NULLPTR)
308 {
309 // Maybe we can only pause while already playing?
310 if (videoObjects[id]->player->state()==QMediaPlayer::StoppedState)
311 videoObjects[id]->player->play();
312
313 if (verbose)
314 qDebug() << "StelVideoMgr::pauseVideo() ...";
315 videoObjects[id]->player->pause();
316 }
317 }
318 else qDebug() << "StelVideoMgr::pauseVideo()" << id << ": no such video";
319 }
320
stopVideo(const QString & id)321 void StelVideoMgr::stopVideo(const QString& id)
322 {
323 if (videoObjects.contains(id))
324 {
325 if (videoObjects[id]->player!=Q_NULLPTR)
326 {
327 videoObjects[id]->player->stop();
328 }
329 }
330 else qDebug() << "StelVideoMgr::stopVideo()" << id << ": no such video";
331 }
332
seekVideo(const QString & id,const qint64 ms,bool pause)333 void StelVideoMgr::seekVideo(const QString& id, const qint64 ms, bool pause)
334 {
335 if (verbose)
336 qDebug() << "StelVideoMgr::seekVideo: " << id << " to:" << ms << (pause ? " (pausing)" : " (playing)");
337 if (videoObjects.contains(id))
338 {
339 if (videoObjects[id]->player!=Q_NULLPTR)
340 {
341 if (videoObjects[id]->player->isSeekable())
342 {
343 videoObjects[id]->player->setPosition(ms);
344 // Seek capability depends on the backend used and likely media codec.
345 }
346 else
347 {
348 qDebug() << "[StelVideoMgr] Cannot seek media source" << id;
349 }
350 // visual update only happens if we play. So even with pause set, we must play to freeze the frame!
351 videoObjects[id]->player->play();
352 if (pause)
353 videoObjects[id]->player->pause();
354 }
355 }
356 else qDebug() << "StelVideoMgr::seekVideo()" << id << ": no such video";
357 }
358
dropVideo(const QString & id)359 void StelVideoMgr::dropVideo(const QString& id)
360 {
361 if (!videoObjects.contains(id))
362 return;
363 if (videoObjects[id]->player!=Q_NULLPTR)
364 {
365 if (verbose)
366 qDebug() << "About to drop (unload) video " << id << "(" << videoObjects[id]->player->mediaStatus() << ")";
367 videoObjects[id]->player->stop();
368 StelMainView::getInstance().scene()->removeItem(videoObjects[id]->videoItem);
369 delete videoObjects[id]->player;
370 delete videoObjects[id]->videoItem;
371 delete videoObjects[id];
372 videoObjects.remove(id);
373 }
374 }
375
376 // setVideoXY(100, 200, false): absolute, as before
377 // setVideoXY(100, 200, true): shift 100 right, 200 down
378 // setVideoXY(0.5, 0.25, false): (on fullHD), set to (960/270)
379 // setVideoXY(0.5, 0.25, true): (on fullHD), shift by (960/270)
380 // setVideoXY(600, 0.25, false): (on fullHD), set to (600/270)
381 // setVideoXY(600, 0.25, true): (on fullHD), shift by (600/270)
setVideoXY(const QString & id,const float x,const float y,const bool relative)382 void StelVideoMgr::setVideoXY(const QString& id, const float x, const float y, const bool relative)
383 {
384 if (videoObjects.contains(id))
385 {
386 if (videoObjects[id]->videoItem!=Q_NULLPTR)
387 {
388 // if w or h < 1 we scale proportional to mainview!
389 int viewportWidth=StelMainView::getInstance().size().width();
390 int viewportHeight=StelMainView::getInstance().size().height();
391 float newX=x;
392 float newY=y;
393 if (x>-1 && x<1)
394 newX *= viewportWidth;
395 if (y>-1 && y<1)
396 newY *= viewportHeight;
397 if (relative)
398 {
399 QPointF pos = videoObjects[id]->videoItem->pos();
400 videoObjects[id]->videoItem->setPos(pos.x()+newX, pos.y()+newY);
401 }
402 else
403 videoObjects[id]->videoItem->setPos(newX, newY);
404 if (verbose)
405 qDebug() << "Setting video XY= " << newX << "/" << newY << (relative? "(relative)":"");
406 }
407 }
408 else qDebug() << "StelVideoMgr::setVideoXY()" << id << ": no such video";
409 }
410
setVideoAlpha(const QString & id,const float alpha)411 void StelVideoMgr::setVideoAlpha(const QString& id, const float alpha)
412 {
413 if (videoObjects.contains(id))
414 {
415 if (videoObjects[id]->videoItem!=Q_NULLPTR)
416 {
417 videoObjects[id]->videoItem->setOpacity(alpha);
418 }
419 }
420 else qDebug() << "StelVideoMgr::setVideoAlpha()" << id << ": no such video";
421 }
422
resizeVideo(const QString & id,float w,float h)423 void StelVideoMgr::resizeVideo(const QString& id, float w, float h)
424 {
425 if (videoObjects.contains(id))
426 {
427 if (videoObjects[id]->videoItem!=Q_NULLPTR)
428 {
429 // if w or h <= 1 we scale proportional to mainview!
430 // Note that w or h thus cannot be set to 1.0, i.e. single-pixel rows/columns!
431 // This is likely not tragic, else set to 1.0001
432 int viewportWidth=StelMainView::getInstance().size().width();
433 int viewportHeight=StelMainView::getInstance().size().height();
434
435 if (w>0 && w<=1)
436 w*=viewportWidth;
437 if (h>0 && h<=1)
438 h*=viewportHeight;
439
440 QSize videoSize=videoObjects[id]->resolution;
441 if (verbose)
442 qDebug() << "resizeVideo(): native resolution=" << videoSize;
443
444 if (!videoSize.isValid() && (w==-1 || h==-1))
445 {
446 if (verbose)
447 qDebug() << "StelVideoMgr::resizeVideo()" << id << ": size not yet determined, cannot resize with -1 argument. We do that in next update().";
448 // mark necessity of deferred resize.
449 videoObjects[id]->needResize=true;
450 videoObjects[id]->targetFrameSize=QSizeF(w, h); // w|h can be -1 or >1, no longer 0<(w|h)<1.
451 return;
452 }
453 float aspectRatio=(float)videoSize.width()/(float)videoSize.height();
454 if (verbose)
455 qDebug() << "aspect ratio:" << aspectRatio; // 1 for invalid size.
456 if (w!=-1.0f && h!=-1.0f)
457 videoObjects[id]->videoItem->setAspectRatioMode(Qt::IgnoreAspectRatio);
458 else
459 {
460 videoObjects[id]->videoItem->setAspectRatioMode(Qt::KeepAspectRatio);
461 if (w==-1.0f && h==-1.0f)
462 {
463 h=videoSize.height();
464 w=videoSize.width();
465 }
466 else if (h==-1.0f)
467 h=w/aspectRatio;
468 else if (w==-1.0f)
469 w=h*aspectRatio;
470 }
471 if (verbose)
472 qDebug() << "Resizing to:" << w << "x" << h;
473 videoObjects[id]->targetFrameSize=QSizeF(w, h); // w|h cannot be -1 or >1 here.
474 videoObjects[id]->videoItem->setSize(QSizeF(w, h));
475 videoObjects[id]->needResize=false;
476 }
477 }
478 else qDebug() << "StelVideoMgr::resizeVideo()" << id << ": no such video";
479 }
480
showVideo(const QString & id,const bool show)481 void StelVideoMgr::showVideo(const QString& id, const bool show)
482 {
483 if (videoObjects.contains(id))
484 {
485 if (videoObjects[id]->videoItem!=Q_NULLPTR)
486 {
487 videoObjects[id]->videoItem->setVisible(show);
488 }
489 }
490 else qDebug() << "StelVideoMgr::showVideo()" << id << ": no such video";
491 }
492
getVideoDuration(const QString & id) const493 qint64 StelVideoMgr::getVideoDuration(const QString& id) const
494 {
495 if (videoObjects.contains(id))
496 {
497 return videoObjects[id]->duration;
498 }
499 else qDebug() << "StelVideoMgr::getDuration()" << id << ": no such video";
500 return -1;
501 }
502
getVideoPosition(const QString & id) const503 qint64 StelVideoMgr::getVideoPosition(const QString& id) const
504 {
505 if (videoObjects.contains(id))
506 {
507 return videoObjects[id]->player->position();
508 }
509 else qDebug() << "StelVideoMgr::getPosition()" << id << ": no such video";
510 return -1;
511 }
512
513 //! returns native resolution (in pixels) of loaded video. Returned value may be invalid before video has been fully loaded.
getVideoResolution(const QString & id) const514 QSize StelVideoMgr::getVideoResolution(const QString& id) const
515 {
516 if (videoObjects.contains(id))
517 {
518 return videoObjects[id]->resolution;
519 }
520 else qDebug() << "StelVideoMgr::getResolution()" << id << ": no such video";
521 return QSize();
522 }
523
getVideoWidth(const QString & id) const524 int StelVideoMgr::getVideoWidth(const QString& id) const
525 {
526 if (videoObjects.contains(id))
527 {
528 if (videoObjects[id]->resolution.isValid())
529 return videoObjects[id]->resolution.width();
530 else
531 return -1;
532 }
533 else qDebug() << "StelVideoMgr::getWidth()" << id << ": no such video";
534 return -1;
535 }
536
getVideoHeight(const QString & id) const537 int StelVideoMgr::getVideoHeight(const QString& id) const
538 {
539 if (videoObjects.contains(id))
540 {
541 if (videoObjects[id]->resolution.isValid())
542 return videoObjects[id]->resolution.height();
543 else
544 return -1;
545 }
546 else qDebug() << "StelVideoMgr::getHeight()" << id << ": no such video";
547 return -1;
548 }
549
550
muteVideo(const QString & id,bool mute)551 void StelVideoMgr::muteVideo(const QString& id, bool mute)
552 {
553 if (videoObjects.contains(id))
554 {
555 if (videoObjects[id]->player!=Q_NULLPTR)
556 {
557 videoObjects[id]->player->setMuted(mute);
558 }
559 }
560 else qDebug() << "StelVideoMgr::mute()" << id << ": no such video";
561 }
562
setVideoVolume(const QString & id,int newVolume)563 void StelVideoMgr::setVideoVolume(const QString& id, int newVolume)
564 {
565 if (videoObjects.contains(id))
566 {
567 if (videoObjects[id]->player!=Q_NULLPTR)
568 {
569 videoObjects[id]->player->setVolume(newVolume);
570 }
571 }
572 else qDebug() << "StelVideoMgr::setVolume()" << id << ": no such video";
573 }
574
getVideoVolume(const QString & id) const575 int StelVideoMgr::getVideoVolume(const QString& id) const
576 {
577 int volume=-1;
578 if (videoObjects.contains(id))
579 {
580 if (videoObjects[id]->player!=Q_NULLPTR)
581 {
582 volume=videoObjects[id]->player->volume();
583 }
584 }
585 else qDebug() << "StelVideoMgr::getVolume()" << id << ": no such video";
586 return volume;
587 }
588
isVideoPlaying(const QString & id) const589 bool StelVideoMgr::isVideoPlaying(const QString& id) const
590 {
591 bool playing=false;
592 if (videoObjects.contains(id))
593 {
594 if (videoObjects[id]->player!=Q_NULLPTR)
595 {
596 playing= (videoObjects[id]->player->state() == QMediaPlayer::PlayingState );
597 }
598 }
599 else qDebug() << "StelVideoMgr::isPlaying()" << id << ": no such video";
600 return playing;
601 }
602
603
604 /* *************************************************
605 * Signal handlers for all signals of QMediaPlayer. Usually for now this only writes a message to logfile.
606 */
handleAudioAvailableChanged(bool available)607 void StelVideoMgr::handleAudioAvailableChanged(bool available)
608 {
609 if (verbose)
610 qDebug() << "StelVideoMgr: " << this->sender()->property("Stel_id").toString() << ": Audio is now available:" << available;
611 }
612 // It seems this is never called in practice.
handleBufferStatusChanged(int percentFilled)613 void StelVideoMgr::handleBufferStatusChanged(int percentFilled)
614 {
615 if (verbose)
616 qDebug() << "StelVideoMgr: " << QObject::sender()->property("Stel_id").toString() << ": Buffer filled (%):" << percentFilled;
617 }
618
handleDurationChanged(qint64 duration)619 void StelVideoMgr::handleDurationChanged(qint64 duration)
620 {
621 QString id=QObject::sender()->property("Stel_id").toString();
622 if (verbose)
623 qDebug() << "StelVideoMgr: " << id << ": Duration changed to:" << duration;
624 if (videoObjects.contains(id))
625 {
626 videoObjects[id]->duration=duration;
627 }
628 }
handleError(QMediaPlayer::Error error)629 void StelVideoMgr::handleError(QMediaPlayer::Error error)
630 {
631 if (verbose)
632 qDebug() << "StelVideoMgr: " << QObject::sender()->property("Stel_id").toString() << ": error:" << error;
633 }
634
635 /*
636 // This gets called in loadVideo with player->setMedia(content). Unnecessary, disabled.
637 void StelVideoMgr::handleCurrentMediaChanged(const QMediaContent & media)
638 {
639 qDebug() << "QMediaplayer: " << QObject::sender()->property("Stel_id").toString() << ": Current media changed:" << media.canonicalUrl();
640 }
641
642 // This gets called in loadVideo with player->setMedia(content). Unnecessary, disabled.
643 void StelVideoMgr::handleMediaChanged(const QMediaContent & media)
644 {
645 qDebug() << "QMediaPlayer: " << QObject::sender()->property("Stel_id").toString() << ": Media changed:" << media.canonicalUrl();
646 }
647 */
648
649 // This may have been useful to trigger the pause-at-end if it was fast enough.
650 // It seems that this is too slow! At EndOfMedia the player window disappears, then reappears. This is ugly!
651 // Currently we deal with polling status changes within update().
handleMediaStatusChanged(QMediaPlayer::MediaStatus status)652 void StelVideoMgr::handleMediaStatusChanged(QMediaPlayer::MediaStatus status) // debug-log messages
653 {
654 QString id=QObject::sender()->property("Stel_id").toString();
655 if (verbose)
656 qDebug() << "StelVideoMgr: " << id << ": MediaStatus changed to:" << status;
657 }
658
handleMutedChanged(bool muted)659 void StelVideoMgr::handleMutedChanged(bool muted)
660 {
661 if (verbose)
662 qDebug() << "StelVideoMgr: " << QObject::sender()->property("Stel_id").toString() << ": mute changed:" << muted;
663 }
664
665
666
667 /* // USELESS. This is called not often enough (could be configured), but update() is better suited to check for video-at-end.
668 void StelVideoMgr::handlePositionChanged(qint64 position)
669 {
670 QString senderId=QObject::sender()->property("Stel_id").toString();
671 qDebug() << "StelVideoMgr: " << senderId << ": position changed to (ms):" << position;
672 // We could deal with the keep-visible here, however this is not called often enough by default, and we have update anyways
673 if ((position==videoObjects[senderId]->duration) && (videoObjects[senderId]->keepVisible))
674 {
675 videoObjects[senderId]->player->setPosition(videoObjects[senderId]->duration - 1);
676 videoObjects[senderId]->player->pause();
677 qDebug() << " ---> paused at end as requested" ;
678 }
679 }
680 */
681
682 // USELESS?
handleSeekableChanged(bool seekable)683 void StelVideoMgr::handleSeekableChanged(bool seekable)
684 {
685 if (verbose)
686 qDebug() << "StelVideoMgr: handleSeekableChanged()" << QObject::sender()->property("Stel_id").toString() << ": seekable changed to:" << seekable;
687 }
handleStateChanged(QMediaPlayer::State state)688 void StelVideoMgr::handleStateChanged(QMediaPlayer::State state)
689 {
690 QString senderId=QObject::sender()->property("Stel_id").toString();
691 if (verbose)
692 qDebug() << "StelVideoMgr: " << senderId << ": state changed to:" << state
693 << "(Media Status: " << videoObjects[senderId]->player->mediaStatus() << ")";
694 }
695
handleVideoAvailableChanged(bool videoAvailable)696 void StelVideoMgr::handleVideoAvailableChanged(bool videoAvailable)
697 {
698 QString senderId=QObject::sender()->property("Stel_id").toString();
699 if (verbose)
700 qDebug() << "StelVideoMgr: " << senderId << ": Video available:" << videoAvailable;
701 // Sometimes it appears the video has not fully loaded when popup stars, and the movie is not shown.
702 // Maybe force showing here? --> NO, breaks our own logic...
703 //videoObjects[senderId]->videoItem->setVisible(videoAvailable);
704 }
705
handleVolumeChanged(int volume)706 void StelVideoMgr::handleVolumeChanged(int volume)
707 {
708 if (verbose)
709 qDebug() << "StelVideoMgr: " << QObject::sender()->property("Stel_id").toString() << ": volume changed to:" << volume;
710 }
711
handleAvailabilityChanged(bool available)712 void StelVideoMgr::handleAvailabilityChanged(bool available)
713 {
714 if (verbose)
715 qDebug() << "StelVideoMgr::handleAvailabilityChanged(bool) " << QObject::sender()->property("Stel_id").toString() << ": available:" << available;
716 }
717
handleAvailabilityChanged(QMultimedia::AvailabilityStatus availability)718 void StelVideoMgr::handleAvailabilityChanged(QMultimedia::AvailabilityStatus availability)
719 {
720 if (verbose)
721 qDebug() << "StelVideoMgr::availabilityChanged(QMultimedia::AvailabilityStatus) " << QObject::sender()->property("Stel_id").toString() << ": availability:" << availability;
722 }
723
724
725
726 // The signal sequence (at least on Windows7/MinGW/Qt5.4) seems to be:
727 // metadatachanged() as soon as video has been loaded. But Result: no metadata.
728 // After media has started playing and frame is already growing,
729 // audioAvailable() (but no audio in this movie...)
730 // videoAvailable() (true)
731 // durationChanged() --> only now duration becomes known!
732 // status changed: QMediaPlayer::BufferedMedia
733 // metadataAvailablechanged(true): Duration, PixelAspectRatio(unknown!), Resolution(unknown!), Videobitrate, VideoFramerate
734 // metadataChanged() now true, and finally here also PixelAspectRatio and Resolution are known.
735 // Then periodically, positionChanged(). We can skip that because in update() we can check position() if required.
736 // Sequence is the same on Win/MSVC and Qt5.3.2, so metadataChanged(.,.) is NOT called on Windows.
737 // This is also already observed on MacOSX and listed as QTBUG-42034.
738 // This signal is sent several times during replay because min/max rates often change, and we must go through the metadata list each time. We avoid setting resolution more than once.
handleMetaDataChanged()739 void StelVideoMgr::handleMetaDataChanged()
740 {
741 QString id=QObject::sender()->property("Stel_id").toString();
742 if (verbose)
743 qDebug() << "StelVideoMgr: " << id << ": Metadata changed (global notification).";
744
745 if (videoObjects.contains(id) && videoObjects[id]->player->isMetaDataAvailable())
746 {
747 if (verbose)
748 qDebug() << "StelVideoMgr: " << id << ": Following metadata are available:";
749 QStringList metadataList=videoObjects[id]->player->availableMetaData();
750 for (const auto& md : metadataList)
751 {
752 QString key = md.toLocal8Bit().constData();
753 if (verbose)
754 qDebug() << "\t" << key << "==>" << videoObjects[id]->player->metaData(key);
755
756 if ((key=="Resolution") && !(videoObjects[id]->resolution.isValid()))
757 {
758 if (verbose)
759 qDebug() << "StelVideoMgr: Resolution becomes available: " << videoObjects[id]->player->metaData(key).toSize();
760 videoObjects[id]->resolution=videoObjects[id]->player->metaData(key).toSize();
761 }
762 }
763 }
764 else if (videoObjects.contains(id) && !(videoObjects[id]->player->isMetaDataAvailable()) &&verbose)
765 qDebug() << "StelVideoMgr::handleMetaDataChanged()" << id << ": no metadata now.";
766 else
767 qDebug() << "StelVideoMgr::handleMetaDataChanged()" << id << ": no such video - this is absurd.";
768 }
769
770
771
772 /*
773 // Either this signal or metadataChanged() must be evaluated. On Linux, both are fired, on Windows only the (void) version.
774 // I (GZ) cannot say which may work on MacOSX, but will disable this for now, the required data handling is done in handleMetaDataChanged(void).
775 void StelVideoMgr::handleMetaDataChanged(const QString & key, const QVariant & value)
776 {
777 qDebug() << "!!! StelVideoMgr::handleMetadataChanged(.,.): Is this called on Windows when built with MSVC? "; // NOT WITH MinGW and Qt5.4!!!
778 qDebug() << "THIS IS TO ENSURE YOU SEE A CRASH! (If you see it, be happy!) CURRENTLY THE SIGNAL IS NOT SENT ON WINDOWS WHEN BUILT WITH minGW Qt5.4 and not with MSVC on Qt5.3.2";
779 Q_ASSERT(0); // Remove the Q_ASSERT and write a comment that it works on (which) Windows/Mac/...!
780 QString id=QObject::sender()->property("Stel_id").toString();
781 qDebug() << "StelVideoMgr: " << id << ": Metadata change:" << key << "=>" << value;
782 if (key=="Resolution")
783 {
784 qDebug() << "hah, resolution becomes available!";
785 if (videoObjects.contains(id))
786 {
787 videoObjects[id]->resolution=value.toSize();
788 videoObjects[id]->videoItem->setSize(videoObjects[id]->resolution);
789 }
790 else qDebug() << "StelVideoMgr::handleMetaDataChanged(.,.)" << id << ": no such video - this is absurd.";
791 }
792 }
793 */
794
795
796 // update() has only to deal with the faders in all videos, and (re)set positions and sizes of video windows.
update(double deltaTime)797 void StelVideoMgr::update(double deltaTime)
798 {
799 for (auto voIter = videoObjects.constBegin(); voIter != videoObjects.constEnd(); ++voIter)
800 {
801 QMediaPlayer::MediaStatus mediaStatus = (*voIter)->player->mediaStatus();
802 QString id=voIter.key();
803 // Maybe we have verbose as int with levels of verbosity, and output the next line with verbose>=2?
804 if (verbose)
805 qDebug() << "StelVideoMgr::update() for" << id << ": PlayerState:" << (*voIter)->player->state() << "MediaStatus: " << mediaStatus;
806
807 // fader must be updated here, else the video may not be visible when not yet fully loaded?
808 (*voIter)->fader.update(static_cast<int>(deltaTime*1000));
809
810 // It seems we need a more thorough analysis of MediaStatus!
811 // In all not-ready status we immediately leave further handling, usually in the hope that loading is successful really soon.
812 switch (mediaStatus)
813 {
814 case QMediaPlayer::UnknownMediaStatus:
815 case QMediaPlayer::NoMedia:
816 case QMediaPlayer::InvalidMedia:
817 case QMediaPlayer::LoadingMedia:
818 case QMediaPlayer::StalledMedia:
819 if (verbose)
820 qDebug() << "StelVideoMgr::update(): " << id << mediaStatus << "\t==> no further update action";
821 continue;
822 case QMediaPlayer::LoadedMedia:
823 case QMediaPlayer::BufferingMedia:
824 case QMediaPlayer::BufferedMedia:
825 case QMediaPlayer::EndOfMedia:
826 default:
827 break;
828 }
829
830 // if (verbose)
831 // qDebug() << "update() Still alive";
832
833 // First fix targetFrameSize if needed and possible.
834 if ((*voIter)->needResize && ((*voIter)->resolution.isValid()))
835 {
836 // we have buffered our final size (which may be relative to screen size) in targetFrameSize. So, simply
837 resizeVideo(voIter.key(), (*voIter)->targetFrameSize.width(), (*voIter)->targetFrameSize.height());
838 }
839
840 // if keepVisible, pause video if already close to end.
841 if ((*voIter)->simplePlay)
842 {
843 if ( ((*voIter)->keepVisible) && ((*voIter)->duration > 0) && ((*voIter)->player->position() >= ((*voIter)->duration - 250)) )
844 {
845 if (verbose)
846 qDebug() << "update(): pausing" << id << "at end";
847 (*voIter)->player->pause();
848 }
849
850 continue;
851 }
852
853 if ((*voIter)->player->state()==QMediaPlayer::StoppedState)
854 continue;
855
856 // the rest of the loop is only needed if we are in popup playing mode.
857
858 QSizeF currentFrameSize= (*voIter)->fader.getInterstate() * (*voIter)->targetFrameSize;
859 QPointF frameCenter= (*voIter)->popupOrigin + ( (*voIter)->popupTargetCenter - (*voIter)->popupOrigin ) * (*voIter)->fader.getInterstate();
860 QPointF frameXY=frameCenter - 0.5*QPointF(currentFrameSize.width(), currentFrameSize.height());
861 (*voIter)->videoItem->setPos(frameXY);
862 (*voIter)->videoItem->setSize(currentFrameSize);
863 if (verbose)
864 qDebug() << "StelVideoMgr::update(): Video" << id << "at" << (*voIter)->player->position()
865 << ", player state=" << (*voIter)->player->state() << ", media status=" << (*voIter)->player->mediaStatus();
866 int newPos=(*voIter)->player->position();
867 // if ((newPos==(*voIter)->lastPos) && ((*voIter)->player->state()==QMediaPlayer::PlayingState))
868 // {
869 // // I (GZ) have no idea how this can happen, but it does, every couple of runs.
870 // // I see this happen only in the grow-while-play phase, but I see no logical error.
871 // // It seemed to indicate not-fully-loaded, but this would have been caught in the intro test.
872 // // But this even can happen if MediaStatus==BufferedMedia, i.e. fully loaded. Really a shame!
873 // // TODO: check with every version of Qt whether this can still happen.
874 // // SILLY! Of course if Stellarium framerate > video framerate...
875 // if (verbose)
876 // {
877 // qDebug() << "StelVideoMgr::update(): player state" << (*voIter)->player->state() << "with MediaStatus" << mediaStatus;
878 // qDebug() << "This should not be: video should play but position" << newPos << "does not increase? Bumping video.";
879 // }
880 // //(*voIter)->player->stop(); // GZ Dec2: Do we really need to stop?
881 // (*voIter)->player->setPosition(newPos+1); // GZ Dec2 flipped 2 lines.
882 // (*voIter)->player->play();
883 // qDebug() << "We had an issue with a stuck mediaplayer, should play again!";
884 // }
885 (*voIter)->lastPos=newPos;
886
887 if (verbose)
888 qDebug() << "StelVideoMgr::update(): fader at " << (*voIter)->fader.getInterstate() << "; update frame size " << currentFrameSize << "to XY" << frameXY;
889
890 // if we want still videos during transition, we must grow/shrink paused, and run only when reached full size.
891 // We must detect a frame close to end, pause there, and trigger the fader back to 0.
892 if ((*voIter)->keepVisible)
893 {
894 if ((*voIter)->fader.getInterstate()==0.0f)
895 { // interstate is >0 on startup, so 0 is reached only at end of loop. we can send stop here.
896 (*voIter)->player->stop(); // this immediately hides the video window.
897 (*voIter)->simplePlay=true; // to reset
898 (*voIter)->keepVisible=false;
899 }
900 else if ((*voIter)->fader.getInterstate()<1.0f)
901 {
902 if (verbose)
903 qDebug() << "StelVideoMgr::update(): not fully grown: pausing video at start or end!";
904 (*voIter)->player->pause();
905 }
906 else if (((*voIter)->duration > 0) && ((*voIter)->player->position() >= ((*voIter)->duration - 250))) // allow stop 250ms before end. 100ms was too short!
907 {
908 if (verbose)
909 qDebug() << "StelVideoMgr::update(): position " << (*voIter)->player->position() << "close to end of duration " << (*voIter)->duration << "--> pause and shutdown with fader ";
910 (*voIter)->player->pause();
911 // TBD: If we set some very last frame position here, it takes a very long while to seek (may disturb/flicker!)
912 // (*voIter)->player->setPosition((*voIter)->duration-10);
913 (*voIter)->fader=false; // trigger shutdown (sending again does not disturb)
914 }
915 else if (((*voIter)->player->state() != QMediaPlayer::PlayingState))
916 {
917 if (verbose)
918 qDebug() << "StelVideoMgr::update(): fully grown: play!";
919 (*voIter)->player->play();
920 }
921 }
922 // If the videos come with their own fade-in/-out, this can be played during transitions. (keepVisible configured to false)
923 // In this case we must trigger shrinking *before* end of video!
924 else
925 {
926 if ((*voIter)->fader.getInterstate()>0.0f)
927 {
928 if (((*voIter)->player->state() != QMediaPlayer::PlayingState))
929 (*voIter)->player->play();
930 if (( ((*voIter)->duration > 0) && ((*voIter)->player->position() >= ((*voIter)->duration - (*voIter)->fader.getDuration() - 200))))
931 {
932 if (verbose)
933 qDebug() << "StelVideoMgr::update(): position " << (*voIter)->player->position() << "close to end of duration " << (*voIter)->duration <<
934 "minus fader duration " << (*voIter)->fader.getDuration() << "--> shutdown with fader ";
935 (*voIter)->fader=false; // trigger shutdown (sending again does not disturb)
936 }
937 }
938 else // interstate==0: end of everything.
939 {
940 if (verbose)
941 qDebug() << "StelVideoMgr::update(): Stopping at Interstate " << (*voIter)->fader.getInterstate();
942 (*voIter)->player->stop(); // immediately hides video window.
943 (*voIter)->simplePlay=true; // reset
944 }
945 }
946 }
947 }
948
949
950
951 #else
loadVideo(const QString & filename,const QString & id,float x,float y,bool show,float alpha)952 void StelVideoMgr::loadVideo(const QString& filename, const QString& id, float x, float y, bool show, float alpha)
953 {
954 qWarning() << "[StelVideoMgr] This build of Stellarium does not support video - cannot load video" << QDir::toNativeSeparators(filename) << id << x << y << show << alpha;
955 }
~StelVideoMgr()956 StelVideoMgr::~StelVideoMgr() {;}
update(double)957 void StelVideoMgr::update(double){;}
playVideo(const QString &,const bool)958 void StelVideoMgr::playVideo(const QString&, const bool) {;}
playVideoPopout(const QString &,float,float,float,float,float,float,float,bool)959 void StelVideoMgr::playVideoPopout(const QString&, float, float, float, float, float, float, float, bool){;}
pauseVideo(const QString &)960 void StelVideoMgr::pauseVideo(const QString&) {;}
stopVideo(const QString &)961 void StelVideoMgr::stopVideo(const QString&) {;}
dropVideo(const QString &)962 void StelVideoMgr::dropVideo(const QString&) {;}
seekVideo(const QString &,qint64,bool)963 void StelVideoMgr::seekVideo(const QString&, qint64, bool) {;}
setVideoXY(const QString &,float,float,const bool)964 void StelVideoMgr::setVideoXY(const QString&, float, float, const bool) {;}
setVideoAlpha(const QString &,float)965 void StelVideoMgr::setVideoAlpha(const QString&, float) {;}
resizeVideo(const QString &,float,float)966 void StelVideoMgr::resizeVideo(const QString&, float, float) {;}
showVideo(const QString &,bool)967 void StelVideoMgr::showVideo(const QString&, bool) {;}
968 // New functions for 0.15
getVideoDuration(const QString &) const969 qint64 StelVideoMgr::getVideoDuration(const QString&)const{return -1;}
getVideoPosition(const QString &) const970 qint64 StelVideoMgr::getVideoPosition(const QString&)const{return -1;}
getVideoResolution(const QString &) const971 QSize StelVideoMgr::getVideoResolution(const QString&)const{return QSize(0,0);}
getVideoWidth(const QString &) const972 int StelVideoMgr::getVideoWidth(const QString&)const{return -1;}
getVideoHeight(const QString &) const973 int StelVideoMgr::getVideoHeight(const QString&)const{return -1;}
muteVideo(const QString &,bool)974 void StelVideoMgr::muteVideo(const QString&, bool){;}
setVideoVolume(const QString &,int)975 void StelVideoMgr::setVideoVolume(const QString&, int){;}
getVideoVolume(const QString &) const976 int StelVideoMgr::getVideoVolume(const QString&)const{return -1;}
isVideoPlaying(const QString & id) const977 bool StelVideoMgr::isVideoPlaying(const QString& id) const
978 {
979 Q_UNUSED(id)
980 return false;
981 }
982
983 #endif // ENABLE_MEDIA
984
985
986