2010-04-06 18:57:02 +02:00
|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> *
|
|
|
|
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> *
|
|
|
|
* Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> *
|
|
|
|
* *
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
* (at your option) any later version. *
|
|
|
|
* *
|
|
|
|
* This program 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 General Public License for more details. *
|
|
|
|
* *
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
|
|
|
* along with this program; if not, write to the *
|
|
|
|
* Free Software Foundation, Inc., *
|
|
|
|
* 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. *
|
|
|
|
***************************************************************************/
|
|
|
|
|
2010-08-01 20:09:38 +02:00
|
|
|
#include "config.h"
|
2010-04-06 18:57:02 +02:00
|
|
|
#include "gstengine.h"
|
2010-04-11 21:47:21 +02:00
|
|
|
#include "gstenginepipeline.h"
|
2011-04-22 18:50:29 +02:00
|
|
|
#include "core/logging.h"
|
2012-01-23 15:58:10 +01:00
|
|
|
#include "core/taskmanager.h"
|
2010-12-09 13:34:08 +01:00
|
|
|
#include "core/utilities.h"
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2012-05-27 18:28:07 +02:00
|
|
|
#ifdef HAVE_MOODBAR
|
|
|
|
# include "gst/moodbar/spectrum.h"
|
|
|
|
#endif
|
|
|
|
|
2010-04-06 18:57:02 +02:00
|
|
|
#include <math.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <vector>
|
2010-04-21 19:11:50 +02:00
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#include <boost/bind.hpp>
|
2010-04-06 18:57:02 +02:00
|
|
|
|
|
|
|
#include <QTimer>
|
|
|
|
#include <QRegExp>
|
|
|
|
#include <QFile>
|
2010-04-07 15:51:14 +02:00
|
|
|
#include <QSettings>
|
2010-04-06 18:57:02 +02:00
|
|
|
#include <QtDebug>
|
2010-04-11 16:26:30 +02:00
|
|
|
#include <QCoreApplication>
|
2010-04-11 23:40:26 +02:00
|
|
|
#include <QTimeLine>
|
2010-06-11 14:22:21 +02:00
|
|
|
#include <QDir>
|
2010-08-27 15:57:39 +02:00
|
|
|
#include <QtConcurrentRun>
|
2010-04-06 18:57:02 +02:00
|
|
|
|
|
|
|
#include <gst/gst.h>
|
|
|
|
|
|
|
|
|
|
|
|
using std::vector;
|
2010-04-11 21:47:21 +02:00
|
|
|
using boost::shared_ptr;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
const char* GstEngine::kSettingsGroup = "GstEngine";
|
|
|
|
const char* GstEngine::kAutoSink = "autoaudiosink";
|
2012-01-10 17:00:17 +01:00
|
|
|
const char* GstEngine::kHypnotoadPipeline =
|
2010-07-14 13:16:56 +02:00
|
|
|
"audiotestsrc wave=6 ! "
|
|
|
|
"audioecho intensity=1 delay=50000000 ! "
|
|
|
|
"audioecho intensity=1 delay=25000000 ! "
|
|
|
|
"equalizer-10bands "
|
|
|
|
"band0=-24 band1=-3 band2=7.5 band3=12 band4=8 "
|
|
|
|
"band5=6 band6=5 band7=6 band8=0 band9=-24";
|
2012-01-10 17:00:17 +01:00
|
|
|
const char* GstEngine::kEnterprisePipeline =
|
|
|
|
"audiotestsrc wave=5 ! "
|
|
|
|
"audiocheblimit mode=0 cutoff=120";
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2012-01-23 15:58:10 +01:00
|
|
|
GstEngine::GstEngine(TaskManager* task_manager)
|
2010-04-07 00:58:41 +02:00
|
|
|
: Engine::Base(),
|
2012-01-23 15:58:10 +01:00
|
|
|
task_manager_(task_manager),
|
|
|
|
buffering_task_id_(-1),
|
2013-06-01 10:03:59 +02:00
|
|
|
latest_buffer_(NULL),
|
2010-04-22 18:54:09 +02:00
|
|
|
equalizer_enabled_(false),
|
2013-04-27 05:05:42 +02:00
|
|
|
stereo_balance_(0.0f),
|
2010-05-23 15:07:15 +02:00
|
|
|
rg_enabled_(false),
|
|
|
|
rg_mode_(0),
|
|
|
|
rg_preamp_(0.0),
|
|
|
|
rg_compression_(true),
|
2011-02-14 20:34:37 +01:00
|
|
|
buffer_duration_nanosec_(1 * kNsecPerSec), // 1s
|
2012-05-20 20:50:25 +02:00
|
|
|
mono_playback_(false),
|
2010-05-03 16:55:00 +02:00
|
|
|
seek_timer_(new QTimer(this)),
|
2010-08-02 20:13:40 +02:00
|
|
|
timer_id_(-1),
|
2013-04-22 21:42:04 +02:00
|
|
|
next_element_id_(0),
|
|
|
|
is_fading_out_to_pause_(false),
|
|
|
|
has_faded_out_(false)
|
2010-04-06 18:57:02 +02:00
|
|
|
{
|
2010-04-22 18:54:09 +02:00
|
|
|
seek_timer_->setSingleShot(true);
|
2011-02-14 20:34:37 +01:00
|
|
|
seek_timer_->setInterval(kSeekDelayNanosec / kNsecPerMsec);
|
2010-04-22 18:54:09 +02:00
|
|
|
connect(seek_timer_, SIGNAL(timeout()), SLOT(SeekNow()));
|
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
ReloadSettings();
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
GstEngine::~GstEngine() {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_.reset();
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
// Save configuration
|
|
|
|
gst_deinit();
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
bool GstEngine::Init() {
|
2011-05-19 18:34:33 +02:00
|
|
|
initialising_ = QtConcurrent::run(&GstEngine::InitialiseGstreamer);
|
2010-08-27 15:57:39 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::InitialiseGstreamer() {
|
|
|
|
gst_init(NULL, NULL);
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2012-05-27 18:28:07 +02:00
|
|
|
#ifdef HAVE_MOODBAR
|
|
|
|
gstmoodbar_register_static();
|
|
|
|
#endif
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
void GstEngine::ReloadSettings() {
|
2010-04-12 01:03:39 +02:00
|
|
|
Engine::Base::ReloadSettings();
|
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(kSettingsGroup);
|
|
|
|
|
|
|
|
sink_ = s.value("sink", kAutoSink).toString();
|
2010-04-08 22:14:11 +02:00
|
|
|
device_ = s.value("device").toString();
|
2010-05-04 17:51:23 +02:00
|
|
|
|
2010-08-27 18:41:35 +02:00
|
|
|
if (sink_.isEmpty())
|
|
|
|
sink_ = kAutoSink;
|
2010-05-23 15:07:15 +02:00
|
|
|
|
|
|
|
rg_enabled_ = s.value("rgenabled", false).toBool();
|
|
|
|
rg_mode_ = s.value("rgmode", 0).toInt();
|
2010-05-25 23:33:16 +02:00
|
|
|
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
|
2010-05-23 15:07:15 +02:00
|
|
|
rg_compression_ = s.value("rgcompression", true).toBool();
|
2010-10-11 17:58:05 +02:00
|
|
|
|
2011-04-16 16:04:12 +02:00
|
|
|
buffer_duration_nanosec_ = s.value("bufferduration", 4000).toLongLong() * kNsecPerMsec;
|
2012-05-20 20:50:25 +02:00
|
|
|
|
|
|
|
mono_playback_ = s.value("monoplayback", false).toBool();
|
2010-04-07 15:51:14 +02:00
|
|
|
}
|
|
|
|
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-02-13 19:29:27 +01:00
|
|
|
qint64 GstEngine::position_nanosec() const {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return 0;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-02-13 19:29:27 +01:00
|
|
|
qint64 result = current_pipeline_->position() - beginning_nanosec_;
|
|
|
|
return qint64(qMax(0ll, result));
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2011-02-13 19:29:27 +01:00
|
|
|
qint64 GstEngine::length_nanosec() const {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return 0;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-02-13 19:29:27 +01:00
|
|
|
qint64 result = end_nanosec_ - beginning_nanosec_;
|
|
|
|
return qint64(qMax(0ll, result));
|
2010-04-11 21:47:21 +02:00
|
|
|
}
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
Engine::State GstEngine::state() const {
|
|
|
|
if (!current_pipeline_)
|
2010-04-12 01:24:03 +02:00
|
|
|
return url_.isEmpty() ? Engine::Empty : Engine::Idle;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
switch (current_pipeline_->state()) {
|
2010-04-07 00:58:41 +02:00
|
|
|
case GST_STATE_NULL: return Engine::Empty;
|
|
|
|
case GST_STATE_READY: return Engine::Idle;
|
|
|
|
case GST_STATE_PLAYING: return Engine::Playing;
|
|
|
|
case GST_STATE_PAUSED: return Engine::Paused;
|
|
|
|
default: return Engine::Empty;
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2011-03-20 22:40:53 +01:00
|
|
|
void GstEngine::ConsumeBuffer(GstBuffer* buffer, int pipeline_id) {
|
2010-06-06 16:06:23 +02:00
|
|
|
// Schedule this to run in the GUI thread. The buffer gets added to the
|
|
|
|
// queue and unreffed by UpdateScope.
|
|
|
|
if (!QMetaObject::invokeMethod(this, "AddBufferToScope",
|
|
|
|
Q_ARG(GstBuffer*, buffer),
|
2011-03-20 22:40:53 +01:00
|
|
|
Q_ARG(int, pipeline_id))) {
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Failed to invoke AddBufferToScope on GstEngine";
|
2010-06-06 16:06:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-20 22:40:53 +01:00
|
|
|
void GstEngine::AddBufferToScope(GstBuffer* buf, int pipeline_id) {
|
|
|
|
if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) {
|
2010-04-12 18:41:44 +02:00
|
|
|
gst_buffer_unref(buf);
|
2010-04-12 18:39:48 +02:00
|
|
|
return;
|
2010-04-12 18:41:44 +02:00
|
|
|
}
|
2010-04-12 18:39:48 +02:00
|
|
|
|
2013-06-01 10:03:59 +02:00
|
|
|
if (latest_buffer_ != NULL) {
|
|
|
|
gst_buffer_unref(latest_buffer_);
|
|
|
|
}
|
|
|
|
latest_buffer_ = buf;
|
2010-04-11 21:47:21 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
const Engine::Scope& GstEngine::scope() {
|
2013-06-01 10:03:59 +02:00
|
|
|
if (latest_buffer_ != NULL) {
|
|
|
|
UpdateScope();
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
return scope_;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
void GstEngine::UpdateScope() {
|
2013-06-01 10:50:25 +02:00
|
|
|
typedef Engine::Scope::value_type sample_type;
|
2010-04-07 00:58:41 +02:00
|
|
|
|
|
|
|
// determine the number of channels
|
2013-06-01 10:03:59 +02:00
|
|
|
GstStructure* structure = gst_caps_get_structure(
|
|
|
|
GST_BUFFER_CAPS(latest_buffer_), 0);
|
2010-04-07 00:58:41 +02:00
|
|
|
int channels = 2;
|
2013-06-01 10:03:59 +02:00
|
|
|
gst_structure_get_int(structure, "channels", &channels);
|
2010-04-07 00:58:41 +02:00
|
|
|
|
|
|
|
// scope does not support >2 channels
|
|
|
|
if (channels > 2)
|
|
|
|
return;
|
|
|
|
|
2013-06-01 10:03:59 +02:00
|
|
|
const sample_type* source = reinterpret_cast<sample_type*>(
|
|
|
|
GST_BUFFER_DATA(latest_buffer_));
|
|
|
|
sample_type* dest = scope_.data();
|
|
|
|
const int bytes = qMin(
|
|
|
|
static_cast<Engine::Scope::size_type>(GST_BUFFER_SIZE(latest_buffer_)),
|
|
|
|
scope_.size() * sizeof(sample_type));
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2013-06-01 10:03:59 +02:00
|
|
|
memcpy(dest, source, bytes);
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2013-06-01 10:03:59 +02:00
|
|
|
gst_buffer_unref(latest_buffer_);
|
|
|
|
latest_buffer_ = NULL;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2011-03-29 00:11:07 +02:00
|
|
|
void GstEngine::StartPreloading(const QUrl& url, bool force_stop_at_end,
|
|
|
|
qint64 beginning_nanosec, qint64 end_nanosec) {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-12-20 20:00:48 +01:00
|
|
|
QUrl gst_url = FixupUrl(url);
|
|
|
|
|
2011-03-13 19:37:46 +01:00
|
|
|
// No crossfading, so we can just queue the new URL in the existing
|
|
|
|
// pipeline and get gapless playback (hopefully)
|
|
|
|
if (current_pipeline_)
|
2011-03-29 00:11:07 +02:00
|
|
|
current_pipeline_->SetNextUrl(gst_url, beginning_nanosec,
|
|
|
|
force_stop_at_end ? end_nanosec : 0);
|
2010-12-20 20:00:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QUrl GstEngine::FixupUrl(const QUrl& url) {
|
|
|
|
QUrl copy = url;
|
|
|
|
|
|
|
|
// It's a file:// url with a hostname set. QUrl::fromLocalFile does this
|
|
|
|
// when given a \\host\share\file path on Windows. Munge it back into a
|
|
|
|
// path that gstreamer will recognise.
|
|
|
|
if (url.scheme() == "file" && !url.host().isEmpty()) {
|
|
|
|
copy.setPath("//" + copy.host() + copy.path());
|
|
|
|
copy.setHost(QString());
|
2010-05-08 19:39:12 +02:00
|
|
|
}
|
2010-12-20 20:00:48 +01:00
|
|
|
|
|
|
|
return copy;
|
2010-04-21 15:55:30 +02:00
|
|
|
}
|
|
|
|
|
2011-03-13 19:37:46 +01:00
|
|
|
bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change,
|
2011-03-29 00:11:07 +02:00
|
|
|
bool force_stop_at_end,
|
2011-02-13 19:29:27 +01:00
|
|
|
quint64 beginning_nanosec, qint64 end_nanosec) {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2011-03-29 00:11:07 +02:00
|
|
|
Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-12-20 20:00:48 +01:00
|
|
|
QUrl gst_url = FixupUrl(url);
|
2010-06-03 20:39:42 +02:00
|
|
|
|
2011-03-13 19:37:46 +01:00
|
|
|
bool crossfade = current_pipeline_ &&
|
|
|
|
((crossfade_enabled_ && change & Engine::Manual) ||
|
|
|
|
(autocrossfade_enabled_ && change & Engine::Auto));
|
|
|
|
|
|
|
|
if (change & Engine::Auto && change & Engine::SameAlbum && !crossfade_same_album_)
|
|
|
|
crossfade = false;
|
2010-04-12 01:03:39 +02:00
|
|
|
|
2010-06-03 20:39:42 +02:00
|
|
|
if (!crossfade && current_pipeline_ && current_pipeline_->url() == gst_url &&
|
2011-03-13 19:37:46 +01:00
|
|
|
change & Engine::Auto) {
|
2010-05-08 19:39:12 +02:00
|
|
|
// We're not crossfading, and the pipeline is already playing the URI we
|
|
|
|
// want, so just do nothing.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-03-29 00:11:07 +02:00
|
|
|
shared_ptr<GstEnginePipeline> pipeline = CreatePipeline(gst_url,
|
|
|
|
force_stop_at_end ? end_nanosec : 0);
|
2011-03-13 19:37:46 +01:00
|
|
|
if (!pipeline)
|
|
|
|
return false;
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2010-04-12 03:59:21 +02:00
|
|
|
if (crossfade)
|
2010-04-12 01:03:39 +02:00
|
|
|
StartFadeout();
|
|
|
|
|
2012-01-23 15:58:10 +01:00
|
|
|
BufferingFinished();
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_ = pipeline;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
SetVolume(volume_);
|
|
|
|
SetEqualizerEnabled(equalizer_enabled_);
|
|
|
|
SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
|
2013-04-27 05:05:42 +02:00
|
|
|
SetStereoBalance(stereo_balance_);
|
2010-04-12 01:03:39 +02:00
|
|
|
|
|
|
|
// Maybe fade in this track
|
2010-04-12 03:59:21 +02:00
|
|
|
if (crossfade)
|
2011-02-13 19:29:27 +01:00
|
|
|
current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward);
|
2010-04-12 01:03:39 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
return true;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:03:39 +02:00
|
|
|
void GstEngine::StartFadeout() {
|
2013-04-22 21:42:04 +02:00
|
|
|
if (is_fading_out_to_pause_)
|
|
|
|
return;
|
|
|
|
|
2010-04-12 01:03:39 +02:00
|
|
|
fadeout_pipeline_ = current_pipeline_;
|
|
|
|
disconnect(fadeout_pipeline_.get(), 0, 0, 0);
|
2010-06-06 16:06:23 +02:00
|
|
|
fadeout_pipeline_->RemoveAllBufferConsumers();
|
2010-04-12 01:03:39 +02:00
|
|
|
|
2011-02-13 19:29:27 +01:00
|
|
|
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
|
2010-04-12 01:03:39 +02:00
|
|
|
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
|
|
|
|
}
|
|
|
|
|
2013-04-22 21:42:04 +02:00
|
|
|
void GstEngine::StartFadeoutPause() {
|
|
|
|
fadeout_pause_pipeline_ = current_pipeline_;
|
|
|
|
disconnect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
|
|
|
|
|
|
|
|
fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_,
|
|
|
|
QTimeLine::Backward,
|
|
|
|
QTimeLine::EaseInOutCurve,
|
|
|
|
false);
|
|
|
|
if (fadeout_pipeline_ && fadeout_pipeline_->state() == GST_STATE_PLAYING) {
|
|
|
|
fadeout_pipeline_->StartFader(fadeout_pause_duration_nanosec_,
|
|
|
|
QTimeLine::Backward,
|
|
|
|
QTimeLine::LinearCurve,
|
|
|
|
false);
|
|
|
|
}
|
|
|
|
connect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutPauseFinished()));
|
|
|
|
is_fading_out_to_pause_ = true;
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-02-13 19:29:27 +01:00
|
|
|
bool GstEngine::Play(quint64 offset_nanosec) {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2012-01-23 15:58:10 +01:00
|
|
|
if (!current_pipeline_ || current_pipeline_->is_buffering())
|
2010-09-26 22:59:41 +02:00
|
|
|
return false;
|
|
|
|
|
2011-05-04 00:38:24 +02:00
|
|
|
QFuture<GstStateChangeReturn> future = current_pipeline_->SetState(GST_STATE_PLAYING);
|
|
|
|
PlayFutureWatcher* watcher = new PlayFutureWatcher(
|
|
|
|
PlayFutureWatcherArg(offset_nanosec, current_pipeline_->id()), this);
|
|
|
|
watcher->setFuture(future);
|
|
|
|
connect(watcher, SIGNAL(finished()), SLOT(PlayDone()));
|
2010-06-23 13:47:54 +02:00
|
|
|
|
2013-04-22 21:42:04 +02:00
|
|
|
if (is_fading_out_to_pause_) {
|
|
|
|
current_pipeline_->SetState(GST_STATE_PAUSED);
|
|
|
|
}
|
|
|
|
|
2011-05-04 00:38:24 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::PlayDone() {
|
|
|
|
PlayFutureWatcher* watcher = static_cast<PlayFutureWatcher*>(sender());
|
|
|
|
watcher->deleteLater();
|
|
|
|
|
|
|
|
GstStateChangeReturn ret = watcher->result();
|
|
|
|
quint64 offset_nanosec = watcher->data().first;
|
|
|
|
|
|
|
|
if (!current_pipeline_ || watcher->data().second != current_pipeline_->id()) {
|
|
|
|
return;
|
2011-03-20 20:18:54 +01:00
|
|
|
}
|
|
|
|
|
2010-08-04 14:13:43 +02:00
|
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
2010-06-23 13:47:54 +02:00
|
|
|
// Failure, but we got a redirection URL - try loading that instead
|
|
|
|
QUrl redirect_url = current_pipeline_->redirect_url();
|
|
|
|
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) {
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Info) << "Redirecting to" << redirect_url;
|
2011-03-06 17:35:47 +01:00
|
|
|
current_pipeline_ = CreatePipeline(redirect_url, end_nanosec_);
|
2011-02-13 19:29:27 +01:00
|
|
|
Play(offset_nanosec);
|
2011-05-04 00:38:24 +02:00
|
|
|
return;
|
2010-06-23 13:47:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Failure - give up
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Could not set thread to PLAYING.";
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_.reset();
|
2012-01-23 15:58:10 +01:00
|
|
|
BufferingFinished();
|
2011-05-04 00:38:24 +02:00
|
|
|
return;
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
StartTimers();
|
2010-05-28 19:14:00 +02:00
|
|
|
|
2011-01-02 19:53:45 +01:00
|
|
|
// initial offset
|
2011-02-13 19:29:27 +01:00
|
|
|
if(offset_nanosec != 0 || beginning_nanosec_ != 0) {
|
|
|
|
Seek(offset_nanosec);
|
2010-08-04 14:13:43 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
emit StateChanged(Engine::Playing);
|
2011-03-10 19:01:35 +01:00
|
|
|
// we've successfully started playing a media stream with this url
|
|
|
|
emit ValidSongRequested(url_);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::Stop() {
|
2010-06-30 22:36:16 +02:00
|
|
|
StopTimers();
|
2010-05-03 16:15:42 +02:00
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
url_ = QUrl(); // To ensure we return Empty from state()
|
2011-02-13 19:29:27 +01:00
|
|
|
beginning_nanosec_ = end_nanosec_ = 0;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-12 02:21:41 +02:00
|
|
|
if (fadeout_enabled_ && current_pipeline_)
|
2010-04-12 01:03:39 +02:00
|
|
|
StartFadeout();
|
2010-04-11 23:40:26 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_.reset();
|
2012-01-23 15:58:10 +01:00
|
|
|
BufferingFinished();
|
2010-04-12 01:24:03 +02:00
|
|
|
emit StateChanged(Engine::Empty);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-11 23:40:26 +02:00
|
|
|
void GstEngine::FadeoutFinished() {
|
|
|
|
fadeout_pipeline_.reset();
|
2011-02-16 23:43:05 +01:00
|
|
|
emit FadeoutFinishedSignal();
|
2010-04-11 23:40:26 +02:00
|
|
|
}
|
|
|
|
|
2013-04-22 21:42:04 +02:00
|
|
|
void GstEngine::FadeoutPauseFinished() {
|
|
|
|
fadeout_pause_pipeline_->SetState(GST_STATE_PAUSED);
|
|
|
|
current_pipeline_->SetState(GST_STATE_PAUSED);
|
|
|
|
emit StateChanged(Engine::Paused);
|
|
|
|
StopTimers();
|
|
|
|
|
|
|
|
is_fading_out_to_pause_ = false;
|
|
|
|
has_faded_out_ = true;
|
|
|
|
fadeout_pause_pipeline_.reset();
|
|
|
|
fadeout_pipeline_.reset();
|
|
|
|
|
|
|
|
emit FadeoutFinishedSignal();
|
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::Pause() {
|
2012-01-23 15:58:10 +01:00
|
|
|
if (!current_pipeline_ || current_pipeline_->is_buffering())
|
2010-04-11 21:47:21 +02:00
|
|
|
return;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2013-04-22 21:42:04 +02:00
|
|
|
// Check if we started a fade out. If it isn't finished yet and the user
|
|
|
|
// pressed play, we inverse the fader and resume the playback.
|
|
|
|
if (is_fading_out_to_pause_) {
|
|
|
|
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
|
|
|
|
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_,
|
|
|
|
QTimeLine::Forward,
|
|
|
|
QTimeLine::EaseInOutCurve,
|
|
|
|
false);
|
|
|
|
is_fading_out_to_pause_ = false;
|
|
|
|
has_faded_out_ = false;
|
|
|
|
emit StateChanged(Engine::Playing);
|
|
|
|
return;
|
|
|
|
}
|
2010-06-25 10:48:19 +02:00
|
|
|
|
2013-04-22 21:42:04 +02:00
|
|
|
if ( current_pipeline_->state() == GST_STATE_PLAYING ) {
|
|
|
|
if (fadeout_pause_enabled_) {
|
|
|
|
StartFadeoutPause();
|
|
|
|
} else {
|
|
|
|
current_pipeline_->SetState(GST_STATE_PAUSED);
|
|
|
|
emit StateChanged(Engine::Paused);
|
|
|
|
StopTimers();
|
|
|
|
}
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::Unpause() {
|
2012-01-23 15:58:10 +01:00
|
|
|
if (!current_pipeline_ || current_pipeline_->is_buffering())
|
2010-04-11 21:47:21 +02:00
|
|
|
return;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
if ( current_pipeline_->state() == GST_STATE_PAUSED ) {
|
|
|
|
current_pipeline_->SetState(GST_STATE_PLAYING);
|
2013-04-22 21:42:04 +02:00
|
|
|
|
|
|
|
// Check if we faded out last time. If yes, fade in no matter what the
|
|
|
|
// settings say. If we pause with fadeout, deactivate fadeout and resume
|
|
|
|
// playback, the player would be muted if not faded in.
|
|
|
|
if (has_faded_out_) {
|
|
|
|
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
|
|
|
|
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_,
|
|
|
|
QTimeLine::Forward,
|
|
|
|
QTimeLine::EaseInOutCurve,
|
|
|
|
false);
|
|
|
|
has_faded_out_ = false;
|
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
emit StateChanged(Engine::Playing);
|
2010-06-25 10:48:19 +02:00
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
StartTimers();
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-02-13 19:29:27 +01:00
|
|
|
void GstEngine::Seek(quint64 offset_nanosec) {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (!current_pipeline_)
|
|
|
|
return;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-02-13 19:29:27 +01:00
|
|
|
seek_pos_ = beginning_nanosec_ + offset_nanosec;
|
2010-04-22 18:54:09 +02:00
|
|
|
waiting_to_seek_ = true;
|
|
|
|
|
|
|
|
if (!seek_timer_->isActive()) {
|
|
|
|
SeekNow();
|
|
|
|
seek_timer_->start(); // Stop us from seeking again for a little while
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::SeekNow() {
|
|
|
|
if (!waiting_to_seek_) return;
|
2010-04-22 19:02:06 +02:00
|
|
|
waiting_to_seek_ = false;
|
|
|
|
|
|
|
|
if (!current_pipeline_)
|
|
|
|
return;
|
2010-04-22 18:54:09 +02:00
|
|
|
|
2013-06-01 10:03:59 +02:00
|
|
|
if (!current_pipeline_->Seek(seek_pos_)) {
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Seek failed";
|
2013-06-01 10:03:59 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::SetEqualizerEnabled(bool enabled) {
|
2010-04-07 00:58:41 +02:00
|
|
|
equalizer_enabled_= enabled;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->SetEqualizerEnabled(enabled);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-12 22:33:59 +02:00
|
|
|
void GstEngine::SetEqualizerParameters(int preamp, const QList<int>& band_gains) {
|
2010-04-07 00:58:41 +02:00
|
|
|
equalizer_preamp_ = preamp;
|
2010-04-07 18:26:04 +02:00
|
|
|
equalizer_gains_ = band_gains;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->SetEqualizerParams(preamp, band_gains);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2013-04-27 05:05:42 +02:00
|
|
|
void GstEngine::SetStereoBalance(float value) {
|
|
|
|
stereo_balance_ = value;
|
|
|
|
|
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->SetStereoBalance(value);
|
|
|
|
}
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
void GstEngine::SetVolumeSW( uint percent ) {
|
2010-04-11 21:47:21 +02:00
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->SetVolume(percent);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
void GstEngine::StartTimers() {
|
|
|
|
StopTimers();
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-02-14 20:34:37 +01:00
|
|
|
timer_id_ = startTimer(kTimerIntervalNanosec / kNsecPerMsec);
|
2010-06-30 22:36:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::StopTimers() {
|
2010-07-02 21:23:08 +02:00
|
|
|
if (timer_id_ != -1) {
|
|
|
|
killTimer(timer_id_);
|
|
|
|
timer_id_ = -1;
|
2010-06-30 22:36:16 +02:00
|
|
|
}
|
|
|
|
}
|
2010-04-12 01:52:16 +02:00
|
|
|
|
2010-06-30 22:36:16 +02:00
|
|
|
void GstEngine::timerEvent(QTimerEvent* e) {
|
2010-07-02 21:23:08 +02:00
|
|
|
if (e->timerId() != timer_id_)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (current_pipeline_) {
|
2011-02-13 19:29:27 +01:00
|
|
|
const qint64 current_position = position_nanosec();
|
|
|
|
const qint64 current_length = length_nanosec();
|
2011-01-02 19:53:45 +01:00
|
|
|
|
|
|
|
const qint64 remaining = current_length - current_position;
|
|
|
|
|
2011-02-14 20:34:37 +01:00
|
|
|
const qint64 fudge = kTimerIntervalNanosec + 100 * kNsecPerMsec; // Mmm fudge
|
2013-06-01 09:15:37 +02:00
|
|
|
const qint64 gap = buffer_duration_nanosec_ + (
|
|
|
|
autocrossfade_enabled_ ?
|
|
|
|
fadeout_duration_nanosec_ :
|
|
|
|
kPreloadGapNanosec);
|
2010-07-02 21:23:08 +02:00
|
|
|
|
2011-01-02 19:53:45 +01:00
|
|
|
// only if we know the length of the current stream...
|
|
|
|
if(current_length > 0) {
|
|
|
|
// emit TrackAboutToEnd when we're a few seconds away from finishing
|
|
|
|
if (remaining < gap + fudge) {
|
|
|
|
EmitAboutToEnd();
|
|
|
|
}
|
|
|
|
}
|
2010-04-12 01:52:16 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2011-03-20 22:40:53 +01:00
|
|
|
void GstEngine::HandlePipelineError(int pipeline_id, const QString& message,
|
|
|
|
int domain, int error_code) {
|
|
|
|
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
2011-03-20 20:18:54 +01:00
|
|
|
return;
|
|
|
|
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Gstreamer error:" << message;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
current_pipeline_.reset();
|
2011-03-10 19:01:35 +01:00
|
|
|
|
2012-01-23 15:58:10 +01:00
|
|
|
BufferingFinished();
|
2010-04-19 14:04:35 +02:00
|
|
|
emit StateChanged(Engine::Empty);
|
2011-03-10 19:01:35 +01:00
|
|
|
// unable to play media stream with this url
|
|
|
|
emit InvalidSongRequested(url_);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-03-12 14:24:30 +01:00
|
|
|
// TODO: the types of errors listed below won't be shown to user - they will
|
|
|
|
// get logged and the current song will be skipped; instead of maintaining
|
|
|
|
// the list we should probably:
|
|
|
|
// - don't report any engine's errors to user (always just log and skip)
|
|
|
|
// - come up with a less intrusive error box (not a dialog but a notification
|
|
|
|
// popup of some kind) and then report all errors
|
|
|
|
if(!(domain == GST_RESOURCE_ERROR && error_code == GST_RESOURCE_ERROR_NOT_FOUND) &&
|
2011-03-13 23:57:49 +01:00
|
|
|
!(domain == GST_STREAM_ERROR && error_code == GST_STREAM_ERROR_TYPE_NOT_FOUND) &&
|
|
|
|
!(domain == GST_RESOURCE_ERROR && error_code == GST_RESOURCE_ERROR_OPEN_READ)) {
|
2011-03-10 19:01:35 +01:00
|
|
|
emit Error(message);
|
|
|
|
}
|
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2011-03-20 22:40:53 +01:00
|
|
|
void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
|
|
|
|
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
2011-03-06 17:35:47 +01:00
|
|
|
return;
|
|
|
|
|
2012-01-23 15:58:10 +01:00
|
|
|
if (!has_next_track) {
|
2010-05-08 19:39:12 +02:00
|
|
|
current_pipeline_.reset();
|
2012-01-23 15:58:10 +01:00
|
|
|
BufferingFinished();
|
|
|
|
}
|
2010-04-12 01:24:03 +02:00
|
|
|
emit TrackEnded();
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2011-03-20 22:40:53 +01:00
|
|
|
void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle& bundle) {
|
|
|
|
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
|
|
|
return;
|
|
|
|
|
2010-04-12 01:24:03 +02:00
|
|
|
emit MetaData(bundle);
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-08-02 20:13:40 +02:00
|
|
|
GstElement* GstEngine::CreateElement(const QString& factoryName, GstElement* bin) {
|
|
|
|
// Make a unique name
|
|
|
|
QString name = factoryName + "-" + QString::number(next_element_id_ ++);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-08-02 20:13:40 +02:00
|
|
|
GstElement* element = gst_element_factory_make(
|
|
|
|
factoryName.toAscii().constData(), name.toAscii().constData());
|
|
|
|
|
|
|
|
if (!element) {
|
2010-04-21 13:14:12 +02:00
|
|
|
emit Error(QString("GStreamer could not create the element: %1. "
|
|
|
|
"Please make sure that you have installed all necessary GStreamer plugins (e.g. OGG and MP3)").arg( factoryName ) );
|
2010-08-02 20:13:40 +02:00
|
|
|
gst_object_unref(GST_OBJECT(bin));
|
|
|
|
return NULL;
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-08-02 20:13:40 +02:00
|
|
|
if (bin)
|
|
|
|
gst_bin_add(GST_BIN(bin), element);
|
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
return element;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
GstEngine::PluginDetailsList
|
|
|
|
GstEngine::GetPluginList(const QString& classname) const {
|
2010-08-28 21:22:58 +02:00
|
|
|
const_cast<GstEngine*>(this)->EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-04-07 15:51:14 +02:00
|
|
|
PluginDetailsList ret;
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
GstRegistry* registry = gst_registry_get_default();
|
2010-05-28 21:51:51 +02:00
|
|
|
GList* const features =
|
2010-04-07 15:51:14 +02:00
|
|
|
gst_registry_get_feature_list(registry, GST_TYPE_ELEMENT_FACTORY);
|
|
|
|
|
2010-05-28 21:51:51 +02:00
|
|
|
GList* p = features;
|
|
|
|
while (p) {
|
|
|
|
GstElementFactory* factory = GST_ELEMENT_FACTORY(p->data);
|
2010-04-07 15:51:14 +02:00
|
|
|
if (QString(factory->details.klass).contains(classname)) {
|
|
|
|
PluginDetails details;
|
2010-05-28 21:51:51 +02:00
|
|
|
details.name = QString::fromUtf8(GST_PLUGIN_FEATURE_NAME(p->data));
|
2010-04-07 15:51:14 +02:00
|
|
|
details.long_name = QString::fromUtf8(factory->details.longname);
|
|
|
|
details.description = QString::fromUtf8(factory->details.description);
|
|
|
|
details.author = QString::fromUtf8(factory->details.author);
|
|
|
|
ret << details;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
2010-05-28 21:51:51 +02:00
|
|
|
p = g_list_next(p);
|
2010-04-07 00:58:41 +02:00
|
|
|
}
|
2010-04-07 15:51:14 +02:00
|
|
|
|
2010-04-07 00:58:41 +02:00
|
|
|
gst_plugin_feature_list_free(features);
|
2010-04-07 15:51:14 +02:00
|
|
|
return ret;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-07-14 13:16:56 +02:00
|
|
|
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
2010-08-28 21:22:58 +02:00
|
|
|
EnsureInitialised();
|
2010-08-27 15:57:39 +02:00
|
|
|
|
2010-04-21 13:14:12 +02:00
|
|
|
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
|
2010-04-11 21:47:21 +02:00
|
|
|
ret->set_output_device(sink_, device_);
|
2010-05-23 15:07:15 +02:00
|
|
|
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
|
2011-02-13 19:29:27 +01:00
|
|
|
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
|
2012-05-20 20:50:25 +02:00
|
|
|
ret->set_mono_playback(mono_playback_);
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-06-06 16:06:23 +02:00
|
|
|
ret->AddBufferConsumer(this);
|
2011-03-10 19:01:35 +01:00
|
|
|
foreach (BufferConsumer* consumer, buffer_consumers_) {
|
2010-06-06 16:06:23 +02:00
|
|
|
ret->AddBufferConsumer(consumer);
|
2011-03-10 19:01:35 +01:00
|
|
|
}
|
2010-06-06 16:06:23 +02:00
|
|
|
|
2011-03-20 22:40:53 +01:00
|
|
|
connect(ret.get(), SIGNAL(EndOfStreamReached(int, bool)), SLOT(EndOfStreamReached(int, bool)));
|
|
|
|
connect(ret.get(), SIGNAL(Error(int, QString,int,int)), SLOT(HandlePipelineError(int, QString,int,int)));
|
|
|
|
connect(ret.get(), SIGNAL(MetadataFound(int, Engine::SimpleMetaBundle)),
|
|
|
|
SLOT(NewMetaData(int, Engine::SimpleMetaBundle)));
|
2012-01-23 15:58:10 +01:00
|
|
|
connect(ret.get(), SIGNAL(BufferingStarted()), SLOT(BufferingStarted()));
|
|
|
|
connect(ret.get(), SIGNAL(BufferingProgress(int)), SLOT(BufferingProgress(int)));
|
|
|
|
connect(ret.get(), SIGNAL(BufferingFinished()), SLOT(BufferingFinished()));
|
2010-04-06 18:57:02 +02:00
|
|
|
|
2010-07-14 13:16:56 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-03-06 17:35:47 +01:00
|
|
|
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl& url,
|
|
|
|
qint64 end_nanosec) {
|
2010-07-14 13:16:56 +02:00
|
|
|
shared_ptr<GstEnginePipeline> ret = CreatePipeline();
|
2010-12-03 14:53:43 +01:00
|
|
|
|
|
|
|
if (url.scheme() == "hypnotoad") {
|
|
|
|
ret->InitFromString(kHypnotoadPipeline);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-01-10 17:00:17 +01:00
|
|
|
if (url.scheme() == "enterprise") {
|
|
|
|
ret->InitFromString(kEnterprisePipeline);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-03-06 17:35:47 +01:00
|
|
|
if (!ret->InitFromUrl(url, end_nanosec))
|
2010-04-11 21:47:21 +02:00
|
|
|
ret.reset();
|
2010-04-07 00:58:41 +02:00
|
|
|
|
2010-04-11 21:47:21 +02:00
|
|
|
return ret;
|
2010-04-06 18:57:02 +02:00
|
|
|
}
|
|
|
|
|
2010-04-08 22:17:57 +02:00
|
|
|
bool GstEngine::DoesThisSinkSupportChangingTheOutputDeviceToAUserEditableString(const QString &name) {
|
2010-04-08 22:14:11 +02:00
|
|
|
return (name == "alsasink" || name == "osssink" || name == "pulsesink");
|
|
|
|
}
|
2010-06-06 16:06:23 +02:00
|
|
|
|
|
|
|
void GstEngine::AddBufferConsumer(BufferConsumer *consumer) {
|
|
|
|
buffer_consumers_ << consumer;
|
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->AddBufferConsumer(consumer);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::RemoveBufferConsumer(BufferConsumer *consumer) {
|
|
|
|
buffer_consumers_.removeAll(consumer);
|
|
|
|
if (current_pipeline_)
|
|
|
|
current_pipeline_->RemoveBufferConsumer(consumer);
|
|
|
|
}
|
2010-06-14 21:15:10 +02:00
|
|
|
|
2010-07-14 13:43:23 +02:00
|
|
|
int GstEngine::AddBackgroundStream(shared_ptr<GstEnginePipeline> pipeline) {
|
2010-06-14 21:15:10 +02:00
|
|
|
// We don't want to get metadata messages or end notifications.
|
2011-05-21 21:27:23 +02:00
|
|
|
disconnect(pipeline.get(), SIGNAL(MetadataFound(int,Engine::SimpleMetaBundle)), this, 0);
|
|
|
|
disconnect(pipeline.get(), SIGNAL(EndOfStreamReached(int,bool)), this, 0);
|
|
|
|
connect(pipeline.get(), SIGNAL(EndOfStreamReached(int,bool)), SLOT(BackgroundStreamFinished()));
|
2010-10-02 14:51:09 +02:00
|
|
|
|
2010-07-14 13:43:23 +02:00
|
|
|
const int stream_id = next_background_stream_id_++;
|
2010-06-14 21:15:10 +02:00
|
|
|
background_streams_[stream_id] = pipeline;
|
2010-10-02 14:51:09 +02:00
|
|
|
|
2011-05-04 00:38:24 +02:00
|
|
|
QFuture<GstStateChangeReturn> future = pipeline->SetState(GST_STATE_PLAYING);
|
|
|
|
BoundFutureWatcher<GstStateChangeReturn, int>* watcher =
|
|
|
|
new BoundFutureWatcher<GstStateChangeReturn, int>(stream_id, this);
|
|
|
|
watcher->setFuture(future);
|
|
|
|
connect(watcher, SIGNAL(finished()), SLOT(BackgroundStreamPlayDone()));
|
|
|
|
|
|
|
|
return stream_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::BackgroundStreamPlayDone() {
|
|
|
|
BoundFutureWatcher<GstStateChangeReturn, int>* watcher =
|
|
|
|
static_cast<BoundFutureWatcher<GstStateChangeReturn, int>*>(sender());
|
|
|
|
watcher->deleteLater();
|
|
|
|
|
|
|
|
const int stream_id = watcher->data();
|
|
|
|
GstStateChangeReturn ret = watcher->result();
|
2010-10-02 14:51:09 +02:00
|
|
|
|
|
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Could not set thread to PLAYING.";
|
2010-10-02 14:51:09 +02:00
|
|
|
background_streams_.remove(stream_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-14 13:43:23 +02:00
|
|
|
int GstEngine::AddBackgroundStream(const QUrl& url) {
|
2011-03-06 17:35:47 +01:00
|
|
|
shared_ptr<GstEnginePipeline> pipeline = CreatePipeline(url, 0);
|
2010-07-14 13:43:23 +02:00
|
|
|
if (!pipeline) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pipeline->SetVolume(30);
|
2011-03-06 17:35:47 +01:00
|
|
|
pipeline->SetNextUrl(url, 0, 0);
|
2010-07-14 13:43:23 +02:00
|
|
|
return AddBackgroundStream(pipeline);
|
|
|
|
}
|
|
|
|
|
2010-06-14 21:15:10 +02:00
|
|
|
void GstEngine::StopBackgroundStream(int id) {
|
|
|
|
background_streams_.remove(id); // Removes last shared_ptr reference.
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::BackgroundStreamFinished() {
|
|
|
|
GstEnginePipeline* pipeline = qobject_cast<GstEnginePipeline*>(sender());
|
2011-03-06 17:35:47 +01:00
|
|
|
pipeline->SetNextUrl(pipeline->url(), 0, 0);
|
2010-06-14 21:15:10 +02:00
|
|
|
}
|
2010-07-14 13:16:56 +02:00
|
|
|
|
2010-12-03 14:53:43 +01:00
|
|
|
void GstEngine::SetBackgroundStreamVolume(int id, int volume) {
|
|
|
|
shared_ptr<GstEnginePipeline> pipeline = background_streams_[id];
|
|
|
|
Q_ASSERT(pipeline);
|
|
|
|
pipeline->SetVolume(volume);
|
2010-07-14 13:16:56 +02:00
|
|
|
}
|
2012-01-23 15:58:10 +01:00
|
|
|
|
|
|
|
void GstEngine::BufferingStarted() {
|
|
|
|
if (buffering_task_id_ != -1) {
|
|
|
|
task_manager_->SetTaskFinished(buffering_task_id_);
|
|
|
|
}
|
|
|
|
|
|
|
|
buffering_task_id_ = task_manager_->StartTask(tr("Buffering"));
|
|
|
|
task_manager_->SetTaskProgress(buffering_task_id_, 0, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::BufferingProgress(int percent) {
|
|
|
|
task_manager_->SetTaskProgress(buffering_task_id_, percent, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GstEngine::BufferingFinished() {
|
|
|
|
if (buffering_task_id_ != -1) {
|
|
|
|
task_manager_->SetTaskFinished(buffering_task_id_);
|
|
|
|
buffering_task_id_ = -1;
|
|
|
|
}
|
|
|
|
}
|