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
42static const int ABOUT_TO_FINISH_TIME = 2000;
43
44namespace Phonon {
45namespace VLC {
46
47MediaObject::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> >(typeName: "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(sender: m_player, SIGNAL(seekableChanged(bool)), receiver: this, SIGNAL(seekableChanged(bool)));
65 connect(sender: m_player, SIGNAL(timeChanged(qint64)), receiver: this, SLOT(timeChanged(qint64)));
66 connect(sender: m_player, SIGNAL(stateChanged(MediaPlayer::State)), receiver: this, SLOT(updateState(MediaPlayer::State)));
67 connect(sender: m_player, SIGNAL(hasVideoChanged(bool)), receiver: this, SLOT(onHasVideoChanged(bool)));
68 connect(sender: m_player, SIGNAL(bufferChanged(int)), receiver: this, SLOT(setBufferStatus(int)));
69 connect(sender: m_player, SIGNAL(timeChanged(qint64)), receiver: this, SLOT(timeChanged(qint64)));
70
71 // Internal Signals.
72 connect(asender: this, SIGNAL(moveToNext()), SLOT(moveToNextSource()));
73 connect(sender: m_refreshTimer, SIGNAL(timeout()), receiver: this, SLOT(refreshDescriptors()));
74
75 resetMembers();
76}
77
78MediaObject::~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
94void 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
118void 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
137void 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
155void 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
164void 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
195void 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(msecToEnd: 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
221void 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
231void 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(newState: 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 expected 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(newState: Phonon::StoppedState);
250}
251
252void MediaObject::loadMedia(const QString &mrl)
253{
254 loadMedia(mrl: mrl.toUtf8());
255}
256
257qint32 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 */
266void MediaObject::setTickInterval(qint32 interval)
267{
268 m_tickInterval = interval;
269}
270
271qint64 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
293Phonon::State MediaObject::state() const
294{
295 return m_state;
296}
297
298Phonon::ErrorType MediaObject::errorType() const
299{
300 return Phonon::NormalError;
301}
302
303MediaSource MediaObject::source() const
304{
305 return m_mediaSource;
306}
307
308void 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 exchange 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(sender: m_player, SIGNAL(seekableChanged(bool)), receiver: 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(c: '/'))
345 url.append(a: QFile::encodeName(fileName: QDir::currentPath()) + '/');
346 }
347 url += source.url().toEncoded();
348 loadMedia(mrl: 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(QStringLiteral("cdda://") % m_mediaSource.deviceName());
357 break;
358 case Phonon::Dvd:
359 loadMedia(QStringLiteral("dvd://") % m_mediaSource.deviceName());
360 break;
361 case Phonon::Vcd:
362 loadMedia(QStringLiteral("vcd://") % m_mediaSource.deviceName());
363 break;
364 case Phonon::BluRay:
365 loadMedia(QStringLiteral("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(QStringLiteral("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(s: QLatin1String("default"))) {
393 deviceName.replace(i: 0, len: 7, after: "hw");
394 }
395 if (deviceName.startsWith(s: QLatin1String("plughw"))) {
396 deviceName.replace(i: 0, len: 6, after: "hw");
397 }
398 if (deviceName.startsWith(s: QLatin1String("x-phonon"))) {
399 deviceName.replace(i: 0, len: 8, after: "hw");
400 }
401
402 loadMedia(QStringLiteral("alsa://") % deviceName);
403 } else if (driverName == "screen") {
404 loadMedia(QStringLiteral("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(sender: m_streamReader, SIGNAL(streamSeekableChanged(bool)), receiver: this, SIGNAL(seekableChanged(bool)));
421 disconnect(sender: m_player, SIGNAL(seekableChanged(bool)), receiver: this, SIGNAL(seekableChanged(bool)));
422 // Only connect now to avoid seekability detection before we are connected.
423 m_streamReader->connectToSource(mediaSource: source);
424 loadMedia(mrl: QByteArray("imem://"));
425 break;
426 }
427
428 debug() << "Sending currentSourceChanged";
429 emit currentSourceChanged(newSource: m_mediaSource);
430}
431
432void 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
447qint32 MediaObject::prefinishMark() const
448{
449 return m_prefinishMark;
450}
451
452void 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
461qint32 MediaObject::transitionTime() const
462{
463 return m_transitionTime;
464}
465
466void MediaObject::setTransitionTime(qint32 time)
467{
468 m_transitionTime = time;
469}
470
471void 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.
481void 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(milliseconds: 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(newState: m_state, oldState: previousState);
507}
508
509void 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
526inline bool MediaObject::hasNextTrack()
527{
528 return m_nextSource.type() != MediaSource::Invalid && m_nextSource.type() != MediaSource::Empty;
529}
530
531inline void MediaObject::unloadMedia()
532{
533 if (m_media) {
534 m_media->disconnect(receiver: this);
535 m_media->deleteLater();
536 m_media = 0;
537 }
538}
539
540void 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
550 if (m_isScreen) {
551 m_media->addOption(option: QLatin1String("screen-fps=24.0"));
552 m_media->addOption(option: QLatin1String("screen-caching=300"));
553 }
554
555 if (source().discType() == Cd && m_currentTitle > 0)
556 m_media->setCdTrack(m_currentTitle);
557
558 if (m_streamReader)
559 // StreamReader is no sink but a source, for this we have no concept right now
560 // also we do not need one since the reader is the only source we have.
561 // Consequently we need to manually tell the StreamReader to attach to the Media.
562 m_streamReader->addToMedia(media: m_media);
563
564 if (!m_subtitleAutodetect)
565 m_media->addOption(option: QLatin1String(":no-sub-autodetect-file"));
566
567 if (m_subtitleEncoding != QLatin1String("UTF-8")) // utf8 is phonon default, so let vlc handle it
568 m_media->addOption(option: QLatin1String(":subsdec-encoding="), argument: m_subtitleEncoding);
569
570 if (!m_subtitleFontChanged) // Update font settings
571 m_subtitleFont = QFont();
572
573#ifdef __GNUC__
574#warning freetype module is not working as expected - font api not working
575#endif
576 // BUG: VLC's freetype module doesn't pick up per-media options
577 // vlc -vvvv --freetype-font="Comic Sans MS" multiple_sub_sample.mkv :freetype-font=Arial
578 // https://trac.videolan.org/vlc/ticket/9797
579 m_media->addOption(option: QLatin1String(":freetype-font="), argument: m_subtitleFont.family());
580 m_media->addOption(option: QLatin1String(":freetype-fontsize="), functionPtr: m_subtitleFont.pointSize());
581 if (m_subtitleFont.bold())
582 m_media->addOption(option: QLatin1String(":freetype-bold"));
583 else
584 m_media->addOption(option: QLatin1String(":no-freetype-bold"));
585
586 foreach (SinkNode *sink, m_sinks) {
587 sink->addToMedia(media: m_media);
588 }
589
590 // Connect to Media signals. Disconnection is done at unloading.
591 connect(sender: m_media, SIGNAL(durationChanged(qint64)),
592 receiver: this, SLOT(updateDuration(qint64)));
593 connect(sender: m_media, SIGNAL(metaDataChanged()),
594 receiver: this, SLOT(updateMetaData()));
595
596 // Update available audio channels/subtitles/angles/chapters/etc...
597 // i.e everything from MediaController
598 // There is no audio channel/subtitle/angle/chapter events inside libvlc
599 // so let's send our own events...
600 // This will reset the GUI
601 resetMediaController();
602
603 // Play
604 m_player->setMedia(m_media);
605}
606
607QString MediaObject::errorString() const
608{
609 return libvlc_errmsg();
610}
611
612bool MediaObject::hasVideo() const
613{
614 // Cached: sometimes 4.0.0-dev sends the vout event but then
615 // has_vout is still false. Guard against this by simply always reporting
616 // the last hasVideoChanged value. If that is off we can still drop into
617 // libvlc in case it changed meanwhile.
618 return m_hasVideo || m_player->hasVideoOutput();
619}
620
621bool MediaObject::isSeekable() const
622{
623 if (m_streamReader)
624 return m_streamReader->streamSeekable();
625 return m_player->isSeekable();
626}
627
628void MediaObject::updateDuration(qint64 newDuration)
629{
630 // This here cache is needed because we need to provide -1 as totalTime()
631 // for as long as we do not get a proper update through this slot.
632 // VLC reports -1 with no media but 0 if it does not know the duration, so
633 // apps that assume 0 = unknown get screwed if they query too early.
634 // http://bugs.tomahawk-player.org/browse/TWK-1029
635 m_totalTime = newDuration;
636 emit totalTimeChanged(newTotalTime: m_totalTime);
637}
638
639void MediaObject::updateMetaData()
640{
641 QMultiMap<QString, QString> metaDataMap;
642
643 const QString artist = m_media->meta(meta: libvlc_meta_Artist);
644 const QString title = m_media->meta(meta: libvlc_meta_Title);
645 const QString nowPlaying = m_media->meta(meta: libvlc_meta_NowPlaying);
646
647 // Streams sometimes have the artist and title munged in nowplaying.
648 // With ALBUM = Title and TITLE = NowPlaying it will still show up nicely in Amarok.
649 if (artist.isEmpty() && !nowPlaying.isEmpty()) {
650 metaDataMap.insert(key: QLatin1String("ALBUM"), value: title);
651 metaDataMap.insert(key: QLatin1String("TITLE"), value: nowPlaying);
652 } else {
653 metaDataMap.insert(key: QLatin1String("ALBUM"), value: m_media->meta(meta: libvlc_meta_Album));
654 metaDataMap.insert(key: QLatin1String("TITLE"), value: title);
655 }
656
657 metaDataMap.insert(key: QLatin1String("ARTIST"), value: artist);
658 metaDataMap.insert(key: QLatin1String("DATE"), value: m_media->meta(meta: libvlc_meta_Date));
659 metaDataMap.insert(key: QLatin1String("GENRE"), value: m_media->meta(meta: libvlc_meta_Genre));
660 metaDataMap.insert(key: QLatin1String("TRACKNUMBER"), value: m_media->meta(meta: libvlc_meta_TrackNumber));
661 metaDataMap.insert(key: QLatin1String("DESCRIPTION"), value: m_media->meta(meta: libvlc_meta_Description));
662 metaDataMap.insert(key: QLatin1String("COPYRIGHT"), value: m_media->meta(meta: libvlc_meta_Copyright));
663 metaDataMap.insert(key: QLatin1String("URL"), value: m_media->meta(meta: libvlc_meta_URL));
664 metaDataMap.insert(key: QLatin1String("ENCODEDBY"), value: m_media->meta(meta: libvlc_meta_EncodedBy));
665
666 if (metaDataMap == m_vlcMetaData) {
667 // No need to issue any change, the data is the same
668 return;
669 }
670 m_vlcMetaData = metaDataMap;
671
672 emit metaDataChanged(metaData: metaDataMap);
673}
674
675void MediaObject::updateState(MediaPlayer::State state)
676{
677 DEBUG_BLOCK;
678 debug() << state;
679 debug() << "attempted autoplay?" << m_attemptingAutoplay;
680
681 if (m_attemptingAutoplay) {
682 switch (state) {
683 case MediaPlayer::PlayingState:
684 case MediaPlayer::PausedState:
685 m_attemptingAutoplay = false;
686 break;
687 case MediaPlayer::ErrorState:
688 debug() << "autoplay failed, must be end of media.";
689 // The error should not be reflected to the consumer. So we swap it
690 // for finished() which is actually what is happening here.
691 // Or so we think ;)
692 state = MediaPlayer::EndedState;
693 --m_currentTitle;
694 break;
695 default:
696 debug() << "not handling as part of autplay:" << state;
697 break;
698 }
699 }
700
701 switch (state) {
702 case MediaPlayer::NoState:
703 changeState(newState: LoadingState);
704 break;
705 case MediaPlayer::OpeningState:
706 changeState(newState: LoadingState);
707 break;
708 case MediaPlayer::BufferingState:
709 changeState(newState: BufferingState);
710 break;
711 case MediaPlayer::PlayingState:
712 changeState(newState: PlayingState);
713 break;
714 case MediaPlayer::PausedState:
715 changeState(newState: PausedState);
716 break;
717 case MediaPlayer::StoppedState:
718 changeState(newState: StoppedState);
719 break;
720 case MediaPlayer::EndedState:
721 if (hasNextTrack()) {
722 moveToNextSource();
723 } else if (source().discType() == Cd && m_autoPlayTitles && !m_attemptingAutoplay) {
724 debug() << "trying to simulate autoplay";
725 m_attemptingAutoplay = true;
726 m_player->setCdTrack(++m_currentTitle);
727 } else {
728 m_attemptingAutoplay = false;
729 emitAboutToFinish();
730 emit finished();
731 changeState(newState: StoppedState);
732 }
733 break;
734 case MediaPlayer::ErrorState:
735 debug() << errorString();
736 emitAboutToFinish();
737 emit finished();
738 changeState(newState: ErrorState);
739 break;
740 }
741
742 if (m_buffering) {
743 switch (state) {
744 case MediaPlayer::BufferingState:
745 break;
746 case MediaPlayer::PlayingState:
747 debug() << "Restoring buffering state after state change to Playing";
748 changeState(newState: BufferingState);
749 m_stateAfterBuffering = PlayingState;
750 break;
751 case MediaPlayer::PausedState:
752 debug() << "Restoring buffering state after state change to Paused";
753 changeState(newState: BufferingState);
754 m_stateAfterBuffering = PausedState;
755 break;
756 default:
757 debug() << "Buffering aborted!";
758 m_buffering = false;
759 break;
760 }
761 }
762
763 return;
764}
765
766void MediaObject::onHasVideoChanged(bool hasVideo)
767{
768 DEBUG_BLOCK;
769 if (m_hasVideo != hasVideo) {
770 m_hasVideo = hasVideo;
771 emit hasVideoChanged(b_has_video: m_hasVideo);
772 } else {
773 // We can simply return if we are have the appropriate caching already.
774 // Otherwise we'd do pointless rescans of mediacontroller stuff.
775 // MC and MO are force-reset on media changes anyway.
776 return;
777 }
778
779 refreshDescriptors();
780}
781
782void MediaObject::setBufferStatus(int percent)
783{
784 // VLC does not have a buffering state (surprise!) but instead only sends the
785 // event (surprise!). Hence we need to simulate the state change.
786 // Problem with BufferingState is that it is actually concurrent to Playing or Paused
787 // meaning that while you are buffering you can also pause, thus triggering
788 // a state change to paused. To handle this we let updateState change the
789 // state accordingly (as we need to allow the UI to update itself, and
790 // immediately after that we change back to buffering again.
791 // This loop can only be interrupted by a state change to !Playing & !Paused
792 // or by reaching a 100 % buffer caching (i.e. full cache).
793
794 m_buffering = true;
795 if (m_state != BufferingState) {
796 m_stateAfterBuffering = m_state;
797 changeState(newState: BufferingState);
798 }
799
800 emit bufferStatus(percentFilled: percent);
801
802 // Transit to actual state only after emission so the signal is still
803 // delivered while in BufferingState.
804 if (percent >= 100) { // http://trac.videolan.org/vlc/ticket/5277
805 m_buffering = false;
806 changeState(newState: m_stateAfterBuffering);
807 }
808}
809
810void MediaObject::refreshDescriptors()
811{
812 if (m_player->titleCount() > 0)
813 refreshTitles();
814
815 if (hasVideo()) {
816 refreshAudioChannels();
817 refreshSubtitles();
818
819 if (m_player->videoChapterCount() > 0)
820 refreshChapters(title: m_player->title());
821 }
822}
823
824qint64 MediaObject::totalTime() const
825{
826 return m_totalTime;
827}
828
829void MediaObject::addSink(SinkNode *node)
830{
831 Q_ASSERT(!m_sinks.contains(node));
832 m_sinks.append(t: node);
833}
834
835void MediaObject::removeSink(SinkNode *node)
836{
837 Q_ASSERT(node);
838 m_sinks.removeAll(t: node);
839}
840
841} // namespace VLC
842} // namespace Phonon
843

source code of phonon-vlc/src/mediaobject.cpp