1
/* This file is part of the KDE project.
3
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
5
This library is free software: you can redistribute it and/or modify
6
it under the terms of the GNU Lesser General Public License as published by
7
the Free Software Foundation, either version 2.1 or 3 of the License.
9
This library is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU Lesser General Public License for more details.
14
You should have received a copy of the GNU Lesser General Public License
15
along with this library. If not, see <http://www.gnu.org/licenses/>.
18
#include <gst/interfaces/navigation.h>
19
#include <gst/interfaces/propertyprobe.h>
21
#include "mediaobject.h"
22
#include "videowidget.h"
25
#include "streamreader.h"
26
#include "phononsrc.h"
27
#include "phonon-config-gstreamer.h"
28
#include "gsthelper.h"
30
#include <QtCore/QByteRef>
31
#include <QtCore/QEvent>
32
#include <QtCore/QFile>
33
#include <QtCore/QLibrary>
34
#include <QtCore/QStringList>
35
#include <QtCore/QTimer>
36
#include <QtCore/QVector>
37
#include <QtGui/QApplication>
39
#define ABOUT_TO_FINNISH_TIME 2000
40
#define MAX_QUEUE_TIME 20 * GST_SECOND
49
MediaObject::MediaObject(Backend *backend, QObject *parent)
51
, MediaNode(backend, AudioSource | VideoSource)
52
, m_resumeState(false)
53
, m_oldState(Phonon::LoadingState)
55
, m_state(Phonon::LoadingState)
56
, m_pendingState(Phonon::LoadingState)
57
, m_tickTimer(new QTimer(this))
62
, m_prefinishMarkReachedNotEmitted(true)
63
, m_aboutToFinishEmitted(false)
73
, m_videoStreamFound(false)
76
, m_atEndOfStream(false)
77
, m_atStartOfStream(false)
78
, m_error(Phonon::NoError)
82
, m_previousTickTime(-1)
83
, m_resetNeeded(false)
84
, m_autoplayTitles(true)
85
, m_availableTitles(0)
88
, m_installingPlugin(true)
90
qRegisterMetaType<GstCaps*>("GstCaps*");
91
qRegisterMetaType<State>("State");
94
m_name = "MediaObject" + QString::number(count++);
96
if (!m_backend->isValid()) {
97
setError(tr("Cannot start playback. \n\nCheck your GStreamer installation and make sure you "
98
"\nhave libgstreamer-plugins-base installed."), Phonon::FatalError);
102
m_backend->addBusWatcher(this);
103
connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick()));
105
connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
106
this, SLOT(notifyStateChange(Phonon::State, Phonon::State)));
109
MediaObject::~MediaObject()
111
m_backend->removeBusWatcher(this);
113
gst_element_set_state(m_pipeline, GST_STATE_NULL);
114
gst_object_unref(m_pipeline);
117
gst_element_set_state(m_audioGraph, GST_STATE_NULL);
118
gst_object_unref(m_audioGraph);
121
gst_element_set_state(m_videoGraph, GST_STATE_NULL);
122
gst_object_unref(m_videoGraph);
126
QString stateString(const Phonon::State &state)
129
case Phonon::LoadingState:
130
return QString("LoadingState");
131
case Phonon::StoppedState:
132
return QString("StoppedState");
133
case Phonon::PlayingState:
134
return QString("PlayingState");
135
case Phonon::BufferingState:
136
return QString("BufferingState");
137
case Phonon::PausedState:
138
return QString("PausedState");
139
case Phonon::ErrorState:
140
return QString("ErrorState");
145
void MediaObject::saveState()
147
//Only first resumeState is respected
151
if (m_pendingState == Phonon::PlayingState || m_pendingState == Phonon::PausedState) {
152
m_resumeState = true;
153
m_oldState = m_pendingState;
154
m_oldPos = getPipelinePos();
158
void MediaObject::resumeState()
161
QMetaObject::invokeMethod(this, "setState", Qt::QueuedConnection, Q_ARG(State, m_oldState));
164
void MediaObject::newPadAvailable (GstPad *pad)
168
caps = gst_pad_get_caps (pad);
170
str = gst_caps_get_structure (caps, 0);
171
QString mediaString(gst_structure_get_name (str));
173
if (mediaString.startsWith("video")) {
175
} else if (mediaString.startsWith("audio")) {
178
m_backend->logMessage("Could not connect pad", Backend::Warning);
180
gst_caps_unref (caps);
184
void MediaObject::cb_newpad (GstElement *decodebin,
194
MediaObject *media = static_cast<MediaObject*>(data);
196
media->newPadAvailable(pad);
199
#ifdef PLUGIN_INSTALL_API
200
void MediaObject::pluginInstallationResult(GstInstallPluginsReturn result)
202
bool wasInstalling = m_installingPlugin;
203
m_installingPlugin = false;
204
bool canPlay = (m_hasAudio || m_videoStreamFound);
205
Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
207
case GST_INSTALL_PLUGINS_INVALID:
208
setError(QString(tr("Phonon attempted to install an invalid codec name.")));
210
case GST_INSTALL_PLUGINS_CRASHED:
211
setError(QString(tr("The codec installer crashed.")), error);
213
case GST_INSTALL_PLUGINS_NOT_FOUND:
214
setError(QString(tr("The required codec could not be found for installation.")), error);
216
case GST_INSTALL_PLUGINS_ERROR:
217
setError(QString(tr("An unspecified error occurred during codec installation.")), error);
219
case GST_INSTALL_PLUGINS_PARTIAL_SUCCESS:
220
setError(QString(tr("Not all codecs could be installed.")), error);
222
case GST_INSTALL_PLUGINS_USER_ABORT:
223
setError(QString(tr("User aborted codec installation")), error);
225
//These four should never ever be passed in.
226
//If they have, gstreamer has probably imploded in on itself.
227
case GST_INSTALL_PLUGINS_STARTED_OK:
228
case GST_INSTALL_PLUGINS_INTERNAL_FAILURE:
229
case GST_INSTALL_PLUGINS_HELPER_MISSING:
230
case GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS:
231
//But this one is OK.
232
case GST_INSTALL_PLUGINS_SUCCESS:
233
m_backend->logMessage("Updating registry");
234
if (gst_update_registry()) {
235
m_backend->logMessage("Registry updated failed");
245
void MediaObject::pluginInstallationDone(GstInstallPluginsReturn result, gpointer userData)
247
MediaObject *mediaObject = static_cast<MediaObject*>(userData);
248
Q_ASSERT(mediaObject);
249
qRegisterMetaType<GstInstallPluginsReturn>("GstInstallPluginsReturn");
250
QMetaObject::invokeMethod(mediaObject, "pluginInstallationResult", Qt::QueuedConnection, Q_ARG(GstInstallPluginsReturn, result));
252
#endif // PLUGIN_INSTALL_API
254
void MediaObject::installMissingCodecs()
256
if (m_missingCodecs.size() > 0) {
257
bool canPlay = (m_hasAudio || m_videoStreamFound);
258
Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
259
#ifdef PLUGIN_INSTALL_API
260
GstInstallPluginsContext *ctx = gst_install_plugins_context_new();
261
QWidget *activeWindow = QApplication::activeWindow();
263
gst_install_plugins_context_set_xid(ctx, static_cast<int>(activeWindow->winId()));
266
QByteArray missingCodec = m_missingCodecs.first().toLocal8Bit();
267
details[0] = missingCodec.data();
269
GstInstallPluginsReturn status;
271
status = gst_install_plugins_async(details, ctx, pluginInstallationDone, this);
272
gst_install_plugins_context_free(ctx);
274
if (status != GST_INSTALL_PLUGINS_STARTED_OK) {
275
if (status == GST_INSTALL_PLUGINS_HELPER_MISSING)
276
setError(QString(tr("Missing codec helper script assistant.")), Phonon::FatalError);
278
setError(QString(tr("Plugin codec installation failed for codec: %1"))
279
.arg(m_missingCodecs[0].split('|')[3]), error);
281
m_installingPlugin = true;
282
setState(Phonon::LoadingState);
284
m_missingCodecs.clear();
286
QString codecs = m_missingCodecs.join(", ");
288
if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) {
290
emit hasVideoChanged(false);
292
setError(QString(tr("A required codec is missing. You need to install the following codec(s) to play this content: %0")).arg(codecs), error);
293
m_missingCodecs.clear();
298
typedef void (*Ptr_gst_pb_utils_init)();
299
typedef gchar* (*Ptr_gst_pb_utils_get_codec_description)(const GstCaps *);
301
void MediaObject::cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps *caps, gpointer data)
305
MediaObject *media = static_cast<MediaObject*>(data);
308
QString value = "unknown codec";
310
// These functions require GStreamer > 0.10.12
311
#ifndef QT_NO_LIBRARY
312
static Ptr_gst_pb_utils_init p_gst_pb_utils_init = 0;
313
static Ptr_gst_pb_utils_get_codec_description p_gst_pb_utils_get_codec_description = 0;
314
if (!p_gst_pb_utils_init) {
315
p_gst_pb_utils_init = (Ptr_gst_pb_utils_init)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_init");
316
p_gst_pb_utils_get_codec_description = (Ptr_gst_pb_utils_get_codec_description)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_get_codec_description");
317
if (p_gst_pb_utils_init)
318
p_gst_pb_utils_init();
320
if (p_gst_pb_utils_get_codec_description) {
321
gchar *codecName = NULL;
322
codecName = p_gst_pb_utils_get_codec_description (caps);
323
value = QString::fromUtf8(codecName);
326
#endif //QT_NO_LIBRARY
328
// For GStreamer versions < 0.10.12
329
GstStructure *str = gst_caps_get_structure (caps, 0);
330
value = QString::fromUtf8(gst_structure_get_name (str));
334
#ifdef PLUGIN_INSTALL_API
335
QString plugins = QString("gstreamer|0.10|%0|%1|decoder-%2")
336
.arg( qApp->applicationName() )
338
.arg( QString::fromUtf8(gst_caps_to_string (caps) ) );
339
media->addMissingCodecName( plugins );
341
media->addMissingCodecName( value );
346
static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data)
348
GstPad *pad = GST_PAD(obj);
349
GstCaps *caps = gst_pad_get_caps (pad);
351
MediaObject *media = static_cast<MediaObject*>(data);
353
// We do not want any more notifications until the source changes
354
g_signal_handler_disconnect(pad, media->capsHandler());
356
// setVideoCaps calls loadingComplete(), meaning we cannot call it from
357
// the streaming thread
358
QMetaObject::invokeMethod(media, "setVideoCaps", Qt::QueuedConnection, Q_ARG(GstCaps *, caps));
361
void MediaObject::setVideoCaps(GstCaps *caps)
366
if ((str = gst_caps_get_structure (caps, 0))) {
367
if (gst_structure_get_int (str, "width", &width) && gst_structure_get_int (str, "height", &height)) {
369
gint aspectDenum = 0;
370
if (gst_structure_get_fraction(str, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) {
372
width = width*aspectNum/aspectDenum;
374
// Let child nodes know about our new video size
375
QSize size(width, height);
376
MediaNodeEvent event(MediaNodeEvent::VideoSizeChanged, &size);
380
gst_caps_unref(caps);
383
// Adds an element to the pipeline if not previously added
384
bool MediaObject::addToPipeline(GstElement *elem)
387
if (!GST_ELEMENT_PARENT(elem)) { // If not already in pipeline
388
success = gst_bin_add(GST_BIN(m_pipeline), elem);
393
void MediaObject::connectVideo(GstPad *pad)
395
GstState currentState = GST_STATE(m_pipeline);
396
if (addToPipeline(m_videoGraph)) {
397
GstPad *videopad = gst_element_get_pad (m_videoGraph, "sink");
398
if (!GST_PAD_IS_LINKED (videopad) && (gst_pad_link (pad, videopad) == GST_PAD_LINK_OK)) {
399
gst_element_set_state(m_videoGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
400
m_videoStreamFound = true;
401
m_backend->logMessage("Video track connected", Backend::Info, this);
402
// Note that the notify::caps _must_ be installed after linking to work with Dapper
403
m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this);
405
if (!m_loading && !m_hasVideo) {
406
m_hasVideo = m_videoStreamFound;
407
emit hasVideoChanged(m_hasVideo);
410
gst_object_unref (videopad);
412
m_backend->logMessage("The video stream could not be plugged.", Backend::Info, this);
416
void MediaObject::connectAudio(GstPad *pad)
418
GstState currentState = GST_STATE(m_pipeline);
419
if (addToPipeline(m_audioGraph)) {
420
GstPad *audiopad = gst_element_get_pad (m_audioGraph, "sink");
421
if (!GST_PAD_IS_LINKED (audiopad) && (gst_pad_link (pad, audiopad)==GST_PAD_LINK_OK)) {
422
gst_element_set_state(m_audioGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
424
m_backend->logMessage("Audio track connected", Backend::Info, this);
426
gst_object_unref (audiopad);
428
m_backend->logMessage("The audio stream could not be plugged.", Backend::Info, this);
432
void MediaObject::cb_pad_added(GstElement *decodebin,
437
GstPad *decodepad = static_cast<GstPad*>(data);
438
gst_pad_link (pad, decodepad);
439
//gst_object_unref (decodepad);
442
bool MediaObject::createV4lPipe(const DeviceAccess &access, const MediaSource &source)
445
QString v4lDevice = access.second;
447
gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
450
m_datasource = gst_element_factory_make("v4l2src", "v4l2src");
452
m_backend->logMessage("Couldn't create v4l2src element");
455
g_object_set(G_OBJECT(m_datasource), "device", v4lDevice.toUtf8().data(), (const char*)NULL);
456
m_backend->logMessage("Created video device element");
457
gst_bin_add(GST_BIN(m_pipeline), m_datasource);
458
gst_element_link(m_datasource, m_decodebin);
462
bool MediaObject::createPipefromDevice(const MediaSource &source)
464
foreach(DeviceAccess access, source.deviceAccessList()) {
465
if (access.first == "v4l2") {
466
return createV4lPipe(access, source);
469
qWarning() << "Only v4l2 devices supported.";
474
* Create a media source from a given URL.
476
* returns true if successful
478
bool MediaObject::createPipefromURL(const QUrl &url)
480
// Remove any existing data source
482
gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
483
// m_pipeline has the only ref to datasource
487
// Verify that the uri can be parsed
488
if (!url.isValid()) {
489
m_backend->logMessage(QString("%1 is not a valid URI").arg(url.toString()));
493
// Create a new datasource based on the input URL
494
// add the 'file' scheme if it's missing; the double '/' is needed!
495
QByteArray encoded_cstr_url;
496
if (url.scheme() == QLatin1String("")) {
497
encoded_cstr_url = QFile::encodeName("file://" + url.toString());
498
} else if (url.scheme() == QLatin1String("file")) {
502
// TODO 4.5: investigate whether this is necessary. Harald was a bit worrid
503
// that QFile::encodeName on an actual streaming URI could cause problems.
504
encoded_cstr_url = QFile::encodeName(url.toString());
506
encoded_cstr_url = url.toEncoded();
508
m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL);
512
// Set the device for MediaSource::Disc
513
if (m_source.type() == MediaSource::Disc) {
515
if (g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "device")) {
516
QByteArray mediaDevice = QFile::encodeName(m_source.deviceName());
517
if (!mediaDevice.isEmpty())
518
g_object_set (G_OBJECT (m_datasource), "device", mediaDevice.constData(), (const char*)NULL);
521
// Also Set optical disc speed to 2X for Audio CD
522
if (m_source.discType() == Phonon::Cd
523
&& (g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "read-speed"))) {
524
g_object_set (G_OBJECT (m_datasource), "read-speed", 2, (const char*)NULL);
525
m_backend->logMessage(QString("new device speed : 2X"), Backend::Info, this);
529
/* make HTTP sources send extra headers so we get icecast
530
* metadata in case the stream is an icecast stream */
531
if (encoded_cstr_url.startsWith("http://")
532
&& g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "iradio-mode")) {
533
g_object_set (m_datasource, "iradio-mode", TRUE, NULL);
537
// Link data source into pipeline
538
gst_bin_add(GST_BIN(m_pipeline), m_datasource);
539
if (!gst_element_link(m_datasource, m_decodebin)) {
540
// For sources with dynamic pads (such as RtspSrc) we need to connect dynamically
541
GstPad *decodepad = gst_element_get_pad (m_decodebin, "sink");
542
g_signal_connect (m_datasource, "pad-added", G_CALLBACK (&cb_pad_added), decodepad);
549
* Create a media source from a media stream
551
* returns true if successful
553
bool MediaObject::createPipefromStream(const MediaSource &source)
555
#ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
556
// Remove any existing data source
558
gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
559
// m_pipeline has the only ref to datasource
563
m_datasource = GST_ELEMENT(g_object_new(phonon_src_get_type(), NULL));
567
StreamReader *streamReader = new StreamReader(source, this);
568
g_object_set (G_OBJECT (m_datasource), "iodevice", streamReader, (const char*)NULL);
570
// Link data source into pipeline
571
gst_bin_add(GST_BIN(m_pipeline), m_datasource);
572
if (!gst_element_link(m_datasource, m_decodebin)) {
573
gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
577
#else //QT_NO_PHONON_ABSTRACTMEDIASTREAM
583
void MediaObject::createPipeline()
585
m_pipeline = gst_pipeline_new (NULL);
586
gst_object_ref (GST_OBJECT (m_pipeline));
587
gst_object_sink (GST_OBJECT (m_pipeline));
589
m_decodebin = gst_element_factory_make ("decodebin2", NULL);
590
g_signal_connect (m_decodebin, "new-decoded-pad", G_CALLBACK (&cb_newpad), this);
591
g_signal_connect (m_decodebin, "unknown-type", G_CALLBACK (&cb_unknown_type), this);
593
gst_bin_add(GST_BIN(m_pipeline), m_decodebin);
595
// Create a bin to contain the gst elements for this medianode
597
// Set up audio graph
598
m_audioGraph = gst_bin_new(NULL);
599
gst_object_ref (GST_OBJECT (m_audioGraph));
600
gst_object_sink (GST_OBJECT (m_audioGraph));
602
// Note that these queues are only required for streaming content
603
// And should ideally be created on demand as they will disable
604
// pull-mode access. Also note that the max-size-time are increased to
605
// reduce buffer overruns as these are not gracefully handled at the moment.
606
m_audioPipe = gst_element_factory_make("queue", NULL);
607
g_object_set(G_OBJECT(m_audioPipe), "max-size-time", MAX_QUEUE_TIME, (const char*)NULL);
609
QByteArray tegraEnv = qgetenv("TEGRA_GST_OPENMAX");
610
if (!tegraEnv.isEmpty()) {
611
g_object_set(G_OBJECT(m_audioPipe), "max-size-time", 0, (const char*)NULL);
612
g_object_set(G_OBJECT(m_audioPipe), "max-size-buffers", 0, (const char*)NULL);
613
g_object_set(G_OBJECT(m_audioPipe), "max-size-bytes", 0, (const char*)NULL);
616
gst_bin_add(GST_BIN(m_audioGraph), m_audioPipe);
617
GstPad *audiopad = gst_element_get_pad (m_audioPipe, "sink");
618
gst_element_add_pad (m_audioGraph, gst_ghost_pad_new ("sink", audiopad));
619
gst_object_unref (audiopad);
621
// Set up video graph
622
m_videoGraph = gst_bin_new(NULL);
623
gst_object_ref (GST_OBJECT (m_videoGraph));
624
gst_object_sink (GST_OBJECT (m_videoGraph));
626
m_videoPipe = gst_element_factory_make("queue", NULL);
627
g_object_set(G_OBJECT(m_videoPipe), "max-size-time", MAX_QUEUE_TIME, (const char*)NULL);
628
if (!tegraEnv.isEmpty()) {
629
g_object_set(G_OBJECT(m_videoPipe), "max-size-time", 33000, (const char*)NULL);
630
g_object_set(G_OBJECT(m_audioPipe), "max-size-buffers", 1, (const char*)NULL);
631
g_object_set(G_OBJECT(m_audioPipe), "max-size-bytes", 0, (const char*)NULL);
633
gst_bin_add(GST_BIN(m_videoGraph), m_videoPipe);
634
GstPad *videopad = gst_element_get_pad (m_videoPipe, "sink");
635
gst_element_add_pad (m_videoGraph, gst_ghost_pad_new ("sink", videopad));
636
gst_object_unref (videopad);
638
if (m_pipeline && m_decodebin && m_audioGraph && m_videoGraph && m_audioPipe && m_videoPipe)
641
m_backend->logMessage("Could not create pipeline for media object", Backend::Warning);
647
State MediaObject::state() const
655
bool MediaObject::hasVideo() const
663
bool MediaObject::isSeekable() const
671
qint64 MediaObject::currentTime() const
677
case Phonon::PausedState:
678
case Phonon::BufferingState:
679
case Phonon::PlayingState:
680
return getPipelinePos();
681
case Phonon::StoppedState:
682
case Phonon::LoadingState:
684
case Phonon::ErrorState:
693
qint32 MediaObject::tickInterval() const
695
return m_tickInterval;
701
void MediaObject::setTickInterval(qint32 newTickInterval)
703
m_tickInterval = newTickInterval;
704
if (m_tickInterval <= 0)
705
m_tickTimer->setInterval(50);
707
m_tickTimer->setInterval(newTickInterval);
713
void MediaObject::play()
715
setState(Phonon::PlayingState);
716
m_resumeState = false;
722
QString MediaObject::errorString() const
724
return m_errorString;
730
Phonon::ErrorType MediaObject::errorType() const
736
* Set the current state of the mediaObject.
738
* !### Note that both Playing and Paused states are set immediately
739
* This should obviously be done in response to actual gstreamer state changes
741
void MediaObject::setState(State newstate)
746
if (m_state == newstate)
750
// We are still loading. The state will be requested
751
// when loading has completed.
752
m_pendingState = newstate;
756
GstState currentState;
757
gst_element_get_state (m_pipeline, ¤tState, NULL, 1000);
760
case Phonon::BufferingState:
761
m_backend->logMessage("phonon state request: buffering", Backend::Info, this);
764
case Phonon::PausedState:
765
m_backend->logMessage("phonon state request: paused", Backend::Info, this);
766
if (currentState == GST_STATE_PAUSED) {
767
changeState(Phonon::PausedState);
768
} else if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) {
769
m_pendingState = Phonon::PausedState;
771
m_backend->logMessage("phonon state request failed", Backend::Info, this);
775
case Phonon::StoppedState:
776
m_backend->logMessage("phonon state request: Stopped", Backend::Info, this);
777
if (currentState == GST_STATE_READY) {
778
changeState(Phonon::StoppedState);
779
} else if (gst_element_set_state(m_pipeline, GST_STATE_READY) != GST_STATE_CHANGE_FAILURE) {
780
m_pendingState = Phonon::StoppedState;
782
m_backend->logMessage("phonon state request failed", Backend::Info, this);
784
m_atEndOfStream = false;
787
case Phonon::PlayingState:
791
// TODO 4.5: drop m_resetNeeded completely and use live connections, whatever
793
if (m_source.url().host().contains(QLatin1String("last.fm"))) {
794
// Never reset for last.fm as they only allow one connection attempt.
795
// https://bugs.kde.org/show_bug.cgi?id=252649
796
m_resetNeeded = false;
799
// ### Note this is a workaround and it should really be gracefully
800
// handled by medianode when we implement live connections.
801
// This generally happens if medianodes have been connected after the MediaSource was set
802
// Note that a side-effect of this is that we resend all meta data.
803
gst_element_set_state(m_pipeline, GST_STATE_NULL);
804
m_resetNeeded = false;
805
// Send a source change so the X11 renderer
806
// will re-set the overlay
807
MediaNodeEvent event(MediaNodeEvent::SourceChanged);
810
m_backend->logMessage("phonon state request: Playing", Backend::Info, this);
811
if (m_atEndOfStream) {
812
m_backend->logMessage("EOS already reached", Backend::Info, this);
813
} else if (currentState == GST_STATE_PLAYING) {
814
m_backend->logMessage("Already playing", Backend::Info, this);
815
changeState(Phonon::PlayingState);
817
GstStateChangeReturn status = gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
818
if (status == GST_STATE_CHANGE_ASYNC) {
819
m_backend->logMessage("Playing state is now pending");
820
m_pendingState = Phonon::PlayingState;
821
} else if (status == GST_STATE_CHANGE_FAILURE) {
822
m_backend->logMessage("phonon state request failed", Backend::Info, this);
823
changeState(Phonon::ErrorState);
828
case Phonon::ErrorState:
829
m_backend->logMessage("phonon state request : Error", Backend::Warning, this);
830
m_backend->logMessage(QString("Last error : %0").arg(errorString()) , Backend::Warning, this);
831
changeState(Phonon::ErrorState); //immediately set error state
834
case Phonon::LoadingState:
835
m_backend->logMessage("phonon state request: Loading", Backend::Info, this);
836
changeState(Phonon::LoadingState);
842
* Signals that the requested state has completed
843
* by emitting stateChanged and updates the internal state.
845
void MediaObject::changeState(State newstate)
847
if (newstate == m_state)
850
Phonon::State oldState = m_state;
851
m_state = newstate; // m_state must be set before emitting, since
852
// Error state requires that state() will return the new value
853
m_pendingState = newstate;
856
case Phonon::PausedState:
857
m_backend->logMessage("phonon state changed: paused", Backend::Info, this);
860
case Phonon::BufferingState:
861
m_backend->logMessage("phonon state changed: buffering", Backend::Info, this);
864
case Phonon::PlayingState:
865
m_backend->logMessage("phonon state changed: Playing", Backend::Info, this);
868
case Phonon::StoppedState:
869
m_backend->logMessage("phonon state changed: Stopped", Backend::Info, this);
870
// We must reset the pipeline when playing again
871
m_resetNeeded = true;
875
case Phonon::ErrorState:
877
m_backend->logMessage("phonon state changed : Error", Backend::Info, this);
878
m_backend->logMessage(errorString(), Backend::Warning, this);
881
case Phonon::LoadingState:
882
m_backend->logMessage("phonon state changed: Loading", Backend::Info, this);
886
emit stateChanged(newstate, oldState);
889
void MediaObject::setError(const QString &errorString, Phonon::ErrorType error)
891
m_backend->logMessage(QString("Phonon error: %1 (code %2)").arg(errorString).arg(error), Backend::Warning);
892
m_errorString = errorString;
896
if (error == Phonon::FatalError) {
898
emit hasVideoChanged(false);
899
gst_element_set_state(m_pipeline, GST_STATE_READY);
900
changeState(Phonon::ErrorState);
902
if (m_loading) //Flag error only after loading has completed
903
m_pendingState = Phonon::ErrorState;
905
changeState(Phonon::ErrorState);
909
qint64 MediaObject::totalTime() const
914
qint32 MediaObject::prefinishMark() const
916
return m_prefinishMark;
919
qint32 MediaObject::transitionTime() const
921
return m_transitionTime;
924
void MediaObject::setTransitionTime(qint32 time)
926
m_transitionTime = time;
929
qint64 MediaObject::remainingTime() const
931
return totalTime() - currentTime();
934
MediaSource MediaObject::source() const
939
void MediaObject::setNextSource(const MediaSource &source)
941
if (source.type() == MediaSource::Invalid &&
942
source.type() == MediaSource::Empty)
944
m_nextSource = source;
948
* Update total time value from the pipeline
950
bool MediaObject::updateTotalTime()
952
GstFormat format = GST_FORMAT_TIME;
954
if (gst_element_query_duration (GST_ELEMENT(m_pipeline), &format, &duration)) {
955
setTotalTime(duration / GST_MSECOND);
962
* Checks if the current source is seekable
964
void MediaObject::updateSeekable()
972
query = gst_query_new_seeking(GST_FORMAT_TIME);
973
result = gst_element_query (m_pipeline, query);
977
gst_query_parse_seeking (query, &format, &seekable, &start, &stop);
979
if (m_seekable != seekable) {
980
m_seekable = seekable;
981
emit seekableChanged(m_seekable);
985
m_backend->logMessage("Stream is seekable", Backend::Info, this);
987
m_backend->logMessage("Stream is non-seekable", Backend::Info, this);
989
m_backend->logMessage("updateSeekable query failed", Backend::Info, this);
991
gst_query_unref (query);
994
qint64 MediaObject::getPipelinePos() const
996
Q_ASSERT(m_pipeline);
998
// Note some formats (usually mpeg) do not allow us to accurately seek to the
999
// beginning or end of the file so we 'fake' it here rather than exposing the front end to potential issues.
1000
if (m_atEndOfStream)
1002
if (m_atStartOfStream)
1004
if (m_posAtSeek >= 0)
1008
GstFormat format = GST_FORMAT_TIME;
1009
gst_element_query_position (GST_ELEMENT(m_pipeline), &format, &pos);
1010
return (pos / GST_MSECOND);
1014
* Internal method to set a new total time for the media object
1016
void MediaObject::setTotalTime(qint64 newTime)
1019
if (newTime == m_totalTime)
1022
m_totalTime = newTime;
1024
emit totalTimeChanged(m_totalTime);
1030
void MediaObject::setSource(const MediaSource &source)
1035
m_installingPlugin = false;
1037
// We have to reset the state completely here, otherwise
1038
// remnants of the old pipeline can result in strangenes
1039
// such as failing duration queries etc
1041
gst_element_set_state(m_pipeline, GST_STATE_NULL);
1042
gst_element_get_state(m_pipeline, &state, NULL, 2000);
1045
emit currentSourceChanged(m_source);
1046
m_previousTickTime = -1;
1047
m_missingCodecs.clear();
1049
// Go into to loading state
1050
changeState(Phonon::LoadingState);
1052
// IMPORTANT: Honor the m_resetNeeded flag as it currently stands.
1053
// See https://qa.mandriva.com/show_bug.cgi?id=56807
1054
//m_resetNeeded = false;
1055
m_resumeState = false;
1056
m_pendingState = Phonon::StoppedState;
1058
// Make sure we start out unconnected
1059
if (GST_ELEMENT_PARENT(m_audioGraph))
1060
gst_bin_remove(GST_BIN(m_pipeline), m_audioGraph);
1061
if (GST_ELEMENT_PARENT(m_videoGraph))
1062
gst_bin_remove(GST_BIN(m_pipeline), m_videoGraph);
1064
// Clear any existing errors
1065
m_aboutToFinishEmitted = false;
1067
m_errorString.clear();
1069
m_bufferPercent = 0;
1070
m_prefinishMarkReachedNotEmitted = true;
1071
m_aboutToFinishEmitted = false;
1073
m_videoStreamFound = false;
1075
m_atEndOfStream = false;
1077
m_availableTitles = 0;
1081
// Clear existing meta tags
1085
switch (source.type()) {
1086
case MediaSource::Url: {
1087
if (!createPipefromURL(source.url()))
1088
setError(tr("Could not open media source."));
1092
case MediaSource::LocalFile: {
1093
if (!createPipefromURL(QUrl::fromLocalFile(source.fileName())))
1094
setError(tr("Could not open media source."));
1098
case MediaSource::Invalid:
1099
setError(tr("Invalid source type."), Phonon::NormalError);
1102
case MediaSource::Empty:
1105
case MediaSource::Stream:
1106
if (!createPipefromStream(source))
1107
setError(tr("Could not open media source."));
1110
case MediaSource::Disc:
1113
switch (source.discType()) {
1114
case Phonon::NoDisc:
1115
qWarning() << "I should never get to see a MediaSource that is a disc but doesn't specify which one";
1117
case Phonon::Cd: // CD tracks can be specified by setting the url in the following way uri=cdda:4
1118
mediaUrl = QLatin1String("cdda://");
1121
mediaUrl = QLatin1String("dvd://");
1124
mediaUrl = QLatin1String("vcd://");
1127
qWarning() << "media " << source.discType() << " not implemented";
1130
if (mediaUrl.isEmpty() || !createPipefromURL(QUrl(mediaUrl)))
1131
setError(tr("Could not open media source."));
1135
case MediaSource::CaptureDevice:
1136
if (!createPipefromDevice(source))
1137
setError(tr("Could not open capture device."));
1142
m_backend->logMessage("Source type not currently supported", Backend::Warning, this);
1143
setError(tr("Could not open media source."), Phonon::NormalError);
1147
MediaNodeEvent event(MediaNodeEvent::SourceChanged);
1150
// We need to link this node to ensure that fake sinks are connected
1151
// before loading, otherwise the stream will be blocked
1156
void MediaObject::beginLoad()
1158
if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) {
1159
m_backend->logMessage("Begin source load", Backend::Info, this);
1161
setError(tr("Could not open media source."));
1165
// Called when we are ready to leave the loading state
1166
void MediaObject::loadingComplete()
1168
if (m_videoStreamFound) {
1169
MediaNodeEvent event(MediaNodeEvent::VideoAvailable);
1175
setState(m_pendingState);
1176
emit metaDataChanged(m_metaData);
1179
void MediaObject::getStreamInfo()
1184
if (m_videoStreamFound != m_hasVideo) {
1185
m_hasVideo = m_videoStreamFound;
1186
emit hasVideoChanged(m_hasVideo);
1189
if (m_source.discType() == Phonon::Cd) {
1191
GstFormat format = gst_format_get_by_nick("track");
1192
if (gst_element_query_duration (m_pipeline, &format, &titleCount)) {
1193
//check if returned format is still "track",
1194
//gstreamer sometimes returns the total time, if tracks information is not available.
1195
if (qstrcmp(gst_format_get_name(format), "track") == 0) {
1196
int oldAvailableTitles = m_availableTitles;
1197
m_availableTitles = (int)titleCount;
1198
if (m_availableTitles != oldAvailableTitles) {
1199
emit availableTitlesChanged(m_availableTitles);
1200
m_backend->logMessage(QString("Available titles changed: %0").arg(m_availableTitles), Backend::Info, this);
1207
void MediaObject::setPrefinishMark(qint32 newPrefinishMark)
1209
m_prefinishMark = newPrefinishMark;
1210
if (currentTime() < totalTime() - m_prefinishMark) // not about to finish
1211
m_prefinishMarkReachedNotEmitted = true;
1214
void MediaObject::pause()
1216
m_backend->logMessage("pause()", Backend::Info, this);
1217
if (state() != Phonon::PausedState)
1218
setState(Phonon::PausedState);
1219
m_resumeState = false;
1222
void MediaObject::stop()
1224
if (state() != Phonon::StoppedState) {
1225
setState(Phonon::StoppedState);
1226
m_prefinishMarkReachedNotEmitted = true;
1228
m_resumeState = false;
1231
void MediaObject::seek(qint64 time)
1238
case Phonon::PlayingState:
1239
case Phonon::StoppedState:
1240
case Phonon::PausedState:
1241
case Phonon::BufferingState:
1242
m_backend->logMessage(QString("Seek to pos %0").arg(time), Backend::Info, this);
1245
m_atStartOfStream = true;
1247
m_atStartOfStream = false;
1249
m_posAtSeek = getPipelinePos();
1250
m_tickTimer->stop();
1252
if (gst_element_seek(m_pipeline, 1.0, GST_FORMAT_TIME,
1253
GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET,
1254
time * GST_MSECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
1256
case Phonon::LoadingState:
1257
case Phonon::ErrorState:
1261
quint64 current = currentTime();
1262
quint64 total = totalTime();
1264
if (current < total - m_prefinishMark)
1265
m_prefinishMarkReachedNotEmitted = true;
1266
if (current < total - ABOUT_TO_FINNISH_TIME)
1267
m_aboutToFinishEmitted = false;
1268
m_atEndOfStream = false;
1272
void MediaObject::emitTick()
1274
if (m_resumeState) {
1278
qint64 currentTime = getPipelinePos();
1279
qint64 totalTime = m_totalTime;
1281
if (m_tickInterval > 0 && currentTime != m_previousTickTime) {
1282
emit tick(currentTime);
1283
m_previousTickTime = currentTime;
1285
if (m_state == Phonon::PlayingState) {
1286
if (currentTime >= totalTime - m_prefinishMark) {
1287
if (m_prefinishMarkReachedNotEmitted) {
1288
m_prefinishMarkReachedNotEmitted = false;
1289
emit prefinishMarkReached(totalTime - currentTime);
1292
// Prepare load of next source
1293
if (currentTime >= totalTime - ABOUT_TO_FINNISH_TIME) {
1294
if (m_source.type() == MediaSource::Disc &&
1296
m_availableTitles > 1 &&
1297
m_currentTitle < m_availableTitles) {
1298
m_aboutToFinishEmitted = false;
1299
} else if (!m_aboutToFinishEmitted) {
1300
m_aboutToFinishEmitted = true; // track is about to finish
1301
emit aboutToFinish();
1308
* Used to iterate through the gst_tag_list and extract values
1310
void foreach_tag_function(const GstTagList *list, const gchar *tag, gpointer user_data)
1312
TagMap *newData = static_cast<TagMap *>(user_data);
1314
GType type = gst_tag_get_type(tag);
1316
case G_TYPE_STRING: {
1318
gst_tag_list_get_string(list, tag, &str);
1319
value = QString::fromUtf8(str);
1324
case G_TYPE_BOOLEAN: {
1326
gst_tag_list_get_boolean(list, tag, &bval);
1327
value = QString::number(bval);
1333
gst_tag_list_get_int(list, tag, &ival);
1334
value = QString::number(ival);
1340
gst_tag_list_get_uint(list, tag, &uival);
1341
value = QString::number(uival);
1345
case G_TYPE_FLOAT: {
1347
gst_tag_list_get_float(list, tag, &fval);
1348
value = QString::number(fval);
1352
case G_TYPE_DOUBLE: {
1354
gst_tag_list_get_double(list, tag, &dval);
1355
value = QString::number(dval);
1360
//qDebug("Unsupported tag type: %s", g_type_name(type));
1364
QString key = QString(tag).toUpper();
1365
QString currVal = newData->value(key);
1366
if (!value.isEmpty() && !(newData->contains(key) && currVal == value))
1367
newData->insert(key, value);
1371
* Triggers playback after a song has completed in the current media queue
1373
void MediaObject::beginPlay()
1375
setSource(m_nextSource);
1376
m_nextSource = MediaSource();
1377
m_pendingState = Phonon::PlayingState;
1381
* Handle the GST_MESSAGE_ERROR message
1383
void MediaObject::handleErrorMessage(GstMessage *gstMessage)
1388
gst_message_parse_error (gstMessage, &err, &debug);
1389
gchar *errorMessage = gst_error_get_message (err->domain, err->code);
1390
logMessage.sprintf("Error: %s Message: %s (%s) Code:%d", debug, err->message, errorMessage, err->code);
1391
m_backend->logMessage(logMessage, Backend::Warning);
1392
g_free(errorMessage);
1395
if (err->domain == GST_RESOURCE_ERROR) {
1396
if (err->code == GST_RESOURCE_ERROR_NOT_FOUND) {
1397
setError(tr("Could not locate media source."), Phonon::FatalError);
1398
} else if (err->code == GST_RESOURCE_ERROR_OPEN_READ) {
1399
setError(tr("Could not open media source."), Phonon::FatalError);
1400
} else if (err->code == GST_RESOURCE_ERROR_BUSY) {
1401
// We need to check if this comes from an audio device by looking at sink caps
1402
GstPad* sinkPad = gst_element_get_static_pad(GST_ELEMENT(gstMessage->src), "sink");
1404
GstCaps *caps = gst_pad_get_caps (sinkPad);
1405
GstStructure *str = gst_caps_get_structure (caps, 0);
1406
if (g_strrstr (gst_structure_get_name (str), "audio"))
1407
setError(tr("Could not open audio device. The device is already in use."), Phonon::NormalError);
1409
setError(err->message, Phonon::FatalError);
1410
gst_caps_unref (caps);
1411
gst_object_unref (sinkPad);
1414
setError(QString(err->message), Phonon::FatalError);
1416
} else if (err->domain == GST_CORE_ERROR) {
1418
case GST_CORE_ERROR_MISSING_PLUGIN:
1419
installMissingCodecs();
1424
} else if (err->domain == GST_STREAM_ERROR) {
1425
switch (err->code) {
1426
case GST_STREAM_ERROR_CODEC_NOT_FOUND:
1427
installMissingCodecs();
1429
case GST_STREAM_ERROR_TYPE_NOT_FOUND:
1430
if (!m_installingPlugin)
1431
setError(tr("Could not find media type."), Phonon::FatalError);
1433
case GST_STREAM_ERROR_WRONG_TYPE:
1434
setError(tr("Wrong media type encountered in GStreamer pipeline."), Phonon::FatalError);
1436
case GST_STREAM_ERROR_DECODE:
1437
setError(tr("Could not decode media."), Phonon::FatalError);
1439
case GST_STREAM_ERROR_ENCODE:
1440
setError(tr("Could not encode media."), Phonon::FatalError);
1442
case GST_STREAM_ERROR_MUX:
1443
setError(tr("Could not demux media."), Phonon::FatalError);
1445
case GST_STREAM_ERROR_DECRYPT:
1446
setError(tr("Could not decrypt media."), Phonon::FatalError);
1448
case GST_STREAM_ERROR_DECRYPT_NOKEY:
1449
setError(tr("No suitable decryption key found."), Phonon::FatalError);
1452
if (!m_installingPlugin)
1453
setError(tr("Could not open media source."), Phonon::FatalError);
1457
setError(QString(err->message), Phonon::FatalError);
1462
void MediaObject::handleWarningMessage(GstMessage *gstMessage)
1466
gst_message_parse_warning(gstMessage, &err, &debug);
1468
msgString.sprintf("Warning: %s\nMessage:%s", debug, err->message);
1469
m_backend->logMessage(msgString, Backend::Warning);
1475
* Handles GST_MESSAGE_BUFFERING messages
1477
void MediaObject::handleBufferingMessage(GstMessage *gstMessage)
1480
gst_structure_get_int (gstMessage->structure, "buffer-percent", &percent); //gst_message_parse_buffering was introduced in 0.10.11
1482
if (m_bufferPercent != percent) {
1483
emit bufferStatus(percent);
1484
m_backend->logMessage(QString("Stream buffering %0").arg(percent), Backend::Debug, this);
1485
m_bufferPercent = percent;
1488
if (m_state != Phonon::BufferingState)
1489
emit stateChanged(m_state, Phonon::BufferingState);
1490
else if (percent == 100)
1491
emit stateChanged(Phonon::BufferingState, m_state);
1495
* Handle the GST_MESSAGE_STATE_CHANGED message
1497
void MediaObject::handleStateMessage(GstMessage *gstMessage)
1501
GstState pendingState;
1502
gst_message_parse_state_changed (gstMessage, &oldState, &newState, &pendingState);
1504
if (gstMessage->src != GST_OBJECT(m_pipeline)) {
1505
m_backend->logMessage("State changed from "+GstHelper::stateName(oldState)+" to "+GstHelper::stateName(newState), Backend::Debug, this);
1509
if (newState == pendingState)
1516
case GST_STATE_PLAYING :
1517
m_atStartOfStream = false;
1518
m_backend->logMessage("gstreamer: pipeline state set to playing", Backend::Info, this);
1519
m_tickTimer->start();
1520
changeState(Phonon::PlayingState);
1521
if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) {
1522
setTrack(m_pendingTitle);
1524
if (m_resumeState && m_oldState == Phonon::PlayingState) {
1526
m_resumeState = false;
1530
case GST_STATE_NULL:
1531
m_backend->logMessage("gstreamer: pipeline state set to null", Backend::Info, this);
1532
m_tickTimer->stop();
1535
case GST_STATE_PAUSED :
1536
m_backend->logMessage("gstreamer: pipeline state set to paused", Backend::Info, this);
1537
m_tickTimer->start();
1538
if (state() == Phonon::LoadingState) {
1540
} else if (m_resumeState && m_oldState == Phonon::PausedState) {
1541
changeState(Phonon::PausedState);
1542
m_resumeState = false;
1545
// A lot of autotests can break if we allow all paused changes through.
1546
if (m_pendingState == Phonon::PausedState) {
1547
changeState(Phonon::PausedState);
1552
case GST_STATE_READY :
1553
if (!m_loading && m_pendingState == Phonon::StoppedState)
1554
changeState(Phonon::StoppedState);
1555
m_backend->logMessage("gstreamer: pipeline state set to ready", Backend::Debug, this);
1556
m_tickTimer->stop();
1557
if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) {
1558
setTrack(m_pendingTitle);
1562
case GST_STATE_VOID_PENDING :
1563
m_backend->logMessage("gstreamer: pipeline state set to pending (void)", Backend::Debug, this);
1564
m_tickTimer->stop();
1570
* Handle the GST_MESSAGE_TAG message type
1572
void MediaObject::handleTagMessage(GstMessage *msg)
1574
GstTagList* tag_list = 0;
1575
gst_message_parse_tag(msg, &tag_list);
1578
gst_tag_list_foreach (tag_list, &foreach_tag_function, &newTags);
1579
gst_tag_list_free(tag_list);
1581
// Determine if we should no fake the album/artist tags.
1582
// This is a little confusing as we want to fake it on initial
1583
// connection where title, album and artist are all missing.
1584
// There are however times when we get just other information,
1585
// e.g. codec, and so we want to only do clever stuff if we
1586
// have a commonly available tag (ORGANIZATION) or we have a
1590
&& ((!newTags.contains("TITLE")
1591
&& newTags.contains("ORGANIZATION"))
1592
|| (newTags.contains("TITLE")
1593
&& m_metaData.value("TITLE") != newTags.value("TITLE")))
1594
&& !newTags.contains("ALBUM")
1595
&& !newTags.contains("ARTIST"));
1597
TagMap oldMap = m_metaData; // Keep a copy of the old one for reference
1599
// Now we've checked the new data, append any new meta tags to the existing tag list
1600
// We cannot use TagMap::iterator as this is a multimap and when streaming data
1601
// could in theory be lost.
1602
QList<QString> keys = newTags.keys();
1603
for (QList<QString>::iterator i = keys.begin(); i != keys.end(); ++i) {
1606
// If we're streaming, we need to remove data in m_metaData
1607
// in order to stop it filling up indefinitely (as it's a multimap)
1608
m_metaData.remove(key);
1610
QList<QString> values = newTags.values(key);
1611
for (QList<QString>::iterator j = values.begin(); j != values.end(); ++j) {
1613
QString currVal = m_metaData.value(key);
1614
if (!m_metaData.contains(key) || currVal != value) {
1615
m_metaData.insert(key, value);
1620
m_backend->logMessage("Meta tags found", Backend::Info, this);
1621
if (oldMap != m_metaData) {
1622
// This is a bit of a hack to ensure that stream metadata is
1623
// returned. We get as much as we can from the Shoutcast server's
1624
// StreamTitle= header. If further info is decoded from the stream
1625
// itself later, then it will overwrite this info.
1626
if (m_isStream && fake_it) {
1627
m_metaData.remove("ALBUM");
1628
m_metaData.remove("ARTIST");
1630
// Detect whether we want to "fill in the blanks"
1632
if (m_metaData.contains("TITLE"))
1634
str = m_metaData.value("TITLE");
1636
// Check to see if our title matches "%s - %s"
1637
// Where neither %s are empty...
1638
if ((splitpoint = str.indexOf(" - ")) > 0
1639
&& str.size() > (splitpoint+3)) {
1640
m_metaData.insert("ARTIST", str.left(splitpoint));
1641
m_metaData.replace("TITLE", str.mid(splitpoint+3));
1644
str = m_metaData.value("GENRE");
1646
m_metaData.insert("TITLE", str);
1648
m_metaData.insert("TITLE", "Streaming Data");
1650
if (!m_metaData.contains("ARTIST")) {
1651
str = m_metaData.value("LOCATION");
1653
m_metaData.insert("ARTIST", str);
1655
m_metaData.insert("ARTIST", "Streaming Data");
1657
str = m_metaData.value("ORGANIZATION");
1659
m_metaData.insert("ALBUM", str);
1661
m_metaData.insert("ALBUM", "Streaming Data");
1663
// As we manipulate the title, we need to recompare
1664
// oldMap and m_metaData here...
1665
if (oldMap != m_metaData && !m_loading)
1666
emit metaDataChanged(m_metaData);
1672
* Handle element messages
1674
void MediaObject::handleElementMessage(GstMessage *gstMessage)
1676
const GstStructure *gstStruct = gst_message_get_structure(gstMessage); //do not free this
1677
if (g_strrstr (gst_structure_get_name (gstStruct), "prepare-xwindow-id")) {
1678
MediaNodeEvent videoHandleEvent(MediaNodeEvent::VideoHandleRequest);
1679
notify(&videoHandleEvent);
1683
void MediaObject::handleEOSMessage(GstMessage *gstMessage)
1685
Q_UNUSED(gstMessage);
1686
m_backend->logMessage("EOS received", Backend::Info, this);
1687
handleEndOfStream();
1690
void MediaObject::handleDurationMessage(GstMessage *gstMessage)
1692
Q_UNUSED(gstMessage);
1693
m_backend->logMessage("GST_MESSAGE_DURATION", Backend::Debug, this);
1698
* Handle GStreamer bus messages
1700
void MediaObject::handleBusMessage(const Message &message)
1705
GstMessage *gstMessage = message.rawMessage();
1706
Q_ASSERT(m_pipeline);
1708
if (m_backend->debugLevel() >= Backend::Debug) {
1709
int type = GST_MESSAGE_TYPE(gstMessage);
1710
gchar* name = gst_element_get_name(gstMessage->src);
1711
QString msgString = QString("Bus: %0 (%1)").arg(gst_message_type_get_name ((GstMessageType)type)).arg(name);
1713
m_backend->logMessage(msgString, Backend::Debug, this);
1716
switch (GST_MESSAGE_TYPE (gstMessage)) {
1718
case GST_MESSAGE_EOS:
1719
handleEOSMessage(gstMessage);
1722
case GST_MESSAGE_TAG:
1723
handleTagMessage(gstMessage);
1726
case GST_MESSAGE_STATE_CHANGED :
1727
handleStateMessage(gstMessage);
1730
case GST_MESSAGE_ERROR:
1731
handleErrorMessage(gstMessage);
1734
case GST_MESSAGE_WARNING:
1735
handleWarningMessage(gstMessage);
1738
case GST_MESSAGE_ELEMENT:
1739
handleElementMessage(gstMessage);
1742
case GST_MESSAGE_DURATION:
1743
handleDurationMessage(gstMessage);
1746
case GST_MESSAGE_BUFFERING:
1747
handleBufferingMessage(gstMessage);
1749
//case GST_MESSAGE_INFO:
1750
//case GST_MESSAGE_STREAM_STATUS:
1751
//case GST_MESSAGE_CLOCK_PROVIDE:
1752
//case GST_MESSAGE_NEW_CLOCK:
1753
//case GST_MESSAGE_STEP_DONE:
1754
//case GST_MESSAGE_LATENCY: only from 0.10.12
1755
//case GST_MESSAGE_ASYNC_DONE: only from 0.10.13
1760
#if GST_VERSION >= GST_VERSION_CHECK(0,10,23,0)
1761
switch (gst_navigation_message_get_type(gstMessage)) {
1762
case GST_NAVIGATION_MESSAGE_MOUSE_OVER: {
1764
if (!gst_navigation_message_parse_mouse_over(gstMessage, &active)) {
1767
MediaNodeEvent mouseOverEvent(MediaNodeEvent::VideoMouseOver, &active);
1768
notify(&mouseOverEvent);
1774
#endif // GST_VERSION
1777
void MediaObject::handleEndOfStream()
1779
// If the stream is not seekable ignore
1780
// otherwise chained radio broadcasts would stop
1782
if (m_atEndOfStream)
1786
m_atEndOfStream = true;
1788
if (m_source.type() == MediaSource::Disc &&
1790
m_availableTitles > 1 &&
1791
m_currentTitle < m_availableTitles) {
1792
_iface_setCurrentTitle(m_currentTitle + 1);
1796
if (m_nextSource.type() != MediaSource::Invalid
1797
&& m_nextSource.type() != MediaSource::Empty) { // We only emit finish when the queue is actually empty
1798
QTimer::singleShot (qMax(0, transitionTime()), this, SLOT(beginPlay()));
1800
m_pendingState = Phonon::PausedState;
1803
setState(Phonon::StoppedState);
1804
// Note the behavior for live streams is not properly defined
1805
// But since we cant seek to 0, we don't have much choice other than stopping
1808
// Only emit paused if the finished signal
1809
// did not result in a new state
1810
if (m_pendingState == Phonon::PausedState)
1811
setState(m_pendingState);
1816
void MediaObject::invalidateGraph()
1818
m_resetNeeded = true;
1819
if (m_state == Phonon::PlayingState || m_state == Phonon::PausedState) {
1820
changeState(Phonon::StoppedState);
1824
// Notifes the pipeline about state changes in the media object
1825
void MediaObject::notifyStateChange(Phonon::State newstate, Phonon::State oldstate)
1828
MediaNodeEvent event(MediaNodeEvent::StateChanged, &newstate);
1832
#ifndef QT_NO_PHONON_MEDIACONTROLLER
1833
//interface management
1834
bool MediaObject::hasInterface(Interface iface) const
1836
return iface == AddonInterface::TitleInterface;
1839
QVariant MediaObject::interfaceCall(Interface iface, int command, const QList<QVariant> ¶ms)
1841
if (hasInterface(iface)) {
1845
case TitleInterface:
1848
case availableTitles:
1849
return _iface_availableTitles();
1851
return _iface_currentTitle();
1853
_iface_setCurrentTitle(params.first().toInt());
1855
case autoplayTitles:
1856
return m_autoplayTitles;
1857
case setAutoplayTitles:
1858
m_autoplayTitles = params.first().toBool();
1870
int MediaObject::_iface_availableTitles() const
1872
return m_availableTitles;
1875
int MediaObject::_iface_currentTitle() const
1877
return m_currentTitle;
1880
void MediaObject::_iface_setCurrentTitle(int title)
1882
m_backend->logMessage(QString("setCurrentTitle %0").arg(title), Backend::Info, this);
1883
if ((title == m_currentTitle) || (title == m_pendingTitle))
1886
m_pendingTitle = title;
1888
if (m_state == Phonon::PlayingState || m_state == Phonon::StoppedState) {
1889
setTrack(m_pendingTitle);
1891
setState(Phonon::StoppedState);
1895
void MediaObject::setTrack(int title)
1897
if (((m_state != Phonon::PlayingState) && (m_state != Phonon::StoppedState)) || (title < 1) || (title > m_availableTitles))
1900
//let's seek to the beginning of the song
1901
GstFormat trackFormat = gst_format_get_by_nick("track");
1902
m_backend->logMessage(QString("setTrack %0").arg(title), Backend::Info, this);
1903
if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, title - 1)) {
1904
m_currentTitle = title;
1906
m_atEndOfStream = false;
1907
emit titleChanged(title);
1908
emit totalTimeChanged(totalTime());
1917
#include "moc_mediaobject.cpp"