src/3rdparty/phonon/gstreamer/mediaobject.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /*  This file is part of the KDE project.
       
     2 
       
     3     Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 
       
     5     This library is free software: you can redistribute it and/or modify
       
     6     it under the terms of the GNU Lesser General Public License as published by
       
     7     the Free Software Foundation, either version 2.1 or 3 of the License.
       
     8 
       
     9     This library is distributed in the hope that it will be useful,
       
    10     but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    12     GNU Lesser General Public License for more details.
       
    13 
       
    14     You should have received a copy of the GNU Lesser General Public License
       
    15     along with this library.  If not, see <http://www.gnu.org/licenses/>.
       
    16 */
       
    17 #include <cmath>
       
    18 #include <gst/interfaces/propertyprobe.h>
       
    19 #include "common.h"
       
    20 #include "mediaobject.h"
       
    21 #include "videowidget.h"
       
    22 #include "message.h"
       
    23 #include "backend.h"
       
    24 #include "streamreader.h"
       
    25 #include "phononsrc.h"
       
    26 #include <QtCore>
       
    27 #include <QtCore/QTimer>
       
    28 #include <QtCore/QVector>
       
    29 #include <QtCore/QFile>
       
    30 #include <QtCore/QByteRef>
       
    31 #include <QtCore/QStringList>
       
    32 #include <QtCore/QEvent>
       
    33 #include <QApplication>
       
    34 
       
    35 #define ABOUT_TO_FINNISH_TIME 2000
       
    36 #define MAX_QUEUE_TIME 20 * GST_SECOND
       
    37 
       
    38 QT_BEGIN_NAMESPACE
       
    39 
       
    40 namespace Phonon
       
    41 {
       
    42 namespace Gstreamer
       
    43 {
       
    44 
       
    45 MediaObject::MediaObject(Backend *backend, QObject *parent)
       
    46         : QObject(parent)
       
    47         , MediaNode(backend, AudioSource | VideoSource)
       
    48         , m_resumeState(false)
       
    49         , m_oldState(Phonon::LoadingState)
       
    50         , m_oldPos(0)
       
    51         , m_state(Phonon::LoadingState)
       
    52         , m_pendingState(Phonon::LoadingState)
       
    53         , m_tickTimer(new QTimer(this))
       
    54         , m_prefinishMark(0)
       
    55         , m_transitionTime(0)
       
    56         , m_posAtSeek(-1)
       
    57         , m_prefinishMarkReachedNotEmitted(true)
       
    58         , m_aboutToFinishEmitted(false)
       
    59         , m_loading(false)
       
    60         , m_capsHandler(0)
       
    61         , m_datasource(0)
       
    62         , m_decodebin(0)
       
    63         , m_audioPipe(0)
       
    64         , m_videoPipe(0)
       
    65         , m_totalTime(-1)
       
    66         , m_bufferPercent(0)
       
    67         , m_hasVideo(false)
       
    68         , m_videoStreamFound(false)
       
    69         , m_hasAudio(false)
       
    70         , m_seekable(false)
       
    71         , m_atEndOfStream(false)
       
    72         , m_atStartOfStream(false)
       
    73         , m_error(Phonon::NoError)
       
    74         , m_pipeline(0)
       
    75         , m_audioGraph(0)
       
    76         , m_videoGraph(0)
       
    77         , m_previousTickTime(-1)
       
    78         , m_resetNeeded(false)
       
    79         , m_autoplayTitles(true)
       
    80         , m_availableTitles(0)
       
    81         , m_currentTitle(1)
       
    82 {
       
    83     qRegisterMetaType<GstCaps*>("GstCaps*");
       
    84     qRegisterMetaType<State>("State");
       
    85 
       
    86     static int count = 0;
       
    87     m_name = "MediaObject" + QString::number(count++);
       
    88 
       
    89     if (!m_backend->isValid()) {
       
    90         setError(tr("Cannot start playback. \n\nCheck your Gstreamer installation and make sure you "
       
    91                     "\nhave libgstreamer-plugins-base installed."), Phonon::FatalError);
       
    92     } else {
       
    93         m_root = this;
       
    94         createPipeline();
       
    95         m_backend->addBusWatcher(this);
       
    96         connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick()));
       
    97     }
       
    98     connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 
       
    99             this, SLOT(notifyStateChange(Phonon::State, Phonon::State)));
       
   100 
       
   101 }
       
   102 
       
   103 MediaObject::~MediaObject()
       
   104 {
       
   105     m_backend->removeBusWatcher(this);
       
   106     if (m_pipeline) {
       
   107         gst_element_set_state(m_pipeline, GST_STATE_NULL);
       
   108         gst_object_unref(m_pipeline);
       
   109     }
       
   110     if (m_audioGraph) {
       
   111         gst_element_set_state(m_audioGraph, GST_STATE_NULL);
       
   112         gst_object_unref(m_audioGraph);
       
   113     }
       
   114     if (m_videoGraph) {
       
   115         gst_element_set_state(m_videoGraph, GST_STATE_NULL);
       
   116         gst_object_unref(m_videoGraph);
       
   117     }
       
   118 }
       
   119 
       
   120 QString stateString(const Phonon::State &state)
       
   121 {
       
   122     switch (state) {
       
   123     case Phonon::LoadingState:
       
   124         return QString("LoadingState");
       
   125     case Phonon::StoppedState:
       
   126         return QString("StoppedState");
       
   127     case Phonon::PlayingState:
       
   128         return QString("PlayingState");
       
   129     case Phonon::BufferingState:
       
   130         return QString("BufferingState");
       
   131     case Phonon::PausedState:
       
   132         return QString("PausedState");
       
   133     case Phonon::ErrorState:
       
   134         return QString("ErrorState");
       
   135     }
       
   136     return QString();
       
   137 }
       
   138 
       
   139 void MediaObject::saveState()
       
   140 {
       
   141     //Only first resumeState is respected
       
   142     if (m_resumeState)
       
   143         return;
       
   144 
       
   145     if (m_pendingState == Phonon::PlayingState || m_pendingState == Phonon::PausedState) {
       
   146         m_resumeState = true;
       
   147         m_oldState = m_pendingState;
       
   148         m_oldPos = getPipelinePos();
       
   149     }
       
   150 }
       
   151 
       
   152 void MediaObject::resumeState()
       
   153 {
       
   154     if (m_resumeState)
       
   155         QMetaObject::invokeMethod(this, "setState", Qt::QueuedConnection, Q_ARG(State, m_oldState));
       
   156 }
       
   157 
       
   158 void MediaObject::newPadAvailable (GstPad *pad)
       
   159 {
       
   160     GstCaps *caps;
       
   161     GstStructure *str;
       
   162     caps = gst_pad_get_caps (pad);
       
   163     if (caps) {
       
   164         str = gst_caps_get_structure (caps, 0);
       
   165         QString mediaString(gst_structure_get_name (str));
       
   166 
       
   167         if (mediaString.startsWith("video")) {
       
   168             connectVideo(pad);
       
   169         } else if (mediaString.startsWith("audio")) {
       
   170             connectAudio(pad);
       
   171         } else {
       
   172             m_backend->logMessage("Could not connect pad", Backend::Warning);
       
   173         }
       
   174         gst_caps_unref (caps);
       
   175     }
       
   176 }
       
   177 
       
   178 void MediaObject::cb_newpad (GstElement *decodebin,
       
   179                              GstPad     *pad,
       
   180                              gboolean    last,
       
   181                              gpointer    data)
       
   182 {
       
   183     Q_UNUSED(decodebin);
       
   184     Q_UNUSED(pad);
       
   185     Q_UNUSED(last);
       
   186     Q_UNUSED(data);
       
   187 
       
   188     MediaObject *media = static_cast<MediaObject*>(data);
       
   189     Q_ASSERT(media);
       
   190     media->newPadAvailable(pad);
       
   191 }
       
   192 
       
   193 void MediaObject::noMorePadsAvailable ()
       
   194 {
       
   195     if (m_missingCodecs.size() > 0) {
       
   196         bool canPlay = (m_hasAudio || m_videoStreamFound);
       
   197         Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
       
   198         if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) {
       
   199             m_hasVideo = false;
       
   200             emit hasVideoChanged(false);
       
   201         }
       
   202         QString codecs = m_missingCodecs.join(", ");
       
   203         setError(QString(tr("A required codec is missing. You need to install the following codec(s) to play this content: %0")).arg(codecs), error);
       
   204         m_missingCodecs.clear();
       
   205     }
       
   206 }
       
   207 
       
   208 void MediaObject::cb_no_more_pads (GstElement * decodebin, gpointer data)
       
   209 {
       
   210     Q_UNUSED(decodebin);
       
   211     MediaObject *media = static_cast<MediaObject*>(data);
       
   212     Q_ASSERT(media);
       
   213     QMetaObject::invokeMethod(media, "noMorePadsAvailable", Qt::QueuedConnection);
       
   214 }
       
   215 
       
   216 typedef void (*Ptr_gst_pb_utils_init)();
       
   217 typedef gchar* (*Ptr_gst_pb_utils_get_codec_description)(const GstCaps *);
       
   218 
       
   219 void MediaObject::cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps *caps, gpointer data)
       
   220 {
       
   221     Q_UNUSED(decodebin);
       
   222     Q_UNUSED(pad);
       
   223     MediaObject *media = static_cast<MediaObject*>(data);
       
   224     Q_ASSERT(media);
       
   225 
       
   226     QString value = "unknown codec";
       
   227 
       
   228     // These functions require GStreamer > 0.10.12
       
   229     static Ptr_gst_pb_utils_init p_gst_pb_utils_init = 0;
       
   230     static Ptr_gst_pb_utils_get_codec_description p_gst_pb_utils_get_codec_description = 0;
       
   231     if (!p_gst_pb_utils_init) {
       
   232         p_gst_pb_utils_init =  (Ptr_gst_pb_utils_init)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_init");
       
   233         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");
       
   234         if (p_gst_pb_utils_init)
       
   235 	    p_gst_pb_utils_init();
       
   236     }
       
   237     if (p_gst_pb_utils_get_codec_description) {
       
   238         gchar *codecName = NULL;
       
   239         codecName = p_gst_pb_utils_get_codec_description (caps);
       
   240         value = QString::fromUtf8(codecName);
       
   241         g_free (codecName);
       
   242     } else {
       
   243         // For GStreamer versions < 0.10.12
       
   244         GstStructure *str = gst_caps_get_structure (caps, 0);
       
   245         value = QString::fromUtf8(gst_structure_get_name (str));
       
   246     }
       
   247     media->addMissingCodecName(value);
       
   248 }
       
   249 
       
   250 static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data)
       
   251 {
       
   252     GstPad *pad = GST_PAD(obj);
       
   253     GstCaps *caps = gst_pad_get_caps (pad);
       
   254     Q_ASSERT(caps);
       
   255     MediaObject *media = static_cast<MediaObject*>(data);
       
   256 
       
   257     // We do not want any more notifications until the source changes
       
   258     g_signal_handler_disconnect(pad, media->capsHandler());
       
   259 
       
   260     // setVideoCaps calls loadingComplete(), meaning we cannot call it from
       
   261     // the streaming thread
       
   262     QMetaObject::invokeMethod(media, "setVideoCaps", Qt::QueuedConnection, Q_ARG(GstCaps *, caps));
       
   263 }
       
   264 
       
   265 void MediaObject::setVideoCaps(GstCaps *caps)
       
   266 {
       
   267     GstStructure *str;
       
   268     gint width, height;
       
   269 
       
   270     if ((str = gst_caps_get_structure (caps, 0))) {
       
   271         if (gst_structure_get_int (str, "width", &width) && gst_structure_get_int (str, "height", &height)) {
       
   272             gint aspectNum = 0;
       
   273             gint aspectDenum = 0;
       
   274             if (gst_structure_get_fraction(str, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) {
       
   275                 if (aspectDenum > 0)
       
   276                     width = width*aspectNum/aspectDenum;
       
   277             }
       
   278             // Let child nodes know about our new video size
       
   279             QSize size(width, height);
       
   280             MediaNodeEvent event(MediaNodeEvent::VideoSizeChanged, &size);
       
   281             notify(&event);
       
   282         }
       
   283     }
       
   284     gst_caps_unref(caps);
       
   285 }
       
   286 
       
   287 // Adds an element to the pipeline if not previously added
       
   288 bool MediaObject::addToPipeline(GstElement *elem)
       
   289 {
       
   290     bool success = true;
       
   291     if (!GST_ELEMENT_PARENT(elem)) { // If not already in pipeline
       
   292         success = gst_bin_add(GST_BIN(m_pipeline), elem);
       
   293     }
       
   294     return success;
       
   295 }
       
   296 
       
   297 void MediaObject::connectVideo(GstPad *pad)
       
   298 {
       
   299     GstState currentState = GST_STATE(m_pipeline);
       
   300     if (addToPipeline(m_videoGraph)) {
       
   301         GstPad *videopad = gst_element_get_pad (m_videoGraph, "sink");
       
   302         if (!GST_PAD_IS_LINKED (videopad) && (gst_pad_link (pad, videopad) == GST_PAD_LINK_OK)) {
       
   303             gst_element_set_state(m_videoGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
       
   304             m_videoStreamFound = true;
       
   305             m_backend->logMessage("Video track connected", Backend::Info, this);
       
   306             // Note that the notify::caps _must_ be installed after linking to work with Dapper
       
   307             m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this);
       
   308  
       
   309             if (!m_loading && !m_hasVideo) {
       
   310                 m_hasVideo = m_videoStreamFound;
       
   311                 emit hasVideoChanged(m_hasVideo);
       
   312             }
       
   313         }
       
   314         gst_object_unref (videopad);
       
   315     } else {
       
   316         m_backend->logMessage("The video stream could not be plugged.", Backend::Info, this);
       
   317     }
       
   318 }
       
   319 
       
   320 void MediaObject::connectAudio(GstPad *pad)
       
   321 {
       
   322     GstState currentState = GST_STATE(m_pipeline);
       
   323     if (addToPipeline(m_audioGraph)) {
       
   324         GstPad *audiopad = gst_element_get_pad (m_audioGraph, "sink");
       
   325         if (!GST_PAD_IS_LINKED (audiopad) && (gst_pad_link (pad, audiopad)==GST_PAD_LINK_OK)) {
       
   326             gst_element_set_state(m_audioGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
       
   327             m_hasAudio = true;
       
   328             m_backend->logMessage("Audio track connected", Backend::Info, this);
       
   329         }
       
   330         gst_object_unref (audiopad);
       
   331     } else {
       
   332         m_backend->logMessage("The audio stream could not be plugged.", Backend::Info, this);
       
   333     }
       
   334 }
       
   335 
       
   336 void MediaObject::cb_pad_added(GstElement *decodebin,
       
   337                                GstPad     *pad,
       
   338                                gpointer    data)
       
   339 {
       
   340     Q_UNUSED(decodebin);
       
   341     GstPad *decodepad = static_cast<GstPad*>(data);
       
   342     gst_pad_link (pad, decodepad);
       
   343     gst_object_unref (decodepad);
       
   344 }
       
   345 
       
   346 /**
       
   347  * Create a media source from a given URL.
       
   348  *
       
   349  * returns true if successful
       
   350  */
       
   351 bool MediaObject::createPipefromURL(const QUrl &url)
       
   352 {
       
   353     // Remove any existing data source
       
   354     if (m_datasource) {
       
   355         gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
       
   356         // m_pipeline has the only ref to datasource
       
   357         m_datasource = 0;
       
   358     }
       
   359 
       
   360     // Verify that the uri can be parsed
       
   361     if (!url.isValid()) {
       
   362         m_backend->logMessage(QString("%1 is not a valid URI").arg(url.toString()));
       
   363         return false;
       
   364     }
       
   365 
       
   366     // Create a new datasource based on the input URL
       
   367     QByteArray encoded_cstr_url = url.toEncoded();
       
   368     m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL);
       
   369     if (!m_datasource)
       
   370         return false;
       
   371 
       
   372     // Set the device for MediaSource::Disc
       
   373     QByteArray mediaDevice = QFile::encodeName(m_source.deviceName());
       
   374     if (!mediaDevice.isEmpty())
       
   375         g_object_set (m_datasource, "device", mediaDevice.constData(), (const char*)NULL);
       
   376 
       
   377     // Link data source into pipeline
       
   378     gst_bin_add(GST_BIN(m_pipeline), m_datasource);
       
   379     if (!gst_element_link(m_datasource, m_decodebin)) {
       
   380         // For sources with dynamic pads (such as RtspSrc) we need to connect dynamically
       
   381         GstPad *decodepad = gst_element_get_pad (m_decodebin, "sink");
       
   382         g_signal_connect (m_datasource, "pad-added", G_CALLBACK (&cb_pad_added), decodepad);
       
   383     }
       
   384 
       
   385     return true;
       
   386 }
       
   387 
       
   388 /**
       
   389  * Create a media source from a media stream
       
   390  *
       
   391  * returns true if successful
       
   392  */
       
   393 bool MediaObject::createPipefromStream(const MediaSource &source)
       
   394 {
       
   395     // Remove any existing data source
       
   396     if (m_datasource) {
       
   397         gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
       
   398         // m_pipeline has the only ref to datasource
       
   399         m_datasource = 0;
       
   400     }
       
   401 
       
   402     m_datasource = GST_ELEMENT(g_object_new(phonon_src_get_type(), NULL));
       
   403     if (!m_datasource)
       
   404         return false;
       
   405 
       
   406     StreamReader *streamReader = new StreamReader(source);
       
   407     g_object_set (G_OBJECT (m_datasource), "iodevice", streamReader, (const char*)NULL);
       
   408 
       
   409     // Link data source into pipeline
       
   410     gst_bin_add(GST_BIN(m_pipeline), m_datasource);
       
   411     if (!gst_element_link(m_datasource, m_decodebin)) {
       
   412         gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
       
   413         return false;
       
   414     }
       
   415     return true;
       
   416 }
       
   417 
       
   418 void MediaObject::createPipeline()
       
   419 {
       
   420     m_pipeline = gst_pipeline_new (NULL);
       
   421     gst_object_ref (GST_OBJECT (m_pipeline));
       
   422     gst_object_sink (GST_OBJECT (m_pipeline));
       
   423 
       
   424     m_decodebin = gst_element_factory_make ("decodebin", NULL);
       
   425     g_signal_connect (m_decodebin, "new-decoded-pad", G_CALLBACK (&cb_newpad), this);
       
   426     g_signal_connect (m_decodebin, "unknown-type", G_CALLBACK (&cb_unknown_type), this);
       
   427     g_signal_connect (m_decodebin, "no-more-pads", G_CALLBACK (&cb_no_more_pads), this);
       
   428 
       
   429     gst_bin_add(GST_BIN(m_pipeline), m_decodebin);
       
   430 
       
   431     // Create a bin to contain the gst elements for this medianode
       
   432 
       
   433     // Set up audio graph
       
   434     m_audioGraph = gst_bin_new(NULL);
       
   435     gst_object_ref (GST_OBJECT (m_audioGraph));
       
   436     gst_object_sink (GST_OBJECT (m_audioGraph));
       
   437 
       
   438     // Note that these queues are only required for streaming content
       
   439     // And should ideally be created on demand as they will disable
       
   440     // pull-mode access. Also note that the max-size-time are increased to
       
   441     // reduce buffer overruns as these are not gracefully handled at the moment.
       
   442     m_audioPipe = gst_element_factory_make("queue", NULL);
       
   443     g_object_set(G_OBJECT(m_audioPipe), "max-size-time",  MAX_QUEUE_TIME, (const char*)NULL);
       
   444     gst_bin_add(GST_BIN(m_audioGraph), m_audioPipe);
       
   445     GstPad *audiopad = gst_element_get_pad (m_audioPipe, "sink");
       
   446     gst_element_add_pad (m_audioGraph, gst_ghost_pad_new ("sink", audiopad));
       
   447     gst_object_unref (audiopad);
       
   448 
       
   449     // Set up video graph
       
   450     m_videoGraph = gst_bin_new(NULL);
       
   451     gst_object_ref (GST_OBJECT (m_videoGraph));
       
   452     gst_object_sink (GST_OBJECT (m_videoGraph));
       
   453 
       
   454     m_videoPipe = gst_element_factory_make("queue", NULL);
       
   455     g_object_set(G_OBJECT(m_videoPipe), "max-size-time", MAX_QUEUE_TIME, (const char*)NULL);
       
   456     gst_bin_add(GST_BIN(m_videoGraph), m_videoPipe);
       
   457     GstPad *videopad = gst_element_get_pad (m_videoPipe, "sink");
       
   458     gst_element_add_pad (m_videoGraph, gst_ghost_pad_new ("sink", videopad));
       
   459     gst_object_unref (videopad);
       
   460 
       
   461     if (m_pipeline && m_decodebin && m_audioGraph && m_videoGraph && m_audioPipe && m_videoPipe)
       
   462         m_isValid = true;
       
   463     else
       
   464         m_backend->logMessage("Could not create pipeline for media object", Backend::Warning);
       
   465 }
       
   466 
       
   467 /**
       
   468  * !reimp
       
   469  */
       
   470 State MediaObject::state() const
       
   471 {
       
   472     return m_state;
       
   473 }
       
   474 
       
   475 /**
       
   476  * !reimp
       
   477  */
       
   478 bool MediaObject::hasVideo() const
       
   479 {
       
   480     return m_hasVideo;
       
   481 }
       
   482 
       
   483 /**
       
   484  * !reimp
       
   485  */
       
   486 bool MediaObject::isSeekable() const
       
   487 {
       
   488     return m_seekable;
       
   489 }
       
   490 
       
   491 /**
       
   492  * !reimp
       
   493  */
       
   494 qint64 MediaObject::currentTime() const
       
   495 {
       
   496     if (m_resumeState)
       
   497         return m_oldPos;
       
   498 
       
   499     switch (state()) {
       
   500     case Phonon::PausedState:
       
   501     case Phonon::BufferingState:
       
   502     case Phonon::PlayingState:
       
   503         return getPipelinePos();
       
   504     case Phonon::StoppedState:
       
   505     case Phonon::LoadingState:
       
   506         return 0;
       
   507     case Phonon::ErrorState:
       
   508         break;
       
   509     }
       
   510     return -1;
       
   511 }
       
   512 
       
   513 /**
       
   514  * !reimp
       
   515  */
       
   516 qint32 MediaObject::tickInterval() const
       
   517 {
       
   518     return m_tickInterval;
       
   519 }
       
   520 
       
   521 /**
       
   522  * !reimp
       
   523  */
       
   524 void MediaObject::setTickInterval(qint32 newTickInterval)
       
   525 {
       
   526     m_tickInterval = newTickInterval;
       
   527     if (m_tickInterval <= 0)
       
   528         m_tickTimer->setInterval(50);
       
   529     else
       
   530         m_tickTimer->setInterval(newTickInterval);
       
   531 }
       
   532 
       
   533 /**
       
   534  * !reimp
       
   535  */
       
   536 void MediaObject::play()
       
   537 {
       
   538     setState(Phonon::PlayingState);
       
   539     m_resumeState = false;
       
   540 }
       
   541 
       
   542 /**
       
   543  * !reimp
       
   544  */
       
   545 QString MediaObject::errorString() const
       
   546 {
       
   547     return m_errorString;
       
   548 }
       
   549 
       
   550 /**
       
   551  * !reimp
       
   552  */
       
   553 Phonon::ErrorType MediaObject::errorType() const
       
   554 {
       
   555     return m_error;
       
   556 }
       
   557 
       
   558 /**
       
   559  * Set the current state of the mediaObject.
       
   560  *
       
   561  * !### Note that both Playing and Paused states are set immediately
       
   562  *     This should obviously be done in response to actual gstreamer state changes
       
   563  */
       
   564 void MediaObject::setState(State newstate)
       
   565 {
       
   566     if (!isValid())
       
   567         return;
       
   568 
       
   569     if (m_state == newstate)
       
   570         return;
       
   571 
       
   572     if (m_loading) {
       
   573         // We are still loading. The state will be requested
       
   574         // when loading has completed.
       
   575         m_pendingState = newstate;
       
   576         return;
       
   577     }
       
   578 
       
   579     GstState currentState;
       
   580     gst_element_get_state (m_pipeline, &currentState, NULL, 1000);
       
   581 
       
   582     switch (newstate) {
       
   583     case Phonon::BufferingState:
       
   584         m_backend->logMessage("phonon state request: buffering", Backend::Info, this);
       
   585         break;
       
   586 
       
   587     case Phonon::PausedState:
       
   588         m_backend->logMessage("phonon state request: paused", Backend::Info, this);
       
   589         if (currentState == GST_STATE_PAUSED) {
       
   590             changeState(Phonon::PausedState);
       
   591         } else if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) {
       
   592             m_pendingState = Phonon::PausedState;
       
   593         } else {
       
   594             m_backend->logMessage("phonon state request failed", Backend::Info, this);
       
   595         }
       
   596         break;
       
   597 
       
   598     case Phonon::StoppedState:
       
   599         m_backend->logMessage("phonon state request: Stopped", Backend::Info, this);
       
   600         if (currentState == GST_STATE_READY) {
       
   601             changeState(Phonon::StoppedState);
       
   602         } else if (gst_element_set_state(m_pipeline, GST_STATE_READY) != GST_STATE_CHANGE_FAILURE) {
       
   603             m_pendingState = Phonon::StoppedState;
       
   604         } else {
       
   605             m_backend->logMessage("phonon state request failed", Backend::Info, this);
       
   606         }
       
   607         m_atEndOfStream = false;
       
   608         break;
       
   609 
       
   610     case Phonon::PlayingState:
       
   611        if (m_resetNeeded) {
       
   612             // ### Note this is a workaround and it should really be gracefully
       
   613             // handled by medianode when we implement live connections.
       
   614             // This generally happens if medianodes have been connected after the MediaSource was set
       
   615             // Note that a side-effect of this is that we resend all meta data.
       
   616             gst_element_set_state(m_pipeline, GST_STATE_NULL);
       
   617             m_resetNeeded = false;
       
   618             // Send a source change so the X11 renderer
       
   619             // will re-set the overlay
       
   620             MediaNodeEvent event(MediaNodeEvent::SourceChanged);
       
   621             notify(&event);
       
   622         }
       
   623         m_backend->logMessage("phonon state request: Playing", Backend::Info, this);
       
   624         if (m_atEndOfStream) {
       
   625             m_backend->logMessage("EOS already reached", Backend::Info, this);
       
   626         } else if (currentState == GST_STATE_PLAYING) {
       
   627             changeState(Phonon::PlayingState);
       
   628         } else if (!m_atEndOfStream && gst_element_set_state(m_pipeline, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE) {
       
   629             m_pendingState = Phonon::PlayingState;
       
   630         } else {
       
   631             m_backend->logMessage("phonon state request failed", Backend::Info, this);
       
   632         }
       
   633         break;
       
   634 
       
   635     case Phonon::ErrorState:
       
   636         m_backend->logMessage("phonon state request : Error", Backend::Warning, this);
       
   637         m_backend->logMessage(QString("Last error : %0").arg(errorString()) , Backend::Warning, this);
       
   638         changeState(Phonon::ErrorState); //immediately set error state
       
   639         break;
       
   640 
       
   641     case Phonon::LoadingState:
       
   642         m_backend->logMessage("phonon state request: Loading", Backend::Info, this);
       
   643         changeState(Phonon::LoadingState);
       
   644         break;
       
   645     }
       
   646 }
       
   647 
       
   648 /*
       
   649  * Signals that the requested state has completed
       
   650  * by emitting stateChanged and updates the internal state.
       
   651  */
       
   652 void MediaObject::changeState(State newstate)
       
   653 {
       
   654     if (newstate == m_state)
       
   655         return;
       
   656 
       
   657     Phonon::State oldState = m_state;
       
   658     m_state = newstate; // m_state must be set before emitting, since 
       
   659                         // Error state requires that state() will return the new value
       
   660     m_pendingState = newstate;
       
   661     emit stateChanged(newstate, oldState);
       
   662 
       
   663     switch (newstate) {
       
   664     case Phonon::PausedState:
       
   665         m_backend->logMessage("phonon state changed: paused", Backend::Info, this);
       
   666         break;
       
   667 
       
   668     case Phonon::BufferingState:
       
   669         m_backend->logMessage("phonon state changed: buffering", Backend::Info, this);
       
   670         break;
       
   671 
       
   672     case Phonon::PlayingState:
       
   673         m_backend->logMessage("phonon state changed: Playing", Backend::Info, this);
       
   674         break;
       
   675 
       
   676     case Phonon::StoppedState:
       
   677         m_backend->logMessage("phonon state changed: Stopped", Backend::Info, this);
       
   678         m_tickTimer->stop();
       
   679         break;
       
   680 
       
   681     case Phonon::ErrorState:
       
   682         m_loading = false;
       
   683         m_backend->logMessage("phonon state changed : Error", Backend::Info, this);
       
   684         m_backend->logMessage(errorString(), Backend::Warning, this);
       
   685         break;
       
   686 
       
   687     case Phonon::LoadingState:
       
   688         m_backend->logMessage("phonon state changed: Loading", Backend::Info, this);
       
   689         break;
       
   690     }
       
   691 }
       
   692 
       
   693 void MediaObject::setError(const QString &errorString, Phonon::ErrorType error)
       
   694 {
       
   695     m_errorString = errorString;
       
   696     m_error = error;
       
   697     m_tickTimer->stop();
       
   698 
       
   699     if (error == Phonon::FatalError) {
       
   700         m_hasVideo = false;
       
   701         emit hasVideoChanged(false);
       
   702         gst_element_set_state(m_pipeline, GST_STATE_READY);
       
   703         changeState(Phonon::ErrorState);
       
   704     } else {
       
   705         if (m_loading) //Flag error only after loading has completed
       
   706             m_pendingState = Phonon::ErrorState;
       
   707         else
       
   708             changeState(Phonon::ErrorState);
       
   709     }
       
   710 }
       
   711 
       
   712 qint64 MediaObject::totalTime() const
       
   713 {
       
   714     return m_totalTime;
       
   715 }
       
   716 
       
   717 qint32 MediaObject::prefinishMark() const
       
   718 {
       
   719     return m_prefinishMark;
       
   720 }
       
   721 
       
   722 qint32 MediaObject::transitionTime() const
       
   723 {
       
   724     return m_transitionTime;
       
   725 }
       
   726 
       
   727 void MediaObject::setTransitionTime(qint32 time)
       
   728 {
       
   729     m_transitionTime = time;
       
   730 }
       
   731 
       
   732 qint64 MediaObject::remainingTime() const
       
   733 {
       
   734     return totalTime() - currentTime();
       
   735 }
       
   736 
       
   737 MediaSource MediaObject::source() const
       
   738 {
       
   739     return m_source;
       
   740 }
       
   741 
       
   742 void MediaObject::setNextSource(const MediaSource &source)
       
   743 {
       
   744     if (source.type() == MediaSource::Invalid &&
       
   745         source.type() == MediaSource::Empty)
       
   746         return;
       
   747     m_nextSource = source;
       
   748 }
       
   749 
       
   750 /**
       
   751  * Update total time value from the pipeline
       
   752  */
       
   753 bool MediaObject::updateTotalTime()
       
   754 {
       
   755     GstFormat   format = GST_FORMAT_TIME;
       
   756     gint64 duration = 0;
       
   757     if (gst_element_query_duration (GST_ELEMENT(m_pipeline), &format, &duration)) {
       
   758         setTotalTime(duration / GST_MSECOND);
       
   759         return true;
       
   760     }
       
   761     return false;
       
   762 }
       
   763 
       
   764 /**
       
   765  * Checks if the current source is seekable
       
   766  */
       
   767 void MediaObject::updateSeekable()
       
   768 {
       
   769     if (!isValid())
       
   770         return;
       
   771 
       
   772     GstQuery *query;
       
   773     gboolean result;
       
   774     gint64 start, stop;
       
   775     query = gst_query_new_seeking(GST_FORMAT_TIME);
       
   776     result = gst_element_query (m_pipeline, query);
       
   777     if (result) {
       
   778         gboolean seekable;
       
   779         GstFormat format;
       
   780         gst_query_parse_seeking (query, &format, &seekable, &start, &stop);
       
   781 
       
   782         if (m_seekable != seekable) {
       
   783             m_seekable = seekable;
       
   784             emit seekableChanged(m_seekable);
       
   785         }
       
   786 
       
   787         if (m_seekable)
       
   788             m_backend->logMessage("Stream is seekable", Backend::Info, this);
       
   789         else
       
   790             m_backend->logMessage("Stream is non-seekable", Backend::Info, this);
       
   791     } else {
       
   792         m_backend->logMessage("updateSeekable query failed", Backend::Info, this);
       
   793     }
       
   794     gst_query_unref (query);
       
   795 }
       
   796 
       
   797 qint64 MediaObject::getPipelinePos() const
       
   798 {
       
   799     Q_ASSERT(m_pipeline);
       
   800 
       
   801     // Note some formats (usually mpeg) do not allow us to accurately seek to the
       
   802     // beginning or end of the file so we 'fake' it here rather than exposing the front end to potential issues.
       
   803     if (m_atEndOfStream)
       
   804         return totalTime();
       
   805     if (m_atStartOfStream)
       
   806         return 0;
       
   807     if (m_posAtSeek >= 0)
       
   808         return m_posAtSeek;
       
   809 
       
   810     gint64 pos = 0;
       
   811     GstFormat format = GST_FORMAT_TIME;
       
   812     gst_element_query_position (GST_ELEMENT(m_pipeline), &format, &pos);
       
   813     return (pos / GST_MSECOND);
       
   814 }
       
   815 
       
   816 /*
       
   817  * Internal method to set a new total time for the media object
       
   818  */
       
   819 void MediaObject::setTotalTime(qint64 newTime)
       
   820 {
       
   821 
       
   822     if (newTime == m_totalTime)
       
   823         return;
       
   824 
       
   825     m_totalTime = newTime;
       
   826 
       
   827     emit totalTimeChanged(m_totalTime);
       
   828 }
       
   829 
       
   830 /*
       
   831  * !reimp
       
   832  */
       
   833 void MediaObject::setSource(const MediaSource &source)
       
   834 {
       
   835     if (!isValid())
       
   836         return;
       
   837 
       
   838     // We have to reset the state completely here, otherwise
       
   839     // remnants of the old pipeline can result in strangenes
       
   840     // such as failing duration queries etc
       
   841     GstState state;
       
   842     gst_element_set_state(m_pipeline, GST_STATE_NULL);
       
   843     gst_element_get_state (m_pipeline, &state, NULL, 2000);
       
   844 
       
   845     m_source = source;
       
   846     emit currentSourceChanged(m_source);
       
   847     m_previousTickTime = -1;
       
   848     m_missingCodecs.clear();
       
   849 
       
   850     // Go into to loading state
       
   851     changeState(Phonon::LoadingState);
       
   852     m_loading = true;
       
   853     m_resetNeeded = false;
       
   854     m_resumeState = false;
       
   855     m_pendingState = Phonon::StoppedState;
       
   856 
       
   857      // Make sure we start out unconnected
       
   858     if (GST_ELEMENT_PARENT(m_audioGraph))
       
   859         gst_bin_remove(GST_BIN(m_pipeline), m_audioGraph);
       
   860     if (GST_ELEMENT_PARENT(m_videoGraph))
       
   861         gst_bin_remove(GST_BIN(m_pipeline), m_videoGraph);
       
   862 
       
   863     // Clear any existing errors
       
   864     m_aboutToFinishEmitted = false;
       
   865     m_error = NoError;
       
   866     m_errorString = QString();
       
   867     
       
   868     m_bufferPercent = 0;
       
   869     m_prefinishMarkReachedNotEmitted = true;
       
   870     m_aboutToFinishEmitted = false;
       
   871     m_hasAudio = false;
       
   872     m_videoStreamFound = false;
       
   873     setTotalTime(-1);
       
   874     m_atEndOfStream = false;
       
   875 
       
   876     // Clear exising meta tags
       
   877     m_metaData.clear();
       
   878 
       
   879     switch (source.type()) {
       
   880     case MediaSource::Url: {            
       
   881             if (createPipefromURL(source.url()))
       
   882                 m_loading = true;
       
   883             else
       
   884                 setError(tr("Could not open media source."));
       
   885         }
       
   886         break;
       
   887 
       
   888     case MediaSource::LocalFile: {
       
   889             if (createPipefromURL(QUrl::fromLocalFile(source.fileName())))
       
   890                 m_loading = true;
       
   891             else
       
   892                 setError(tr("Could not open media source."));
       
   893         }
       
   894         break;
       
   895 
       
   896     case MediaSource::Invalid:
       
   897         setError(tr("Invalid source type."), Phonon::NormalError);
       
   898         break;
       
   899 
       
   900     case MediaSource::Empty:
       
   901         break;
       
   902 
       
   903     case MediaSource::Stream:
       
   904         if (createPipefromStream(source))
       
   905             m_loading = true;
       
   906         else
       
   907             setError(tr("Could not open media source."));
       
   908         break;
       
   909 
       
   910     case MediaSource::Disc: // CD tracks can be specified by setting the url in the following way uri=cdda:4
       
   911         {
       
   912             QUrl url;
       
   913             switch (source.discType()) {
       
   914                 case Phonon::Cd:
       
   915                     url = QUrl(QLatin1String("cdda://"));
       
   916                     break;
       
   917                 case Phonon::Dvd:
       
   918                     url = QUrl(QLatin1String("dvd://"));
       
   919                     break;
       
   920                 case Phonon::Vcd:
       
   921                     url = QUrl(QLatin1String("vcd://"));
       
   922                     break;
       
   923                 default:
       
   924                     break;
       
   925             }
       
   926             if (!url.isEmpty() && createPipefromURL(url))
       
   927                 m_loading = true;
       
   928             else
       
   929                 setError(tr("Could not open media source."));
       
   930         }
       
   931         break;
       
   932 
       
   933     default:
       
   934         m_backend->logMessage("Source type not currently supported", Backend::Warning, this);
       
   935         setError(tr("Could not open media source."), Phonon::NormalError);
       
   936         break;
       
   937     }
       
   938 
       
   939     MediaNodeEvent event(MediaNodeEvent::SourceChanged);
       
   940     notify(&event);
       
   941 
       
   942     // We need to link this node to ensure that fake sinks are connected
       
   943     // before loading, otherwise the stream will be blocked
       
   944     if (m_loading)
       
   945         link();
       
   946     beginLoad();
       
   947 }
       
   948 
       
   949 void MediaObject::beginLoad()
       
   950 {
       
   951     if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) {
       
   952         m_backend->logMessage("Begin source load", Backend::Info, this);
       
   953     } else {
       
   954         setError(tr("Could not open media source."));
       
   955     }
       
   956 }
       
   957 
       
   958 // Called when we are ready to leave the loading state
       
   959 void MediaObject::loadingComplete()
       
   960 {
       
   961     if (m_videoStreamFound) {
       
   962         MediaNodeEvent event(MediaNodeEvent::VideoAvailable);
       
   963         notify(&event);
       
   964     }
       
   965     getStreamInfo();
       
   966     m_loading = false;
       
   967 
       
   968     setState(m_pendingState);
       
   969     emit metaDataChanged(m_metaData);
       
   970 }
       
   971 
       
   972 void MediaObject::getStreamInfo()
       
   973 {
       
   974     updateSeekable();
       
   975     updateTotalTime();
       
   976 
       
   977     if (m_videoStreamFound != m_hasVideo) {
       
   978         m_hasVideo = m_videoStreamFound;
       
   979         emit hasVideoChanged(m_hasVideo);
       
   980     }
       
   981 
       
   982     m_availableTitles = 1;
       
   983     gint64 titleCount;
       
   984     GstFormat format = gst_format_get_by_nick("track");
       
   985     if (gst_element_query_duration (m_pipeline, &format, &titleCount)) {
       
   986         //check if returned format is still "track",
       
   987         //gstreamer sometimes returns the total time, if tracks information is not available.
       
   988         if (qstrcmp(gst_format_get_name(format), "track") == 0)  {
       
   989             int oldAvailableTitles = m_availableTitles;
       
   990             m_availableTitles = (int)titleCount;
       
   991             if (m_availableTitles != oldAvailableTitles) {
       
   992                 emit availableTitlesChanged(m_availableTitles);
       
   993                 m_backend->logMessage(QString("Available titles changed: %0").arg(m_availableTitles), Backend::Info, this);
       
   994             }
       
   995         }
       
   996     }
       
   997 
       
   998 }
       
   999 
       
  1000 void MediaObject::setPrefinishMark(qint32 newPrefinishMark)
       
  1001 {
       
  1002     m_prefinishMark = newPrefinishMark;
       
  1003     if (currentTime() < totalTime() - m_prefinishMark) // not about to finish
       
  1004         m_prefinishMarkReachedNotEmitted = true;
       
  1005 }
       
  1006 
       
  1007 void MediaObject::pause()
       
  1008 {
       
  1009     m_backend->logMessage("pause()", Backend::Info, this);
       
  1010     if (state() != Phonon::PausedState)
       
  1011         setState(Phonon::PausedState);
       
  1012     m_resumeState = false;
       
  1013 }
       
  1014 
       
  1015 void MediaObject::stop()
       
  1016 {
       
  1017     if (state() != Phonon::StoppedState) {
       
  1018         setState(Phonon::StoppedState);
       
  1019         m_prefinishMarkReachedNotEmitted = true;
       
  1020     }
       
  1021     m_resumeState = false;
       
  1022 }
       
  1023 
       
  1024 void MediaObject::seek(qint64 time)
       
  1025 {
       
  1026     if (!isValid())
       
  1027         return;
       
  1028 
       
  1029     if (isSeekable()) {
       
  1030         switch (state()) {
       
  1031         case Phonon::PlayingState:
       
  1032         case Phonon::StoppedState:
       
  1033         case Phonon::PausedState:
       
  1034         case Phonon::BufferingState:
       
  1035             m_backend->logMessage(QString("Seek to pos %0").arg(time), Backend::Info, this);
       
  1036 
       
  1037             if (time <= 0)
       
  1038                 m_atStartOfStream = true;
       
  1039             else
       
  1040                 m_atStartOfStream = false;
       
  1041 
       
  1042             m_posAtSeek = getPipelinePos();
       
  1043             m_tickTimer->stop();
       
  1044 
       
  1045             if (gst_element_seek(m_pipeline, 1.0, GST_FORMAT_TIME,
       
  1046                                  GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET,
       
  1047                                  time * GST_MSECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
       
  1048             break;
       
  1049         case Phonon::LoadingState:
       
  1050         case Phonon::ErrorState:
       
  1051             return;
       
  1052         }
       
  1053 
       
  1054         quint64 current = currentTime();
       
  1055         quint64 total = totalTime(); 
       
  1056 
       
  1057         if (current < total - m_prefinishMark)
       
  1058             m_prefinishMarkReachedNotEmitted = true;
       
  1059         if (current < total - ABOUT_TO_FINNISH_TIME)
       
  1060             m_aboutToFinishEmitted = false;
       
  1061         m_atEndOfStream = false;
       
  1062     }
       
  1063 }
       
  1064 
       
  1065 void MediaObject::emitTick()
       
  1066 {
       
  1067     if (m_resumeState) {
       
  1068         return;
       
  1069     }
       
  1070 
       
  1071     qint64 currentTime = getPipelinePos();
       
  1072     qint64 totalTime = m_totalTime;
       
  1073 
       
  1074     if (m_tickInterval > 0 && currentTime != m_previousTickTime) {
       
  1075         emit tick(currentTime);
       
  1076         m_previousTickTime = currentTime;        
       
  1077     }
       
  1078     if (m_state == Phonon::PlayingState) {
       
  1079         if (currentTime >= totalTime - m_prefinishMark) {
       
  1080             if (m_prefinishMarkReachedNotEmitted) {
       
  1081                 m_prefinishMarkReachedNotEmitted = false;
       
  1082                 emit prefinishMarkReached(totalTime - currentTime);
       
  1083             }
       
  1084         }
       
  1085         // Prepare load of next source
       
  1086         if (currentTime >= totalTime - ABOUT_TO_FINNISH_TIME) {
       
  1087             if (!m_aboutToFinishEmitted) {
       
  1088                 m_aboutToFinishEmitted = true; // track is about to finish
       
  1089                 emit aboutToFinish();
       
  1090             }
       
  1091         }
       
  1092     }
       
  1093 }
       
  1094 
       
  1095 
       
  1096 /*
       
  1097  * Used to iterate through the gst_tag_list and extract values
       
  1098  */
       
  1099 void foreach_tag_function(const GstTagList *list, const gchar *tag, gpointer user_data)
       
  1100 {
       
  1101     TagMap *newData = static_cast<TagMap *>(user_data);
       
  1102     QString value;
       
  1103     GType type = gst_tag_get_type(tag);
       
  1104     switch (type) {
       
  1105     case G_TYPE_STRING: {
       
  1106             char *str = 0;
       
  1107             gst_tag_list_get_string(list, tag, &str);
       
  1108             value = QString::fromUtf8(str);
       
  1109             g_free(str);
       
  1110         }
       
  1111         break;
       
  1112 
       
  1113     case G_TYPE_BOOLEAN: {
       
  1114             int bval;
       
  1115             gst_tag_list_get_boolean(list, tag, &bval);
       
  1116             value = QString::number(bval);
       
  1117         }
       
  1118         break;
       
  1119 
       
  1120     case G_TYPE_INT: {
       
  1121             int ival;
       
  1122             gst_tag_list_get_int(list, tag, &ival);
       
  1123             value = QString::number(ival);
       
  1124         }
       
  1125         break;
       
  1126 
       
  1127     case G_TYPE_UINT: {
       
  1128             unsigned int uival;
       
  1129             gst_tag_list_get_uint(list, tag, &uival);
       
  1130             value = QString::number(uival);
       
  1131         }
       
  1132         break;
       
  1133 
       
  1134     case G_TYPE_FLOAT: {
       
  1135             float fval;
       
  1136             gst_tag_list_get_float(list, tag, &fval);
       
  1137             value = QString::number(fval);
       
  1138         }
       
  1139         break;
       
  1140 
       
  1141     case G_TYPE_DOUBLE: {
       
  1142             double dval;
       
  1143             gst_tag_list_get_double(list, tag, &dval);
       
  1144             value = QString::number(dval);
       
  1145         }
       
  1146         break;
       
  1147 
       
  1148     default:
       
  1149         //qDebug("Unsupported tag type: %s", g_type_name(type));
       
  1150         break;
       
  1151     }
       
  1152 
       
  1153     QString key = QString(tag).toUpper();
       
  1154     QString currVal = newData->value(key);
       
  1155     if (!value.isEmpty() && !(newData->contains(key) && currVal == value))
       
  1156         newData->insert(key, value);
       
  1157 }
       
  1158 
       
  1159 /**
       
  1160  * Triggers playback after a song has completed in the current media queue
       
  1161  */
       
  1162 void MediaObject::beginPlay()
       
  1163 {
       
  1164     setSource(m_nextSource);
       
  1165     m_nextSource = MediaSource();
       
  1166     m_pendingState = Phonon::PlayingState;
       
  1167 }
       
  1168 
       
  1169 /**
       
  1170  * Handle GStreamer bus messages
       
  1171  */
       
  1172 void MediaObject::handleBusMessage(const Message &message)
       
  1173 {
       
  1174 
       
  1175     if (!isValid())
       
  1176         return;
       
  1177 
       
  1178     GstMessage *gstMessage = message.rawMessage();
       
  1179     Q_ASSERT(m_pipeline);
       
  1180 
       
  1181     if (m_backend->debugLevel() >= Backend::Debug) {
       
  1182         int type = GST_MESSAGE_TYPE(gstMessage);
       
  1183         gchar* name = gst_element_get_name(gstMessage->src);
       
  1184         QString msgString = QString("Bus: %0 (%1)").arg(gst_message_type_get_name ((GstMessageType)type)).arg(name);
       
  1185         g_free(name);
       
  1186         m_backend->logMessage(msgString, Backend::Debug, this);
       
  1187     }
       
  1188 
       
  1189     switch (GST_MESSAGE_TYPE (gstMessage)) {
       
  1190 
       
  1191     case GST_MESSAGE_EOS: 
       
  1192         m_backend->logMessage("EOS recieved", Backend::Info, this);
       
  1193         handleEndOfStream();
       
  1194         break;
       
  1195 
       
  1196     case GST_MESSAGE_TAG: {
       
  1197             GstTagList* tag_list = 0;
       
  1198             gst_message_parse_tag(gstMessage, &tag_list);
       
  1199             if (tag_list) {
       
  1200                 TagMap oldMap = m_metaData; // Keep a copy of the old one for reference
       
  1201                 // Append any new meta tags to the existing tag list
       
  1202                 gst_tag_list_foreach (tag_list, &foreach_tag_function, &m_metaData);
       
  1203                 m_backend->logMessage("Meta tags found", Backend::Info, this);
       
  1204                 if (oldMap != m_metaData && !m_loading)
       
  1205                     emit metaDataChanged(m_metaData);
       
  1206                 gst_tag_list_free(tag_list);
       
  1207             }
       
  1208         }
       
  1209         break;
       
  1210 
       
  1211     case GST_MESSAGE_STATE_CHANGED : {
       
  1212 
       
  1213             if (gstMessage->src != GST_OBJECT(m_pipeline))
       
  1214                 return;
       
  1215 
       
  1216             GstState oldState;
       
  1217             GstState newState;
       
  1218             GstState pendingState;
       
  1219             gst_message_parse_state_changed (gstMessage, &oldState, &newState, &pendingState);
       
  1220 
       
  1221             if (newState == pendingState)
       
  1222                 return;
       
  1223 
       
  1224             m_posAtSeek = -1;
       
  1225 
       
  1226             switch (newState) {
       
  1227 
       
  1228             case GST_STATE_PLAYING :
       
  1229                 m_atStartOfStream = false;
       
  1230                 m_backend->logMessage("gstreamer: pipeline state set to playing", Backend::Info, this);
       
  1231                 m_tickTimer->start();
       
  1232                 changeState(Phonon::PlayingState);
       
  1233                 if (m_resumeState && m_oldState == Phonon::PlayingState) {
       
  1234                     seek(m_oldPos);
       
  1235                     m_resumeState = false;
       
  1236                 }
       
  1237                 break;
       
  1238 
       
  1239             case GST_STATE_NULL:
       
  1240                 m_backend->logMessage("gstreamer: pipeline state set to null", Backend::Info, this);
       
  1241                 m_tickTimer->stop();
       
  1242                 break;
       
  1243 
       
  1244             case GST_STATE_PAUSED :
       
  1245                 m_backend->logMessage("gstreamer: pipeline state set to paused", Backend::Info, this);
       
  1246                 m_tickTimer->start();
       
  1247                 if (state() == Phonon::LoadingState) {
       
  1248                     // No_more_pads is not emitted from the decodebin in older versions (0.10.4)
       
  1249                     noMorePadsAvailable();
       
  1250                     loadingComplete();
       
  1251                 } else if (m_resumeState && m_oldState == Phonon::PausedState) {
       
  1252                     changeState(Phonon::PausedState);
       
  1253                     m_resumeState = false;
       
  1254                     break;
       
  1255                 } else {
       
  1256                     // A lot of autotests can break if we allow all paused changes through.
       
  1257                     if (m_pendingState == Phonon::PausedState) {
       
  1258                         changeState(Phonon::PausedState);
       
  1259                     }
       
  1260                 }
       
  1261                 break;
       
  1262 
       
  1263             case GST_STATE_READY :
       
  1264                 if (!m_loading && m_pendingState == Phonon::StoppedState)
       
  1265                     changeState(Phonon::StoppedState);
       
  1266                 m_backend->logMessage("gstreamer: pipeline state set to ready", Backend::Debug, this);
       
  1267                 m_tickTimer->stop();
       
  1268                 break;
       
  1269 
       
  1270             case GST_STATE_VOID_PENDING :
       
  1271                 m_backend->logMessage("gstreamer: pipeline state set to pending (void)", Backend::Debug, this);
       
  1272                 m_tickTimer->stop();
       
  1273                 break;
       
  1274             }
       
  1275             break;
       
  1276         }
       
  1277 
       
  1278     case GST_MESSAGE_ERROR: {
       
  1279             gchar *debug;
       
  1280             GError *err;
       
  1281             QString logMessage;
       
  1282             gst_message_parse_error (gstMessage, &err, &debug);
       
  1283             gchar *errorMessage = gst_error_get_message (err->domain, err->code);
       
  1284             logMessage.sprintf("Error: %s Message:%s (%s) Code:%d", debug, err->message, errorMessage, err->code);
       
  1285             m_backend->logMessage(logMessage, Backend::Warning);
       
  1286             g_free(errorMessage);
       
  1287             g_free (debug);
       
  1288 
       
  1289             if (err->domain == GST_RESOURCE_ERROR) {
       
  1290                 if (err->code == GST_RESOURCE_ERROR_NOT_FOUND) {
       
  1291                     setError(tr("Could not locate media source."), Phonon::FatalError);
       
  1292                 } else if (err->code == GST_RESOURCE_ERROR_OPEN_READ) {
       
  1293                     setError(tr("Could not open media source."), Phonon::FatalError);
       
  1294                 } else if (err->code == GST_RESOURCE_ERROR_BUSY) {
       
  1295                    // We need to check if this comes from an audio device by looking at sink caps
       
  1296                    GstPad* sinkPad = gst_element_get_static_pad(GST_ELEMENT(gstMessage->src), "sink");
       
  1297                    if (sinkPad) {
       
  1298                         GstCaps *caps = gst_pad_get_caps (sinkPad);
       
  1299                         GstStructure *str = gst_caps_get_structure (caps, 0);
       
  1300                         if (g_strrstr (gst_structure_get_name (str), "audio"))
       
  1301                             setError(tr("Could not open audio device. The device is already in use."), Phonon::NormalError);
       
  1302                         else
       
  1303                             setError(err->message, Phonon::FatalError);
       
  1304                         gst_caps_unref (caps);
       
  1305                         gst_object_unref (sinkPad);
       
  1306                    } 
       
  1307                } else {
       
  1308                     setError(QString(err->message), Phonon::FatalError);
       
  1309                }
       
  1310            } else if (err->domain == GST_STREAM_ERROR) {
       
  1311                 switch (err->code) {
       
  1312                 case GST_STREAM_ERROR_WRONG_TYPE:
       
  1313                 case GST_STREAM_ERROR_TYPE_NOT_FOUND:
       
  1314                     setError(tr("Could not decode media source."), Phonon::FatalError);
       
  1315                     break;
       
  1316                 default:
       
  1317                     setError(tr("Could not open media source."), Phonon::FatalError);
       
  1318                     break;
       
  1319                 }
       
  1320            } else {
       
  1321                 setError(QString(err->message), Phonon::FatalError);
       
  1322             }
       
  1323             g_error_free (err);
       
  1324             break;
       
  1325         }
       
  1326 
       
  1327     case GST_MESSAGE_WARNING: {
       
  1328             gchar *debug;
       
  1329             GError *err;
       
  1330             gst_message_parse_warning(gstMessage, &err, &debug);
       
  1331             QString msgString;
       
  1332             msgString.sprintf("Warning: %s\nMessage:%s", debug, err->message);
       
  1333             m_backend->logMessage(msgString, Backend::Warning);
       
  1334             g_free (debug);
       
  1335             g_error_free (err);
       
  1336             break;
       
  1337         }
       
  1338 
       
  1339     case GST_MESSAGE_ELEMENT: {
       
  1340             GstMessage *gstMessage = message.rawMessage();
       
  1341             const GstStructure *gstStruct = gst_message_get_structure(gstMessage); //do not free this
       
  1342             if (g_strrstr (gst_structure_get_name (gstStruct), "prepare-xwindow-id")) {
       
  1343                 MediaNodeEvent videoHandleEvent(MediaNodeEvent::VideoHandleRequest);
       
  1344                 notify(&videoHandleEvent);
       
  1345             }
       
  1346             break;
       
  1347         }
       
  1348 
       
  1349     case GST_MESSAGE_DURATION: {
       
  1350             m_backend->logMessage("GST_MESSAGE_DURATION", Backend::Debug, this);
       
  1351             updateTotalTime();
       
  1352             break;
       
  1353         }
       
  1354 
       
  1355     case GST_MESSAGE_BUFFERING: {
       
  1356             gint percent = 0;
       
  1357             gst_structure_get_int (gstMessage->structure, "buffer-percent", &percent); //gst_message_parse_buffering was introduced in 0.10.11
       
  1358 
       
  1359             if (m_bufferPercent != percent) {
       
  1360                 emit bufferStatus(percent);
       
  1361                 m_backend->logMessage(QString("Stream buffering %0").arg(percent), Backend::Debug, this);
       
  1362                 m_bufferPercent = percent;
       
  1363             }
       
  1364 
       
  1365             if (m_state != Phonon::BufferingState)
       
  1366                 emit stateChanged(m_state, Phonon::BufferingState);
       
  1367             else if (percent == 100)
       
  1368                 emit stateChanged(Phonon::BufferingState, m_state);
       
  1369             break;
       
  1370         }
       
  1371         //case GST_MESSAGE_INFO:
       
  1372         //case GST_MESSAGE_STREAM_STATUS:
       
  1373         //case GST_MESSAGE_CLOCK_PROVIDE:
       
  1374         //case GST_MESSAGE_NEW_CLOCK:
       
  1375         //case GST_MESSAGE_STEP_DONE:
       
  1376         //case GST_MESSAGE_LATENCY: only from 0.10.12
       
  1377         //case GST_MESSAGE_ASYNC_DONE: only from 0.10.13
       
  1378     default: 
       
  1379         break; 
       
  1380     }
       
  1381 }
       
  1382 
       
  1383 void MediaObject::handleEndOfStream()
       
  1384 {
       
  1385     // If the stream is not seekable ignore
       
  1386     // otherwise chained radio broadcasts would stop
       
  1387 
       
  1388 
       
  1389     if (m_atEndOfStream)
       
  1390         return;
       
  1391 
       
  1392     if (!m_seekable)
       
  1393         m_atEndOfStream = true;
       
  1394 
       
  1395     if (m_autoplayTitles &&
       
  1396         m_availableTitles > 1 &&
       
  1397         m_currentTitle < m_availableTitles) {
       
  1398         _iface_setCurrentTitle(m_currentTitle + 1);
       
  1399         return;
       
  1400     }
       
  1401 
       
  1402     if (m_nextSource.type() != MediaSource::Invalid
       
  1403         && m_nextSource.type() != MediaSource::Empty) {  // We only emit finish when the queue is actually empty
       
  1404         QTimer::singleShot (qMax(0, transitionTime()), this, SLOT(beginPlay()));
       
  1405     } else {
       
  1406         m_pendingState = Phonon::PausedState;
       
  1407         emit finished();
       
  1408         if (!m_seekable) {
       
  1409             setState(Phonon::StoppedState);
       
  1410             // Note the behavior for live streams is not properly defined
       
  1411             // But since we cant seek to 0, we don't have much choice other than stopping
       
  1412             // the stream
       
  1413         } else {
       
  1414             // Only emit paused if the finished signal
       
  1415             // did not result in a new state
       
  1416             if (m_pendingState == Phonon::PausedState)
       
  1417                 setState(m_pendingState);
       
  1418         }
       
  1419     }
       
  1420 }
       
  1421 
       
  1422 // Notifes the pipeline about state changes in the media object
       
  1423 void MediaObject::notifyStateChange(Phonon::State newstate, Phonon::State oldstate)
       
  1424 {
       
  1425     Q_UNUSED(oldstate);
       
  1426     MediaNodeEvent event(MediaNodeEvent::StateChanged, &newstate);
       
  1427     notify(&event);
       
  1428 }
       
  1429 
       
  1430 #ifndef QT_NO_PHONON_MEDIACONTROLLER
       
  1431 //interface management
       
  1432 bool MediaObject::hasInterface(Interface iface) const
       
  1433 {
       
  1434     return iface == AddonInterface::TitleInterface;
       
  1435 }
       
  1436 
       
  1437 QVariant MediaObject::interfaceCall(Interface iface, int command, const QList<QVariant> &params)
       
  1438 {
       
  1439     if (hasInterface(iface)) {
       
  1440 
       
  1441         switch (iface)
       
  1442         {
       
  1443         case TitleInterface:
       
  1444             switch (command)
       
  1445             {
       
  1446             case availableTitles:
       
  1447                 return _iface_availableTitles();
       
  1448             case title:
       
  1449                 return _iface_currentTitle();
       
  1450             case setTitle:
       
  1451                 _iface_setCurrentTitle(params.first().toInt());
       
  1452                 break;
       
  1453             case autoplayTitles:
       
  1454                 return m_autoplayTitles;
       
  1455             case setAutoplayTitles:
       
  1456                 m_autoplayTitles = params.first().toBool();
       
  1457                 break;
       
  1458             }
       
  1459             break;
       
  1460                 default:
       
  1461             break;
       
  1462         }
       
  1463     }
       
  1464     return QVariant();
       
  1465 }
       
  1466 #endif
       
  1467 
       
  1468 int MediaObject::_iface_availableTitles() const
       
  1469 {
       
  1470     return m_availableTitles;
       
  1471 }
       
  1472 
       
  1473 int MediaObject::_iface_currentTitle() const
       
  1474 {
       
  1475     return m_currentTitle;
       
  1476 }
       
  1477 
       
  1478 void MediaObject::_iface_setCurrentTitle(int title)
       
  1479 {
       
  1480     GstFormat trackFormat = gst_format_get_by_nick("track");
       
  1481     m_backend->logMessage(QString("setCurrentTitle %0").arg(title), Backend::Info, this);
       
  1482     if ((title == m_currentTitle) || (title < 1) || (title > m_availableTitles))
       
  1483         return;
       
  1484 
       
  1485     m_currentTitle = title;
       
  1486 
       
  1487     //let's seek to the beginning of the song
       
  1488     if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, m_currentTitle - 1)) {
       
  1489         updateTotalTime();
       
  1490         m_atEndOfStream = false;
       
  1491         emit titleChanged(title);
       
  1492         emit totalTimeChanged(totalTime());
       
  1493     }
       
  1494 }
       
  1495 
       
  1496 } // ns Gstreamer
       
  1497 } // ns Phonon
       
  1498 
       
  1499 QT_END_NAMESPACE
       
  1500 
       
  1501 #include "moc_mediaobject.cpp"