<commit_before>/*  This file is part of the KDE project.

    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).

    This library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2.1 or 3 of the License.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#include <gst/interfaces/propertyprobe.h>
#include "common.h"
#include "mediaobject.h"
#include "videowidget.h"
#include "message.h"
#include "backend.h"
#include "streamreader.h"
#include "phononsrc.h"
#include <QtCore>
#include <QtCore/QTimer>
#include <QtCore/QVector>
#include <QtCore/QFile>
#include <QtCore/QByteRef>
#include <QtCore/QStringList>
#include <QtCore/QEvent>
#include <QApplication>

#define ABOUT_TO_FINNISH_TIME 2000
#define MAX_QUEUE_TIME 20 * GST_SECOND

QT_BEGIN_NAMESPACE

namespace Phonon
{
namespace Gstreamer
{

MediaObject::MediaObject(Backend *backend, QObject *parent)
        : QObject(parent)
        , MediaNode(backend, AudioSource | VideoSource)
        , m_resumeState(false)
        , m_oldState(Phonon::LoadingState)
        , m_oldPos(0)
        , m_state(Phonon::LoadingState)
        , m_pendingState(Phonon::LoadingState)
        , m_tickTimer(new QTimer(this))
        , m_prefinishMark(0)
        , m_transitionTime(0)
        , m_posAtSeek(-1)
        , m_prefinishMarkReachedNotEmitted(true)
        , m_aboutToFinishEmitted(false)
        , m_loading(false)
        , m_capsHandler(0)
        , m_datasource(0)
        , m_decodebin(0)
        , m_audioPipe(0)
        , m_videoPipe(0)
        , m_totalTime(-1)
        , m_bufferPercent(0)
        , m_hasVideo(false)
        , m_videoStreamFound(false)
        , m_hasAudio(false)
        , m_seekable(false)
        , m_atEndOfStream(false)
        , m_atStartOfStream(false)
        , m_error(Phonon::NoError)
        , m_pipeline(0)
        , m_audioGraph(0)
        , m_videoGraph(0)
        , m_previousTickTime(-1)
        , m_resetNeeded(false)
        , m_autoplayTitles(true)
        , m_availableTitles(0)
        , m_currentTitle(1)
{
    qRegisterMetaType<GstCaps*>("GstCaps*");
    qRegisterMetaType<State>("State");

    static int count = 0;
    m_name = "MediaObject" + QString::number(count++);

    if (!m_backend->isValid()) {
        setError(tr("Cannot start playback. \n\nCheck your GStreamer installation and make sure you "
                    "\nhave libgstreamer-plugins-base installed."), Phonon::FatalError);
    } else {
        m_root = this;
        createPipeline();
        m_backend->addBusWatcher(this);
        connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick()));
    }
    connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 
            this, SLOT(notifyStateChange(Phonon::State, Phonon::State)));

}

MediaObject::~MediaObject()
{
    m_backend->removeBusWatcher(this);
    if (m_pipeline) {
        gst_element_set_state(m_pipeline, GST_STATE_NULL);
        gst_object_unref(m_pipeline);
    }
    if (m_audioGraph) {
        gst_element_set_state(m_audioGraph, GST_STATE_NULL);
        gst_object_unref(m_audioGraph);
    }
    if (m_videoGraph) {
        gst_element_set_state(m_videoGraph, GST_STATE_NULL);
        gst_object_unref(m_videoGraph);
    }
}

QString stateString(const Phonon::State &state)
{
    switch (state) {
    case Phonon::LoadingState:
        return QString("LoadingState");
    case Phonon::StoppedState:
        return QString("StoppedState");
    case Phonon::PlayingState:
        return QString("PlayingState");
    case Phonon::BufferingState:
        return QString("BufferingState");
    case Phonon::PausedState:
        return QString("PausedState");
    case Phonon::ErrorState:
        return QString("ErrorState");
    }
    return QString();
}

void MediaObject::saveState()
{
    //Only first resumeState is respected
    if (m_resumeState)
        return;

    if (m_pendingState == Phonon::PlayingState || m_pendingState == Phonon::PausedState) {
        m_resumeState = true;
        m_oldState = m_pendingState;
        m_oldPos = getPipelinePos();
    }
}

void MediaObject::resumeState()
{
    if (m_resumeState)
        QMetaObject::invokeMethod(this, "setState", Qt::QueuedConnection, Q_ARG(State, m_oldState));
}

void MediaObject::newPadAvailable (GstPad *pad)
{
    GstCaps *caps;
    GstStructure *str;
    caps = gst_pad_get_caps (pad);
    if (caps) {
        str = gst_caps_get_structure (caps, 0);
        QString mediaString(gst_structure_get_name (str));

        if (mediaString.startsWith("video")) {
            connectVideo(pad);
        } else if (mediaString.startsWith("audio")) {
            connectAudio(pad);
        } else {
            m_backend->logMessage("Could not connect pad", Backend::Warning);
        }
        gst_caps_unref (caps);
    }
}

void MediaObject::cb_newpad (GstElement *decodebin,
                             GstPad     *pad,
                             gboolean    last,
                             gpointer    data)
{
    Q_UNUSED(decodebin);
    Q_UNUSED(pad);
    Q_UNUSED(last);
    Q_UNUSED(data);

    MediaObject *media = static_cast<MediaObject*>(data);
    Q_ASSERT(media);
    media->newPadAvailable(pad);
}

void MediaObject::noMorePadsAvailable ()
{
    if (m_missingCodecs.size() > 0) {
        bool canPlay = (m_hasAudio || m_videoStreamFound);
        Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
        if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) {
            m_hasVideo = false;
            emit hasVideoChanged(false);
        }
        QString codecs = m_missingCodecs.join(", ");
        setError(QString(tr("A required codec is missing. You need to install the following codec(s) to play this content: %0")).arg(codecs), error);
        m_missingCodecs.clear();
    }
}

void MediaObject::cb_no_more_pads (GstElement * decodebin, gpointer data)
{
    Q_UNUSED(decodebin);
    MediaObject *media = static_cast<MediaObject*>(data);
    Q_ASSERT(media);
    QMetaObject::invokeMethod(media, "noMorePadsAvailable", Qt::QueuedConnection);
}

typedef void (*Ptr_gst_pb_utils_init)();
typedef gchar* (*Ptr_gst_pb_utils_get_codec_description)(const GstCaps *);

void MediaObject::cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps *caps, gpointer data)
{
    Q_UNUSED(decodebin);
    Q_UNUSED(pad);
    MediaObject *media = static_cast<MediaObject*>(data);
    Q_ASSERT(media);

    QString value = "unknown codec";

    // These functions require GStreamer > 0.10.12
#ifndef QT_NO_LIBRARY
    static Ptr_gst_pb_utils_init p_gst_pb_utils_init = 0;
    static Ptr_gst_pb_utils_get_codec_description p_gst_pb_utils_get_codec_description = 0;
    if (!p_gst_pb_utils_init) {
        p_gst_pb_utils_init =  (Ptr_gst_pb_utils_init)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_init");
        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");
        if (p_gst_pb_utils_init)
	    p_gst_pb_utils_init();
    }
    if (p_gst_pb_utils_get_codec_description) {
        gchar *codecName = NULL;
        codecName = p_gst_pb_utils_get_codec_description (caps);
        value = QString::fromUtf8(codecName);
        g_free (codecName);
    } else
#endif //QT_NO_LIBRARY
    {
        // For GStreamer versions < 0.10.12
        GstStructure *str = gst_caps_get_structure (caps, 0);
        value = QString::fromUtf8(gst_structure_get_name (str));

    }
    media->addMissingCodecName(value);
}

static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data)
{
    GstPad *pad = GST_PAD(obj);
    GstCaps *caps = gst_pad_get_caps (pad);
    Q_ASSERT(caps);
    MediaObject *media = static_cast<MediaObject*>(data);

    // We do not want any more notifications until the source changes
    g_signal_handler_disconnect(pad, media->capsHandler());

    // setVideoCaps calls loadingComplete(), meaning we cannot call it from
    // the streaming thread
    QMetaObject::invokeMethod(media, "setVideoCaps", Qt::QueuedConnection, Q_ARG(GstCaps *, caps));
}

void MediaObject::setVideoCaps(GstCaps *caps)
{
    GstStructure *str;
    gint width, height;

    if ((str = gst_caps_get_structure (caps, 0))) {
        if (gst_structure_get_int (str, "width", &width) && gst_structure_get_int (str, "height", &height)) {
            gint aspectNum = 0;
            gint aspectDenum = 0;
            if (gst_structure_get_fraction(str, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) {
                if (aspectDenum > 0)
                    width = width*aspectNum/aspectDenum;
            }
            // Let child nodes know about our new video size
            QSize size(width, height);
            MediaNodeEvent event(MediaNodeEvent::VideoSizeChanged, &size);
            notify(&event);
        }
    }
    gst_caps_unref(caps);
}

// Adds an element to the pipeline if not previously added
bool MediaObject::addToPipeline(GstElement *elem)
{
    bool success = true;
    if (!GST_ELEMENT_PARENT(elem)) { // If not already in pipeline
        success = gst_bin_add(GST_BIN(m_pipeline), elem);
    }
    return success;
}

void MediaObject::connectVideo(GstPad *pad)
{
    GstState currentState = GST_STATE(m_pipeline);
    if (addToPipeline(m_videoGraph)) {
        GstPad *videopad = gst_element_get_pad (m_videoGraph, "sink");
        if (!GST_PAD_IS_LINKED (videopad) && (gst_pad_link (pad, videopad) == GST_PAD_LINK_OK)) {
            gst_element_set_state(m_videoGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
            m_videoStreamFound = true;
            m_backend->logMessage("Video track connected", Backend::Info, this);
            // Note that the notify::caps _must_ be installed after linking to work with Dapper
            m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this);
 
            if (!m_loading && !m_hasVideo) {
                m_hasVideo = m_videoStreamFound;
                emit hasVideoChanged(m_hasVideo);
            }
        }
        gst_object_unref (videopad);
    } else {
        m_backend->logMessage("The video stream could not be plugged.", Backend::Info, this);
    }
}

void MediaObject::connectAudio(GstPad *pad)
{
    GstState currentState = GST_STATE(m_pipeline);
    if (addToPipeline(m_audioGraph)) {
        GstPad *audiopad = gst_element_get_pad (m_audioGraph, "sink");
        if (!GST_PAD_IS_LINKED (audiopad) && (gst_pad_link (pad, audiopad)==GST_PAD_LINK_OK)) {
            gst_element_set_state(m_audioGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
            m_hasAudio = true;
            m_backend->logMessage("Audio track connected", Backend::Info, this);
        }
        gst_object_unref (audiopad);
    } else {
        m_backend->logMessage("The audio stream could not be plugged.", Backend::Info, this);
    }
}

void MediaObject::cb_pad_added(GstElement *decodebin,
                               GstPad     *pad,
                               gpointer    data)
{
    Q_UNUSED(decodebin);
    GstPad *decodepad = static_cast<GstPad*>(data);
    gst_pad_link (pad, decodepad);
    gst_object_unref (decodepad);
}

/**
 * Create a media source from a given URL.
 *
 * returns true if successful
 */
bool MediaObject::createPipefromURL(const QUrl &url)
{
    // Remove any existing data source
    if (m_datasource) {
        gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
        // m_pipeline has the only ref to datasource
        m_datasource = 0;
    }

    // Verify that the uri can be parsed
    if (!url.isValid()) {
        m_backend->logMessage(QString("%1 is not a valid URI").arg(url.toString()));
        return false;
    }

    // Create a new datasource based on the input URL
    QByteArray encoded_cstr_url = url.toEncoded();
    m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL);
    if (!m_datasource)
        return false;

    // Set the device for MediaSource::Disc
    QByteArray mediaDevice = QFile::encodeName(m_source.deviceName());
    if (!mediaDevice.isEmpty())
        g_object_set (m_datasource, "device", mediaDevice.constData(), (const char*)NULL);

    // Link data source into pipeline
    gst_bin_add(GST_BIN(m_pipeline), m_datasource);
    if (!gst_element_link(m_datasource, m_decodebin)) {
        // For sources with dynamic pads (such as RtspSrc) we need to connect dynamically
        GstPad *decodepad = gst_element_get_pad (m_decodebin, "sink");
        g_signal_connect (m_datasource, "pad-added", G_CALLBACK (&cb_pad_added), decodepad);
    }

    return true;
}

/**<commit_msg>reduce the speed of CD drive to 2X while playing CD audio to limit CD drive noise<commit_after>/*  This file is part of the KDE project.

    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).

    This library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2.1 or 3 of the License.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#include <gst/interfaces/propertyprobe.h>
#include "common.h"
#include "mediaobject.h"
#include "videowidget.h"
#include "message.h"
#include "backend.h"
#include "streamreader.h"
#include "phononsrc.h"
#include <QtCore>
#include <QtCore/QTimer>
#include <QtCore/QVector>
#include <QtCore/QFile>
#include <QtCore/QByteRef>
#include <QtCore/QStringList>
#include <QtCore/QEvent>
#include <QApplication>

#define ABOUT_TO_FINNISH_TIME 2000
#define MAX_QUEUE_TIME 20 * GST_SECOND

QT_BEGIN_NAMESPACE

namespace Phonon
{
namespace Gstreamer
{

MediaObject::MediaObject(Backend *backend, QObject *parent)
        : QObject(parent)
        , MediaNode(backend, AudioSource | VideoSource)
        , m_resumeState(false)
        , m_oldState(Phonon::LoadingState)
        , m_oldPos(0)
        , m_state(Phonon::LoadingState)
        , m_pendingState(Phonon::LoadingState)
        , m_tickTimer(new QTimer(this))
        , m_prefinishMark(0)
        , m_transitionTime(0)
        , m_posAtSeek(-1)
        , m_prefinishMarkReachedNotEmitted(true)
        , m_aboutToFinishEmitted(false)
        , m_loading(false)
        , m_capsHandler(0)
        , m_datasource(0)
        , m_decodebin(0)
        , m_audioPipe(0)
        , m_videoPipe(0)
        , m_totalTime(-1)
        , m_bufferPercent(0)
        , m_hasVideo(false)
        , m_videoStreamFound(false)
        , m_hasAudio(false)
        , m_seekable(false)
        , m_atEndOfStream(false)
        , m_atStartOfStream(false)
        , m_error(Phonon::NoError)
        , m_pipeline(0)
        , m_audioGraph(0)
        , m_videoGraph(0)
        , m_previousTickTime(-1)
        , m_resetNeeded(false)
        , m_autoplayTitles(true)
        , m_availableTitles(0)
        , m_currentTitle(1)
{
    qRegisterMetaType<GstCaps*>("GstCaps*");
    qRegisterMetaType<State>("State");

    static int count = 0;
    m_name = "MediaObject" + QString::number(count++);

    if (!m_backend->isValid()) {
        setError(tr("Cannot start playback. \n\nCheck your GStreamer installation and make sure you "
                    "\nhave libgstreamer-plugins-base installed."), Phonon::FatalError);
    } else {
        m_root = this;
        createPipeline();
        m_backend->addBusWatcher(this);
        connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick()));
    }
    connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 
            this, SLOT(notifyStateChange(Phonon::State, Phonon::State)));

}

MediaObject::~MediaObject()
{
    m_backend->removeBusWatcher(this);
    if (m_pipeline) {
        gst_element_set_state(m_pipeline, GST_STATE_NULL);
        gst_object_unref(m_pipeline);
    }
    if (m_audioGraph) {
        gst_element_set_state(m_audioGraph, GST_STATE_NULL);
        gst_object_unref(m_audioGraph);
    }
    if (m_videoGraph) {
        gst_element_set_state(m_videoGraph, GST_STATE_NULL);
        gst_object_unref(m_videoGraph);
    }
}

QString stateString(const Phonon::State &state)
{
    switch (state) {
    case Phonon::LoadingState:
        return QString("LoadingState");
    case Phonon::StoppedState:
        return QString("StoppedState");
    case Phonon::PlayingState:
        return QString("PlayingState");
    case Phonon::BufferingState:
        return QString("BufferingState");
    case Phonon::PausedState:
        return QString("PausedState");
    case Phonon::ErrorState:
        return QString("ErrorState");
    }
    return QString();
}

void MediaObject::saveState()
{
    //Only first resumeState is respected
    if (m_resumeState)
        return;

    if (m_pendingState == Phonon::PlayingState || m_pendingState == Phonon::PausedState) {
        m_resumeState = true;
        m_oldState = m_pendingState;
        m_oldPos = getPipelinePos();
    }
}

void MediaObject::resumeState()
{
    if (m_resumeState)
        QMetaObject::invokeMethod(this, "setState", Qt::QueuedConnection, Q_ARG(State, m_oldState));
}

void MediaObject::newPadAvailable (GstPad *pad)
{
    GstCaps *caps;
    GstStructure *str;
    caps = gst_pad_get_caps (pad);
    if (caps) {
        str = gst_caps_get_structure (caps, 0);
        QString mediaString(gst_structure_get_name (str));

        if (mediaString.startsWith("video")) {
            connectVideo(pad);
        } else if (mediaString.startsWith("audio")) {
            connectAudio(pad);
        } else {
            m_backend->logMessage("Could not connect pad", Backend::Warning);
        }
        gst_caps_unref (caps);
    }
}

void MediaObject::cb_newpad (GstElement *decodebin,
                             GstPad     *pad,
                             gboolean    last,
                             gpointer    data)
{
    Q_UNUSED(decodebin);
    Q_UNUSED(pad);
    Q_UNUSED(last);
    Q_UNUSED(data);

    MediaObject *media = static_cast<MediaObject*>(data);
    Q_ASSERT(media);
    media->newPadAvailable(pad);
}

void MediaObject::noMorePadsAvailable ()
{
    if (m_missingCodecs.size() > 0) {
        bool canPlay = (m_hasAudio || m_videoStreamFound);
        Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError;
        if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) {
            m_hasVideo = false;
            emit hasVideoChanged(false);
        }
        QString codecs = m_missingCodecs.join(", ");
        setError(QString(tr("A required codec is missing. You need to install the following codec(s) to play this content: %0")).arg(codecs), error);
        m_missingCodecs.clear();
    }
}

void MediaObject::cb_no_more_pads (GstElement * decodebin, gpointer data)
{
    Q_UNUSED(decodebin);
    MediaObject *media = static_cast<MediaObject*>(data);
    Q_ASSERT(media);
    QMetaObject::invokeMethod(media, "noMorePadsAvailable", Qt::QueuedConnection);
}

typedef void (*Ptr_gst_pb_utils_init)();
typedef gchar* (*Ptr_gst_pb_utils_get_codec_description)(const GstCaps *);

void MediaObject::cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps *caps, gpointer data)
{
    Q_UNUSED(decodebin);
    Q_UNUSED(pad);
    MediaObject *media = static_cast<MediaObject*>(data);
    Q_ASSERT(media);

    QString value = "unknown codec";

    // These functions require GStreamer > 0.10.12
#ifndef QT_NO_LIBRARY
    static Ptr_gst_pb_utils_init p_gst_pb_utils_init = 0;
    static Ptr_gst_pb_utils_get_codec_description p_gst_pb_utils_get_codec_description = 0;
    if (!p_gst_pb_utils_init) {
        p_gst_pb_utils_init =  (Ptr_gst_pb_utils_init)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_init");
        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");
        if (p_gst_pb_utils_init)
	    p_gst_pb_utils_init();
    }
    if (p_gst_pb_utils_get_codec_description) {
        gchar *codecName = NULL;
        codecName = p_gst_pb_utils_get_codec_description (caps);
        value = QString::fromUtf8(codecName);
        g_free (codecName);
    } else
#endif //QT_NO_LIBRARY
    {
        // For GStreamer versions < 0.10.12
        GstStructure *str = gst_caps_get_structure (caps, 0);
        value = QString::fromUtf8(gst_structure_get_name (str));

    }
    media->addMissingCodecName(value);
}

static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data)
{
    GstPad *pad = GST_PAD(obj);
    GstCaps *caps = gst_pad_get_caps (pad);
    Q_ASSERT(caps);
    MediaObject *media = static_cast<MediaObject*>(data);

    // We do not want any more notifications until the source changes
    g_signal_handler_disconnect(pad, media->capsHandler());

    // setVideoCaps calls loadingComplete(), meaning we cannot call it from
    // the streaming thread
    QMetaObject::invokeMethod(media, "setVideoCaps", Qt::QueuedConnection, Q_ARG(GstCaps *, caps));
}

void MediaObject::setVideoCaps(GstCaps *caps)
{
    GstStructure *str;
    gint width, height;

    if ((str = gst_caps_get_structure (caps, 0))) {
        if (gst_structure_get_int (str, "width", &width) && gst_structure_get_int (str, "height", &height)) {
            gint aspectNum = 0;
            gint aspectDenum = 0;
            if (gst_structure_get_fraction(str, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) {
                if (aspectDenum > 0)
                    width = width*aspectNum/aspectDenum;
            }
            // Let child nodes know about our new video size
            QSize size(width, height);
            MediaNodeEvent event(MediaNodeEvent::VideoSizeChanged, &size);
            notify(&event);
        }
    }
    gst_caps_unref(caps);
}

// Adds an element to the pipeline if not previously added
bool MediaObject::addToPipeline(GstElement *elem)
{
    bool success = true;
    if (!GST_ELEMENT_PARENT(elem)) { // If not already in pipeline
        success = gst_bin_add(GST_BIN(m_pipeline), elem);
    }
    return success;
}

void MediaObject::connectVideo(GstPad *pad)
{
    GstState currentState = GST_STATE(m_pipeline);
    if (addToPipeline(m_videoGraph)) {
        GstPad *videopad = gst_element_get_pad (m_videoGraph, "sink");
        if (!GST_PAD_IS_LINKED (videopad) && (gst_pad_link (pad, videopad) == GST_PAD_LINK_OK)) {
            gst_element_set_state(m_videoGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
            m_videoStreamFound = true;
            m_backend->logMessage("Video track connected", Backend::Info, this);
            // Note that the notify::caps _must_ be installed after linking to work with Dapper
            m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this);
 
            if (!m_loading && !m_hasVideo) {
                m_hasVideo = m_videoStreamFound;
                emit hasVideoChanged(m_hasVideo);
            }
        }
        gst_object_unref (videopad);
    } else {
        m_backend->logMessage("The video stream could not be plugged.", Backend::Info, this);
    }
}

void MediaObject::connectAudio(GstPad *pad)
{
    GstState currentState = GST_STATE(m_pipeline);
    if (addToPipeline(m_audioGraph)) {
        GstPad *audiopad = gst_element_get_pad (m_audioGraph, "sink");
        if (!GST_PAD_IS_LINKED (audiopad) && (gst_pad_link (pad, audiopad)==GST_PAD_LINK_OK)) {
            gst_element_set_state(m_audioGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED);
            m_hasAudio = true;
            m_backend->logMessage("Audio track connected", Backend::Info, this);
        }
        gst_object_unref (audiopad);
    } else {
        m_backend->logMessage("The audio stream could not be plugged.", Backend::Info, this);
    }
}

void MediaObject::cb_pad_added(GstElement *decodebin,
                               GstPad     *pad,
                               gpointer    data)
{
    Q_UNUSED(decodebin);
    GstPad *decodepad = static_cast<GstPad*>(data);
    gst_pad_link (pad, decodepad);
    gst_object_unref (decodepad);
}

/**
 * Create a media source from a given URL.
 *
 * returns true if successful
 */
bool MediaObject::createPipefromURL(const QUrl &url)
{
    // Remove any existing data source
    if (m_datasource) {
        gst_bin_remove(GST_BIN(m_pipeline), m_datasource);
        // m_pipeline has the only ref to datasource
        m_datasource = 0;
    }

    // Verify that the uri can be parsed
    if (!url.isValid()) {
        m_backend->logMessage(QString("%1 is not a valid URI").arg(url.toString()));
        return false;
    }

    // Create a new datasource based on the input URL
    QByteArray encoded_cstr_url = url.toEncoded();
    m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL);
    if (!m_datasource)
        return false;

    // Set the device for MediaSource::Disc
    if (m_source.type() == MediaSource::Disc) {

        if (g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "device")) {
            QByteArray mediaDevice = QFile::encodeName(m_source.deviceName());
            if (!mediaDevice.isEmpty())
                g_object_set (G_OBJECT (m_datasource), "device", mediaDevice.constData(), (const char*)NULL);
        }

        // Also Set optical disc speed to 2X for Audio CD
        if (m_source.discType() == Phonon::Cd
            && (g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "read-speed"))) {
            g_object_set (G_OBJECT (m_datasource), "read-speed", 2, (const char*)NULL);
            m_backend->logMessage(QString("new device speed : 2X"), Backend::Info, this);
        }
    }

    // Link data source into pipeline
    gst_bin_add(GST_BIN(m_pipeline), m_datasource);
    if (!gst_element_link(m_datasource, m_decodebin)) {
        // For sources with dynamic pads (such as RtspSrc) we need to connect dynamically
        GstPad *decodepad = gst_element_get_pad (m_decodebin, "sink");
        g_signal_connect (m_datasource, "pad-added", G_CALLBACK (&cb_pad_added), decodepad);
    }

    return true;
}

/**
{'fraction_non_alphanumeric': 0.10835422504055449, 'fraction_numerical': 0.004940274295826574, 'mean_word_length': 3.138693927372597, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 70, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6710894', 'n_tokens_mistral': 9069, 'n_tokens_neox': 8881, 'n_words': 1859}