1 /*
2 Copyright (C) 2007-2008 Tanguy Krotoff <tkrotoff@gmail.com>
3 Copyright (C) 2008 Lukas Durfina <lukas.durfina@gmail.com>
4 Copyright (C) 2009 Fathi Boudra <fabo@kde.org>
5 Copyright (C) 2010 Ben Cooksley <sourtooth@gmail.com>
6 Copyright (C) 2009-2011 vlc-phonon AUTHORS <kde-multimedia@kde.org>
7 Copyright (C) 2010-2021 Harald Sitter <sitter@kde.org>
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "mediaobject.h"
24
25 #include <QtCore/QDir>
26 #include <QtCore/QStringBuilder>
27 #include <QtCore/QUrl>
28
29 #include <phonon/pulsesupport.h>
30
31 #include <vlc/libvlc_version.h>
32 #include <vlc/vlc.h>
33
34 #include "utils/debug.h"
35 #include "utils/libvlc.h"
36 #include "media.h"
37 #include "sinknode.h"
38 #include "streamreader.h"
39
40 //Time in milliseconds before sending aboutToFinish() signal
41 //2 seconds
42 static const int ABOUT_TO_FINISH_TIME = 2000;
43
44 namespace Phonon {
45 namespace VLC {
46
MediaObject(QObject * parent)47 MediaObject::MediaObject(QObject *parent)
48 : QObject(parent)
49 , m_nextSource(MediaSource(QUrl()))
50 , m_streamReader(0)
51 , m_state(Phonon::StoppedState)
52 , m_tickInterval(0)
53 , m_transitionTime(0)
54 , m_media(0)
55 {
56 qRegisterMetaType<QMultiMap<QString, QString> >("QMultiMap<QString, QString>");
57
58 m_player = new MediaPlayer(this);
59 Q_ASSERT(m_player);
60 if (!m_player->libvlc_media_player())
61 error() << "libVLC:" << LibVLC::errorMessage();
62
63 // Player signals.
64 connect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
65 connect(m_player, SIGNAL(timeChanged(qint64)), this, SLOT(timeChanged(qint64)));
66 connect(m_player, SIGNAL(stateChanged(MediaPlayer::State)), this, SLOT(updateState(MediaPlayer::State)));
67 connect(m_player, SIGNAL(hasVideoChanged(bool)), this, SLOT(onHasVideoChanged(bool)));
68 connect(m_player, SIGNAL(bufferChanged(int)), this, SLOT(setBufferStatus(int)));
69 connect(m_player, SIGNAL(timeChanged(qint64)), this, SLOT(timeChanged(qint64)));
70
71 // Internal Signals.
72 connect(this, SIGNAL(moveToNext()), SLOT(moveToNextSource()));
73 connect(m_refreshTimer, SIGNAL(timeout()), this, SLOT(refreshDescriptors()));
74
75 resetMembers();
76 }
77
~MediaObject()78 MediaObject::~MediaObject()
79 {
80 unloadMedia();
81 // Shutdown the pulseaudio mainloop before the MediaPlayer gets destroyed
82 // (it is a child of the MO). There appears to be a peculiar race condition
83 // between the pa_thread_mainloop used by VLC and the pa_glib_mainloop used
84 // by Phonon's PulseSupport where for a very short time frame after the
85 // former was stopped and freed the latter can run and fall over
86 // Invalid read from eventfd: Bad file descriptor
87 // Code should not be reached at pulsecore/fdsem.c:157, function flush(). Aborting.
88 // Since we don't use PulseSupport since VLC 2.2 we can simply force a
89 // loop shutdown even when the application isn't about to terminate.
90 // The instance gets created again anyway.
91 PulseSupport::shutdown();
92 }
93
resetMembers()94 void MediaObject::resetMembers()
95 {
96 // default to -1, so that streams won't break and to comply with the docs (-1 if unknown)
97 m_totalTime = -1;
98 m_hasVideo = false;
99 m_seekpoint = 0;
100
101 m_prefinishEmitted = false;
102 m_aboutToFinishEmitted = false;
103
104 m_lastTick = 0;
105
106 m_timesVideoChecked = 0;
107
108 m_buffering = false;
109 m_stateAfterBuffering = ErrorState;
110
111 resetMediaController();
112
113 // Forcefully shutdown plusesupport to prevent crashing between the PS PA glib mainloop
114 // and the VLC PA threaded mainloop. See destructor.
115 PulseSupport::shutdown();
116 }
117
play()118 void MediaObject::play()
119 {
120 DEBUG_BLOCK;
121
122 switch (m_state) {
123 case PlayingState:
124 // Do not do anything if we are already playing (as per documentation).
125 return;
126 case PausedState:
127 m_player->resume();
128 break;
129 default:
130 setupMedia();
131 if (m_player->play())
132 error() << "libVLC:" << LibVLC::errorMessage();
133 break;
134 }
135 }
136
pause()137 void MediaObject::pause()
138 {
139 DEBUG_BLOCK;
140 switch (m_state) {
141 case BufferingState:
142 case PlayingState:
143 m_player->pause();
144 break;
145 case PausedState:
146 return;
147 default:
148 debug() << "doing paused play";
149 setupMedia();
150 m_player->pausedPlay();
151 break;
152 }
153 }
154
stop()155 void MediaObject::stop()
156 {
157 DEBUG_BLOCK;
158 if (m_streamReader)
159 m_streamReader->unlock();
160 m_nextSource = MediaSource(QUrl());
161 m_player->stop();
162 }
163
seek(qint64 milliseconds)164 void MediaObject::seek(qint64 milliseconds)
165 {
166 DEBUG_BLOCK;
167
168 switch (m_state) {
169 case PlayingState:
170 case PausedState:
171 case BufferingState:
172 break;
173 default:
174 // Seeking while not being in a playingish state is cached for later.
175 m_seekpoint = milliseconds;
176 return;
177 }
178
179 debug() << "seeking" << milliseconds << "msec";
180
181 m_player->setTime(milliseconds);
182
183 const qint64 time = currentTime();
184 const qint64 total = totalTime();
185
186 // Reset last tick marker so we emit time even after seeking
187 if (time < m_lastTick)
188 m_lastTick = time;
189 if (time < total - m_prefinishMark)
190 m_prefinishEmitted = false;
191 if (time < total - ABOUT_TO_FINISH_TIME)
192 m_aboutToFinishEmitted = false;
193 }
194
timeChanged(qint64 time)195 void MediaObject::timeChanged(qint64 time)
196 {
197 const qint64 totalTime = m_totalTime;
198
199 switch (m_state) {
200 case PlayingState:
201 case BufferingState:
202 case PausedState:
203 emitTick(time);
204 default:
205 break;
206 }
207
208 if (m_state == PlayingState || m_state == BufferingState) { // Buffering is concurrent
209 if (time >= totalTime - m_prefinishMark) {
210 if (!m_prefinishEmitted) {
211 m_prefinishEmitted = true;
212 emit prefinishMarkReached(totalTime - time);
213 }
214 }
215 // Note that when the totalTime is <= 0 we cannot calculate any sane delta.
216 if (totalTime > 0 && time >= totalTime - ABOUT_TO_FINISH_TIME)
217 emitAboutToFinish();
218 }
219 }
220
emitTick(qint64 time)221 void MediaObject::emitTick(qint64 time)
222 {
223 if (m_tickInterval == 0) // Make sure we do not ever emit ticks when deactivated.\]
224 return;
225 if (time + m_tickInterval >= m_lastTick) {
226 m_lastTick = time;
227 emit tick(time);
228 }
229 }
230
loadMedia(const QByteArray & mrl)231 void MediaObject::loadMedia(const QByteArray &mrl)
232 {
233 DEBUG_BLOCK;
234
235 // Initial state is loading, from which we quickly progress to stopped because
236 // libvlc does not provide feedback on loading and the media does not get loaded
237 // until we play it.
238 // FIXME: libvlc should really allow for this as it can cause unexpected delay
239 // even though the GUI might indicate that playback should start right away.
240 changeState(Phonon::LoadingState);
241
242 m_mrl = mrl;
243 debug() << "loading encoded:" << m_mrl;
244
245 // We do not have a loading state generally speaking, usually the backend
246 // is exepected to go to loading state and then at some point reach stopped,
247 // at which point playback can be started.
248 // See state enum documentation for more information.
249 changeState(Phonon::StoppedState);
250 }
251
loadMedia(const QString & mrl)252 void MediaObject::loadMedia(const QString &mrl)
253 {
254 loadMedia(mrl.toUtf8());
255 }
256
tickInterval() const257 qint32 MediaObject::tickInterval() const
258 {
259 return m_tickInterval;
260 }
261
262 /**
263 * Supports runtime changes.
264 * If the user goes to tick(0) we stop the timer, otherwise we fire it up.
265 */
setTickInterval(qint32 interval)266 void MediaObject::setTickInterval(qint32 interval)
267 {
268 m_tickInterval = interval;
269 }
270
currentTime() const271 qint64 MediaObject::currentTime() const
272 {
273 qint64 time = -1;
274
275 switch (state()) {
276 case Phonon::PausedState:
277 case Phonon::BufferingState:
278 case Phonon::PlayingState:
279 time = m_player->time();
280 break;
281 case Phonon::StoppedState:
282 case Phonon::LoadingState:
283 time = 0;
284 break;
285 case Phonon::ErrorState:
286 time = -1;
287 break;
288 }
289
290 return time;
291 }
292
state() const293 Phonon::State MediaObject::state() const
294 {
295 return m_state;
296 }
297
errorType() const298 Phonon::ErrorType MediaObject::errorType() const
299 {
300 return Phonon::NormalError;
301 }
302
source() const303 MediaSource MediaObject::source() const
304 {
305 return m_mediaSource;
306 }
307
setSource(const MediaSource & source)308 void MediaObject::setSource(const MediaSource &source)
309 {
310 DEBUG_BLOCK;
311
312 // Reset previous streamereaders
313 if (m_streamReader) {
314 m_streamReader->unlock();
315 delete m_streamReader;
316 m_streamReader = 0;
317 // For streamreaders we exchanage the player's seekability with the
318 // reader's so here we change it back.
319 // Note: the reader auto-disconnects due to destruction.
320 connect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
321 }
322
323 // Reset previous isScreen flag
324 m_isScreen = false;
325
326 m_mediaSource = source;
327
328 QByteArray url;
329 switch (source.type()) {
330 case MediaSource::Invalid:
331 error() << Q_FUNC_INFO << "MediaSource Type is Invalid:" << source.type();
332 break;
333 case MediaSource::Empty:
334 error() << Q_FUNC_INFO << "MediaSource is empty.";
335 break;
336 case MediaSource::LocalFile:
337 case MediaSource::Url:
338 debug() << "MediaSource::Url:" << source.url();
339 if (source.url().scheme().isEmpty()) {
340 url = "file://";
341 // QUrl considers url.scheme.isEmpty() == url.isRelative(),
342 // so to be sure the url is not actually absolute we just
343 // check the first character
344 if (!source.url().toString().startsWith('/'))
345 url.append(QFile::encodeName(QDir::currentPath()) + '/');
346 }
347 url += source.url().toEncoded();
348 loadMedia(url);
349 break;
350 case MediaSource::Disc:
351 switch (source.discType()) {
352 case Phonon::NoDisc:
353 error() << Q_FUNC_INFO << "the MediaSource::Disc doesn't specify which one (Phonon::NoDisc)";
354 return;
355 case Phonon::Cd:
356 loadMedia(QLatin1Literal("cdda://") % m_mediaSource.deviceName());
357 break;
358 case Phonon::Dvd:
359 loadMedia(QLatin1Literal("dvd://") % m_mediaSource.deviceName());
360 break;
361 case Phonon::Vcd:
362 loadMedia(QLatin1Literal("vcd://") % m_mediaSource.deviceName());
363 break;
364 case Phonon::BluRay:
365 loadMedia(QLatin1Literal("bluray://") % m_mediaSource.deviceName());
366 break;
367 }
368 break;
369 case MediaSource::CaptureDevice: {
370 QByteArray driverName;
371 QString deviceName;
372
373 if (source.deviceAccessList().isEmpty()) {
374 error() << Q_FUNC_INFO << "No device access list for this capture device";
375 break;
376 }
377
378 // TODO try every device in the access list until it works, not just the first one
379 driverName = source.deviceAccessList().first().first;
380 deviceName = source.deviceAccessList().first().second;
381
382 if (driverName == QByteArray("v4l2")) {
383 loadMedia(QLatin1Literal("v4l2://") % deviceName);
384 } else if (driverName == QByteArray("alsa")) {
385 /*
386 * Replace "default" and "plughw" and "x-phonon" with "hw" for capture device names, because
387 * VLC does not want to open them when using default instead of hw.
388 * plughw also does not work.
389 *
390 * TODO investigate what happens
391 */
392 if (deviceName.startsWith(QLatin1String("default"))) {
393 deviceName.replace(0, 7, "hw");
394 }
395 if (deviceName.startsWith(QLatin1String("plughw"))) {
396 deviceName.replace(0, 6, "hw");
397 }
398 if (deviceName.startsWith(QLatin1String("x-phonon"))) {
399 deviceName.replace(0, 8, "hw");
400 }
401
402 loadMedia(QLatin1Literal("alsa://") % deviceName);
403 } else if (driverName == "screen") {
404 loadMedia(QLatin1Literal("screen://") % deviceName);
405
406 // Set the isScreen flag needed to add extra options in playInternal
407 m_isScreen = true;
408 } else {
409 error() << Q_FUNC_INFO << "Unsupported MediaSource::CaptureDevice:" << driverName;
410 break;
411 }
412 break;
413 }
414 case MediaSource::Stream:
415 m_streamReader = new StreamReader(this);
416 // LibVLC refuses to emit seekability as it does a try-and-seek approach
417 // to work around this we exchange the player's seekability signal
418 // for the readers
419 // https://bugs.kde.org/show_bug.cgi?id=293012
420 connect(m_streamReader, SIGNAL(streamSeekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
421 disconnect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
422 // Only connect now to avoid seekability detection before we are connected.
423 m_streamReader->connectToSource(source);
424 loadMedia(QByteArray("imem://"));
425 break;
426 }
427
428 debug() << "Sending currentSourceChanged";
429 emit currentSourceChanged(m_mediaSource);
430 }
431
setNextSource(const MediaSource & source)432 void MediaObject::setNextSource(const MediaSource &source)
433 {
434 DEBUG_BLOCK;
435 debug() << source.url();
436 m_nextSource = source;
437 // This function is not ever called by the consumer but only libphonon.
438 // Furthermore libphonon only calls this function in its aboutToFinish slot,
439 // iff sources are already in the queue. In case our aboutToFinish was too
440 // late we may already be stopped when the slot gets activated.
441 // Therefore we need to make sure that we move to the next source iff
442 // this function is called when we are in stoppedstate.
443 if (m_state == StoppedState)
444 moveToNext();
445 }
446
prefinishMark() const447 qint32 MediaObject::prefinishMark() const
448 {
449 return m_prefinishMark;
450 }
451
setPrefinishMark(qint32 msecToEnd)452 void MediaObject::setPrefinishMark(qint32 msecToEnd)
453 {
454 m_prefinishMark = msecToEnd;
455 if (currentTime() < totalTime() - m_prefinishMark) {
456 // Not about to finish
457 m_prefinishEmitted = false;
458 }
459 }
460
transitionTime() const461 qint32 MediaObject::transitionTime() const
462 {
463 return m_transitionTime;
464 }
465
setTransitionTime(qint32 time)466 void MediaObject::setTransitionTime(qint32 time)
467 {
468 m_transitionTime = time;
469 }
470
emitAboutToFinish()471 void MediaObject::emitAboutToFinish()
472 {
473 if (!m_aboutToFinishEmitted) {
474 // Track is about to finish
475 m_aboutToFinishEmitted = true;
476 emit aboutToFinish();
477 }
478 }
479
480 // State changes are force queued by libphonon.
changeState(Phonon::State newState)481 void MediaObject::changeState(Phonon::State newState)
482 {
483 DEBUG_BLOCK;
484
485 // State not changed
486 if (newState == m_state)
487 return;
488
489 debug() << m_state << "-->" << newState;
490
491 #ifdef __GNUC__
492 #warning do we actually need m_seekpoint? if a consumer seeks before playing state that is their problem?!
493 #endif
494 // Workaround that seeking needs to work before the file is being played...
495 // We store seeks and apply them when going to seek (or discard them on reset).
496 if (newState == PlayingState) {
497 if (m_seekpoint != 0) {
498 seek(m_seekpoint);
499 m_seekpoint = 0;
500 }
501 }
502
503 // State changed
504 Phonon::State previousState = m_state;
505 m_state = newState;
506 emit stateChanged(m_state, previousState);
507 }
508
moveToNextSource()509 void MediaObject::moveToNextSource()
510 {
511 DEBUG_BLOCK;
512
513 setSource(m_nextSource);
514
515 // The consumer may set an invalid source as final source to force a
516 // queued stop, regardless of how fast the consumer is at actually calling
517 // stop. Such a source must not cause an actual move (moving ~= state
518 // changes towards playing) but instead we only set the source to reflect
519 // that we got the setNextSource call.
520 if (hasNextTrack())
521 play();
522
523 m_nextSource = MediaSource(QUrl());
524 }
525
hasNextTrack()526 inline bool MediaObject::hasNextTrack()
527 {
528 return m_nextSource.type() != MediaSource::Invalid && m_nextSource.type() != MediaSource::Empty;
529 }
530
unloadMedia()531 inline void MediaObject::unloadMedia()
532 {
533 if (m_media) {
534 m_media->disconnect(this);
535 m_media->deleteLater();
536 m_media = 0;
537 }
538 }
539
setupMedia()540 void MediaObject::setupMedia()
541 {
542 DEBUG_BLOCK;
543
544 unloadMedia();
545 resetMembers();
546
547 // Create a media with the given MRL
548 m_media = new Media(m_mrl, this);
549 if (!m_media)
550 error() << "libVLC:" << LibVLC::errorMessage();
551
552 if (m_isScreen) {
553 m_media->addOption(QLatin1String("screen-fps=24.0"));
554 m_media->addOption(QLatin1String("screen-caching=300"));
555 }
556
557 if (source().discType() == Cd && m_currentTitle > 0)
558 m_media->setCdTrack(m_currentTitle);
559
560 if (m_streamReader)
561 // StreamReader is no sink but a source, for this we have no concept right now
562 // also we do not need one since the reader is the only source we have.
563 // Consequently we need to manually tell the StreamReader to attach to the Media.
564 m_streamReader->addToMedia(m_media);
565
566 if (!m_subtitleAutodetect)
567 m_media->addOption(QLatin1String(":no-sub-autodetect-file"));
568
569 if (m_subtitleEncoding != QLatin1String("UTF-8")) // utf8 is phonon default, so let vlc handle it
570 m_media->addOption(QLatin1String(":subsdec-encoding="), m_subtitleEncoding);
571
572 if (!m_subtitleFontChanged) // Update font settings
573 m_subtitleFont = QFont();
574
575 #ifdef __GNUC__
576 #warning freetype module is not working as expected - font api not working
577 #endif
578 // BUG: VLC's freetype module doesn't pick up per-media options
579 // vlc -vvvv --freetype-font="Comic Sans MS" multiple_sub_sample.mkv :freetype-font=Arial
580 // https://trac.videolan.org/vlc/ticket/9797
581 m_media->addOption(QLatin1String(":freetype-font="), m_subtitleFont.family());
582 m_media->addOption(QLatin1String(":freetype-fontsize="), m_subtitleFont.pointSize());
583 if (m_subtitleFont.bold())
584 m_media->addOption(QLatin1String(":freetype-bold"));
585 else
586 m_media->addOption(QLatin1String(":no-freetype-bold"));
587
588 foreach (SinkNode *sink, m_sinks) {
589 sink->addToMedia(m_media);
590 }
591
592 // Connect to Media signals. Disconnection is done at unloading.
593 connect(m_media, SIGNAL(durationChanged(qint64)),
594 this, SLOT(updateDuration(qint64)));
595 connect(m_media, SIGNAL(metaDataChanged()),
596 this, SLOT(updateMetaData()));
597
598 // Update available audio channels/subtitles/angles/chapters/etc...
599 // i.e everything from MediaController
600 // There is no audio channel/subtitle/angle/chapter events inside libvlc
601 // so let's send our own events...
602 // This will reset the GUI
603 resetMediaController();
604
605 // Play
606 m_player->setMedia(m_media);
607 }
608
errorString() const609 QString MediaObject::errorString() const
610 {
611 return libvlc_errmsg();
612 }
613
hasVideo() const614 bool MediaObject::hasVideo() const
615 {
616 // Cached: sometimes 4.0.0-dev sends the vout event but then
617 // has_vout is still false. Guard against this by simply always reporting
618 // the last hasVideoChanged value. If that is off we can still drop into
619 // libvlc in case it changed meanwhile.
620 return m_hasVideo || m_player->hasVideoOutput();
621 }
622
isSeekable() const623 bool MediaObject::isSeekable() const
624 {
625 if (m_streamReader)
626 return m_streamReader->streamSeekable();
627 return m_player->isSeekable();
628 }
629
updateDuration(qint64 newDuration)630 void MediaObject::updateDuration(qint64 newDuration)
631 {
632 // This here cache is needed because we need to provide -1 as totalTime()
633 // for as long as we do not get a proper update through this slot.
634 // VLC reports -1 with no media but 0 if it does not know the duration, so
635 // apps that assume 0 = unknown get screwed if they query too early.
636 // http://bugs.tomahawk-player.org/browse/TWK-1029
637 m_totalTime = newDuration;
638 emit totalTimeChanged(m_totalTime);
639 }
640
updateMetaData()641 void MediaObject::updateMetaData()
642 {
643 QMultiMap<QString, QString> metaDataMap;
644
645 const QString artist = m_media->meta(libvlc_meta_Artist);
646 const QString title = m_media->meta(libvlc_meta_Title);
647 const QString nowPlaying = m_media->meta(libvlc_meta_NowPlaying);
648
649 // Streams sometimes have the artist and title munged in nowplaying.
650 // With ALBUM = Title and TITLE = NowPlaying it will still show up nicely in Amarok.
651 if (artist.isEmpty() && !nowPlaying.isEmpty()) {
652 metaDataMap.insert(QLatin1String("ALBUM"), title);
653 metaDataMap.insert(QLatin1String("TITLE"), nowPlaying);
654 } else {
655 metaDataMap.insert(QLatin1String("ALBUM"), m_media->meta(libvlc_meta_Album));
656 metaDataMap.insert(QLatin1String("TITLE"), title);
657 }
658
659 metaDataMap.insert(QLatin1String("ARTIST"), artist);
660 metaDataMap.insert(QLatin1String("DATE"), m_media->meta(libvlc_meta_Date));
661 metaDataMap.insert(QLatin1String("GENRE"), m_media->meta(libvlc_meta_Genre));
662 metaDataMap.insert(QLatin1String("TRACKNUMBER"), m_media->meta(libvlc_meta_TrackNumber));
663 metaDataMap.insert(QLatin1String("DESCRIPTION"), m_media->meta(libvlc_meta_Description));
664 metaDataMap.insert(QLatin1String("COPYRIGHT"), m_media->meta(libvlc_meta_Copyright));
665 metaDataMap.insert(QLatin1String("URL"), m_media->meta(libvlc_meta_URL));
666 metaDataMap.insert(QLatin1String("ENCODEDBY"), m_media->meta(libvlc_meta_EncodedBy));
667
668 if (metaDataMap == m_vlcMetaData) {
669 // No need to issue any change, the data is the same
670 return;
671 }
672 m_vlcMetaData = metaDataMap;
673
674 emit metaDataChanged(metaDataMap);
675 }
676
updateState(MediaPlayer::State state)677 void MediaObject::updateState(MediaPlayer::State state)
678 {
679 DEBUG_BLOCK;
680 debug() << state;
681 debug() << "attempted autoplay?" << m_attemptingAutoplay;
682
683 if (m_attemptingAutoplay) {
684 switch (state) {
685 case MediaPlayer::PlayingState:
686 case MediaPlayer::PausedState:
687 m_attemptingAutoplay = false;
688 break;
689 case MediaPlayer::ErrorState:
690 debug() << "autoplay failed, must be end of media.";
691 // The error should not be reflected to the consumer. So we swap it
692 // for finished() which is actually what is happening here.
693 // Or so we think ;)
694 state = MediaPlayer::EndedState;
695 --m_currentTitle;
696 break;
697 default:
698 debug() << "not handling as part of autplay:" << state;
699 break;
700 }
701 }
702
703 switch (state) {
704 case MediaPlayer::NoState:
705 changeState(LoadingState);
706 break;
707 case MediaPlayer::OpeningState:
708 changeState(LoadingState);
709 break;
710 case MediaPlayer::BufferingState:
711 changeState(BufferingState);
712 break;
713 case MediaPlayer::PlayingState:
714 changeState(PlayingState);
715 break;
716 case MediaPlayer::PausedState:
717 changeState(PausedState);
718 break;
719 case MediaPlayer::StoppedState:
720 changeState(StoppedState);
721 break;
722 case MediaPlayer::EndedState:
723 if (hasNextTrack()) {
724 moveToNextSource();
725 } else if (source().discType() == Cd && m_autoPlayTitles && !m_attemptingAutoplay) {
726 debug() << "trying to simulate autoplay";
727 m_attemptingAutoplay = true;
728 m_player->setCdTrack(++m_currentTitle);
729 } else {
730 m_attemptingAutoplay = false;
731 emitAboutToFinish();
732 emit finished();
733 changeState(StoppedState);
734 }
735 break;
736 case MediaPlayer::ErrorState:
737 debug() << errorString();
738 emitAboutToFinish();
739 emit finished();
740 changeState(ErrorState);
741 break;
742 }
743
744 if (m_buffering) {
745 switch (state) {
746 case MediaPlayer::BufferingState:
747 break;
748 case MediaPlayer::PlayingState:
749 debug() << "Restoring buffering state after state change to Playing";
750 changeState(BufferingState);
751 m_stateAfterBuffering = PlayingState;
752 break;
753 case MediaPlayer::PausedState:
754 debug() << "Restoring buffering state after state change to Paused";
755 changeState(BufferingState);
756 m_stateAfterBuffering = PausedState;
757 break;
758 default:
759 debug() << "Buffering aborted!";
760 m_buffering = false;
761 break;
762 }
763 }
764
765 return;
766 }
767
onHasVideoChanged(bool hasVideo)768 void MediaObject::onHasVideoChanged(bool hasVideo)
769 {
770 DEBUG_BLOCK;
771 if (m_hasVideo != hasVideo) {
772 m_hasVideo = hasVideo;
773 emit hasVideoChanged(m_hasVideo);
774 } else {
775 // We can simply return if we are have the appropriate caching already.
776 // Otherwise we'd do pointless rescans of mediacontroller stuff.
777 // MC and MO are force-reset on media changes anyway.
778 return;
779 }
780
781 refreshDescriptors();
782 }
783
setBufferStatus(int percent)784 void MediaObject::setBufferStatus(int percent)
785 {
786 // VLC does not have a buffering state (surprise!) but instead only sends the
787 // event (surprise!). Hence we need to simulate the state change.
788 // Problem with BufferingState is that it is actually concurrent to Playing or Paused
789 // meaning that while you are buffering you can also pause, thus triggering
790 // a state change to paused. To handle this we let updateState change the
791 // state accordingly (as we need to allow the UI to update itself, and
792 // immediately after that we change back to buffering again.
793 // This loop can only be interrupted by a state change to !Playing & !Paused
794 // or by reaching a 100 % buffer caching (i.e. full cache).
795
796 m_buffering = true;
797 if (m_state != BufferingState) {
798 m_stateAfterBuffering = m_state;
799 changeState(BufferingState);
800 }
801
802 emit bufferStatus(percent);
803
804 // Transit to actual state only after emission so the signal is still
805 // delivered while in BufferingState.
806 if (percent >= 100) { // http://trac.videolan.org/vlc/ticket/5277
807 m_buffering = false;
808 changeState(m_stateAfterBuffering);
809 }
810 }
811
refreshDescriptors()812 void MediaObject::refreshDescriptors()
813 {
814 if (m_player->titleCount() > 0)
815 refreshTitles();
816
817 if (hasVideo()) {
818 refreshAudioChannels();
819 refreshSubtitles();
820
821 if (m_player->videoChapterCount() > 0)
822 refreshChapters(m_player->title());
823 }
824 }
825
totalTime() const826 qint64 MediaObject::totalTime() const
827 {
828 return m_totalTime;
829 }
830
addSink(SinkNode * node)831 void MediaObject::addSink(SinkNode *node)
832 {
833 Q_ASSERT(!m_sinks.contains(node));
834 m_sinks.append(node);
835 }
836
removeSink(SinkNode * node)837 void MediaObject::removeSink(SinkNode *node)
838 {
839 Q_ASSERT(node);
840 m_sinks.removeAll(node);
841 }
842
843 } // namespace VLC
844 } // namespace Phonon
845