~ubuntu-branches/ubuntu/saucy/phonon-backend-gstreamer/saucy-proposed

« back to all changes in this revision

Viewing changes to .pc/kubuntu_01_fix_codec_installation_crash.diff/gstreamer/mediaobject.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Riddell
  • Date: 2011-04-15 14:43:30 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20110415144330-7uif3319lxdu4ltt
Tags: 4:4.7.0really4.5.0-0ubuntu2
* New upstream release
* Add kubuntu_02_install_codec.diff to fix codec install

Show diffs side-by-side

added added

removed removed

Lines of Context:
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/navigation.h>
19
 
#include <gst/interfaces/propertyprobe.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 "phonon-config-gstreamer.h"
28
 
#include "gsthelper.h"
29
 
 
30
 
#include <QtCore/QByteRef>
31
 
#include <QtCore/QEvent>
32
 
#include <QtCore/QFile>
33
 
#include <QtCore/QLibrary>
34
 
#include <QtCore/QStringList>
35
 
#include <QtCore/QTimer>
36
 
#include <QtCore/QVector>
37
 
#include <QtGui/QApplication>
38
 
 
39
 
#define ABOUT_TO_FINNISH_TIME 2000
40
 
#define MAX_QUEUE_TIME 20 * GST_SECOND
41
 
 
42
 
QT_BEGIN_NAMESPACE
43
 
 
44
 
namespace Phonon
45
 
{
46
 
namespace Gstreamer
47
 
{
48
 
 
49
 
MediaObject::MediaObject(Backend *backend, QObject *parent)
50
 
        : QObject(parent)
51
 
        , MediaNode(backend, AudioSource | VideoSource)
52
 
        , m_resumeState(false)
53
 
        , m_oldState(Phonon::LoadingState)
54
 
        , m_oldPos(0)
55
 
        , m_state(Phonon::LoadingState)
56
 
        , m_pendingState(Phonon::LoadingState)
57
 
        , m_tickTimer(new QTimer(this))
58
 
        , m_prefinishMark(0)
59
 
        , m_transitionTime(0)
60
 
        , m_isStream(false)
61
 
        , m_posAtSeek(-1)
62
 
        , m_prefinishMarkReachedNotEmitted(true)
63
 
        , m_aboutToFinishEmitted(false)
64
 
        , m_loading(false)
65
 
        , m_capsHandler(0)
66
 
        , m_datasource(0)
67
 
        , m_decodebin(0)
68
 
        , m_audioPipe(0)
69
 
        , m_videoPipe(0)
70
 
        , m_totalTime(-1)
71
 
        , m_bufferPercent(0)
72
 
        , m_hasVideo(false)
73
 
        , m_videoStreamFound(false)
74
 
        , m_hasAudio(false)
75
 
        , m_seekable(false)
76
 
        , m_atEndOfStream(false)
77
 
        , m_atStartOfStream(false)
78
 
        , m_error(Phonon::NoError)
79
 
        , m_pipeline(0)
80
 
        , m_audioGraph(0)
81
 
        , m_videoGraph(0)
82
 
        , m_previousTickTime(-1)
83
 
        , m_resetNeeded(false)
84
 
        , m_autoplayTitles(true)
85
 
        , m_availableTitles(0)
86
 
        , m_currentTitle(1)
87
 
        , m_pendingTitle(1)
88
 
        , m_installingPlugin(true)
89
 
{
90
 
    qRegisterMetaType<GstCaps*>("GstCaps*");
91
 
    qRegisterMetaType<State>("State");
92
 
 
93
 
    static int count = 0;
94
 
    m_name = "MediaObject" + QString::number(count++);
95
 
 
96
 
    if (!m_backend->isValid()) {
97
 
        setError(tr("Cannot start playback. \n\nCheck your GStreamer installation and make sure you "
98
 
                    "\nhave libgstreamer-plugins-base installed."), Phonon::FatalError);
99
 
    } else {
100
 
        m_root = this;
101
 
        createPipeline();
102
 
        m_backend->addBusWatcher(this);
103
 
        connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick()));
104
 
    }
105
 
    connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
106
 
            this, SLOT(notifyStateChange(Phonon::State, Phonon::State)));
107
 
}
108
 
 
109
 
MediaObject::~MediaObject()
110
 
{
111
 
    m_backend->removeBusWatcher(this);
112
 
    if (m_pipeline) {
113
 
        gst_element_set_state(m_pipeline, GST_STATE_NULL);
114
 
        gst_object_unref(m_pipeline);
115
 
    }
116
 
    if (m_audioGraph) {
117
 
        gst_element_set_state(m_audioGraph, GST_STATE_NULL);
118
 
        gst_object_unref(m_audioGraph);
119
 
    }
120
 
    if (m_videoGraph) {
121
 
        gst_element_set_state(m_videoGraph, GST_STATE_NULL);
122
 
        gst_object_unref(m_videoGraph);
123
 
    }
124
 
}
125
 
 
126
 
QString stateString(const Phonon::State &state)
127
 
{
128
 
    switch (state) {
129
 
    case Phonon::LoadingState:
130
 
        return QString("LoadingState");
131
 
    case Phonon::StoppedState:
132
 
        return QString("StoppedState");
133
 
    case Phonon::PlayingState:
134
 
        return QString("PlayingState");
135
 
    case Phonon::BufferingState:
136
 
        return QString("BufferingState");
137
 
    case Phonon::PausedState:
138
 
        return QString("PausedState");
139
 
    case Phonon::ErrorState:
140
 
        return QString("ErrorState");
141
 
    }
142
 
    return QString();
143
 
}
144
 
 
145
 
void MediaObject::saveState()
146
 
{
147
 
    //Only first resumeState is respected
148
 
    if (m_resumeState)
149
 
        return;
150
 
 
151
 
    if (m_pendingState == Phonon::PlayingState || m_pendingState == Phonon::PausedState) {
152
 
        m_resumeState = true;
153
 
        m_oldState = m_pendingState;
154
 
        m_oldPos = getPipelinePos();
155
 
    }
156
 
}
157
 
 
158
 
void MediaObject::resumeState()
159
 
{
160
 
    if (m_resumeState)
161
 
        QMetaObject::invokeMethod(this, "setState", Qt::QueuedConnection, Q_ARG(State, m_oldState));
162
 
}
163
 
 
164
 
void MediaObject::newPadAvailable (GstPad *pad)
165
 
{
166
 
    GstCaps *caps;
167
 
    GstStructure *str;
168
 
    caps = gst_pad_get_caps (pad);
169
 
    if (caps) {
170
 
        str = gst_caps_get_structure (caps, 0);
171
 
        QString mediaString(gst_structure_get_name (str));
172
 
 
173
 
        if (mediaString.startsWith("video")) {
174
 
            connectVideo(pad);
175
 
        } else if (mediaString.startsWith("audio")) {
176
 
            connectAudio(pad);
177
 
        } else {
178
 
            m_backend->logMessage("Could not connect pad", Backend::Warning);
179
 
        }
180
 
        gst_caps_unref (caps);
181
 
    }
182
 
}
183
 
 
184
 
void MediaObject::cb_newpad (GstElement *decodebin,
185
 
                             GstPad     *pad,
186
 
                             gboolean    last,
187
 
                             gpointer    data)
188
 
{
189
 
    Q_UNUSED(decodebin);
190
 
    Q_UNUSED(pad);
191
 
    Q_UNUSED(last);
192
 
    Q_UNUSED(data);
193
 
 
194
 
    MediaObject *media = static_cast<MediaObject*>(data);
195
 
    Q_ASSERT(media);
196
 
    media->newPadAvailable(pad);
197
 
}
198
 
 
199
 
#ifdef PLUGIN_INSTALL_API
200
 
void MediaObject::pluginInstallationResult(GstInstallPluginsReturn result)
201
 
{
202
 
    bool wasInstalling = m_installingPlugin;
203
 
    m_installingPlugin = false;
204
 
    bool canPlay = (m_hasAudio || m_videoStreamFound);
205
 
    Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
206
 
    switch(result) {
207
 
    case GST_INSTALL_PLUGINS_INVALID:
208
 
        setError(QString(tr("Phonon attempted to install an invalid codec name.")));
209
 
        break;
210
 
    case GST_INSTALL_PLUGINS_CRASHED:
211
 
        setError(QString(tr("The codec installer crashed.")), error);
212
 
        break;
213
 
    case GST_INSTALL_PLUGINS_NOT_FOUND:
214
 
        setError(QString(tr("The required codec could not be found for installation.")), error);
215
 
        break;
216
 
    case GST_INSTALL_PLUGINS_ERROR:
217
 
        setError(QString(tr("An unspecified error occurred during codec installation.")), error);
218
 
        break;
219
 
    case GST_INSTALL_PLUGINS_PARTIAL_SUCCESS:
220
 
        setError(QString(tr("Not all codecs could be installed.")), error);
221
 
        break;
222
 
    case GST_INSTALL_PLUGINS_USER_ABORT:
223
 
        setError(QString(tr("User aborted codec installation")), error);
224
 
        break;
225
 
    //These four should never ever be passed in.
226
 
    //If they have, gstreamer has probably imploded in on itself.
227
 
    case GST_INSTALL_PLUGINS_STARTED_OK:
228
 
    case GST_INSTALL_PLUGINS_INTERNAL_FAILURE:
229
 
    case GST_INSTALL_PLUGINS_HELPER_MISSING:
230
 
    case GST_INSTALL_PLUGINS_INSTALL_IN_PROGRESS:
231
 
    //But this one is OK.
232
 
    case GST_INSTALL_PLUGINS_SUCCESS:
233
 
        m_backend->logMessage("Updating registry");
234
 
        if (gst_update_registry()) {
235
 
            m_backend->logMessage("Registry updated failed");
236
 
        }
237
 
        if (wasInstalling) {
238
 
            setSource(source());
239
 
            play();
240
 
        }
241
 
        break;
242
 
    }
243
 
}
244
 
 
245
 
void MediaObject::pluginInstallationDone(GstInstallPluginsReturn result, gpointer userData)
246
 
{
247
 
    MediaObject *mediaObject = static_cast<MediaObject*>(userData);
248
 
    Q_ASSERT(mediaObject);
249
 
    qRegisterMetaType<GstInstallPluginsReturn>("GstInstallPluginsReturn");
250
 
    QMetaObject::invokeMethod(mediaObject, "pluginInstallationResult", Qt::QueuedConnection, Q_ARG(GstInstallPluginsReturn, result));
251
 
}
252
 
#endif // PLUGIN_INSTALL_API
253
 
 
254
 
void MediaObject::installMissingCodecs()
255
 
{
256
 
    if (m_missingCodecs.size() > 0) {
257
 
        bool canPlay = (m_hasAudio || m_videoStreamFound);
258
 
        Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
259
 
#ifdef PLUGIN_INSTALL_API
260
 
        GstInstallPluginsContext *ctx = gst_install_plugins_context_new();
261
 
        QWidget *activeWindow = QApplication::activeWindow();
262
 
        if (activeWindow) {
263
 
            gst_install_plugins_context_set_xid(ctx, static_cast<int>(activeWindow->winId()));
264
 
        }
265
 
        gchar *details[2];
266
 
        QByteArray missingCodec = m_missingCodecs.first().toLocal8Bit();
267
 
        details[0] = missingCodec.data();
268
 
        details[1] = NULL;
269
 
        GstInstallPluginsReturn status;
270
 
 
271
 
        status = gst_install_plugins_async(details, ctx, pluginInstallationDone, this);
272
 
        gst_install_plugins_context_free(ctx);
273
 
 
274
 
        if (status != GST_INSTALL_PLUGINS_STARTED_OK) {
275
 
            if (status == GST_INSTALL_PLUGINS_HELPER_MISSING)
276
 
                setError(QString(tr("Missing codec helper script assistant.")), Phonon::FatalError);
277
 
            else
278
 
                setError(QString(tr("Plugin codec installation failed for codec: %1"))
279
 
                        .arg(m_missingCodecs[0].split('|')[3]), error);
280
 
        } else {
281
 
            m_installingPlugin = true;
282
 
            setState(Phonon::LoadingState);
283
 
        }
284
 
        m_missingCodecs.clear();
285
 
#else
286
 
        QString codecs = m_missingCodecs.join(", ");
287
 
 
288
 
        if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) {
289
 
            m_hasVideo = false;
290
 
            emit hasVideoChanged(false);
291
 
        }
292
 
        setError(QString(tr("A required codec is missing. You need to install the following codec(s) to play this content: %0")).arg(codecs), error);
293
 
        m_missingCodecs.clear();
294
 
#endif
295
 
    }
296
 
}
297
 
 
298
 
typedef void (*Ptr_gst_pb_utils_init)();
299
 
typedef gchar* (*Ptr_gst_pb_utils_get_codec_description)(const GstCaps *);
300
 
 
301
 
void MediaObject::cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps *caps, gpointer data)
302
 
{
303
 
    Q_UNUSED(decodebin);
304
 
    Q_UNUSED(pad);
305
 
    MediaObject *media = static_cast<MediaObject*>(data);
306
 
    Q_ASSERT(media);
307
 
 
308
 
    QString value = "unknown codec";
309
 
 
310
 
    // These functions require GStreamer > 0.10.12
311
 
#ifndef QT_NO_LIBRARY
312
 
    static Ptr_gst_pb_utils_init p_gst_pb_utils_init = 0;
313
 
    static Ptr_gst_pb_utils_get_codec_description p_gst_pb_utils_get_codec_description = 0;
314
 
    if (!p_gst_pb_utils_init) {
315
 
        p_gst_pb_utils_init =  (Ptr_gst_pb_utils_init)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_init");
316
 
        p_gst_pb_utils_get_codec_description =  (Ptr_gst_pb_utils_get_codec_description)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_get_codec_description");
317
 
        if (p_gst_pb_utils_init)
318
 
            p_gst_pb_utils_init();
319
 
    }
320
 
    if (p_gst_pb_utils_get_codec_description) {
321
 
        gchar *codecName = NULL;
322
 
        codecName = p_gst_pb_utils_get_codec_description (caps);
323
 
        value = QString::fromUtf8(codecName);
324
 
        g_free (codecName);
325
 
    } else
326
 
#endif //QT_NO_LIBRARY
327
 
    {
328
 
        // For GStreamer versions < 0.10.12
329
 
        GstStructure *str = gst_caps_get_structure (caps, 0);
330
 
        value = QString::fromUtf8(gst_structure_get_name (str));
331
 
 
332
 
    }
333
 
 
334
 
#ifdef PLUGIN_INSTALL_API
335
 
    QString plugins = QString("gstreamer|0.10|%0|%1|decoder-%2")
336
 
        .arg( qApp->applicationName() )
337
 
        .arg( value )
338
 
        .arg( QString::fromUtf8(gst_caps_to_string (caps) ) );
339
 
    media->addMissingCodecName( plugins );
340
 
#else
341
 
    media->addMissingCodecName( value );
342
 
#endif
343
 
 
344
 
}
345
 
 
346
 
static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data)
347
 
{
348
 
    GstPad *pad = GST_PAD(obj);
349
 
    GstCaps *caps = gst_pad_get_caps (pad);
350
 
    Q_ASSERT(caps);
351
 
    MediaObject *media = static_cast<MediaObject*>(data);
352
 
 
353
 
    // We do not want any more notifications until the source changes
354
 
    g_signal_handler_disconnect(pad, media->capsHandler());
355
 
 
356
 
    // setVideoCaps calls loadingComplete(), meaning we cannot call it from
357
 
    // the streaming thread
358
 
    QMetaObject::invokeMethod(media, "setVideoCaps", Qt::QueuedConnection, Q_ARG(GstCaps *, caps));
359
 
}
360
 
 
361
 
void MediaObject::setVideoCaps(GstCaps *caps)
362
 
{
363
 
    GstStructure *str;
364
 
    gint width, height;
365
 
 
366
 
    if ((str = gst_caps_get_structure (caps, 0))) {
367
 
        if (gst_structure_get_int (str, "width", &width) && gst_structure_get_int (str, "height", &height)) {
368
 
            gint aspectNum = 0;
369
 
            gint aspectDenum = 0;
370
 
            if (gst_structure_get_fraction(str, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) {
371
 
                if (aspectDenum > 0)
372
 
                    width = width*aspectNum/aspectDenum;
373
 
            }
374
 
            // Let child nodes know about our new video size
375
 
            QSize size(width, height);
376
 
            MediaNodeEvent event(MediaNodeEvent::VideoSizeChanged, &size);
377
 
            notify(&event);
378
 
        }
379
 
    }
380
 
    gst_caps_unref(caps);
381
 
}
382
 
 
383
 
// Adds an element to the pipeline if not previously added
384
 
bool MediaObject::addToPipeline(GstElement *elem)
385
 
{
386
 
    bool success = true;
387
 
    if (!GST_ELEMENT_PARENT(elem)) { // If not already in pipeline
388
 
        success = gst_bin_add(GST_BIN(m_pipeline), elem);
389
 
    }
390
 
    return success;
391
 
}
392
 
 
393
 
void MediaObject::connectVideo(GstPad *pad)
394
 
{
395
 
    GstState currentState = GST_STATE(m_pipeline);
396
 
    if (addToPipeline(m_videoGraph)) {
397
 
        GstPad *videopad = gst_element_get_pad (m_videoGraph, "sink");
398
 
        if (!GST_PAD_IS_LINKED (videopad) && (gst_pad_link (pad, videopad) == GST_PAD_LINK_OK)) {
399
 
            gst_element_set_state(m_videoGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
400
 
            m_videoStreamFound = true;
401
 
            m_backend->logMessage("Video track connected", Backend::Info, this);
402
 
            // Note that the notify::caps _must_ be installed after linking to work with Dapper
403
 
            m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this);
404
 
 
405
 
            if (!m_loading && !m_hasVideo) {
406
 
                m_hasVideo = m_videoStreamFound;
407
 
                emit hasVideoChanged(m_hasVideo);
408
 
            }
409
 
        }
410
 
        gst_object_unref (videopad);
411
 
    } else {
412
 
        m_backend->logMessage("The video stream could not be plugged.", Backend::Info, this);
413
 
    }
414
 
}
415
 
 
416
 
void MediaObject::connectAudio(GstPad *pad)
417
 
{
418
 
    GstState currentState = GST_STATE(m_pipeline);
419
 
    if (addToPipeline(m_audioGraph)) {
420
 
        GstPad *audiopad = gst_element_get_pad (m_audioGraph, "sink");
421
 
        if (!GST_PAD_IS_LINKED (audiopad) && (gst_pad_link (pad, audiopad)==GST_PAD_LINK_OK)) {
422
 
            gst_element_set_state(m_audioGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
423
 
            m_hasAudio = true;
424
 
            m_backend->logMessage("Audio track connected", Backend::Info, this);
425
 
        }
426
 
        gst_object_unref (audiopad);
427
 
    } else {
428
 
        m_backend->logMessage("The audio stream could not be plugged.", Backend::Info, this);
429
 
    }
430
 
}
431
 
 
432
 
void MediaObject::cb_pad_added(GstElement *decodebin,
433
 
                               GstPad     *pad,
434
 
                               gpointer    data)
435
 
{
436
 
    Q_UNUSED(decodebin);
437
 
    GstPad *decodepad = static_cast<GstPad*>(data);
438
 
    gst_pad_link (pad, decodepad);
439
 
    //gst_object_unref (decodepad);
440
 
}
441
 
 
442
 
bool MediaObject::createV4lPipe(const DeviceAccess &access, const MediaSource &source)
443
 
{
444
 
    Q_UNUSED(source);
445
 
    QString v4lDevice = access.second;
446
 
    if (m_datasource) {
447
 
        gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
448
 
        m_datasource = 0;
449
 
    }
450
 
    m_datasource = gst_element_factory_make("v4l2src", "v4l2src");
451
 
    if (!m_datasource) {
452
 
        m_backend->logMessage("Couldn't create v4l2src element");
453
 
        return false;
454
 
    }
455
 
    g_object_set(G_OBJECT(m_datasource), "device", v4lDevice.toUtf8().data(), (const char*)NULL);
456
 
    m_backend->logMessage("Created video device element");
457
 
    gst_bin_add(GST_BIN(m_pipeline), m_datasource);
458
 
    gst_element_link(m_datasource, m_decodebin);
459
 
    return true;
460
 
}
461
 
 
462
 
bool MediaObject::createPipefromDevice(const MediaSource &source)
463
 
{
464
 
    foreach(DeviceAccess access, source.deviceAccessList()) {
465
 
    if (access.first == "v4l2") {
466
 
            return createV4lPipe(access, source);
467
 
        }
468
 
    }
469
 
    qWarning() << "Only v4l2 devices supported.";
470
 
    return false;
471
 
}
472
 
 
473
 
/**
474
 
 * Create a media source from a given URL.
475
 
 *
476
 
 * returns true if successful
477
 
 */
478
 
bool MediaObject::createPipefromURL(const QUrl &url)
479
 
{
480
 
    // Remove any existing data source
481
 
    if (m_datasource) {
482
 
        gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
483
 
        // m_pipeline has the only ref to datasource
484
 
        m_datasource = 0;
485
 
    }
486
 
 
487
 
    // Verify that the uri can be parsed
488
 
    if (!url.isValid()) {
489
 
        m_backend->logMessage(QString("%1 is not a valid URI").arg(url.toString()));
490
 
        return false;
491
 
    }
492
 
 
493
 
    // Create a new datasource based on the input URL
494
 
    // add the 'file' scheme if it's missing; the double '/' is needed!
495
 
    QByteArray encoded_cstr_url;
496
 
    if (url.scheme() == QLatin1String("")) {
497
 
        encoded_cstr_url = QFile::encodeName("file://" + url.toString());
498
 
    } else if (url.scheme() == QLatin1String("file")) {
499
 
#ifdef __GNUC__
500
 
#warning TODO 4.5
501
 
#endif
502
 
        // TODO 4.5: investigate whether this is necessary. Harald was a bit worrid
503
 
        // that QFile::encodeName on an actual streaming URI could cause problems.
504
 
        encoded_cstr_url = QFile::encodeName(url.toString());
505
 
    } else {
506
 
        encoded_cstr_url = url.toEncoded();
507
 
    }
508
 
    m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL);
509
 
    if (!m_datasource)
510
 
        return false;
511
 
 
512
 
    // Set the device for MediaSource::Disc
513
 
    if (m_source.type() == MediaSource::Disc) {
514
 
 
515
 
        if (g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "device")) {
516
 
            QByteArray mediaDevice = QFile::encodeName(m_source.deviceName());
517
 
            if (!mediaDevice.isEmpty())
518
 
                g_object_set (G_OBJECT (m_datasource), "device", mediaDevice.constData(), (const char*)NULL);
519
 
        }
520
 
 
521
 
        // Also Set optical disc speed to 2X for Audio CD
522
 
        if (m_source.discType() == Phonon::Cd
523
 
            && (g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "read-speed"))) {
524
 
            g_object_set (G_OBJECT (m_datasource), "read-speed", 2, (const char*)NULL);
525
 
            m_backend->logMessage(QString("new device speed : 2X"), Backend::Info, this);
526
 
        }
527
 
  }
528
 
 
529
 
    /* make HTTP sources send extra headers so we get icecast
530
 
     * metadata in case the stream is an icecast stream */
531
 
    if (encoded_cstr_url.startsWith("http://")
532
 
        && g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "iradio-mode")) {
533
 
        g_object_set (m_datasource, "iradio-mode", TRUE, NULL);
534
 
        m_isStream = true;
535
 
    }
536
 
 
537
 
    // Link data source into pipeline
538
 
    gst_bin_add(GST_BIN(m_pipeline), m_datasource);
539
 
    if (!gst_element_link(m_datasource, m_decodebin)) {
540
 
        // For sources with dynamic pads (such as RtspSrc) we need to connect dynamically
541
 
        GstPad *decodepad = gst_element_get_pad (m_decodebin, "sink");
542
 
        g_signal_connect (m_datasource, "pad-added", G_CALLBACK (&cb_pad_added), decodepad);
543
 
    }
544
 
 
545
 
    return true;
546
 
}
547
 
 
548
 
/**
549
 
 * Create a media source from a media stream
550
 
 *
551
 
 * returns true if successful
552
 
 */
553
 
bool MediaObject::createPipefromStream(const MediaSource &source)
554
 
{
555
 
#ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
556
 
    // Remove any existing data source
557
 
    if (m_datasource) {
558
 
        gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
559
 
        // m_pipeline has the only ref to datasource
560
 
        m_datasource = 0;
561
 
    }
562
 
 
563
 
    m_datasource = GST_ELEMENT(g_object_new(phonon_src_get_type(), NULL));
564
 
    if (!m_datasource)
565
 
        return false;
566
 
 
567
 
    StreamReader *streamReader = new StreamReader(source, this);
568
 
    g_object_set (G_OBJECT (m_datasource), "iodevice", streamReader, (const char*)NULL);
569
 
 
570
 
    // Link data source into pipeline
571
 
    gst_bin_add(GST_BIN(m_pipeline), m_datasource);
572
 
    if (!gst_element_link(m_datasource, m_decodebin)) {
573
 
        gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
574
 
        return false;
575
 
    }
576
 
    return true;
577
 
#else //QT_NO_PHONON_ABSTRACTMEDIASTREAM
578
 
    Q_UNUSED(source);
579
 
    return false;
580
 
#endif
581
 
}
582
 
 
583
 
void MediaObject::createPipeline()
584
 
{
585
 
    m_pipeline = gst_pipeline_new (NULL);
586
 
    gst_object_ref (GST_OBJECT (m_pipeline));
587
 
    gst_object_sink (GST_OBJECT (m_pipeline));
588
 
 
589
 
    m_decodebin = gst_element_factory_make ("decodebin2", NULL);
590
 
    g_signal_connect (m_decodebin, "new-decoded-pad", G_CALLBACK (&cb_newpad), this);
591
 
    g_signal_connect (m_decodebin, "unknown-type", G_CALLBACK (&cb_unknown_type), this);
592
 
 
593
 
    gst_bin_add(GST_BIN(m_pipeline), m_decodebin);
594
 
 
595
 
    // Create a bin to contain the gst elements for this medianode
596
 
 
597
 
    // Set up audio graph
598
 
    m_audioGraph = gst_bin_new(NULL);
599
 
    gst_object_ref (GST_OBJECT (m_audioGraph));
600
 
    gst_object_sink (GST_OBJECT (m_audioGraph));
601
 
 
602
 
    // Note that these queues are only required for streaming content
603
 
    // And should ideally be created on demand as they will disable
604
 
    // pull-mode access. Also note that the max-size-time are increased to
605
 
    // reduce buffer overruns as these are not gracefully handled at the moment.
606
 
    m_audioPipe = gst_element_factory_make("queue", NULL);
607
 
    g_object_set(G_OBJECT(m_audioPipe), "max-size-time",  MAX_QUEUE_TIME, (const char*)NULL);
608
 
 
609
 
    QByteArray tegraEnv = qgetenv("TEGRA_GST_OPENMAX");
610
 
    if (!tegraEnv.isEmpty()) {
611
 
        g_object_set(G_OBJECT(m_audioPipe), "max-size-time", 0, (const char*)NULL);
612
 
        g_object_set(G_OBJECT(m_audioPipe), "max-size-buffers", 0, (const char*)NULL);
613
 
        g_object_set(G_OBJECT(m_audioPipe), "max-size-bytes", 0, (const char*)NULL);
614
 
    }
615
 
 
616
 
    gst_bin_add(GST_BIN(m_audioGraph), m_audioPipe);
617
 
    GstPad *audiopad = gst_element_get_pad (m_audioPipe, "sink");
618
 
    gst_element_add_pad (m_audioGraph, gst_ghost_pad_new ("sink", audiopad));
619
 
    gst_object_unref (audiopad);
620
 
 
621
 
    // Set up video graph
622
 
    m_videoGraph = gst_bin_new(NULL);
623
 
    gst_object_ref (GST_OBJECT (m_videoGraph));
624
 
    gst_object_sink (GST_OBJECT (m_videoGraph));
625
 
 
626
 
    m_videoPipe = gst_element_factory_make("queue", NULL);
627
 
    g_object_set(G_OBJECT(m_videoPipe), "max-size-time", MAX_QUEUE_TIME, (const char*)NULL);
628
 
    if (!tegraEnv.isEmpty()) {
629
 
        g_object_set(G_OBJECT(m_videoPipe), "max-size-time", 33000, (const char*)NULL);
630
 
        g_object_set(G_OBJECT(m_audioPipe), "max-size-buffers", 1, (const char*)NULL);
631
 
        g_object_set(G_OBJECT(m_audioPipe), "max-size-bytes", 0, (const char*)NULL);
632
 
    }
633
 
    gst_bin_add(GST_BIN(m_videoGraph), m_videoPipe);
634
 
    GstPad *videopad = gst_element_get_pad (m_videoPipe, "sink");
635
 
    gst_element_add_pad (m_videoGraph, gst_ghost_pad_new ("sink", videopad));
636
 
    gst_object_unref (videopad);
637
 
 
638
 
    if (m_pipeline && m_decodebin && m_audioGraph && m_videoGraph && m_audioPipe && m_videoPipe)
639
 
        m_isValid = true;
640
 
    else
641
 
        m_backend->logMessage("Could not create pipeline for media object", Backend::Warning);
642
 
}
643
 
 
644
 
/**
645
 
 * !reimp
646
 
 */
647
 
State MediaObject::state() const
648
 
{
649
 
    return m_state;
650
 
}
651
 
 
652
 
/**
653
 
 * !reimp
654
 
 */
655
 
bool MediaObject::hasVideo() const
656
 
{
657
 
    return m_hasVideo;
658
 
}
659
 
 
660
 
/**
661
 
 * !reimp
662
 
 */
663
 
bool MediaObject::isSeekable() const
664
 
{
665
 
    return m_seekable;
666
 
}
667
 
 
668
 
/**
669
 
 * !reimp
670
 
 */
671
 
qint64 MediaObject::currentTime() const
672
 
{
673
 
    if (m_resumeState)
674
 
        return m_oldPos;
675
 
 
676
 
    switch (state()) {
677
 
    case Phonon::PausedState:
678
 
    case Phonon::BufferingState:
679
 
    case Phonon::PlayingState:
680
 
        return getPipelinePos();
681
 
    case Phonon::StoppedState:
682
 
    case Phonon::LoadingState:
683
 
        return 0;
684
 
    case Phonon::ErrorState:
685
 
        break;
686
 
    }
687
 
    return -1;
688
 
}
689
 
 
690
 
/**
691
 
 * !reimp
692
 
 */
693
 
qint32 MediaObject::tickInterval() const
694
 
{
695
 
    return m_tickInterval;
696
 
}
697
 
 
698
 
/**
699
 
 * !reimp
700
 
 */
701
 
void MediaObject::setTickInterval(qint32 newTickInterval)
702
 
{
703
 
    m_tickInterval = newTickInterval;
704
 
    if (m_tickInterval <= 0)
705
 
        m_tickTimer->setInterval(50);
706
 
    else
707
 
        m_tickTimer->setInterval(newTickInterval);
708
 
}
709
 
 
710
 
/**
711
 
 * !reimp
712
 
 */
713
 
void MediaObject::play()
714
 
{
715
 
    setState(Phonon::PlayingState);
716
 
    m_resumeState = false;
717
 
}
718
 
 
719
 
/**
720
 
 * !reimp
721
 
 */
722
 
QString MediaObject::errorString() const
723
 
{
724
 
    return m_errorString;
725
 
}
726
 
 
727
 
/**
728
 
 * !reimp
729
 
 */
730
 
Phonon::ErrorType MediaObject::errorType() const
731
 
{
732
 
    return m_error;
733
 
}
734
 
 
735
 
/**
736
 
 * Set the current state of the mediaObject.
737
 
 *
738
 
 * !### Note that both Playing and Paused states are set immediately
739
 
 *     This should obviously be done in response to actual gstreamer state changes
740
 
 */
741
 
void MediaObject::setState(State newstate)
742
 
{
743
 
    if (!isValid())
744
 
        return;
745
 
 
746
 
    if (m_state == newstate)
747
 
        return;
748
 
 
749
 
    if (m_loading) {
750
 
        // We are still loading. The state will be requested
751
 
        // when loading has completed.
752
 
        m_pendingState = newstate;
753
 
        return;
754
 
    }
755
 
 
756
 
    GstState currentState;
757
 
    gst_element_get_state (m_pipeline, &currentState, NULL, 1000);
758
 
 
759
 
    switch (newstate) {
760
 
    case Phonon::BufferingState:
761
 
        m_backend->logMessage("phonon state request: buffering", Backend::Info, this);
762
 
        break;
763
 
 
764
 
    case Phonon::PausedState:
765
 
        m_backend->logMessage("phonon state request: paused", Backend::Info, this);
766
 
        if (currentState == GST_STATE_PAUSED) {
767
 
            changeState(Phonon::PausedState);
768
 
        } else if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) {
769
 
            m_pendingState = Phonon::PausedState;
770
 
        } else {
771
 
            m_backend->logMessage("phonon state request failed", Backend::Info, this);
772
 
        }
773
 
        break;
774
 
 
775
 
    case Phonon::StoppedState:
776
 
        m_backend->logMessage("phonon state request: Stopped", Backend::Info, this);
777
 
        if (currentState == GST_STATE_READY) {
778
 
            changeState(Phonon::StoppedState);
779
 
        } else if (gst_element_set_state(m_pipeline, GST_STATE_READY) != GST_STATE_CHANGE_FAILURE) {
780
 
            m_pendingState = Phonon::StoppedState;
781
 
        } else {
782
 
            m_backend->logMessage("phonon state request failed", Backend::Info, this);
783
 
        }
784
 
        m_atEndOfStream = false;
785
 
        break;
786
 
 
787
 
    case Phonon::PlayingState:
788
 
#ifdef __GNUC__
789
 
#warning TODO 4.5
790
 
#endif
791
 
        // TODO 4.5: drop m_resetNeeded completely and use live connections, whatever
792
 
        // those might be.
793
 
        if (m_source.url().host().contains(QLatin1String("last.fm"))) {
794
 
            // Never reset for last.fm as they only allow one connection attempt.
795
 
            // https://bugs.kde.org/show_bug.cgi?id=252649
796
 
            m_resetNeeded = false;
797
 
        }
798
 
        if (m_resetNeeded) {
799
 
            // ### Note this is a workaround and it should really be gracefully
800
 
            // handled by medianode when we implement live connections.
801
 
            // This generally happens if medianodes have been connected after the MediaSource was set
802
 
            // Note that a side-effect of this is that we resend all meta data.
803
 
            gst_element_set_state(m_pipeline, GST_STATE_NULL);
804
 
            m_resetNeeded = false;
805
 
            // Send a source change so the X11 renderer
806
 
            // will re-set the overlay
807
 
            MediaNodeEvent event(MediaNodeEvent::SourceChanged);
808
 
            notify(&event);
809
 
        }
810
 
        m_backend->logMessage("phonon state request: Playing", Backend::Info, this);
811
 
        if (m_atEndOfStream) {
812
 
            m_backend->logMessage("EOS already reached", Backend::Info, this);
813
 
        } else if (currentState == GST_STATE_PLAYING) {
814
 
            m_backend->logMessage("Already playing", Backend::Info, this);
815
 
            changeState(Phonon::PlayingState);
816
 
        } else {
817
 
            GstStateChangeReturn status = gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
818
 
            if (status == GST_STATE_CHANGE_ASYNC) {
819
 
                m_backend->logMessage("Playing state is now pending");
820
 
                m_pendingState = Phonon::PlayingState;
821
 
            } else if (status == GST_STATE_CHANGE_FAILURE) {
822
 
                m_backend->logMessage("phonon state request failed", Backend::Info, this);
823
 
                changeState(Phonon::ErrorState);
824
 
            }
825
 
        }
826
 
        break;
827
 
 
828
 
    case Phonon::ErrorState:
829
 
        m_backend->logMessage("phonon state request : Error", Backend::Warning, this);
830
 
        m_backend->logMessage(QString("Last error : %0").arg(errorString()) , Backend::Warning, this);
831
 
        changeState(Phonon::ErrorState); //immediately set error state
832
 
        break;
833
 
 
834
 
    case Phonon::LoadingState:
835
 
        m_backend->logMessage("phonon state request: Loading", Backend::Info, this);
836
 
        changeState(Phonon::LoadingState);
837
 
        break;
838
 
    }
839
 
}
840
 
 
841
 
/*
842
 
 * Signals that the requested state has completed
843
 
 * by emitting stateChanged and updates the internal state.
844
 
 */
845
 
void MediaObject::changeState(State newstate)
846
 
{
847
 
    if (newstate == m_state)
848
 
        return;
849
 
 
850
 
    Phonon::State oldState = m_state;
851
 
    m_state = newstate; // m_state must be set before emitting, since
852
 
                        // Error state requires that state() will return the new value
853
 
    m_pendingState = newstate;
854
 
 
855
 
    switch (newstate) {
856
 
    case Phonon::PausedState:
857
 
        m_backend->logMessage("phonon state changed: paused", Backend::Info, this);
858
 
        break;
859
 
 
860
 
    case Phonon::BufferingState:
861
 
        m_backend->logMessage("phonon state changed: buffering", Backend::Info, this);
862
 
        break;
863
 
 
864
 
    case Phonon::PlayingState:
865
 
        m_backend->logMessage("phonon state changed: Playing", Backend::Info, this);
866
 
        break;
867
 
 
868
 
    case Phonon::StoppedState:
869
 
        m_backend->logMessage("phonon state changed: Stopped", Backend::Info, this);
870
 
        // We must reset the pipeline when playing again
871
 
        m_resetNeeded = true;
872
 
        m_tickTimer->stop();
873
 
        break;
874
 
 
875
 
    case Phonon::ErrorState:
876
 
        m_loading = false;
877
 
        m_backend->logMessage("phonon state changed : Error", Backend::Info, this);
878
 
        m_backend->logMessage(errorString(), Backend::Warning, this);
879
 
        break;
880
 
 
881
 
    case Phonon::LoadingState:
882
 
        m_backend->logMessage("phonon state changed: Loading", Backend::Info, this);
883
 
        break;
884
 
    }
885
 
 
886
 
    emit stateChanged(newstate, oldState);
887
 
}
888
 
 
889
 
void MediaObject::setError(const QString &errorString, Phonon::ErrorType error)
890
 
{
891
 
    m_backend->logMessage(QString("Phonon error: %1 (code %2)").arg(errorString).arg(error), Backend::Warning);
892
 
    m_errorString = errorString;
893
 
    m_error = error;
894
 
    m_tickTimer->stop();
895
 
 
896
 
    if (error == Phonon::FatalError) {
897
 
        m_hasVideo = false;
898
 
        emit hasVideoChanged(false);
899
 
        gst_element_set_state(m_pipeline, GST_STATE_READY);
900
 
        changeState(Phonon::ErrorState);
901
 
    } else {
902
 
        if (m_loading) //Flag error only after loading has completed
903
 
            m_pendingState = Phonon::ErrorState;
904
 
        else
905
 
            changeState(Phonon::ErrorState);
906
 
    }
907
 
}
908
 
 
909
 
qint64 MediaObject::totalTime() const
910
 
{
911
 
    return m_totalTime;
912
 
}
913
 
 
914
 
qint32 MediaObject::prefinishMark() const
915
 
{
916
 
    return m_prefinishMark;
917
 
}
918
 
 
919
 
qint32 MediaObject::transitionTime() const
920
 
{
921
 
    return m_transitionTime;
922
 
}
923
 
 
924
 
void MediaObject::setTransitionTime(qint32 time)
925
 
{
926
 
    m_transitionTime = time;
927
 
}
928
 
 
929
 
qint64 MediaObject::remainingTime() const
930
 
{
931
 
    return totalTime() - currentTime();
932
 
}
933
 
 
934
 
MediaSource MediaObject::source() const
935
 
{
936
 
    return m_source;
937
 
}
938
 
 
939
 
void MediaObject::setNextSource(const MediaSource &source)
940
 
{
941
 
    if (source.type() == MediaSource::Invalid &&
942
 
        source.type() == MediaSource::Empty)
943
 
        return;
944
 
    m_nextSource = source;
945
 
}
946
 
 
947
 
/**
948
 
 * Update total time value from the pipeline
949
 
 */
950
 
bool MediaObject::updateTotalTime()
951
 
{
952
 
    GstFormat   format = GST_FORMAT_TIME;
953
 
    gint64 duration = 0;
954
 
    if (gst_element_query_duration (GST_ELEMENT(m_pipeline), &format, &duration)) {
955
 
        setTotalTime(duration / GST_MSECOND);
956
 
        return true;
957
 
    }
958
 
    return false;
959
 
}
960
 
 
961
 
/**
962
 
 * Checks if the current source is seekable
963
 
 */
964
 
void MediaObject::updateSeekable()
965
 
{
966
 
    if (!isValid())
967
 
        return;
968
 
 
969
 
    GstQuery *query;
970
 
    gboolean result;
971
 
    gint64 start, stop;
972
 
    query = gst_query_new_seeking(GST_FORMAT_TIME);
973
 
    result = gst_element_query (m_pipeline, query);
974
 
    if (result) {
975
 
        gboolean seekable;
976
 
        GstFormat format;
977
 
        gst_query_parse_seeking (query, &format, &seekable, &start, &stop);
978
 
 
979
 
        if (m_seekable != seekable) {
980
 
            m_seekable = seekable;
981
 
            emit seekableChanged(m_seekable);
982
 
        }
983
 
 
984
 
        if (m_seekable)
985
 
            m_backend->logMessage("Stream is seekable", Backend::Info, this);
986
 
        else
987
 
            m_backend->logMessage("Stream is non-seekable", Backend::Info, this);
988
 
    } else {
989
 
        m_backend->logMessage("updateSeekable query failed", Backend::Info, this);
990
 
    }
991
 
    gst_query_unref (query);
992
 
}
993
 
 
994
 
qint64 MediaObject::getPipelinePos() const
995
 
{
996
 
    Q_ASSERT(m_pipeline);
997
 
 
998
 
    // Note some formats (usually mpeg) do not allow us to accurately seek to the
999
 
    // beginning or end of the file so we 'fake' it here rather than exposing the front end to potential issues.
1000
 
    if (m_atEndOfStream)
1001
 
        return totalTime();
1002
 
    if (m_atStartOfStream)
1003
 
        return 0;
1004
 
    if (m_posAtSeek >= 0)
1005
 
        return m_posAtSeek;
1006
 
 
1007
 
    gint64 pos = 0;
1008
 
    GstFormat format = GST_FORMAT_TIME;
1009
 
    gst_element_query_position (GST_ELEMENT(m_pipeline), &format, &pos);
1010
 
    return (pos / GST_MSECOND);
1011
 
}
1012
 
 
1013
 
/*
1014
 
 * Internal method to set a new total time for the media object
1015
 
 */
1016
 
void MediaObject::setTotalTime(qint64 newTime)
1017
 
{
1018
 
 
1019
 
    if (newTime == m_totalTime)
1020
 
        return;
1021
 
 
1022
 
    m_totalTime = newTime;
1023
 
 
1024
 
    emit totalTimeChanged(m_totalTime);
1025
 
}
1026
 
 
1027
 
/*
1028
 
 * !reimp
1029
 
 */
1030
 
void MediaObject::setSource(const MediaSource &source)
1031
 
{
1032
 
    if (!isValid())
1033
 
        return;
1034
 
 
1035
 
    m_installingPlugin = false;
1036
 
 
1037
 
    // We have to reset the state completely here, otherwise
1038
 
    // remnants of the old pipeline can result in strangenes
1039
 
    // such as failing duration queries etc
1040
 
    GstState state;
1041
 
    gst_element_set_state(m_pipeline, GST_STATE_NULL);
1042
 
    gst_element_get_state(m_pipeline, &state, NULL, 2000);
1043
 
 
1044
 
    m_source = source;
1045
 
    emit currentSourceChanged(m_source);
1046
 
    m_previousTickTime = -1;
1047
 
    m_missingCodecs.clear();
1048
 
 
1049
 
    // Go into to loading state
1050
 
    changeState(Phonon::LoadingState);
1051
 
    m_loading = true;
1052
 
    // IMPORTANT: Honor the m_resetNeeded flag as it currently stands.
1053
 
    // See https://qa.mandriva.com/show_bug.cgi?id=56807
1054
 
    //m_resetNeeded = false;
1055
 
    m_resumeState = false;
1056
 
    m_pendingState = Phonon::StoppedState;
1057
 
 
1058
 
     // Make sure we start out unconnected
1059
 
    if (GST_ELEMENT_PARENT(m_audioGraph))
1060
 
        gst_bin_remove(GST_BIN(m_pipeline), m_audioGraph);
1061
 
    if (GST_ELEMENT_PARENT(m_videoGraph))
1062
 
        gst_bin_remove(GST_BIN(m_pipeline), m_videoGraph);
1063
 
 
1064
 
    // Clear any existing errors
1065
 
    m_aboutToFinishEmitted = false;
1066
 
    m_error = NoError;
1067
 
    m_errorString.clear();
1068
 
 
1069
 
    m_bufferPercent = 0;
1070
 
    m_prefinishMarkReachedNotEmitted = true;
1071
 
    m_aboutToFinishEmitted = false;
1072
 
    m_hasAudio = false;
1073
 
    m_videoStreamFound = false;
1074
 
    setTotalTime(-1);
1075
 
    m_atEndOfStream = false;
1076
 
 
1077
 
    m_availableTitles = 0;
1078
 
    m_pendingTitle = 1;
1079
 
    m_currentTitle = 1;
1080
 
 
1081
 
    // Clear existing meta tags
1082
 
    m_metaData.clear();
1083
 
    m_isStream = false;
1084
 
 
1085
 
    switch (source.type()) {
1086
 
    case MediaSource::Url: {
1087
 
            if (!createPipefromURL(source.url()))
1088
 
                setError(tr("Could not open media source."));
1089
 
        }
1090
 
        break;
1091
 
 
1092
 
    case MediaSource::LocalFile: {
1093
 
            if (!createPipefromURL(QUrl::fromLocalFile(source.fileName())))
1094
 
                setError(tr("Could not open media source."));
1095
 
        }
1096
 
        break;
1097
 
 
1098
 
    case MediaSource::Invalid:
1099
 
        setError(tr("Invalid source type."), Phonon::NormalError);
1100
 
        break;
1101
 
 
1102
 
    case MediaSource::Empty:
1103
 
        break;
1104
 
 
1105
 
    case MediaSource::Stream:
1106
 
        if (!createPipefromStream(source))
1107
 
            setError(tr("Could not open media source."));
1108
 
        break;
1109
 
 
1110
 
    case MediaSource::Disc:
1111
 
        {
1112
 
            QString mediaUrl;
1113
 
            switch (source.discType()) {
1114
 
            case Phonon::NoDisc:
1115
 
                qWarning() << "I should never get to see a MediaSource that is a disc but doesn't specify which one";
1116
 
                return;
1117
 
            case Phonon::Cd:  // CD tracks can be specified by setting the url in the following way uri=cdda:4
1118
 
                mediaUrl = QLatin1String("cdda://");
1119
 
                break;
1120
 
            case Phonon::Dvd:
1121
 
                mediaUrl = QLatin1String("dvd://");
1122
 
                break;
1123
 
            case Phonon::Vcd:
1124
 
                mediaUrl = QLatin1String("vcd://");
1125
 
                break;
1126
 
            default:
1127
 
                qWarning() <<  "media " << source.discType() << " not implemented";
1128
 
                return;
1129
 
            }
1130
 
            if (mediaUrl.isEmpty() || !createPipefromURL(QUrl(mediaUrl)))
1131
 
                setError(tr("Could not open media source."));
1132
 
        }
1133
 
        break;
1134
 
 
1135
 
        case MediaSource::CaptureDevice:
1136
 
        if (!createPipefromDevice(source))
1137
 
            setError(tr("Could not open capture device."));
1138
 
        break;
1139
 
 
1140
 
 
1141
 
    default:
1142
 
        m_backend->logMessage("Source type not currently supported", Backend::Warning, this);
1143
 
        setError(tr("Could not open media source."), Phonon::NormalError);
1144
 
        break;
1145
 
    }
1146
 
 
1147
 
    MediaNodeEvent event(MediaNodeEvent::SourceChanged);
1148
 
    notify(&event);
1149
 
 
1150
 
    // We need to link this node to ensure that fake sinks are connected
1151
 
    // before loading, otherwise the stream will be blocked
1152
 
    link();
1153
 
    beginLoad();
1154
 
}
1155
 
 
1156
 
void MediaObject::beginLoad()
1157
 
{
1158
 
    if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) {
1159
 
        m_backend->logMessage("Begin source load", Backend::Info, this);
1160
 
    } else {
1161
 
        setError(tr("Could not open media source."));
1162
 
    }
1163
 
}
1164
 
 
1165
 
// Called when we are ready to leave the loading state
1166
 
void MediaObject::loadingComplete()
1167
 
{
1168
 
    if (m_videoStreamFound) {
1169
 
        MediaNodeEvent event(MediaNodeEvent::VideoAvailable);
1170
 
        notify(&event);
1171
 
    }
1172
 
    getStreamInfo();
1173
 
    m_loading = false;
1174
 
 
1175
 
    setState(m_pendingState);
1176
 
    emit metaDataChanged(m_metaData);
1177
 
}
1178
 
 
1179
 
void MediaObject::getStreamInfo()
1180
 
{
1181
 
    updateSeekable();
1182
 
    updateTotalTime();
1183
 
 
1184
 
    if (m_videoStreamFound != m_hasVideo) {
1185
 
        m_hasVideo = m_videoStreamFound;
1186
 
        emit hasVideoChanged(m_hasVideo);
1187
 
    }
1188
 
 
1189
 
    if (m_source.discType() == Phonon::Cd) {
1190
 
        gint64 titleCount;
1191
 
        GstFormat format = gst_format_get_by_nick("track");
1192
 
        if (gst_element_query_duration (m_pipeline, &format, &titleCount)) {
1193
 
        //check if returned format is still "track",
1194
 
        //gstreamer sometimes returns the total time, if tracks information is not available.
1195
 
            if (qstrcmp(gst_format_get_name(format), "track") == 0)  {
1196
 
                int oldAvailableTitles = m_availableTitles;
1197
 
                m_availableTitles = (int)titleCount;
1198
 
                if (m_availableTitles != oldAvailableTitles) {
1199
 
                    emit availableTitlesChanged(m_availableTitles);
1200
 
                    m_backend->logMessage(QString("Available titles changed: %0").arg(m_availableTitles), Backend::Info, this);
1201
 
                }
1202
 
            }
1203
 
        }
1204
 
    }
1205
 
}
1206
 
 
1207
 
void MediaObject::setPrefinishMark(qint32 newPrefinishMark)
1208
 
{
1209
 
    m_prefinishMark = newPrefinishMark;
1210
 
    if (currentTime() < totalTime() - m_prefinishMark) // not about to finish
1211
 
        m_prefinishMarkReachedNotEmitted = true;
1212
 
}
1213
 
 
1214
 
void MediaObject::pause()
1215
 
{
1216
 
    m_backend->logMessage("pause()", Backend::Info, this);
1217
 
    if (state() != Phonon::PausedState)
1218
 
        setState(Phonon::PausedState);
1219
 
    m_resumeState = false;
1220
 
}
1221
 
 
1222
 
void MediaObject::stop()
1223
 
{
1224
 
    if (state() != Phonon::StoppedState) {
1225
 
        setState(Phonon::StoppedState);
1226
 
        m_prefinishMarkReachedNotEmitted = true;
1227
 
    }
1228
 
    m_resumeState = false;
1229
 
}
1230
 
 
1231
 
void MediaObject::seek(qint64 time)
1232
 
{
1233
 
    if (!isValid())
1234
 
        return;
1235
 
 
1236
 
    if (isSeekable()) {
1237
 
        switch (state()) {
1238
 
        case Phonon::PlayingState:
1239
 
        case Phonon::StoppedState:
1240
 
        case Phonon::PausedState:
1241
 
        case Phonon::BufferingState:
1242
 
            m_backend->logMessage(QString("Seek to pos %0").arg(time), Backend::Info, this);
1243
 
 
1244
 
            if (time <= 0)
1245
 
                m_atStartOfStream = true;
1246
 
            else
1247
 
                m_atStartOfStream = false;
1248
 
 
1249
 
            m_posAtSeek = getPipelinePos();
1250
 
            m_tickTimer->stop();
1251
 
 
1252
 
            if (gst_element_seek(m_pipeline, 1.0, GST_FORMAT_TIME,
1253
 
                                 GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET,
1254
 
                                 time * GST_MSECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
1255
 
            break;
1256
 
        case Phonon::LoadingState:
1257
 
        case Phonon::ErrorState:
1258
 
            return;
1259
 
        }
1260
 
 
1261
 
        quint64 current = currentTime();
1262
 
        quint64 total = totalTime();
1263
 
 
1264
 
        if (current < total - m_prefinishMark)
1265
 
            m_prefinishMarkReachedNotEmitted = true;
1266
 
        if (current < total - ABOUT_TO_FINNISH_TIME)
1267
 
            m_aboutToFinishEmitted = false;
1268
 
        m_atEndOfStream = false;
1269
 
    }
1270
 
}
1271
 
 
1272
 
void MediaObject::emitTick()
1273
 
{
1274
 
    if (m_resumeState) {
1275
 
        return;
1276
 
    }
1277
 
 
1278
 
    qint64 currentTime = getPipelinePos();
1279
 
    qint64 totalTime = m_totalTime;
1280
 
 
1281
 
    if (m_tickInterval > 0 && currentTime != m_previousTickTime) {
1282
 
        emit tick(currentTime);
1283
 
        m_previousTickTime = currentTime;
1284
 
    }
1285
 
    if (m_state == Phonon::PlayingState) {
1286
 
        if (currentTime >= totalTime - m_prefinishMark) {
1287
 
            if (m_prefinishMarkReachedNotEmitted) {
1288
 
                m_prefinishMarkReachedNotEmitted = false;
1289
 
                emit prefinishMarkReached(totalTime - currentTime);
1290
 
            }
1291
 
        }
1292
 
        // Prepare load of next source
1293
 
        if (currentTime >= totalTime - ABOUT_TO_FINNISH_TIME) {
1294
 
            if (m_source.type() == MediaSource::Disc &&
1295
 
                m_autoplayTitles &&
1296
 
                m_availableTitles > 1 &&
1297
 
                m_currentTitle < m_availableTitles) {
1298
 
                m_aboutToFinishEmitted = false;
1299
 
            } else if (!m_aboutToFinishEmitted) {
1300
 
                m_aboutToFinishEmitted = true; // track is about to finish
1301
 
                emit aboutToFinish();
1302
 
            }
1303
 
        }
1304
 
    }
1305
 
}
1306
 
 
1307
 
/*
1308
 
 * Used to iterate through the gst_tag_list and extract values
1309
 
 */
1310
 
void foreach_tag_function(const GstTagList *list, const gchar *tag, gpointer user_data)
1311
 
{
1312
 
    TagMap *newData = static_cast<TagMap *>(user_data);
1313
 
    QString value;
1314
 
    GType type = gst_tag_get_type(tag);
1315
 
    switch (type) {
1316
 
    case G_TYPE_STRING: {
1317
 
            char *str = 0;
1318
 
            gst_tag_list_get_string(list, tag, &str);
1319
 
            value = QString::fromUtf8(str);
1320
 
            g_free(str);
1321
 
        }
1322
 
        break;
1323
 
 
1324
 
    case G_TYPE_BOOLEAN: {
1325
 
            int bval;
1326
 
            gst_tag_list_get_boolean(list, tag, &bval);
1327
 
            value = QString::number(bval);
1328
 
        }
1329
 
        break;
1330
 
 
1331
 
    case G_TYPE_INT: {
1332
 
            int ival;
1333
 
            gst_tag_list_get_int(list, tag, &ival);
1334
 
            value = QString::number(ival);
1335
 
        }
1336
 
        break;
1337
 
 
1338
 
    case G_TYPE_UINT: {
1339
 
            unsigned int uival;
1340
 
            gst_tag_list_get_uint(list, tag, &uival);
1341
 
            value = QString::number(uival);
1342
 
        }
1343
 
        break;
1344
 
 
1345
 
    case G_TYPE_FLOAT: {
1346
 
            float fval;
1347
 
            gst_tag_list_get_float(list, tag, &fval);
1348
 
            value = QString::number(fval);
1349
 
        }
1350
 
        break;
1351
 
 
1352
 
    case G_TYPE_DOUBLE: {
1353
 
            double dval;
1354
 
            gst_tag_list_get_double(list, tag, &dval);
1355
 
            value = QString::number(dval);
1356
 
        }
1357
 
        break;
1358
 
 
1359
 
    default:
1360
 
        //qDebug("Unsupported tag type: %s", g_type_name(type));
1361
 
        break;
1362
 
    }
1363
 
 
1364
 
    QString key = QString(tag).toUpper();
1365
 
    QString currVal = newData->value(key);
1366
 
    if (!value.isEmpty() && !(newData->contains(key) && currVal == value))
1367
 
        newData->insert(key, value);
1368
 
}
1369
 
 
1370
 
/**
1371
 
 * Triggers playback after a song has completed in the current media queue
1372
 
 */
1373
 
void MediaObject::beginPlay()
1374
 
{
1375
 
    setSource(m_nextSource);
1376
 
    m_nextSource = MediaSource();
1377
 
    m_pendingState = Phonon::PlayingState;
1378
 
}
1379
 
 
1380
 
/**
1381
 
 * Handle the GST_MESSAGE_ERROR message
1382
 
 */
1383
 
void MediaObject::handleErrorMessage(GstMessage *gstMessage)
1384
 
{
1385
 
    gchar *debug;
1386
 
    GError *err;
1387
 
    QString logMessage;
1388
 
    gst_message_parse_error (gstMessage, &err, &debug);
1389
 
    gchar *errorMessage = gst_error_get_message (err->domain, err->code);
1390
 
    logMessage.sprintf("Error: %s Message: %s (%s) Code:%d", debug, err->message, errorMessage, err->code);
1391
 
    m_backend->logMessage(logMessage, Backend::Warning);
1392
 
    g_free(errorMessage);
1393
 
    g_free (debug);
1394
 
 
1395
 
    if (err->domain == GST_RESOURCE_ERROR) {
1396
 
        if (err->code == GST_RESOURCE_ERROR_NOT_FOUND) {
1397
 
            setError(tr("Could not locate media source."), Phonon::FatalError);
1398
 
        } else if (err->code == GST_RESOURCE_ERROR_OPEN_READ) {
1399
 
            setError(tr("Could not open media source."), Phonon::FatalError);
1400
 
        } else if (err->code == GST_RESOURCE_ERROR_BUSY) {
1401
 
           // We need to check if this comes from an audio device by looking at sink caps
1402
 
           GstPad* sinkPad = gst_element_get_static_pad(GST_ELEMENT(gstMessage->src), "sink");
1403
 
           if (sinkPad) {
1404
 
                GstCaps *caps = gst_pad_get_caps (sinkPad);
1405
 
                GstStructure *str = gst_caps_get_structure (caps, 0);
1406
 
                if (g_strrstr (gst_structure_get_name (str), "audio"))
1407
 
                    setError(tr("Could not open audio device. The device is already in use."), Phonon::NormalError);
1408
 
                else
1409
 
                    setError(err->message, Phonon::FatalError);
1410
 
                gst_caps_unref (caps);
1411
 
                gst_object_unref (sinkPad);
1412
 
           }
1413
 
       } else {
1414
 
            setError(QString(err->message), Phonon::FatalError);
1415
 
       }
1416
 
   } else if (err->domain == GST_CORE_ERROR) {
1417
 
        switch(err->code) {
1418
 
            case GST_CORE_ERROR_MISSING_PLUGIN:
1419
 
                installMissingCodecs();
1420
 
                break;
1421
 
            default:
1422
 
                break;
1423
 
        }
1424
 
   } else if (err->domain == GST_STREAM_ERROR) {
1425
 
        switch (err->code) {
1426
 
        case GST_STREAM_ERROR_CODEC_NOT_FOUND:
1427
 
            installMissingCodecs();
1428
 
            break;
1429
 
        case GST_STREAM_ERROR_TYPE_NOT_FOUND:
1430
 
            if (!m_installingPlugin)
1431
 
                setError(tr("Could not find media type."), Phonon::FatalError);
1432
 
            break;
1433
 
        case GST_STREAM_ERROR_WRONG_TYPE:
1434
 
            setError(tr("Wrong media type encountered in GStreamer pipeline."), Phonon::FatalError);
1435
 
            break;
1436
 
        case GST_STREAM_ERROR_DECODE:
1437
 
            setError(tr("Could not decode media."), Phonon::FatalError);
1438
 
            break;
1439
 
        case GST_STREAM_ERROR_ENCODE:
1440
 
            setError(tr("Could not encode media."), Phonon::FatalError);
1441
 
            break;
1442
 
        case GST_STREAM_ERROR_MUX:
1443
 
            setError(tr("Could not demux media."), Phonon::FatalError);
1444
 
            break;
1445
 
        case GST_STREAM_ERROR_DECRYPT:
1446
 
            setError(tr("Could not decrypt media."), Phonon::FatalError);
1447
 
            break;
1448
 
        case GST_STREAM_ERROR_DECRYPT_NOKEY:
1449
 
            setError(tr("No suitable decryption key found."), Phonon::FatalError);
1450
 
            break;
1451
 
        default:
1452
 
            if (!m_installingPlugin)
1453
 
                setError(tr("Could not open media source."), Phonon::FatalError);
1454
 
            break;
1455
 
        }
1456
 
   } else {
1457
 
        setError(QString(err->message), Phonon::FatalError);
1458
 
    }
1459
 
    g_error_free (err);
1460
 
}
1461
 
 
1462
 
void MediaObject::handleWarningMessage(GstMessage *gstMessage)
1463
 
{
1464
 
    gchar *debug;
1465
 
    GError *err;
1466
 
    gst_message_parse_warning(gstMessage, &err, &debug);
1467
 
    QString msgString;
1468
 
    msgString.sprintf("Warning: %s\nMessage:%s", debug, err->message);
1469
 
    m_backend->logMessage(msgString, Backend::Warning);
1470
 
    g_free (debug);
1471
 
    g_error_free (err);
1472
 
}
1473
 
 
1474
 
/**
1475
 
 * Handles GST_MESSAGE_BUFFERING messages
1476
 
 */
1477
 
void MediaObject::handleBufferingMessage(GstMessage *gstMessage)
1478
 
{
1479
 
    gint percent = 0;
1480
 
    gst_structure_get_int (gstMessage->structure, "buffer-percent", &percent); //gst_message_parse_buffering was introduced in 0.10.11
1481
 
 
1482
 
    if (m_bufferPercent != percent) {
1483
 
        emit bufferStatus(percent);
1484
 
        m_backend->logMessage(QString("Stream buffering %0").arg(percent), Backend::Debug, this);
1485
 
        m_bufferPercent = percent;
1486
 
    }
1487
 
 
1488
 
    if (m_state != Phonon::BufferingState)
1489
 
        emit stateChanged(m_state, Phonon::BufferingState);
1490
 
    else if (percent == 100)
1491
 
        emit stateChanged(Phonon::BufferingState, m_state);
1492
 
}
1493
 
 
1494
 
/**
1495
 
 * Handle the GST_MESSAGE_STATE_CHANGED message
1496
 
 */
1497
 
void MediaObject::handleStateMessage(GstMessage *gstMessage)
1498
 
{
1499
 
    GstState oldState;
1500
 
    GstState newState;
1501
 
    GstState pendingState;
1502
 
    gst_message_parse_state_changed (gstMessage, &oldState, &newState, &pendingState);
1503
 
 
1504
 
    if (gstMessage->src != GST_OBJECT(m_pipeline)) {
1505
 
        m_backend->logMessage("State changed from "+GstHelper::stateName(oldState)+" to "+GstHelper::stateName(newState), Backend::Debug, this);
1506
 
        return;
1507
 
    }
1508
 
 
1509
 
    if (newState == pendingState)
1510
 
        return;
1511
 
 
1512
 
    m_posAtSeek = -1;
1513
 
 
1514
 
    switch (newState) {
1515
 
 
1516
 
    case GST_STATE_PLAYING :
1517
 
        m_atStartOfStream = false;
1518
 
        m_backend->logMessage("gstreamer: pipeline state set to playing", Backend::Info, this);
1519
 
        m_tickTimer->start();
1520
 
        changeState(Phonon::PlayingState);
1521
 
        if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) {
1522
 
            setTrack(m_pendingTitle);
1523
 
        }
1524
 
        if (m_resumeState && m_oldState == Phonon::PlayingState) {
1525
 
            seek(m_oldPos);
1526
 
            m_resumeState = false;
1527
 
        }
1528
 
        break;
1529
 
 
1530
 
    case GST_STATE_NULL:
1531
 
        m_backend->logMessage("gstreamer: pipeline state set to null", Backend::Info, this);
1532
 
        m_tickTimer->stop();
1533
 
        break;
1534
 
 
1535
 
    case GST_STATE_PAUSED :
1536
 
        m_backend->logMessage("gstreamer: pipeline state set to paused", Backend::Info, this);
1537
 
        m_tickTimer->start();
1538
 
        if (state() == Phonon::LoadingState) {
1539
 
            loadingComplete();
1540
 
        } else if (m_resumeState && m_oldState == Phonon::PausedState) {
1541
 
            changeState(Phonon::PausedState);
1542
 
            m_resumeState = false;
1543
 
            break;
1544
 
        } else {
1545
 
            // A lot of autotests can break if we allow all paused changes through.
1546
 
            if (m_pendingState == Phonon::PausedState) {
1547
 
                changeState(Phonon::PausedState);
1548
 
            }
1549
 
        }
1550
 
        break;
1551
 
 
1552
 
    case GST_STATE_READY :
1553
 
        if (!m_loading && m_pendingState == Phonon::StoppedState)
1554
 
            changeState(Phonon::StoppedState);
1555
 
        m_backend->logMessage("gstreamer: pipeline state set to ready", Backend::Debug, this);
1556
 
        m_tickTimer->stop();
1557
 
        if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) {
1558
 
            setTrack(m_pendingTitle);
1559
 
        }
1560
 
        break;
1561
 
 
1562
 
    case GST_STATE_VOID_PENDING :
1563
 
        m_backend->logMessage("gstreamer: pipeline state set to pending (void)", Backend::Debug, this);
1564
 
        m_tickTimer->stop();
1565
 
        break;
1566
 
    }
1567
 
}
1568
 
 
1569
 
/**
1570
 
 * Handle the GST_MESSAGE_TAG message type
1571
 
 */
1572
 
void MediaObject::handleTagMessage(GstMessage *msg)
1573
 
{
1574
 
    GstTagList* tag_list = 0;
1575
 
    gst_message_parse_tag(msg, &tag_list);
1576
 
    if (tag_list) {
1577
 
        TagMap newTags;
1578
 
        gst_tag_list_foreach (tag_list, &foreach_tag_function, &newTags);
1579
 
        gst_tag_list_free(tag_list);
1580
 
 
1581
 
        // Determine if we should no fake the album/artist tags.
1582
 
        // This is a little confusing as we want to fake it on initial
1583
 
        // connection where title, album and artist are all missing.
1584
 
        // There are however times when we get just other information,
1585
 
        // e.g. codec, and so we want to only do clever stuff if we
1586
 
        // have a commonly available tag (ORGANIZATION) or we have a
1587
 
        // change in title
1588
 
        bool fake_it =
1589
 
           (m_isStream
1590
 
            && ((!newTags.contains("TITLE")
1591
 
                 && newTags.contains("ORGANIZATION"))
1592
 
                || (newTags.contains("TITLE")
1593
 
                    && m_metaData.value("TITLE") != newTags.value("TITLE")))
1594
 
            && !newTags.contains("ALBUM")
1595
 
            && !newTags.contains("ARTIST"));
1596
 
 
1597
 
        TagMap oldMap = m_metaData; // Keep a copy of the old one for reference
1598
 
 
1599
 
        // Now we've checked the new data, append any new meta tags to the existing tag list
1600
 
        // We cannot use TagMap::iterator as this is a multimap and when streaming data
1601
 
        // could in theory be lost.
1602
 
        QList<QString> keys = newTags.keys();
1603
 
        for (QList<QString>::iterator i = keys.begin(); i != keys.end(); ++i) {
1604
 
            QString key = *i;
1605
 
            if (m_isStream) {
1606
 
                // If we're streaming, we need to remove data in m_metaData
1607
 
                // in order to stop it filling up indefinitely (as it's a multimap)
1608
 
                m_metaData.remove(key);
1609
 
            }
1610
 
            QList<QString> values = newTags.values(key);
1611
 
            for (QList<QString>::iterator j = values.begin(); j != values.end(); ++j) {
1612
 
                QString value = *j;
1613
 
                QString currVal = m_metaData.value(key);
1614
 
                if (!m_metaData.contains(key) || currVal != value) {
1615
 
                    m_metaData.insert(key, value);
1616
 
                }
1617
 
            }
1618
 
        }
1619
 
 
1620
 
        m_backend->logMessage("Meta tags found", Backend::Info, this);
1621
 
        if (oldMap != m_metaData) {
1622
 
            // This is a bit of a hack to ensure that stream metadata is
1623
 
            // returned. We get as much as we can from the Shoutcast server's
1624
 
            // StreamTitle= header. If further info is decoded from the stream
1625
 
            // itself later, then it will overwrite this info.
1626
 
            if (m_isStream && fake_it) {
1627
 
                m_metaData.remove("ALBUM");
1628
 
                m_metaData.remove("ARTIST");
1629
 
 
1630
 
                // Detect whether we want to "fill in the blanks"
1631
 
                QString str;
1632
 
                if (m_metaData.contains("TITLE"))
1633
 
                {
1634
 
                    str = m_metaData.value("TITLE");
1635
 
                    int splitpoint;
1636
 
                    // Check to see if our title matches "%s - %s"
1637
 
                    // Where neither %s are empty...
1638
 
                    if ((splitpoint = str.indexOf(" - ")) > 0
1639
 
                        && str.size() > (splitpoint+3)) {
1640
 
                        m_metaData.insert("ARTIST", str.left(splitpoint));
1641
 
                        m_metaData.replace("TITLE", str.mid(splitpoint+3));
1642
 
                    }
1643
 
                } else {
1644
 
                    str = m_metaData.value("GENRE");
1645
 
                    if (!str.isEmpty())
1646
 
                        m_metaData.insert("TITLE", str);
1647
 
                    else
1648
 
                        m_metaData.insert("TITLE", "Streaming Data");
1649
 
                }
1650
 
                if (!m_metaData.contains("ARTIST")) {
1651
 
                    str = m_metaData.value("LOCATION");
1652
 
                    if (!str.isEmpty())
1653
 
                        m_metaData.insert("ARTIST", str);
1654
 
                    else
1655
 
                        m_metaData.insert("ARTIST", "Streaming Data");
1656
 
                }
1657
 
                str = m_metaData.value("ORGANIZATION");
1658
 
                if (!str.isEmpty())
1659
 
                    m_metaData.insert("ALBUM", str);
1660
 
                else
1661
 
                    m_metaData.insert("ALBUM", "Streaming Data");
1662
 
            }
1663
 
            // As we manipulate the title, we need to recompare
1664
 
            // oldMap and m_metaData here...
1665
 
            if (oldMap != m_metaData && !m_loading)
1666
 
                emit metaDataChanged(m_metaData);
1667
 
        }
1668
 
    }
1669
 
}
1670
 
 
1671
 
/**
1672
 
 * Handle element messages
1673
 
 */
1674
 
void MediaObject::handleElementMessage(GstMessage *gstMessage)
1675
 
{
1676
 
    const GstStructure *gstStruct = gst_message_get_structure(gstMessage); //do not free this
1677
 
    if (g_strrstr (gst_structure_get_name (gstStruct), "prepare-xwindow-id")) {
1678
 
        MediaNodeEvent videoHandleEvent(MediaNodeEvent::VideoHandleRequest);
1679
 
        notify(&videoHandleEvent);
1680
 
    }
1681
 
}
1682
 
 
1683
 
void MediaObject::handleEOSMessage(GstMessage *gstMessage)
1684
 
{
1685
 
    Q_UNUSED(gstMessage);
1686
 
    m_backend->logMessage("EOS received", Backend::Info, this);
1687
 
    handleEndOfStream();
1688
 
}
1689
 
 
1690
 
void MediaObject::handleDurationMessage(GstMessage *gstMessage)
1691
 
{
1692
 
    Q_UNUSED(gstMessage);
1693
 
    m_backend->logMessage("GST_MESSAGE_DURATION", Backend::Debug, this);
1694
 
    updateTotalTime();
1695
 
}
1696
 
 
1697
 
/**
1698
 
 * Handle GStreamer bus messages
1699
 
 */
1700
 
void MediaObject::handleBusMessage(const Message &message)
1701
 
{
1702
 
    if (!isValid())
1703
 
        return;
1704
 
 
1705
 
    GstMessage *gstMessage = message.rawMessage();
1706
 
    Q_ASSERT(m_pipeline);
1707
 
 
1708
 
    if (m_backend->debugLevel() >= Backend::Debug) {
1709
 
        int type = GST_MESSAGE_TYPE(gstMessage);
1710
 
        gchar* name = gst_element_get_name(gstMessage->src);
1711
 
        QString msgString = QString("Bus: %0 (%1)").arg(gst_message_type_get_name ((GstMessageType)type)).arg(name);
1712
 
        g_free(name);
1713
 
        m_backend->logMessage(msgString, Backend::Debug, this);
1714
 
    }
1715
 
 
1716
 
    switch (GST_MESSAGE_TYPE (gstMessage)) {
1717
 
 
1718
 
    case GST_MESSAGE_EOS:
1719
 
        handleEOSMessage(gstMessage);
1720
 
        break;
1721
 
 
1722
 
    case GST_MESSAGE_TAG:
1723
 
        handleTagMessage(gstMessage);
1724
 
        break;
1725
 
 
1726
 
    case GST_MESSAGE_STATE_CHANGED :
1727
 
        handleStateMessage(gstMessage);
1728
 
        break;
1729
 
 
1730
 
    case GST_MESSAGE_ERROR:
1731
 
        handleErrorMessage(gstMessage);
1732
 
        break;
1733
 
 
1734
 
    case GST_MESSAGE_WARNING:
1735
 
        handleWarningMessage(gstMessage);
1736
 
        break;
1737
 
 
1738
 
    case GST_MESSAGE_ELEMENT:
1739
 
        handleElementMessage(gstMessage);
1740
 
        break;
1741
 
 
1742
 
    case GST_MESSAGE_DURATION:
1743
 
        handleDurationMessage(gstMessage);
1744
 
        break;
1745
 
 
1746
 
    case GST_MESSAGE_BUFFERING:
1747
 
        handleBufferingMessage(gstMessage);
1748
 
        break;
1749
 
        //case GST_MESSAGE_INFO:
1750
 
        //case GST_MESSAGE_STREAM_STATUS:
1751
 
        //case GST_MESSAGE_CLOCK_PROVIDE:
1752
 
        //case GST_MESSAGE_NEW_CLOCK:
1753
 
        //case GST_MESSAGE_STEP_DONE:
1754
 
        //case GST_MESSAGE_LATENCY: only from 0.10.12
1755
 
        //case GST_MESSAGE_ASYNC_DONE: only from 0.10.13
1756
 
    default:
1757
 
        break;
1758
 
    }
1759
 
 
1760
 
#if GST_VERSION >= GST_VERSION_CHECK(0,10,23,0)
1761
 
    switch (gst_navigation_message_get_type(gstMessage)) {
1762
 
    case GST_NAVIGATION_MESSAGE_MOUSE_OVER: {
1763
 
        gboolean active;
1764
 
        if (!gst_navigation_message_parse_mouse_over(gstMessage, &active)) {
1765
 
            break;
1766
 
        }
1767
 
        MediaNodeEvent mouseOverEvent(MediaNodeEvent::VideoMouseOver, &active);
1768
 
        notify(&mouseOverEvent);
1769
 
        break;
1770
 
    }
1771
 
    default:
1772
 
        break;
1773
 
    }
1774
 
#endif // GST_VERSION
1775
 
}
1776
 
 
1777
 
void MediaObject::handleEndOfStream()
1778
 
{
1779
 
    // If the stream is not seekable ignore
1780
 
    // otherwise chained radio broadcasts would stop
1781
 
 
1782
 
    if (m_atEndOfStream)
1783
 
        return;
1784
 
 
1785
 
    if (!m_seekable)
1786
 
        m_atEndOfStream = true;
1787
 
 
1788
 
    if (m_source.type() == MediaSource::Disc &&
1789
 
        m_autoplayTitles &&
1790
 
        m_availableTitles > 1 &&
1791
 
        m_currentTitle < m_availableTitles) {
1792
 
        _iface_setCurrentTitle(m_currentTitle + 1);
1793
 
        return;
1794
 
    }
1795
 
 
1796
 
    if (m_nextSource.type() != MediaSource::Invalid
1797
 
        && m_nextSource.type() != MediaSource::Empty) {  // We only emit finish when the queue is actually empty
1798
 
        QTimer::singleShot (qMax(0, transitionTime()), this, SLOT(beginPlay()));
1799
 
    } else {
1800
 
        m_pendingState = Phonon::PausedState;
1801
 
        emit finished();
1802
 
        if (!m_seekable) {
1803
 
            setState(Phonon::StoppedState);
1804
 
            // Note the behavior for live streams is not properly defined
1805
 
            // But since we cant seek to 0, we don't have much choice other than stopping
1806
 
            // the stream
1807
 
        } else {
1808
 
            // Only emit paused if the finished signal
1809
 
            // did not result in a new state
1810
 
            if (m_pendingState == Phonon::PausedState)
1811
 
                setState(m_pendingState);
1812
 
        }
1813
 
    }
1814
 
}
1815
 
 
1816
 
void MediaObject::invalidateGraph()
1817
 
{
1818
 
    m_resetNeeded = true;
1819
 
    if (m_state == Phonon::PlayingState || m_state == Phonon::PausedState) {
1820
 
        changeState(Phonon::StoppedState);
1821
 
    }
1822
 
}
1823
 
 
1824
 
// Notifes the pipeline about state changes in the media object
1825
 
void MediaObject::notifyStateChange(Phonon::State newstate, Phonon::State oldstate)
1826
 
{
1827
 
    Q_UNUSED(oldstate);
1828
 
    MediaNodeEvent event(MediaNodeEvent::StateChanged, &newstate);
1829
 
    notify(&event);
1830
 
}
1831
 
 
1832
 
#ifndef QT_NO_PHONON_MEDIACONTROLLER
1833
 
//interface management
1834
 
bool MediaObject::hasInterface(Interface iface) const
1835
 
{
1836
 
    return iface == AddonInterface::TitleInterface;
1837
 
}
1838
 
 
1839
 
QVariant MediaObject::interfaceCall(Interface iface, int command, const QList<QVariant> &params)
1840
 
{
1841
 
    if (hasInterface(iface)) {
1842
 
 
1843
 
        switch (iface)
1844
 
        {
1845
 
        case TitleInterface:
1846
 
            switch (command)
1847
 
            {
1848
 
            case availableTitles:
1849
 
                return _iface_availableTitles();
1850
 
            case title:
1851
 
                return _iface_currentTitle();
1852
 
            case setTitle:
1853
 
                _iface_setCurrentTitle(params.first().toInt());
1854
 
                break;
1855
 
            case autoplayTitles:
1856
 
                return m_autoplayTitles;
1857
 
            case setAutoplayTitles:
1858
 
                m_autoplayTitles = params.first().toBool();
1859
 
                break;
1860
 
            }
1861
 
            break;
1862
 
                default:
1863
 
            break;
1864
 
        }
1865
 
    }
1866
 
    return QVariant();
1867
 
}
1868
 
#endif
1869
 
 
1870
 
int MediaObject::_iface_availableTitles() const
1871
 
{
1872
 
    return m_availableTitles;
1873
 
}
1874
 
 
1875
 
int MediaObject::_iface_currentTitle() const
1876
 
{
1877
 
    return m_currentTitle;
1878
 
}
1879
 
 
1880
 
void MediaObject::_iface_setCurrentTitle(int title)
1881
 
{
1882
 
    m_backend->logMessage(QString("setCurrentTitle %0").arg(title), Backend::Info, this);
1883
 
    if ((title == m_currentTitle) || (title == m_pendingTitle))
1884
 
        return;
1885
 
 
1886
 
    m_pendingTitle = title;
1887
 
 
1888
 
    if (m_state == Phonon::PlayingState || m_state == Phonon::StoppedState) {
1889
 
        setTrack(m_pendingTitle);
1890
 
    } else {
1891
 
        setState(Phonon::StoppedState);
1892
 
    }
1893
 
}
1894
 
 
1895
 
void MediaObject::setTrack(int title)
1896
 
{
1897
 
    if (((m_state != Phonon::PlayingState) && (m_state != Phonon::StoppedState)) || (title < 1) || (title > m_availableTitles))
1898
 
        return;
1899
 
 
1900
 
    //let's seek to the beginning of the song
1901
 
    GstFormat trackFormat = gst_format_get_by_nick("track");
1902
 
    m_backend->logMessage(QString("setTrack %0").arg(title), Backend::Info, this);
1903
 
    if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, title - 1)) {
1904
 
        m_currentTitle = title;
1905
 
        updateTotalTime();
1906
 
        m_atEndOfStream = false;
1907
 
        emit titleChanged(title);
1908
 
        emit totalTimeChanged(totalTime());
1909
 
    }
1910
 
}
1911
 
 
1912
 
} // ns Gstreamer
1913
 
} // ns Phonon
1914
 
 
1915
 
QT_END_NAMESPACE
1916
 
 
1917
 
#include "moc_mediaobject.cpp"