src/3rdparty/phonon/gstreamer/mediaobject.cpp
changeset 30 5dc02b23752f
parent 3 41300fa6a67c
child 37 758a864f9613
equal deleted inserted replaced
29:b72c6db6890b 30:5dc02b23752f
    14     You should have received a copy of the GNU Lesser General Public License
    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/>.
    15     along with this library.  If not, see <http://www.gnu.org/licenses/>.
    16 */
    16 */
    17 #include <cmath>
    17 #include <cmath>
    18 #include <gst/interfaces/propertyprobe.h>
    18 #include <gst/interfaces/propertyprobe.h>
       
    19 #include <gst/pbutils/install-plugins.h>
    19 #include "common.h"
    20 #include "common.h"
    20 #include "mediaobject.h"
    21 #include "mediaobject.h"
    21 #include "videowidget.h"
    22 #include "videowidget.h"
    22 #include "message.h"
    23 #include "message.h"
    23 #include "backend.h"
    24 #include "backend.h"
    51         , m_state(Phonon::LoadingState)
    52         , m_state(Phonon::LoadingState)
    52         , m_pendingState(Phonon::LoadingState)
    53         , m_pendingState(Phonon::LoadingState)
    53         , m_tickTimer(new QTimer(this))
    54         , m_tickTimer(new QTimer(this))
    54         , m_prefinishMark(0)
    55         , m_prefinishMark(0)
    55         , m_transitionTime(0)
    56         , m_transitionTime(0)
       
    57         , m_isStream(false)
    56         , m_posAtSeek(-1)
    58         , m_posAtSeek(-1)
    57         , m_prefinishMarkReachedNotEmitted(true)
    59         , m_prefinishMarkReachedNotEmitted(true)
    58         , m_aboutToFinishEmitted(false)
    60         , m_aboutToFinishEmitted(false)
    59         , m_loading(false)
    61         , m_loading(false)
    60         , m_capsHandler(0)
    62         , m_capsHandler(0)
    77         , m_previousTickTime(-1)
    79         , m_previousTickTime(-1)
    78         , m_resetNeeded(false)
    80         , m_resetNeeded(false)
    79         , m_autoplayTitles(true)
    81         , m_autoplayTitles(true)
    80         , m_availableTitles(0)
    82         , m_availableTitles(0)
    81         , m_currentTitle(1)
    83         , m_currentTitle(1)
       
    84         , m_pendingTitle(1)
    82 {
    85 {
    83     qRegisterMetaType<GstCaps*>("GstCaps*");
    86     qRegisterMetaType<GstCaps*>("GstCaps*");
    84     qRegisterMetaType<State>("State");
    87     qRegisterMetaType<State>("State");
    85 
    88 
    86     static int count = 0;
    89     static int count = 0;
    93         m_root = this;
    96         m_root = this;
    94         createPipeline();
    97         createPipeline();
    95         m_backend->addBusWatcher(this);
    98         m_backend->addBusWatcher(this);
    96         connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick()));
    99         connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick()));
    97     }
   100     }
    98     connect(this, SIGNAL(stateChanged(Phonon::State,Phonon::State)), 
   101     connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
    99             this, SLOT(notifyStateChange(Phonon::State,Phonon::State)));
   102             this, SLOT(notifyStateChange(Phonon::State, Phonon::State)));
   100 
   103 
   101 }
   104 }
   102 
   105 
   103 MediaObject::~MediaObject()
   106 MediaObject::~MediaObject()
   104 {
   107 {
   134         return QString("ErrorState");
   137         return QString("ErrorState");
   135     }
   138     }
   136     return QString();
   139     return QString();
   137 }
   140 }
   138 
   141 
       
   142 void
       
   143 pluginInstallationDone( GstInstallPluginsReturn res, gpointer userData )
       
   144 {
       
   145     // Nothing inside yet
       
   146     Q_UNUSED(res);
       
   147     Q_UNUSED(userData);
       
   148 }
       
   149 
   139 void MediaObject::saveState()
   150 void MediaObject::saveState()
   140 {
   151 {
   141     //Only first resumeState is respected
   152     //Only first resumeState is respected
   142     if (m_resumeState)
   153     if (m_resumeState)
   143         return;
   154         return;
   193 void MediaObject::noMorePadsAvailable ()
   204 void MediaObject::noMorePadsAvailable ()
   194 {
   205 {
   195     if (m_missingCodecs.size() > 0) {
   206     if (m_missingCodecs.size() > 0) {
   196         bool canPlay = (m_hasAudio || m_videoStreamFound);
   207         bool canPlay = (m_hasAudio || m_videoStreamFound);
   197         Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
   208         Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
       
   209 #ifdef PLUGIN_INSTALL_API
       
   210         GstInstallPluginsContext *ctx = gst_install_plugins_context_new ();
       
   211         gchar *details[2];
       
   212         details[0] = m_missingCodecs[0].toLocal8Bit().data();
       
   213         details[1] = NULL;
       
   214         GstInstallPluginsReturn status;
       
   215 
       
   216         status = gst_install_plugins_async( details, ctx, pluginInstallationDone, NULL );
       
   217         gst_install_plugins_context_free ( ctx );
       
   218 
       
   219         if ( status != GST_INSTALL_PLUGINS_STARTED_OK )
       
   220         {
       
   221             if( status == GST_INSTALL_PLUGINS_HELPER_MISSING )
       
   222                 setError(QString(tr("Missing codec helper script assistant.")), Phonon::FatalError );
       
   223             else
       
   224                 setError(QString(tr("Plugin codec installation failed for codec: %0"))
       
   225                         .arg(m_missingCodecs[0].split("|")[3]), error);
       
   226         }
       
   227         m_missingCodecs.clear();
       
   228 #else
       
   229         QString codecs = m_missingCodecs.join(", ");
       
   230 
   198         if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) {
   231         if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) {
   199             m_hasVideo = false;
   232             m_hasVideo = false;
   200             emit hasVideoChanged(false);
   233             emit hasVideoChanged(false);
   201         }
   234         }
   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);
   235         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();
   236         m_missingCodecs.clear();
       
   237 #endif
   205     }
   238     }
   206 }
   239 }
   207 
   240 
   208 void MediaObject::cb_no_more_pads (GstElement * decodebin, gpointer data)
   241 void MediaObject::cb_no_more_pads (GstElement * decodebin, gpointer data)
   209 {
   242 {
   246         // For GStreamer versions < 0.10.12
   279         // For GStreamer versions < 0.10.12
   247         GstStructure *str = gst_caps_get_structure (caps, 0);
   280         GstStructure *str = gst_caps_get_structure (caps, 0);
   248         value = QString::fromUtf8(gst_structure_get_name (str));
   281         value = QString::fromUtf8(gst_structure_get_name (str));
   249 
   282 
   250     }
   283     }
   251     media->addMissingCodecName(value);
   284 
       
   285 #ifdef PLUGIN_INSTALL_API
       
   286     QString plugins = QString("gstreamer|0.10|%0|%1|decoder-%2")
       
   287         .arg( qApp->applicationName() )
       
   288         .arg( value )
       
   289         .arg( QString::fromUtf8(gst_caps_to_string (caps) ) );
       
   290     media->addMissingCodecName( plugins );
       
   291 #else
       
   292     media->addMissingCodecName( value );
       
   293 #endif
   252 }
   294 }
   253 
   295 
   254 static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data)
   296 static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data)
   255 {
   297 {
   256     GstPad *pad = GST_PAD(obj);
   298     GstPad *pad = GST_PAD(obj);
   307             gst_element_set_state(m_videoGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
   349             gst_element_set_state(m_videoGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
   308             m_videoStreamFound = true;
   350             m_videoStreamFound = true;
   309             m_backend->logMessage("Video track connected", Backend::Info, this);
   351             m_backend->logMessage("Video track connected", Backend::Info, this);
   310             // Note that the notify::caps _must_ be installed after linking to work with Dapper
   352             // Note that the notify::caps _must_ be installed after linking to work with Dapper
   311             m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this);
   353             m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this);
   312  
   354 
   313             if (!m_loading && !m_hasVideo) {
   355             if (!m_loading && !m_hasVideo) {
   314                 m_hasVideo = m_videoStreamFound;
   356                 m_hasVideo = m_videoStreamFound;
   315                 emit hasVideoChanged(m_hasVideo);
   357                 emit hasVideoChanged(m_hasVideo);
   316             }
   358             }
   317         }
   359         }
   366         m_backend->logMessage(QString("%1 is not a valid URI").arg(url.toString()));
   408         m_backend->logMessage(QString("%1 is not a valid URI").arg(url.toString()));
   367         return false;
   409         return false;
   368     }
   410     }
   369 
   411 
   370     // Create a new datasource based on the input URL
   412     // Create a new datasource based on the input URL
   371     QByteArray encoded_cstr_url = url.toEncoded();
   413     // add the 'file' scheme if it's missing; the double '/' is needed!
       
   414     QByteArray encoded_cstr_url = (url.scheme() == QLatin1String("") ?
       
   415                     "file://" + url.toEncoded() :
       
   416                     url.toEncoded());
   372     m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL);
   417     m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL);
   373     if (!m_datasource)
   418     if (!m_datasource)
   374         return false;
   419         return false;
   375 
   420 
   376     // Set the device for MediaSource::Disc
   421     // Set the device for MediaSource::Disc
   386         if (m_source.discType() == Phonon::Cd
   431         if (m_source.discType() == Phonon::Cd
   387             && (g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "read-speed"))) {
   432             && (g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "read-speed"))) {
   388             g_object_set (G_OBJECT (m_datasource), "read-speed", 2, (const char*)NULL);
   433             g_object_set (G_OBJECT (m_datasource), "read-speed", 2, (const char*)NULL);
   389             m_backend->logMessage(QString("new device speed : 2X"), Backend::Info, this);
   434             m_backend->logMessage(QString("new device speed : 2X"), Backend::Info, this);
   390         }
   435         }
       
   436   }
       
   437 
       
   438     /* make HTTP sources send extra headers so we get icecast
       
   439      * metadata in case the stream is an icecast stream */
       
   440     if (encoded_cstr_url.startsWith("http://")
       
   441         && g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "iradio-mode")) {
       
   442         g_object_set (m_datasource, "iradio-mode", TRUE, NULL);
       
   443         m_isStream = true;
   391     }
   444     }
   392 
   445 
   393     // Link data source into pipeline
   446     // Link data source into pipeline
   394     gst_bin_add(GST_BIN(m_pipeline), m_datasource);
   447     gst_bin_add(GST_BIN(m_pipeline), m_datasource);
   395     if (!gst_element_link(m_datasource, m_decodebin)) {
   448     if (!gst_element_link(m_datasource, m_decodebin)) {
   440 {
   493 {
   441     m_pipeline = gst_pipeline_new (NULL);
   494     m_pipeline = gst_pipeline_new (NULL);
   442     gst_object_ref (GST_OBJECT (m_pipeline));
   495     gst_object_ref (GST_OBJECT (m_pipeline));
   443     gst_object_sink (GST_OBJECT (m_pipeline));
   496     gst_object_sink (GST_OBJECT (m_pipeline));
   444 
   497 
   445     m_decodebin = gst_element_factory_make ("decodebin", NULL);
   498     m_decodebin = gst_element_factory_make ("decodebin2", NULL);
   446     g_signal_connect (m_decodebin, "new-decoded-pad", G_CALLBACK (&cb_newpad), this);
   499     g_signal_connect (m_decodebin, "new-decoded-pad", G_CALLBACK (&cb_newpad), this);
   447     g_signal_connect (m_decodebin, "unknown-type", G_CALLBACK (&cb_unknown_type), this);
   500     g_signal_connect (m_decodebin, "unknown-type", G_CALLBACK (&cb_unknown_type), this);
   448     g_signal_connect (m_decodebin, "no-more-pads", G_CALLBACK (&cb_no_more_pads), this);
   501     g_signal_connect (m_decodebin, "no-more-pads", G_CALLBACK (&cb_no_more_pads), this);
   449 
   502 
   450     gst_bin_add(GST_BIN(m_pipeline), m_decodebin);
   503     gst_bin_add(GST_BIN(m_pipeline), m_decodebin);
   644         m_backend->logMessage("phonon state request: Playing", Backend::Info, this);
   697         m_backend->logMessage("phonon state request: Playing", Backend::Info, this);
   645         if (m_atEndOfStream) {
   698         if (m_atEndOfStream) {
   646             m_backend->logMessage("EOS already reached", Backend::Info, this);
   699             m_backend->logMessage("EOS already reached", Backend::Info, this);
   647         } else if (currentState == GST_STATE_PLAYING) {
   700         } else if (currentState == GST_STATE_PLAYING) {
   648             changeState(Phonon::PlayingState);
   701             changeState(Phonon::PlayingState);
   649         } else if (!m_atEndOfStream && gst_element_set_state(m_pipeline, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE) {
   702         } else if (gst_element_set_state(m_pipeline, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE) {
   650             m_pendingState = Phonon::PlayingState;
   703             m_pendingState = Phonon::PlayingState;
   651         } else {
   704         } else {
   652             m_backend->logMessage("phonon state request failed", Backend::Info, this);
   705             m_backend->logMessage("phonon state request failed", Backend::Info, this);
   653         }
   706         }
   654         break;
   707         break;
   674 {
   727 {
   675     if (newstate == m_state)
   728     if (newstate == m_state)
   676         return;
   729         return;
   677 
   730 
   678     Phonon::State oldState = m_state;
   731     Phonon::State oldState = m_state;
   679     m_state = newstate; // m_state must be set before emitting, since 
   732     m_state = newstate; // m_state must be set before emitting, since
   680                         // Error state requires that state() will return the new value
   733                         // Error state requires that state() will return the new value
   681     m_pendingState = newstate;
   734     m_pendingState = newstate;
   682     emit stateChanged(newstate, oldState);
   735     emit stateChanged(newstate, oldState);
   683 
   736 
   684     switch (newstate) {
   737     switch (newstate) {
   694         m_backend->logMessage("phonon state changed: Playing", Backend::Info, this);
   747         m_backend->logMessage("phonon state changed: Playing", Backend::Info, this);
   695         break;
   748         break;
   696 
   749 
   697     case Phonon::StoppedState:
   750     case Phonon::StoppedState:
   698         m_backend->logMessage("phonon state changed: Stopped", Backend::Info, this);
   751         m_backend->logMessage("phonon state changed: Stopped", Backend::Info, this);
       
   752         // We must reset the pipeline when playing again
       
   753         m_resetNeeded = true;
   699         m_tickTimer->stop();
   754         m_tickTimer->stop();
   700         break;
   755         break;
   701 
   756 
   702     case Phonon::ErrorState:
   757     case Phonon::ErrorState:
   703         m_loading = false;
   758         m_loading = false;
   859     // We have to reset the state completely here, otherwise
   914     // We have to reset the state completely here, otherwise
   860     // remnants of the old pipeline can result in strangenes
   915     // remnants of the old pipeline can result in strangenes
   861     // such as failing duration queries etc
   916     // such as failing duration queries etc
   862     GstState state;
   917     GstState state;
   863     gst_element_set_state(m_pipeline, GST_STATE_NULL);
   918     gst_element_set_state(m_pipeline, GST_STATE_NULL);
   864     gst_element_get_state (m_pipeline, &state, NULL, 2000);
   919     gst_element_get_state(m_pipeline, &state, NULL, 2000);
   865 
   920 
   866     m_source = source;
   921     m_source = source;
   867     emit currentSourceChanged(m_source);
   922     emit currentSourceChanged(m_source);
   868     m_previousTickTime = -1;
   923     m_previousTickTime = -1;
   869     m_missingCodecs.clear();
   924     m_missingCodecs.clear();
   870 
   925 
   871     // Go into to loading state
   926     // Go into to loading state
   872     changeState(Phonon::LoadingState);
   927     changeState(Phonon::LoadingState);
   873     m_loading = true;
   928     m_loading = true;
   874     m_resetNeeded = false;
   929     // IMPORTANT: Honor the m_resetNeeded flag as it currently stands.
       
   930     // See https://qa.mandriva.com/show_bug.cgi?id=56807
       
   931     //m_resetNeeded = false;
   875     m_resumeState = false;
   932     m_resumeState = false;
   876     m_pendingState = Phonon::StoppedState;
   933     m_pendingState = Phonon::StoppedState;
   877 
   934 
   878      // Make sure we start out unconnected
   935      // Make sure we start out unconnected
   879     if (GST_ELEMENT_PARENT(m_audioGraph))
   936     if (GST_ELEMENT_PARENT(m_audioGraph))
   882         gst_bin_remove(GST_BIN(m_pipeline), m_videoGraph);
   939         gst_bin_remove(GST_BIN(m_pipeline), m_videoGraph);
   883 
   940 
   884     // Clear any existing errors
   941     // Clear any existing errors
   885     m_aboutToFinishEmitted = false;
   942     m_aboutToFinishEmitted = false;
   886     m_error = NoError;
   943     m_error = NoError;
   887     m_errorString = QString();
   944     m_errorString.clear();
   888     
   945 
   889     m_bufferPercent = 0;
   946     m_bufferPercent = 0;
   890     m_prefinishMarkReachedNotEmitted = true;
   947     m_prefinishMarkReachedNotEmitted = true;
   891     m_aboutToFinishEmitted = false;
   948     m_aboutToFinishEmitted = false;
   892     m_hasAudio = false;
   949     m_hasAudio = false;
   893     m_videoStreamFound = false;
   950     m_videoStreamFound = false;
   894     setTotalTime(-1);
   951     setTotalTime(-1);
   895     m_atEndOfStream = false;
   952     m_atEndOfStream = false;
   896 
   953 
   897     // Clear exising meta tags
   954     m_availableTitles = 0;
       
   955     m_pendingTitle = 1;
       
   956     m_currentTitle = 1;
       
   957 
       
   958     // Clear existing meta tags
   898     m_metaData.clear();
   959     m_metaData.clear();
       
   960     m_isStream = false;
   899 
   961 
   900     switch (source.type()) {
   962     switch (source.type()) {
   901     case MediaSource::Url: {            
   963     case MediaSource::Url: {
   902             if (createPipefromURL(source.url()))
   964             if (!createPipefromURL(source.url()))
   903                 m_loading = true;
       
   904             else
       
   905                 setError(tr("Could not open media source."));
   965                 setError(tr("Could not open media source."));
   906         }
   966         }
   907         break;
   967         break;
   908 
   968 
   909     case MediaSource::LocalFile: {
   969     case MediaSource::LocalFile: {
   910             if (createPipefromURL(QUrl::fromLocalFile(source.fileName())))
   970             if (!createPipefromURL(QUrl::fromLocalFile(source.fileName())))
   911                 m_loading = true;
       
   912             else
       
   913                 setError(tr("Could not open media source."));
   971                 setError(tr("Could not open media source."));
   914         }
   972         }
   915         break;
   973         break;
   916 
   974 
   917     case MediaSource::Invalid:
   975     case MediaSource::Invalid:
   920 
   978 
   921     case MediaSource::Empty:
   979     case MediaSource::Empty:
   922         break;
   980         break;
   923 
   981 
   924     case MediaSource::Stream:
   982     case MediaSource::Stream:
   925         if (createPipefromStream(source))
   983         if (!createPipefromStream(source))
   926             m_loading = true;
       
   927         else
       
   928             setError(tr("Could not open media source."));
   984             setError(tr("Could not open media source."));
   929         break;
   985         break;
   930 
   986 
   931     case MediaSource::Disc:
   987     case MediaSource::Disc:
   932         {
   988         {
   933             QString mediaUrl;
   989        QString mediaUrl;
   934             switch (source.discType()) {
   990        switch (source.discType()) {
   935             case Phonon::NoDisc:
   991        case Phonon::NoDisc:
   936                 qWarning() << "I should never get to see a MediaSource that is a disc but doesn't specify which one";
   992                 qWarning() << "I should never get to see a MediaSource that is a disc but doesn't specify which one";
   937                 return;
   993                 return;
   938             case Phonon::Cd:  // CD tracks can be specified by setting the url in the following way uri=cdda:4
   994             case Phonon::Cd:  // CD tracks can be specified by setting the url in the following way uri=cdda:4
   939                 mediaUrl = QLatin1String("cdda://");
   995                 mediaUrl = QLatin1String("cdda://");
   940                 break;
   996                 break;
   946                 break;
  1002                 break;
   947             default:
  1003             default:
   948                 qWarning() <<  "media " << source.discType() << " not implemented";
  1004                 qWarning() <<  "media " << source.discType() << " not implemented";
   949                 return;
  1005                 return;
   950             }
  1006             }
   951             if (!mediaUrl.isEmpty() && createPipefromURL(QUrl(mediaUrl)))
  1007             if (mediaUrl.isEmpty() || !createPipefromURL(QUrl(mediaUrl)))
   952                 m_loading = true;
       
   953             else
       
   954                 setError(tr("Could not open media source."));
  1008                 setError(tr("Could not open media source."));
   955         }
  1009         }
   956         break;
  1010         break;
   957 
  1011 
   958     default:
  1012     default:
   964     MediaNodeEvent event(MediaNodeEvent::SourceChanged);
  1018     MediaNodeEvent event(MediaNodeEvent::SourceChanged);
   965     notify(&event);
  1019     notify(&event);
   966 
  1020 
   967     // We need to link this node to ensure that fake sinks are connected
  1021     // We need to link this node to ensure that fake sinks are connected
   968     // before loading, otherwise the stream will be blocked
  1022     // before loading, otherwise the stream will be blocked
   969     if (m_loading)
  1023     link();
   970         link();
       
   971     beginLoad();
  1024     beginLoad();
   972 }
  1025 }
   973 
  1026 
   974 void MediaObject::beginLoad()
  1027 void MediaObject::beginLoad()
   975 {
  1028 {
  1002     if (m_videoStreamFound != m_hasVideo) {
  1055     if (m_videoStreamFound != m_hasVideo) {
  1003         m_hasVideo = m_videoStreamFound;
  1056         m_hasVideo = m_videoStreamFound;
  1004         emit hasVideoChanged(m_hasVideo);
  1057         emit hasVideoChanged(m_hasVideo);
  1005     }
  1058     }
  1006 
  1059 
  1007     m_availableTitles = 1;
  1060     if (m_source.discType() == Phonon::Cd) {
  1008     gint64 titleCount;
  1061         gint64 titleCount;
  1009     GstFormat format = gst_format_get_by_nick("track");
  1062         GstFormat format = gst_format_get_by_nick("track");
  1010     if (gst_element_query_duration (m_pipeline, &format, &titleCount)) {
  1063         if (gst_element_query_duration (m_pipeline, &format, &titleCount)) {
  1011         //check if returned format is still "track",
  1064         //check if returned format is still "track",
  1012         //gstreamer sometimes returns the total time, if tracks information is not available.
  1065         //gstreamer sometimes returns the total time, if tracks information is not available.
  1013         if (qstrcmp(gst_format_get_name(format), "track") == 0)  {
  1066             if (qstrcmp(gst_format_get_name(format), "track") == 0)  {
  1014             int oldAvailableTitles = m_availableTitles;
  1067                 int oldAvailableTitles = m_availableTitles;
  1015             m_availableTitles = (int)titleCount;
  1068                 m_availableTitles = (int)titleCount;
  1016             if (m_availableTitles != oldAvailableTitles) {
  1069                 if (m_availableTitles != oldAvailableTitles) {
  1017                 emit availableTitlesChanged(m_availableTitles);
  1070                     emit availableTitlesChanged(m_availableTitles);
  1018                 m_backend->logMessage(QString("Available titles changed: %0").arg(m_availableTitles), Backend::Info, this);
  1071                     m_backend->logMessage(QString("Available titles changed: %0").arg(m_availableTitles), Backend::Info, this);
       
  1072                 }
  1019             }
  1073             }
  1020         }
  1074         }
  1021     }
  1075     }
  1022 
       
  1023 }
  1076 }
  1024 
  1077 
  1025 void MediaObject::setPrefinishMark(qint32 newPrefinishMark)
  1078 void MediaObject::setPrefinishMark(qint32 newPrefinishMark)
  1026 {
  1079 {
  1027     m_prefinishMark = newPrefinishMark;
  1080     m_prefinishMark = newPrefinishMark;
  1075         case Phonon::ErrorState:
  1128         case Phonon::ErrorState:
  1076             return;
  1129             return;
  1077         }
  1130         }
  1078 
  1131 
  1079         quint64 current = currentTime();
  1132         quint64 current = currentTime();
  1080         quint64 total = totalTime(); 
  1133         quint64 total = totalTime();
  1081 
  1134 
  1082         if (current < total - m_prefinishMark)
  1135         if (current < total - m_prefinishMark)
  1083             m_prefinishMarkReachedNotEmitted = true;
  1136             m_prefinishMarkReachedNotEmitted = true;
  1084         if (current < total - ABOUT_TO_FINNISH_TIME)
  1137         if (current < total - ABOUT_TO_FINNISH_TIME)
  1085             m_aboutToFinishEmitted = false;
  1138             m_aboutToFinishEmitted = false;
  1096     qint64 currentTime = getPipelinePos();
  1149     qint64 currentTime = getPipelinePos();
  1097     qint64 totalTime = m_totalTime;
  1150     qint64 totalTime = m_totalTime;
  1098 
  1151 
  1099     if (m_tickInterval > 0 && currentTime != m_previousTickTime) {
  1152     if (m_tickInterval > 0 && currentTime != m_previousTickTime) {
  1100         emit tick(currentTime);
  1153         emit tick(currentTime);
  1101         m_previousTickTime = currentTime;        
  1154         m_previousTickTime = currentTime;
  1102     }
  1155     }
  1103     if (m_state == Phonon::PlayingState) {
  1156     if (m_state == Phonon::PlayingState) {
  1104         if (currentTime >= totalTime - m_prefinishMark) {
  1157         if (currentTime >= totalTime - m_prefinishMark) {
  1105             if (m_prefinishMarkReachedNotEmitted) {
  1158             if (m_prefinishMarkReachedNotEmitted) {
  1106                 m_prefinishMarkReachedNotEmitted = false;
  1159                 m_prefinishMarkReachedNotEmitted = false;
  1107                 emit prefinishMarkReached(totalTime - currentTime);
  1160                 emit prefinishMarkReached(totalTime - currentTime);
  1108             }
  1161             }
  1109         }
  1162         }
  1110         // Prepare load of next source
  1163         // Prepare load of next source
  1111         if (currentTime >= totalTime - ABOUT_TO_FINNISH_TIME) {
  1164         if (currentTime >= totalTime - ABOUT_TO_FINNISH_TIME) {
  1112             if (!m_aboutToFinishEmitted) {
  1165             if (m_source.type() == MediaSource::Disc &&
       
  1166                 m_autoplayTitles &&
       
  1167                 m_availableTitles > 1 &&
       
  1168                 m_currentTitle < m_availableTitles) {
       
  1169                 m_aboutToFinishEmitted = false;
       
  1170             } else if (!m_aboutToFinishEmitted) {
  1113                 m_aboutToFinishEmitted = true; // track is about to finish
  1171                 m_aboutToFinishEmitted = true; // track is about to finish
  1114                 emit aboutToFinish();
  1172                 emit aboutToFinish();
  1115             }
  1173             }
  1116         }
  1174         }
  1117     }
  1175     }
  1211         m_backend->logMessage(msgString, Backend::Debug, this);
  1269         m_backend->logMessage(msgString, Backend::Debug, this);
  1212     }
  1270     }
  1213 
  1271 
  1214     switch (GST_MESSAGE_TYPE (gstMessage)) {
  1272     switch (GST_MESSAGE_TYPE (gstMessage)) {
  1215 
  1273 
  1216     case GST_MESSAGE_EOS: 
  1274     case GST_MESSAGE_EOS:
  1217         m_backend->logMessage("EOS recieved", Backend::Info, this);
  1275         m_backend->logMessage("EOS received", Backend::Info, this);
  1218         handleEndOfStream();
  1276         handleEndOfStream();
  1219         break;
  1277         break;
  1220 
  1278 
  1221     case GST_MESSAGE_TAG: {
  1279     case GST_MESSAGE_TAG: {
  1222             GstTagList* tag_list = 0;
  1280             GstTagList* tag_list = 0;
  1223             gst_message_parse_tag(gstMessage, &tag_list);
  1281             gst_message_parse_tag(gstMessage, &tag_list);
  1224             if (tag_list) {
  1282             if (tag_list) {
       
  1283                 TagMap newTags;
       
  1284                 gst_tag_list_foreach (tag_list, &foreach_tag_function, &newTags);
       
  1285                 gst_tag_list_free(tag_list);
       
  1286 
       
  1287                 // Determine if we should no fake the album/artist tags.
       
  1288                 // This is a little confusing as we want to fake it on initial
       
  1289                 // connection where title, album and artist are all missing.
       
  1290                 // There are however times when we get just other information,
       
  1291                 // e.g. codec, and so we want to only do clever stuff if we
       
  1292                 // have a commonly available tag (ORGANIZATION) or we have a
       
  1293                 // change in title
       
  1294                 bool fake_it =
       
  1295                    (m_isStream
       
  1296                     && ((!newTags.contains("TITLE")
       
  1297                          && newTags.contains("ORGANIZATION"))
       
  1298                         || (newTags.contains("TITLE")
       
  1299                             && m_metaData.value("TITLE") != newTags.value("TITLE")))
       
  1300                     && !newTags.contains("ALBUM")
       
  1301                     && !newTags.contains("ARTIST"));
       
  1302 
  1225                 TagMap oldMap = m_metaData; // Keep a copy of the old one for reference
  1303                 TagMap oldMap = m_metaData; // Keep a copy of the old one for reference
  1226                 // Append any new meta tags to the existing tag list
  1304 
  1227                 gst_tag_list_foreach (tag_list, &foreach_tag_function, &m_metaData);
  1305                 // Now we've checked the new data, append any new meta tags to the existing tag list
       
  1306                 // We cannot use TagMap::iterator as this is a multimap and when streaming data
       
  1307                 // could in theory be lost.
       
  1308                 QList<QString> keys = newTags.keys();
       
  1309                 for (QList<QString>::iterator i = keys.begin(); i != keys.end(); ++i) {
       
  1310                     QString key = *i;
       
  1311                     if (m_isStream) {
       
  1312                         // If we're streaming, we need to remove data in m_metaData
       
  1313                         // in order to stop it filling up indefinitely (as it's a multimap)
       
  1314                         m_metaData.remove(key);
       
  1315                     }
       
  1316                     QList<QString> values = newTags.values(key);
       
  1317                     for (QList<QString>::iterator j = values.begin(); j != values.end(); ++j) {
       
  1318                         QString value = *j;
       
  1319                         QString currVal = m_metaData.value(key);
       
  1320                         if (!m_metaData.contains(key) || currVal != value) {
       
  1321                             m_metaData.insert(key, value);
       
  1322                         }
       
  1323                     }
       
  1324                 }
       
  1325 
  1228                 m_backend->logMessage("Meta tags found", Backend::Info, this);
  1326                 m_backend->logMessage("Meta tags found", Backend::Info, this);
  1229                 if (oldMap != m_metaData && !m_loading)
  1327                 if (oldMap != m_metaData) {
  1230                     emit metaDataChanged(m_metaData);
  1328                     // This is a bit of a hack to ensure that stream metadata is
  1231                 gst_tag_list_free(tag_list);
  1329                     // returned. We get as much as we can from the Shoutcast server's
  1232             }
  1330                     // StreamTitle= header. If further info is decoded from the stream
       
  1331                     // itself later, then it will overwrite this info.
       
  1332                     if (m_isStream && fake_it) {
       
  1333                         m_metaData.remove("ALBUM");
       
  1334                         m_metaData.remove("ARTIST");
       
  1335 
       
  1336                         // Detect whether we want to "fill in the blanks"
       
  1337                         QString str;
       
  1338                         if (m_metaData.contains("TITLE"))
       
  1339                         {
       
  1340                             str = m_metaData.value("TITLE");
       
  1341                             int splitpoint;
       
  1342                             // Check to see if our title matches "%s - %s"
       
  1343                             // Where neither %s are empty...
       
  1344                             if ((splitpoint = str.indexOf(" - ")) > 0
       
  1345                                 && str.size() > (splitpoint+3)) {
       
  1346                                 m_metaData.insert("ARTIST", str.left(splitpoint));
       
  1347                                 m_metaData.replace("TITLE", str.mid(splitpoint+3));
       
  1348                             }
       
  1349                         } else {
       
  1350                             str = m_metaData.value("GENRE");
       
  1351                             if (!str.isEmpty())
       
  1352                                 m_metaData.insert("TITLE", str);
       
  1353                             else
       
  1354                                 m_metaData.insert("TITLE", "Streaming Data");
       
  1355                         }
       
  1356                         if (!m_metaData.contains("ARTIST")) {
       
  1357                             str = m_metaData.value("LOCATION");
       
  1358                             if (!str.isEmpty())
       
  1359                                 m_metaData.insert("ARTIST", str);
       
  1360                             else
       
  1361                                 m_metaData.insert("ARTIST", "Streaming Data");
       
  1362                         }
       
  1363                         str = m_metaData.value("ORGANIZATION");
       
  1364                         if (!str.isEmpty())
       
  1365                             m_metaData.insert("ALBUM", str);
       
  1366                         else
       
  1367                             m_metaData.insert("ALBUM", "Streaming Data");
       
  1368                     }
       
  1369                     // As we manipulate the title, we need to recompare
       
  1370                     // oldMap and m_metaData here...
       
  1371                     if (oldMap != m_metaData && !m_loading)
       
  1372                         emit metaDataChanged(m_metaData);
       
  1373                 }
       
  1374 			}
  1233         }
  1375         }
  1234         break;
  1376         break;
  1235 
  1377 
  1236     case GST_MESSAGE_STATE_CHANGED : {
  1378     case GST_MESSAGE_STATE_CHANGED : {
  1237 
  1379 
  1253             case GST_STATE_PLAYING :
  1395             case GST_STATE_PLAYING :
  1254                 m_atStartOfStream = false;
  1396                 m_atStartOfStream = false;
  1255                 m_backend->logMessage("gstreamer: pipeline state set to playing", Backend::Info, this);
  1397                 m_backend->logMessage("gstreamer: pipeline state set to playing", Backend::Info, this);
  1256                 m_tickTimer->start();
  1398                 m_tickTimer->start();
  1257                 changeState(Phonon::PlayingState);
  1399                 changeState(Phonon::PlayingState);
       
  1400                 if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) {
       
  1401                     setTrack(m_pendingTitle);
       
  1402                 }
  1258                 if (m_resumeState && m_oldState == Phonon::PlayingState) {
  1403                 if (m_resumeState && m_oldState == Phonon::PlayingState) {
  1259                     seek(m_oldPos);
  1404                     seek(m_oldPos);
  1260                     m_resumeState = false;
  1405                     m_resumeState = false;
  1261                 }
  1406                 }
  1262                 break;
  1407                 break;
  1288             case GST_STATE_READY :
  1433             case GST_STATE_READY :
  1289                 if (!m_loading && m_pendingState == Phonon::StoppedState)
  1434                 if (!m_loading && m_pendingState == Phonon::StoppedState)
  1290                     changeState(Phonon::StoppedState);
  1435                     changeState(Phonon::StoppedState);
  1291                 m_backend->logMessage("gstreamer: pipeline state set to ready", Backend::Debug, this);
  1436                 m_backend->logMessage("gstreamer: pipeline state set to ready", Backend::Debug, this);
  1292                 m_tickTimer->stop();
  1437                 m_tickTimer->stop();
       
  1438                 if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) {
       
  1439                     setTrack(m_pendingTitle);
       
  1440                 }
  1293                 break;
  1441                 break;
  1294 
  1442 
  1295             case GST_STATE_VOID_PENDING :
  1443             case GST_STATE_VOID_PENDING :
  1296                 m_backend->logMessage("gstreamer: pipeline state set to pending (void)", Backend::Debug, this);
  1444                 m_backend->logMessage("gstreamer: pipeline state set to pending (void)", Backend::Debug, this);
  1297                 m_tickTimer->stop();
  1445                 m_tickTimer->stop();
  1326                             setError(tr("Could not open audio device. The device is already in use."), Phonon::NormalError);
  1474                             setError(tr("Could not open audio device. The device is already in use."), Phonon::NormalError);
  1327                         else
  1475                         else
  1328                             setError(err->message, Phonon::FatalError);
  1476                             setError(err->message, Phonon::FatalError);
  1329                         gst_caps_unref (caps);
  1477                         gst_caps_unref (caps);
  1330                         gst_object_unref (sinkPad);
  1478                         gst_object_unref (sinkPad);
  1331                    } 
  1479                    }
  1332                } else {
  1480                } else {
  1333                     setError(QString(err->message), Phonon::FatalError);
  1481                     setError(QString(err->message), Phonon::FatalError);
  1334                }
  1482                }
  1335            } else if (err->domain == GST_STREAM_ERROR) {
  1483            } else if (err->domain == GST_STREAM_ERROR) {
  1336                 switch (err->code) {
  1484                 switch (err->code) {
  1398         //case GST_MESSAGE_CLOCK_PROVIDE:
  1546         //case GST_MESSAGE_CLOCK_PROVIDE:
  1399         //case GST_MESSAGE_NEW_CLOCK:
  1547         //case GST_MESSAGE_NEW_CLOCK:
  1400         //case GST_MESSAGE_STEP_DONE:
  1548         //case GST_MESSAGE_STEP_DONE:
  1401         //case GST_MESSAGE_LATENCY: only from 0.10.12
  1549         //case GST_MESSAGE_LATENCY: only from 0.10.12
  1402         //case GST_MESSAGE_ASYNC_DONE: only from 0.10.13
  1550         //case GST_MESSAGE_ASYNC_DONE: only from 0.10.13
  1403     default: 
  1551     default:
  1404         break; 
  1552         break;
  1405     }
  1553     }
  1406 }
  1554 }
  1407 
  1555 
  1408 void MediaObject::handleEndOfStream()
  1556 void MediaObject::handleEndOfStream()
  1409 {
  1557 {
  1415         return;
  1563         return;
  1416 
  1564 
  1417     if (!m_seekable)
  1565     if (!m_seekable)
  1418         m_atEndOfStream = true;
  1566         m_atEndOfStream = true;
  1419 
  1567 
  1420     if (m_autoplayTitles &&
  1568     if (m_source.type() == MediaSource::Disc &&
       
  1569         m_autoplayTitles &&
  1421         m_availableTitles > 1 &&
  1570         m_availableTitles > 1 &&
  1422         m_currentTitle < m_availableTitles) {
  1571         m_currentTitle < m_availableTitles) {
  1423         _iface_setCurrentTitle(m_currentTitle + 1);
  1572         _iface_setCurrentTitle(m_currentTitle + 1);
  1424         return;
  1573         return;
  1425     }
  1574     }
  1439             // Only emit paused if the finished signal
  1588             // Only emit paused if the finished signal
  1440             // did not result in a new state
  1589             // did not result in a new state
  1441             if (m_pendingState == Phonon::PausedState)
  1590             if (m_pendingState == Phonon::PausedState)
  1442                 setState(m_pendingState);
  1591                 setState(m_pendingState);
  1443         }
  1592         }
       
  1593     }
       
  1594 }
       
  1595 
       
  1596 void MediaObject::invalidateGraph()
       
  1597 {
       
  1598     m_resetNeeded = true;
       
  1599     if (m_state == Phonon::PlayingState || m_state == Phonon::PausedState) {
       
  1600         changeState(Phonon::StoppedState);
  1444     }
  1601     }
  1445 }
  1602 }
  1446 
  1603 
  1447 // Notifes the pipeline about state changes in the media object
  1604 // Notifes the pipeline about state changes in the media object
  1448 void MediaObject::notifyStateChange(Phonon::State newstate, Phonon::State oldstate)
  1605 void MediaObject::notifyStateChange(Phonon::State newstate, Phonon::State oldstate)
  1500     return m_currentTitle;
  1657     return m_currentTitle;
  1501 }
  1658 }
  1502 
  1659 
  1503 void MediaObject::_iface_setCurrentTitle(int title)
  1660 void MediaObject::_iface_setCurrentTitle(int title)
  1504 {
  1661 {
       
  1662     m_backend->logMessage(QString("setCurrentTitle %0").arg(title), Backend::Info, this);
       
  1663     if ((title == m_currentTitle) || (title == m_pendingTitle))
       
  1664         return;
       
  1665 
       
  1666     m_pendingTitle = title;
       
  1667 
       
  1668     if (m_state == Phonon::PlayingState || m_state == Phonon::StoppedState) {
       
  1669         setTrack(m_pendingTitle);
       
  1670     } else {
       
  1671         setState(Phonon::StoppedState);
       
  1672     }
       
  1673 }
       
  1674 
       
  1675 void MediaObject::setTrack(int title)
       
  1676 {
       
  1677     if (((m_state != Phonon::PlayingState) && (m_state != Phonon::StoppedState)) || (title < 1) || (title > m_availableTitles))
       
  1678         return;
       
  1679 
       
  1680 
       
  1681     //let's seek to the beginning of the song
  1505     GstFormat trackFormat = gst_format_get_by_nick("track");
  1682     GstFormat trackFormat = gst_format_get_by_nick("track");
  1506     m_backend->logMessage(QString("setCurrentTitle %0").arg(title), Backend::Info, this);
  1683     m_backend->logMessage(QString("setTrack %0").arg(title), Backend::Info, this);
  1507     if ((title == m_currentTitle) || (title < 1) || (title > m_availableTitles))
  1684     if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, title - 1)) {
  1508         return;
  1685         m_currentTitle = title;
  1509 
       
  1510     m_currentTitle = title;
       
  1511 
       
  1512     //let's seek to the beginning of the song
       
  1513     if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, m_currentTitle - 1)) {
       
  1514         updateTotalTime();
  1686         updateTotalTime();
  1515         m_atEndOfStream = false;
  1687         m_atEndOfStream = false;
  1516         emit titleChanged(title);
  1688         emit titleChanged(title);
  1517         emit totalTimeChanged(totalTime());
  1689         emit totalTimeChanged(totalTime());
  1518     }
  1690     }