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