diff options
Diffstat (limited to 'src/3rdparty/phonon/gstreamer/mediaobject.cpp')
-rw-r--r-- | src/3rdparty/phonon/gstreamer/mediaobject.cpp | 296 |
1 files changed, 234 insertions, 62 deletions
diff --git a/src/3rdparty/phonon/gstreamer/mediaobject.cpp b/src/3rdparty/phonon/gstreamer/mediaobject.cpp index b6d23ec..3e0addc 100644 --- a/src/3rdparty/phonon/gstreamer/mediaobject.cpp +++ b/src/3rdparty/phonon/gstreamer/mediaobject.cpp @@ -16,6 +16,7 @@ */ #include <cmath> #include <gst/interfaces/propertyprobe.h> +#include <gst/pbutils/install-plugins.h> #include "common.h" #include "mediaobject.h" #include "videowidget.h" @@ -53,6 +54,7 @@ MediaObject::MediaObject(Backend *backend, QObject *parent) , m_tickTimer(new QTimer(this)) , m_prefinishMark(0) , m_transitionTime(0) + , m_isStream(false) , m_posAtSeek(-1) , m_prefinishMarkReachedNotEmitted(true) , m_aboutToFinishEmitted(false) @@ -79,6 +81,7 @@ MediaObject::MediaObject(Backend *backend, QObject *parent) , m_autoplayTitles(true) , m_availableTitles(0) , m_currentTitle(1) + , m_pendingTitle(1) { qRegisterMetaType<GstCaps*>("GstCaps*"); qRegisterMetaType<State>("State"); @@ -95,8 +98,8 @@ MediaObject::MediaObject(Backend *backend, QObject *parent) m_backend->addBusWatcher(this); connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick())); } - connect(this, SIGNAL(stateChanged(Phonon::State,Phonon::State)), - this, SLOT(notifyStateChange(Phonon::State,Phonon::State))); + connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)), + this, SLOT(notifyStateChange(Phonon::State, Phonon::State))); } @@ -136,6 +139,14 @@ QString stateString(const Phonon::State &state) return QString(); } +void +pluginInstallationDone( GstInstallPluginsReturn res, gpointer userData ) +{ + // Nothing inside yet + Q_UNUSED(res); + Q_UNUSED(userData); +} + void MediaObject::saveState() { //Only first resumeState is respected @@ -195,13 +206,35 @@ void MediaObject::noMorePadsAvailable () if (m_missingCodecs.size() > 0) { bool canPlay = (m_hasAudio || m_videoStreamFound); Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError; +#ifdef PLUGIN_INSTALL_API + GstInstallPluginsContext *ctx = gst_install_plugins_context_new (); + gchar *details[2]; + details[0] = m_missingCodecs[0].toLocal8Bit().data(); + details[1] = NULL; + GstInstallPluginsReturn status; + + status = gst_install_plugins_async( details, ctx, pluginInstallationDone, NULL ); + gst_install_plugins_context_free ( ctx ); + + if ( status != GST_INSTALL_PLUGINS_STARTED_OK ) + { + if( status == GST_INSTALL_PLUGINS_HELPER_MISSING ) + setError(QString(tr("Missing codec helper script assistant.")), Phonon::FatalError ); + else + setError(QString(tr("Plugin codec installation failed for codec: %0")) + .arg(m_missingCodecs[0].split("|")[3]), error); + } + m_missingCodecs.clear(); +#else + QString codecs = m_missingCodecs.join(", "); + if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) { m_hasVideo = false; emit hasVideoChanged(false); } - QString codecs = m_missingCodecs.join(", "); setError(QString(tr("A required codec is missing. You need to install the following codec(s) to play this content: %0")).arg(codecs), error); m_missingCodecs.clear(); +#endif } } @@ -248,7 +281,16 @@ void MediaObject::cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps * value = QString::fromUtf8(gst_structure_get_name (str)); } - media->addMissingCodecName(value); + +#ifdef PLUGIN_INSTALL_API + QString plugins = QString("gstreamer|0.10|%0|%1|decoder-%2") + .arg( qApp->applicationName() ) + .arg( value ) + .arg( QString::fromUtf8(gst_caps_to_string (caps) ) ); + media->addMissingCodecName( plugins ); +#else + media->addMissingCodecName( value ); +#endif } static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data) @@ -309,7 +351,7 @@ void MediaObject::connectVideo(GstPad *pad) m_backend->logMessage("Video track connected", Backend::Info, this); // Note that the notify::caps _must_ be installed after linking to work with Dapper m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this); - + if (!m_loading && !m_hasVideo) { m_hasVideo = m_videoStreamFound; emit hasVideoChanged(m_hasVideo); @@ -368,7 +410,10 @@ bool MediaObject::createPipefromURL(const QUrl &url) } // Create a new datasource based on the input URL - QByteArray encoded_cstr_url = url.toEncoded(); + // add the 'file' scheme if it's missing; the double '/' is needed! + QByteArray encoded_cstr_url = (url.scheme() == QLatin1String("") ? + "file://" + url.toEncoded() : + url.toEncoded()); m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL); if (!m_datasource) return false; @@ -388,6 +433,14 @@ bool MediaObject::createPipefromURL(const QUrl &url) g_object_set (G_OBJECT (m_datasource), "read-speed", 2, (const char*)NULL); m_backend->logMessage(QString("new device speed : 2X"), Backend::Info, this); } + } + + /* make HTTP sources send extra headers so we get icecast + * metadata in case the stream is an icecast stream */ + if (encoded_cstr_url.startsWith("http://") + && g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "iradio-mode")) { + g_object_set (m_datasource, "iradio-mode", TRUE, NULL); + m_isStream = true; } // Link data source into pipeline @@ -442,7 +495,7 @@ void MediaObject::createPipeline() gst_object_ref (GST_OBJECT (m_pipeline)); gst_object_sink (GST_OBJECT (m_pipeline)); - m_decodebin = gst_element_factory_make ("decodebin", NULL); + m_decodebin = gst_element_factory_make ("decodebin2", NULL); g_signal_connect (m_decodebin, "new-decoded-pad", G_CALLBACK (&cb_newpad), this); g_signal_connect (m_decodebin, "unknown-type", G_CALLBACK (&cb_unknown_type), this); g_signal_connect (m_decodebin, "no-more-pads", G_CALLBACK (&cb_no_more_pads), this); @@ -646,7 +699,7 @@ void MediaObject::setState(State newstate) m_backend->logMessage("EOS already reached", Backend::Info, this); } else if (currentState == GST_STATE_PLAYING) { changeState(Phonon::PlayingState); - } else if (!m_atEndOfStream && gst_element_set_state(m_pipeline, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE) { + } else if (gst_element_set_state(m_pipeline, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE) { m_pendingState = Phonon::PlayingState; } else { m_backend->logMessage("phonon state request failed", Backend::Info, this); @@ -676,7 +729,7 @@ void MediaObject::changeState(State newstate) return; Phonon::State oldState = m_state; - m_state = newstate; // m_state must be set before emitting, since + m_state = newstate; // m_state must be set before emitting, since // Error state requires that state() will return the new value m_pendingState = newstate; emit stateChanged(newstate, oldState); @@ -696,6 +749,8 @@ void MediaObject::changeState(State newstate) case Phonon::StoppedState: m_backend->logMessage("phonon state changed: Stopped", Backend::Info, this); + // We must reset the pipeline when playing again + m_resetNeeded = true; m_tickTimer->stop(); break; @@ -861,7 +916,7 @@ void MediaObject::setSource(const MediaSource &source) // such as failing duration queries etc GstState state; gst_element_set_state(m_pipeline, GST_STATE_NULL); - gst_element_get_state (m_pipeline, &state, NULL, 2000); + gst_element_get_state(m_pipeline, &state, NULL, 2000); m_source = source; emit currentSourceChanged(m_source); @@ -871,7 +926,9 @@ void MediaObject::setSource(const MediaSource &source) // Go into to loading state changeState(Phonon::LoadingState); m_loading = true; - m_resetNeeded = false; + // IMPORTANT: Honor the m_resetNeeded flag as it currently stands. + // See https://qa.mandriva.com/show_bug.cgi?id=56807 + //m_resetNeeded = false; m_resumeState = false; m_pendingState = Phonon::StoppedState; @@ -884,8 +941,8 @@ void MediaObject::setSource(const MediaSource &source) // Clear any existing errors m_aboutToFinishEmitted = false; m_error = NoError; - m_errorString = QString(); - + m_errorString.clear(); + m_bufferPercent = 0; m_prefinishMarkReachedNotEmitted = true; m_aboutToFinishEmitted = false; @@ -894,22 +951,23 @@ void MediaObject::setSource(const MediaSource &source) setTotalTime(-1); m_atEndOfStream = false; - // Clear exising meta tags + m_availableTitles = 0; + m_pendingTitle = 1; + m_currentTitle = 1; + + // Clear existing meta tags m_metaData.clear(); + m_isStream = false; switch (source.type()) { - case MediaSource::Url: { - if (createPipefromURL(source.url())) - m_loading = true; - else + case MediaSource::Url: { + if (!createPipefromURL(source.url())) setError(tr("Could not open media source.")); } break; case MediaSource::LocalFile: { - if (createPipefromURL(QUrl::fromLocalFile(source.fileName()))) - m_loading = true; - else + if (!createPipefromURL(QUrl::fromLocalFile(source.fileName()))) setError(tr("Could not open media source.")); } break; @@ -922,17 +980,15 @@ void MediaObject::setSource(const MediaSource &source) break; case MediaSource::Stream: - if (createPipefromStream(source)) - m_loading = true; - else + if (!createPipefromStream(source)) setError(tr("Could not open media source.")); break; case MediaSource::Disc: { - QString mediaUrl; - switch (source.discType()) { - case Phonon::NoDisc: + QString mediaUrl; + switch (source.discType()) { + case Phonon::NoDisc: qWarning() << "I should never get to see a MediaSource that is a disc but doesn't specify which one"; return; case Phonon::Cd: // CD tracks can be specified by setting the url in the following way uri=cdda:4 @@ -948,9 +1004,7 @@ void MediaObject::setSource(const MediaSource &source) qWarning() << "media " << source.discType() << " not implemented"; return; } - if (!mediaUrl.isEmpty() && createPipefromURL(QUrl(mediaUrl))) - m_loading = true; - else + if (mediaUrl.isEmpty() || !createPipefromURL(QUrl(mediaUrl))) setError(tr("Could not open media source.")); } break; @@ -966,8 +1020,7 @@ void MediaObject::setSource(const MediaSource &source) // We need to link this node to ensure that fake sinks are connected // before loading, otherwise the stream will be blocked - if (m_loading) - link(); + link(); beginLoad(); } @@ -1004,22 +1057,22 @@ void MediaObject::getStreamInfo() emit hasVideoChanged(m_hasVideo); } - m_availableTitles = 1; - gint64 titleCount; - GstFormat format = gst_format_get_by_nick("track"); - if (gst_element_query_duration (m_pipeline, &format, &titleCount)) { + if (m_source.discType() == Phonon::Cd) { + gint64 titleCount; + GstFormat format = gst_format_get_by_nick("track"); + if (gst_element_query_duration (m_pipeline, &format, &titleCount)) { //check if returned format is still "track", //gstreamer sometimes returns the total time, if tracks information is not available. - if (qstrcmp(gst_format_get_name(format), "track") == 0) { - int oldAvailableTitles = m_availableTitles; - m_availableTitles = (int)titleCount; - if (m_availableTitles != oldAvailableTitles) { - emit availableTitlesChanged(m_availableTitles); - m_backend->logMessage(QString("Available titles changed: %0").arg(m_availableTitles), Backend::Info, this); + if (qstrcmp(gst_format_get_name(format), "track") == 0) { + int oldAvailableTitles = m_availableTitles; + m_availableTitles = (int)titleCount; + if (m_availableTitles != oldAvailableTitles) { + emit availableTitlesChanged(m_availableTitles); + m_backend->logMessage(QString("Available titles changed: %0").arg(m_availableTitles), Backend::Info, this); + } } } } - } void MediaObject::setPrefinishMark(qint32 newPrefinishMark) @@ -1077,7 +1130,7 @@ void MediaObject::seek(qint64 time) } quint64 current = currentTime(); - quint64 total = totalTime(); + quint64 total = totalTime(); if (current < total - m_prefinishMark) m_prefinishMarkReachedNotEmitted = true; @@ -1098,7 +1151,7 @@ void MediaObject::emitTick() if (m_tickInterval > 0 && currentTime != m_previousTickTime) { emit tick(currentTime); - m_previousTickTime = currentTime; + m_previousTickTime = currentTime; } if (m_state == Phonon::PlayingState) { if (currentTime >= totalTime - m_prefinishMark) { @@ -1109,7 +1162,12 @@ void MediaObject::emitTick() } // Prepare load of next source if (currentTime >= totalTime - ABOUT_TO_FINNISH_TIME) { - if (!m_aboutToFinishEmitted) { + if (m_source.type() == MediaSource::Disc && + m_autoplayTitles && + m_availableTitles > 1 && + m_currentTitle < m_availableTitles) { + m_aboutToFinishEmitted = false; + } else if (!m_aboutToFinishEmitted) { m_aboutToFinishEmitted = true; // track is about to finish emit aboutToFinish(); } @@ -1213,8 +1271,8 @@ void MediaObject::handleBusMessage(const Message &message) switch (GST_MESSAGE_TYPE (gstMessage)) { - case GST_MESSAGE_EOS: - m_backend->logMessage("EOS recieved", Backend::Info, this); + case GST_MESSAGE_EOS: + m_backend->logMessage("EOS received", Backend::Info, this); handleEndOfStream(); break; @@ -1222,14 +1280,98 @@ void MediaObject::handleBusMessage(const Message &message) GstTagList* tag_list = 0; gst_message_parse_tag(gstMessage, &tag_list); if (tag_list) { + TagMap newTags; + gst_tag_list_foreach (tag_list, &foreach_tag_function, &newTags); + gst_tag_list_free(tag_list); + + // Determine if we should no fake the album/artist tags. + // This is a little confusing as we want to fake it on initial + // connection where title, album and artist are all missing. + // There are however times when we get just other information, + // e.g. codec, and so we want to only do clever stuff if we + // have a commonly available tag (ORGANIZATION) or we have a + // change in title + bool fake_it = + (m_isStream + && ((!newTags.contains("TITLE") + && newTags.contains("ORGANIZATION")) + || (newTags.contains("TITLE") + && m_metaData.value("TITLE") != newTags.value("TITLE"))) + && !newTags.contains("ALBUM") + && !newTags.contains("ARTIST")); + TagMap oldMap = m_metaData; // Keep a copy of the old one for reference - // Append any new meta tags to the existing tag list - gst_tag_list_foreach (tag_list, &foreach_tag_function, &m_metaData); + + // Now we've checked the new data, append any new meta tags to the existing tag list + // We cannot use TagMap::iterator as this is a multimap and when streaming data + // could in theory be lost. + QList<QString> keys = newTags.keys(); + for (QList<QString>::iterator i = keys.begin(); i != keys.end(); ++i) { + QString key = *i; + if (m_isStream) { + // If we're streaming, we need to remove data in m_metaData + // in order to stop it filling up indefinitely (as it's a multimap) + m_metaData.remove(key); + } + QList<QString> values = newTags.values(key); + for (QList<QString>::iterator j = values.begin(); j != values.end(); ++j) { + QString value = *j; + QString currVal = m_metaData.value(key); + if (!m_metaData.contains(key) || currVal != value) { + m_metaData.insert(key, value); + } + } + } + m_backend->logMessage("Meta tags found", Backend::Info, this); - if (oldMap != m_metaData && !m_loading) - emit metaDataChanged(m_metaData); - gst_tag_list_free(tag_list); - } + if (oldMap != m_metaData) { + // This is a bit of a hack to ensure that stream metadata is + // returned. We get as much as we can from the Shoutcast server's + // StreamTitle= header. If further info is decoded from the stream + // itself later, then it will overwrite this info. + if (m_isStream && fake_it) { + m_metaData.remove("ALBUM"); + m_metaData.remove("ARTIST"); + + // Detect whether we want to "fill in the blanks" + QString str; + if (m_metaData.contains("TITLE")) + { + str = m_metaData.value("TITLE"); + int splitpoint; + // Check to see if our title matches "%s - %s" + // Where neither %s are empty... + if ((splitpoint = str.indexOf(" - ")) > 0 + && str.size() > (splitpoint+3)) { + m_metaData.insert("ARTIST", str.left(splitpoint)); + m_metaData.replace("TITLE", str.mid(splitpoint+3)); + } + } else { + str = m_metaData.value("GENRE"); + if (!str.isEmpty()) + m_metaData.insert("TITLE", str); + else + m_metaData.insert("TITLE", "Streaming Data"); + } + if (!m_metaData.contains("ARTIST")) { + str = m_metaData.value("LOCATION"); + if (!str.isEmpty()) + m_metaData.insert("ARTIST", str); + else + m_metaData.insert("ARTIST", "Streaming Data"); + } + str = m_metaData.value("ORGANIZATION"); + if (!str.isEmpty()) + m_metaData.insert("ALBUM", str); + else + m_metaData.insert("ALBUM", "Streaming Data"); + } + // As we manipulate the title, we need to recompare + // oldMap and m_metaData here... + if (oldMap != m_metaData && !m_loading) + emit metaDataChanged(m_metaData); + } + } } break; @@ -1255,6 +1397,9 @@ void MediaObject::handleBusMessage(const Message &message) m_backend->logMessage("gstreamer: pipeline state set to playing", Backend::Info, this); m_tickTimer->start(); changeState(Phonon::PlayingState); + if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) { + setTrack(m_pendingTitle); + } if (m_resumeState && m_oldState == Phonon::PlayingState) { seek(m_oldPos); m_resumeState = false; @@ -1290,6 +1435,9 @@ void MediaObject::handleBusMessage(const Message &message) changeState(Phonon::StoppedState); m_backend->logMessage("gstreamer: pipeline state set to ready", Backend::Debug, this); m_tickTimer->stop(); + if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) { + setTrack(m_pendingTitle); + } break; case GST_STATE_VOID_PENDING : @@ -1328,7 +1476,7 @@ void MediaObject::handleBusMessage(const Message &message) setError(err->message, Phonon::FatalError); gst_caps_unref (caps); gst_object_unref (sinkPad); - } + } } else { setError(QString(err->message), Phonon::FatalError); } @@ -1400,8 +1548,8 @@ void MediaObject::handleBusMessage(const Message &message) //case GST_MESSAGE_STEP_DONE: //case GST_MESSAGE_LATENCY: only from 0.10.12 //case GST_MESSAGE_ASYNC_DONE: only from 0.10.13 - default: - break; + default: + break; } } @@ -1417,7 +1565,8 @@ void MediaObject::handleEndOfStream() if (!m_seekable) m_atEndOfStream = true; - if (m_autoplayTitles && + if (m_source.type() == MediaSource::Disc && + m_autoplayTitles && m_availableTitles > 1 && m_currentTitle < m_availableTitles) { _iface_setCurrentTitle(m_currentTitle + 1); @@ -1444,6 +1593,14 @@ void MediaObject::handleEndOfStream() } } +void MediaObject::invalidateGraph() +{ + m_resetNeeded = true; + if (m_state == Phonon::PlayingState || m_state == Phonon::PausedState) { + changeState(Phonon::StoppedState); + } +} + // Notifes the pipeline about state changes in the media object void MediaObject::notifyStateChange(Phonon::State newstate, Phonon::State oldstate) { @@ -1502,15 +1659,30 @@ int MediaObject::_iface_currentTitle() const void MediaObject::_iface_setCurrentTitle(int title) { - GstFormat trackFormat = gst_format_get_by_nick("track"); m_backend->logMessage(QString("setCurrentTitle %0").arg(title), Backend::Info, this); - if ((title == m_currentTitle) || (title < 1) || (title > m_availableTitles)) + if ((title == m_currentTitle) || (title == m_pendingTitle)) + return; + + m_pendingTitle = title; + + if (m_state == Phonon::PlayingState || m_state == Phonon::StoppedState) { + setTrack(m_pendingTitle); + } else { + setState(Phonon::StoppedState); + } +} + +void MediaObject::setTrack(int title) +{ + if (((m_state != Phonon::PlayingState) && (m_state != Phonon::StoppedState)) || (title < 1) || (title > m_availableTitles)) return; - m_currentTitle = title; //let's seek to the beginning of the song - if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, m_currentTitle - 1)) { + GstFormat trackFormat = gst_format_get_by_nick("track"); + m_backend->logMessage(QString("setTrack %0").arg(title), Backend::Info, this); + if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, title - 1)) { + m_currentTitle = title; updateTotalTime(); m_atEndOfStream = false; emit titleChanged(title); |