Fix setting output/device for Xine and VLC backend

- Fixed setting output and device on Xine and VLC backend
- Fixed track slider for Xine, VLC and Phonon
- Improved backend settings to better support multiple backends
- Added group by samplerate and bitdepth in collection
- Fixed crash on exit when existing instance of the application is already runnung caused by NVIDIA driver
- Changed Q_OS_MAC to Q_OS_MACOS
This commit is contained in:
Jonas Kvinge 2018-06-28 01:15:32 +02:00
parent 6978983dd3
commit 985b91e5f4
56 changed files with 2799 additions and 2589 deletions

View File

@ -93,7 +93,6 @@ pkg_check_modules(GSTREAMER_BASE gstreamer-base-1.0)
pkg_check_modules(GSTREAMER_AUDIO gstreamer-audio-1.0)
pkg_check_modules(GSTREAMER_APP gstreamer-app-1.0)
pkg_check_modules(GSTREAMER_TAG gstreamer-tag-1.0)
pkg_check_modules(GSTREAMER_PBUTILS gstreamer-pbutils-1.0)
pkg_check_modules(LIBXINE libxine)
pkg_check_modules(LIBVLC libvlc)
pkg_check_modules(PHONON phonon4qt5)
@ -265,14 +264,13 @@ optional_component(GSTREAMER ON "Engine: GStreamer backend"
DEPENDS "gstreamer-app-1.0" GSTREAMER_APP_FOUND
DEPENDS "gstreamer-audio-1.0" GSTREAMER_AUDIO_FOUND
DEPENDS "gstreamer-tag-1.0" GSTREAMER_TAG_FOUND
DEPENDS "gstreamer-pbutils-1.0" GSTREAMER_PBUTILS_FOUND
)
optional_component(XINE OFF "Engine: Xine backend"
optional_component(XINE ON "Engine: Xine backend"
DEPENDS "libxine" LIBXINE_FOUND
)
optional_component(VLC OFF "Engine: VLC backend"
optional_component(VLC ON "Engine: VLC backend"
DEPENDS "libvlc" LIBVLC_FOUND
)

View File

@ -44,6 +44,7 @@ static Level sDefaultLevel = Level_Debug;
static QMap<QString, Level>* sClassLevels = nullptr;
static QIODevice *sNullDevice = nullptr;
//const char* kDefaultLogLevels = "*:3";
const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3";
static const char *kMessageHandlerMagic = "__logging_message__";

View File

@ -228,7 +228,7 @@ void WorkerPool<HandlerType>::DoStart() {
QStringList search_path;
search_path << qApp->applicationDirPath();
#ifdef Q_OS_MAC
#ifdef Q_OS_MACOS
search_path << qApp->applicationDirPath() + "/../PlugIns";
#endif

View File

@ -53,7 +53,6 @@ if(HAVE_GSTREAMER)
include_directories(${GSTREAMER_AUDIO_INCLUDE_DIRS})
include_directories(${GSTREAMER_BASE_INCLUDE_DIRS})
include_directories(${GSTREAMER_TAG_INCLUDE_DIRS})
include_directories(${GSTREAMER_PBUTILS_INCLUDE_DIRS})
endif()
if(HAVE_PHONON)
@ -503,7 +502,7 @@ optional_source(HAVE_GSTREAMER
# Xine
optional_source(HAVE_XINE
SOURCES engine/xineengine.cpp engine/xinescope.c
SOURCES engine/xineengine.cpp engine/xinescope.c engine/xinefader.cpp
HEADERS engine/xineengine.h
)
@ -863,7 +862,7 @@ if(HAVE_ALSA)
endif(HAVE_ALSA)
if(HAVE_GSTREAMER)
target_link_libraries(strawberry_lib ${GSTREAMER_LIBRARIES} ${GSTREAMER_BASE_LIBRARIES} ${GSTREAMER_AUDIO_LIBRARIES} ${GSTREAMER_APP_LIBRARIES} ${GSTREAMER_TAG_LIBRARIES} ${GSTREAMER_PBUTILS_LIBRARIES})
target_link_libraries(strawberry_lib ${GSTREAMER_LIBRARIES} ${GSTREAMER_BASE_LIBRARIES} ${GSTREAMER_AUDIO_LIBRARIES} ${GSTREAMER_APP_LIBRARIES} ${GSTREAMER_TAG_LIBRARIES})
endif()
if(HAVE_XINE)

View File

@ -13,7 +13,7 @@
#include <stdbool.h>
#include <vector>
#ifdef Q_WS_MACX
#ifdef Q_OS_MACOS
#include <OpenGL/gl.h> //included for convenience
#include <OpenGL/glu.h> //included for convenience
#else

View File

@ -125,21 +125,4 @@ void Collection::CurrentSongChanged(const Song &song) {
connect(reply, SIGNAL(Finished(bool)), reply, SLOT(deleteLater()));
}
if (song.filetype() == Song::Type_Asf) {
current_wma_song_url_ = song.url();
}
}
SongList Collection::FilterCurrentWMASong(SongList songs, Song* queued) {
for (SongList::iterator it = songs.begin(); it != songs.end(); ) {
if (it->url() == current_wma_song_url_) {
*queued = *it;
it = songs.erase(it);
}
else {
++it;
}
}
return songs;
}

View File

@ -73,9 +73,6 @@ class Collection : public QObject {
void CurrentSongChanged(const Song &song);
void Stopped();
private:
SongList FilterCurrentWMASong(SongList songs, Song* queued);
private:
Application *app_;
CollectionBackend *backend_;
@ -84,10 +81,6 @@ class Collection : public QObject {
CollectionWatcher *watcher_;
Thread *watcher_thread_;
// Hack: Gstreamer doesn't cope well with WMA files being rewritten while being played,
// so we delay statistics and rating changes until the current song has finished playing.
QUrl current_wma_song_url_;
// DB schema versions which should trigger a full collection rescan (each of those with a short reason why).
QHash<int, QString> full_rescan_revisions_;
};

View File

@ -219,7 +219,8 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
case GroupBy_Genre: key = song.genre(); break;
case GroupBy_AlbumArtist: key = song.effective_albumartist(); break;
case GroupBy_Year:
key = QString::number(qMax(0, song.year())); break;
key = QString::number(qMax(0, song.year()));
break;
case GroupBy_OriginalYear:
key = QString::number(qMax(0, song.effective_originalyear()));
break;
@ -227,8 +228,7 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
key = PrettyYearAlbum(qMax(0, song.year()), song.album());
break;
case GroupBy_OriginalYearAlbum:
key = PrettyYearAlbum(qMax(0, song.effective_originalyear()),
song.album());
key = PrettyYearAlbum(qMax(0, song.effective_originalyear()), song.album());
break;
case GroupBy_FileType:
key = song.filetype();
@ -236,6 +236,12 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
case GroupBy_Bitrate:
key = song.bitrate();
break;
case GroupBy_Samplerate:
key = song.samplerate();
break;
case GroupBy_Bitdepth:
key = song.bitdepth();
break;
case GroupBy_None:
qLog(Error) << "GroupBy_None";
break;
@ -325,6 +331,12 @@ QString CollectionModel::DividerKey(GroupBy type, CollectionItem *item) const {
case GroupBy_Bitrate:
return SortTextForNumber(item->metadata.bitrate());
case GroupBy_Samplerate:
return SortTextForNumber(item->metadata.samplerate());
case GroupBy_Bitdepth:
return SortTextForNumber(item->metadata.bitdepth());
case GroupBy_None:
return QString();
@ -364,6 +376,14 @@ QString CollectionModel::DividerDisplayText(GroupBy type, const QString &key) co
case GroupBy_Bitrate:
if (key == "000") return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s
case GroupBy_Samplerate:
if (key == "000") return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s
case GroupBy_Bitdepth:
if (key == "000") return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s
case GroupBy_None:
// fallthrough
@ -836,6 +856,12 @@ void CollectionModel::InitQuery(GroupBy type, CollectionQuery *q) {
case GroupBy_Bitrate:
q->SetColumnSpec("DISTINCT bitrate");
break;
case GroupBy_Samplerate:
q->SetColumnSpec("DISTINCT samplerate");
break;
case GroupBy_Bitdepth:
q->SetColumnSpec("DISTINCT bitdepth");
break;
case GroupBy_None:
q->SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
break;
@ -911,6 +937,12 @@ void CollectionModel::FilterQuery(GroupBy type, CollectionItem *item, Collection
case GroupBy_Bitrate:
q->AddWhere("bitrate", item->key);
break;
case GroupBy_Samplerate:
q->AddWhere("samplerate", item->key);
break;
case GroupBy_Bitdepth:
q->AddWhere("bitdepth", item->key);
break;
case GroupBy_None:
qLog(Error) << "Unknown GroupBy type" << type << "used in filter";
break;
@ -936,10 +968,7 @@ CollectionItem *CollectionModel::InitItem(GroupBy type, bool signal, CollectionI
CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const SqlRow &row, int container_level) {
CollectionItem *item = InitItem(type, signal, parent, container_level);
int year = 0;
int effective_originalyear = 0;
int bitrate = 0;
int disc = 0;
int year(0), effective_originalyear(0), disc(0), bitrate(0), samplerate(0), bitdepth(0);
switch (type) {
case GroupBy_Artist:
@ -972,13 +1001,11 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
item->key = QString::number(year);
item->sort_text = SortTextForNumber(year) + " ";
break;
case GroupBy_OriginalYear:
year = qMax(0, row.value(0).toInt());
item->key = QString::number(year);
item->sort_text = SortTextForNumber(year) + " ";
break;
case GroupBy_Composer:
case GroupBy_Performer:
case GroupBy_Grouping:
@ -1006,6 +1033,18 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
item->key = QString::number(bitrate);
item->sort_text = SortTextForNumber(bitrate) + " ";
break;
case GroupBy_Samplerate:
samplerate = qMax(0, row.value(0).toInt());
item->key = QString::number(samplerate);
item->sort_text = SortTextForNumber(samplerate) + " ";
break;
case GroupBy_Bitdepth:
bitdepth = qMax(0, row.value(0).toInt());
item->key = QString::number(bitdepth);
item->sort_text = SortTextForNumber(bitdepth) + " ";
break;
case GroupBy_None:
item->metadata.InitFromQuery(row, true);
@ -1024,10 +1063,7 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
CollectionItem *CollectionModel::ItemFromSong(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const Song &s, int container_level) {
CollectionItem *item = InitItem(type, signal, parent, container_level);
int year = 0;
int originalyear = 0;
int effective_originalyear = 0;
int bitrate = 0;
int year(0), originalyear(0), effective_originalyear(0), bitrate(0), samplerate(0), bitdepth(0);
switch (type) {
case GroupBy_Artist:
@ -1092,6 +1128,18 @@ CollectionItem *CollectionModel::ItemFromSong(GroupBy type, bool signal, bool cr
item->key = QString::number(bitrate);
item->sort_text = SortTextForNumber(bitrate) + " ";
break;
case GroupBy_Samplerate:
samplerate = qMax(0, s.samplerate());
item->key = QString::number(samplerate);
item->sort_text = SortTextForNumber(samplerate) + " ";
break;
case GroupBy_Bitdepth:
bitdepth = qMax(0, s.bitdepth());
item->key = QString::number(bitdepth);
item->sort_text = SortTextForNumber(bitdepth) + " ";
break;
case GroupBy_None:
item->metadata = s;

View File

@ -99,6 +99,8 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
GroupBy_Disc = 12,
GroupBy_OriginalYearAlbum = 13,
GroupBy_OriginalYear = 14,
GroupBy_Samplerate = 15,
GroupBy_Bitdepth = 16
};
struct Grouping {

View File

@ -230,7 +230,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
ScanTransaction transaction(this, dir.id, true);
transaction.SetKnownSubdirs(subdirs);
transaction.AddToProgressMax(subdirs.count());
for (const Subdirectory& subdir : subdirs) {
for (const Subdirectory &subdir : subdirs) {
if (stop_requested_) return;
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, &transaction);
@ -742,9 +742,9 @@ void CollectionWatcher::ReloadSettings() {
}
else if (monitor_ && !was_monitoring_before) {
// Add all directories to all QFileSystemWatchers again
for (const Directory& dir : watched_dirs_.values()) {
for (const Directory &dir : watched_dirs_.values()) {
SubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
for (const Subdirectory& subdir : subdirs) {
for (const Subdirectory &subdir : subdirs) {
AddWatch(dir, subdir.path);
}
}
@ -783,12 +783,12 @@ void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
void CollectionWatcher::PerformScan(bool incremental, bool ignore_mtimes) {
for (const Directory & dir : watched_dirs_.values()) {
for (const Directory &dir : watched_dirs_.values()) {
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes);
SubdirectoryList subdirs(transaction.GetAllSubdirs());
transaction.AddToProgressMax(subdirs.count());
for (const Subdirectory & subdir : subdirs) {
for (const Subdirectory &subdir : subdirs) {
if (stop_requested_) return;
ScanSubdirectory(subdir.path, subdir, &transaction);

View File

@ -96,11 +96,13 @@ GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_Grou
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbum, 9));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYearAlbum, 10));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitrate, 11));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 12));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 13));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 14));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Samplerate, 12));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitdepth, 13));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 14));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 15));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 16));
connect(ui_->button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()), SLOT(Reset()));
connect(ui_->buttonbox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), SLOT(Reset()));
resize(sizeHint());
}
@ -108,23 +110,23 @@ GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_Grou
GroupByDialog::~GroupByDialog() {}
void GroupByDialog::Reset() {
ui_->first->setCurrentIndex(2); // Artist
ui_->second->setCurrentIndex(1); // Album
ui_->third->setCurrentIndex(0); // None
ui_->combobox_first->setCurrentIndex(2); // Artist
ui_->combobox_second->setCurrentIndex(1); // Album
ui_->combobox_third->setCurrentIndex(0); // None
}
void GroupByDialog::accept() {
emit Accepted(CollectionModel::Grouping(
p_->mapping_.get<tag_index>().find(ui_->first->currentIndex())->group_by,
p_->mapping_.get<tag_index>().find(ui_->second->currentIndex())->group_by,
p_->mapping_.get<tag_index>().find(ui_->third->currentIndex())->group_by)
p_->mapping_.get<tag_index>().find(ui_->combobox_first->currentIndex())->group_by,
p_->mapping_.get<tag_index>().find(ui_->combobox_second->currentIndex())->group_by,
p_->mapping_.get<tag_index>().find(ui_->combobox_third->currentIndex())->group_by)
);
QDialog::accept();
}
void GroupByDialog::CollectionGroupingChanged(const CollectionModel::Grouping &g) {
ui_->first->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[0])->combo_box_index);
ui_->second->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[1])->combo_box_index);
ui_->third->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[2])->combo_box_index);
ui_->combobox_first->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[0])->combo_box_index);
ui_->combobox_second->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[1])->combo_box_index);
ui_->combobox_third->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[2])->combo_box_index);
}

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>354</width>
<height>236</height>
<height>246</height>
</rect>
</property>
<property name="windowTitle">
@ -19,7 +19,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_4">
<widget class="QLabel" name="label">
<property name="text">
<string>You can change the way the songs in the collection are organised.</string>
</property>
@ -35,14 +35,14 @@
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<widget class="QLabel" name="label_first">
<property name="text">
<string>First level</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="first">
<widget class="QComboBox" name="combobox_first">
<item>
<property name="text">
<string>None</string>
@ -103,6 +103,16 @@
<string>Bitrate</string>
</property>
</item>
<item>
<property name="text">
<string>Sample rate</string>
</property>
</item>
<item>
<property name="text">
<string>Bit depth</string>
</property>
</item>
<item>
<property name="text">
<string>Disc</string>
@ -121,14 +131,14 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="label_second">
<property name="text">
<string>Second level</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="second">
<widget class="QComboBox" name="combobox_second">
<item>
<property name="text">
<string>None</string>
@ -189,6 +199,16 @@
<string>Bitrate</string>
</property>
</item>
<item>
<property name="text">
<string>Sample rate</string>
</property>
</item>
<item>
<property name="text">
<string>Bit depth</string>
</property>
</item>
<item>
<property name="text">
<string>Disc</string>
@ -207,14 +227,14 @@
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<widget class="QLabel" name="label_third">
<property name="text">
<string>Third level</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="third">
<widget class="QComboBox" name="combobox_third">
<item>
<property name="text">
<string>None</string>
@ -275,6 +295,16 @@
<string>Bitrate</string>
</property>
</item>
<item>
<property name="text">
<string>Sample rate</string>
</property>
</item>
<item>
<property name="text">
<string>Bit depth</string>
</property>
</item>
<item>
<property name="text">
<string>Disc</string>
@ -309,7 +339,7 @@
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<widget class="QDialogButtonBox" name="buttonbox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -321,17 +351,17 @@
</layout>
</widget>
<tabstops>
<tabstop>first</tabstop>
<tabstop>second</tabstop>
<tabstop>third</tabstop>
<tabstop>button_box</tabstop>
<tabstop>combobox_first</tabstop>
<tabstop>combobox_second</tabstop>
<tabstop>combobox_third</tabstop>
<tabstop>buttonbox</tabstop>
</tabstops>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections>
<connection>
<sender>button_box</sender>
<sender>buttonbox</sender>
<signal>accepted()</signal>
<receiver>GroupByDialog</receiver>
<slot>accept()</slot>
@ -347,7 +377,7 @@
</hints>
</connection>
<connection>
<sender>button_box</sender>
<sender>buttonbox</sender>
<signal>rejected()</signal>
<receiver>GroupByDialog</receiver>
<slot>reject()</slot>

View File

@ -110,6 +110,12 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g)
case CollectionModel::GroupBy_Bitrate: {
return tr("Bitrate");
}
case CollectionModel::GroupBy_Samplerate: {
return tr("Sample rate");
}
case CollectionModel::GroupBy_Bitdepth: {
return tr("Bit depth");
}
case CollectionModel::GroupBy_Disc: {
return tr("Disc");
}

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -60,6 +61,8 @@
#include <iostream>
#endif // Q_OS_WIN32
#include "main.h"
#include "core/logging.h"
#include "qtsingleapplication.h"
@ -132,6 +135,7 @@ int main(int argc, char* argv[]) {
qLog(Info) << "Strawberry is already running - activating existing window";
}
if (a.sendMessage(options.Serialize(), 5000)) {
main_exit_safe(0);
return 0;
}
// Couldn't send the message so start anyway
@ -175,8 +179,8 @@ int main(int argc, char* argv[]) {
QCoreApplication::setAttribute(Qt::AA_NativeWindows, true);
#endif
// Set the permissions on the config file on Unix - it can contain passwords for internet services so it's important that other users can't read it.
// On Windows these are stored in the registry instead.
// Set the permissions on the config file on Unix - it can contain passwords for internet services so it's important that other users can't read it.
// On Windows these are stored in the registry instead.
#ifdef Q_OS_UNIX
{
QSettings s;
@ -226,17 +230,44 @@ int main(int argc, char* argv[]) {
int ret = a.exec();
#ifdef Q_OS_LINUX
QFile self_maps("/proc/self/maps");
if (self_maps.open(QIODevice::ReadOnly)) {
QByteArray data = self_maps.readAll();
if (data.contains("libnvidia-tls.so.")) {
qLog(Warning) << "Exiting immediately to work around NVIDIA driver bug";
_exit(ret);
}
self_maps.close();
}
#endif
main_exit_safe(ret);
return ret;
}
void main_exit_safe(int ret) {
#ifdef Q_OS_LINUX
bool have_nvidia = false;
QFile proc_modules("/proc/modules");
if (proc_modules.open(QIODevice::ReadOnly)) {
forever {
QByteArray line = proc_modules.readLine();
if (line.startsWith("nvidia ") || line.startsWith("nvidia_")) {
have_nvidia = true;
}
if (proc_modules.atEnd()) break;
}
proc_modules.close();
}
QFile self_maps("/proc/self/maps");
if (self_maps.open(QIODevice::ReadOnly)) {
forever {
QByteArray line = self_maps.readLine();
if (line.startsWith("libnvidia-")) {
have_nvidia = true;
}
if (self_maps.atEnd()) break;
}
self_maps.close();
}
if (have_nvidia) {
qLog(Warning) << "Exiting immediately to work around NVIDIA driver bug.";
_exit(ret);
}
#endif
}

28
src/core/main.h Normal file
View File

@ -0,0 +1,28 @@
/*
* Strawberry Music Player
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MAIN_H
#define MAIN_H
#include "config.h"
int main(int argc, char* argv[]);
void main_exit_safe(int ret);
#endif // MAIN_H

View File

@ -741,7 +741,6 @@ void MainWindow::ReloadAllSettings() {
app_->player()->ReloadSettings();
osd_->ReloadSettings();
collection_view_->ReloadSettings();
app_->player()->engine()->ReloadSettings();
ui_->playlist->view()->ReloadSettings();
}

View File

@ -75,8 +75,6 @@ using std::shared_ptr;
Player::Player(Application *app, QObject *parent)
: PlayerInterface(parent),
app_(app),
//engine_(new GstEngine(app_->task_manager())),
//engine_(CreateEngine()),
stream_change_type_(Engine::First),
last_state_(Engine::Empty),
nb_errors_received_(0),
@ -88,7 +86,7 @@ Player::Player(Application *app, QObject *parent)
QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", BackendSettingsPage::EngineText_GStreamer).toString().toLower());
Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", EngineName(Engine::GStreamer)).toString().toLower());
s.endGroup();
CreateEngine(enginetype);
@ -129,19 +127,19 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
enginebase = new XineEngine(app_->task_manager());
break;
#endif
#ifdef HAVE_PHONON
case Engine::Phonon:
engine=true;
enginetype=Engine::Phonon;
enginebase = new PhononEngine(app_->task_manager());
break;
#endif
#ifdef HAVE_VLC
case Engine::VLC:
engine=true;
enginetype=Engine::VLC;
enginebase = new VLCEngine(app_->task_manager());
break;
#endif
#ifdef HAVE_PHONON
case Engine::Phonon:
engine=true;
enginetype=Engine::Phonon;
enginebase = new PhononEngine(app_->task_manager());
break;
#endif
default:
if (i > 1) { qFatal("No engine available!"); return nullptr; }
@ -149,7 +147,7 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.setValue("engine", "");
s.setValue("output", "");
s.setValue("device", "");
s.setValue("device", QVariant(""));
s.endGroup();
enginetype = Engine::None;
break;
@ -158,7 +156,7 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.setValue("engine", Engine::EngineNameFromType(enginetype));
s.setValue("engine", Engine::EngineName(enginetype));
s.endGroup();
if (enginebase == nullptr) {
@ -226,7 +224,7 @@ void Player::ReloadSettings() {
seek_step_sec_ = s.value("seek_step_sec", 10).toInt();
s.endGroup();
engine_->ReloadSettings();
if (engine_.get()) engine_->ReloadSettings();
}

View File

@ -671,7 +671,7 @@ bool IsLaptop() {
return !(status.BatteryFlag & 128); // 128 = no system battery
#elif defined(Q_OS_LINUX)
return !QDir("/proc/acpi/battery").entryList(QDir::Dirs | QDir::NoDotAndDotDot).isEmpty();
#elif defined(Q_OS_MAC)
#elif defined(Q_OS_MACOS)
ScopedCFTypeRef<CFTypeRef> power_sources(IOPSCopyPowerSourcesInfo());
ScopedCFTypeRef<CFArrayRef> power_source_list(IOPSCopyPowerSourcesList(power_sources.get()));
for (CFIndex i = 0; i < CFArrayGetCount(power_source_list.get()); ++i) {

View File

@ -58,6 +58,8 @@ void iLister::EventCallback(const idevice_event_t *event, void *context) {
case IDEVICE_DEVICE_REMOVE:
me->DeviceRemovedCallback(uuid);
break;
default:
break;
}
}

View File

@ -96,7 +96,8 @@ OrganiseDialog::OrganiseDialog(TaskManager *task_manager, QWidget *parent)
tags[tr("Comment")] = "comment";
tags[tr("Length")] = "length";
tags[tr("Bitrate", "Refers to bitrate in file organise dialog.")] = "bitrate";
tags[tr("Samplerate")] = "samplerate";
tags[tr("Sample rate")] = "samplerate";
tags[tr("Bit depth")] = "bitdepth";
tags[tr("File extension")] = "extension";
// Naming scheme input field

View File

@ -35,8 +35,7 @@
#include "alsadevicefinder.h"
AlsaDeviceFinder::AlsaDeviceFinder()
: DeviceFinder("alsa", "alsasink") {
: DeviceFinder("alsa", {"alsa","alsasink"}) {
}
QList<DeviceFinder::Device> AlsaDeviceFinder::ListDevices() {

View File

@ -23,7 +23,7 @@
#include "devicefinder.h"
DeviceFinder::DeviceFinder(const QString &name, const QString &gstsink): name_(name), gstsink_(gstsink) {
DeviceFinder::DeviceFinder(const QString &name, const QStringList &outputs): name_(name), outputs_(outputs) {
}
QString DeviceFinder::GuessIconName(const QString &description) {

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
* Copyright 2017, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -41,8 +42,9 @@ class DeviceFinder {
virtual ~DeviceFinder() {}
// The name of the gstreamer sink element that devices found by this class can be used with.
QString name() const { return name_; }
QStringList outputs() const { return outputs_; }
void add_output(const QString output) { outputs_.append(output); }
// Does any necessary setup, returning false if this DeviceFinder cannot be used.
virtual bool Initialise() = 0;
@ -51,13 +53,13 @@ class DeviceFinder {
virtual QList<Device> ListDevices() = 0;
protected:
explicit DeviceFinder(const QString &name, const QString &gstsink);
explicit DeviceFinder(const QString &name, const QStringList &outputs);
static QString GuessIconName(const QString &description);
private:
QString name_;
QString gstsink_;
QStringList outputs_;
};

View File

@ -35,7 +35,7 @@
#include "core/logging.h"
DirectSoundDeviceFinder::DirectSoundDeviceFinder()
: DeviceFinder("directsound", "directsoundsink") {
: DeviceFinder("directsound", { "directsound", "dsound", "directsoundsink" }) {
}
QList<DeviceFinder::Device> DirectSoundDeviceFinder::ListDevices() {

View File

@ -1,9 +1,10 @@
/*
* Strawberry Music Player
* This file was part of Amarok / Clementine.
* This file was part of Amarok / Clementine
* Copyright 2003 Mark Kretschmann
* Copyright 2004, 2005 Max Howell, <max.howell@methylblue.com>
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2004 - 2005 Max Howell, <max.howell@methylblue.com>
* Copyright 2010 David Sansome <me@davidsansome.com>
* Copyright 2017 - 2018 Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -25,12 +26,14 @@
#include <cmath>
#include <QtGlobal>
#include <QVariant>
#include <QUrl>
#include <QSettings>
#include "core/timeconstants.h"
#include "engine_fwd.h"
#include "enginebase.h"
#include "settings/backendsettingspage.h"
#include "settings/playbacksettingspage.h"
Engine::Base::Base()
@ -38,12 +41,21 @@ Engine::Base::Base()
beginning_nanosec_(0),
end_nanosec_(0),
scope_(kScopeSize),
output_(""),
device_(QVariant("")),
rg_enabled_(false),
rg_mode_(0),
rg_preamp_(0),
rg_compression_(true),
buffer_duration_nanosec_(4000),
buffer_min_fill_(33),
mono_playback_(false),
fadeout_enabled_(true),
fadeout_duration_nanosec_(2 * kNsecPerSec), // 2s
crossfade_enabled_(true),
autocrossfade_enabled_(false),
crossfade_same_album_(false),
next_background_stream_id_(0),
fadeout_duration_(2),
fadeout_duration_nanosec_(2 * kNsecPerSec),
about_to_end_emitted_(false) {}
Engine::Base::~Base() {}
@ -61,6 +73,14 @@ bool Engine::Base::Load(const QUrl &url, TrackChangeFlags, bool force_stop_at_en
}
bool Engine::Base::Play(const QUrl &url, TrackChangeFlags flags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
if (!Load(url, flags, force_stop_at_end, beginning_nanosec, end_nanosec))
return false;
return Play(0);
}
void Engine::Base::SetVolume(uint value) {
volume_ = value;
@ -77,15 +97,30 @@ uint Engine::Base::MakeVolumeLogarithmic(uint volume) {
void Engine::Base::ReloadSettings() {
QSettings s;
s.beginGroup(PlaybackSettingsPage::kSettingsGroup);
s.beginGroup(BackendSettingsPage::kSettingsGroup);
output_ = s.value("output").toString();
device_ = s.value("device");
rg_enabled_ = s.value("rgenabled", false).toBool();
rg_mode_ = s.value("rgmode", 0).toInt();
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
rg_compression_ = s.value("rgcompression", true).toBool();
buffer_duration_nanosec_ = s.value("bufferduration", 4000).toLongLong() * kNsecPerMsec;
buffer_min_fill_ = s.value("bufferminfill", 33).toInt();
mono_playback_ = s.value("monoplayback", false).toBool();
s.endGroup();
s.beginGroup(PlaybackSettingsPage::kSettingsGroup);
fadeout_enabled_ = s.value("FadeoutEnabled", false).toBool();
fadeout_duration_nanosec_ = s.value("FadeoutDuration", 2000).toLongLong() * kNsecPerMsec;
crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool();
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
crossfade_same_album_ = !s.value("NoCrossfadeSameAlbum", true).toBool();
fadeout_pause_enabled_ = s.value("FadeoutPauseEnabled", false).toBool();
fadeout_pause_duration_nanosec_ = s.value("FadeoutPauseDuration", 250).toLongLong() * kNsecPerMsec;
fadeout_duration_ = s.value("FadeoutDuration", 2000).toLongLong();
fadeout_duration_nanosec_ = (fadeout_duration_ * kNsecPerMsec);
fadeout_pause_duration_ = s.value("FadeoutPauseDuration", 250).toLongLong();
fadeout_pause_duration_nanosec_ = (fadeout_pause_duration_ * kNsecPerMsec);
s.endGroup();
}
@ -97,11 +132,3 @@ void Engine::Base::EmitAboutToEnd() {
about_to_end_emitted_ = true;
emit TrackAboutToEnd();
}
bool Engine::Base::Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
if (!Load(u, c, force_stop_at_end, beginning_nanosec, end_nanosec))
return false;
return Play(0);
}

View File

@ -1,9 +1,10 @@
/*
* Strawberry Music Player
* This file was part of Amarok / Clementine.
* This file was part of Amarok / Clementine
* Copyright 2003 Mark Kretschmann
* Copyright 2004, 2005 Max Howell, <max.howell@methylblue.com>
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2004 - 2005 Max Howell, <max.howell@methylblue.com>
* Copyright 2010 David Sansome <me@davidsansome.com>
* Copyright 2017 - 2018 Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -34,6 +35,7 @@
#include <QObject>
#include <QList>
#include <QMetaType>
#include <QVariant>
#include <QString>
#include <QUrl>
@ -49,52 +51,13 @@ typedef std::vector<int16_t> Scope;
class Base : public QObject {
Q_OBJECT
public:
protected:
Base();
public:
virtual ~Base();
virtual bool Init() = 0;
virtual void StartPreloading(const QUrl&, bool, qint64, qint64) {}
virtual bool Play(quint64 offset_nanosec) = 0;
virtual void Stop(bool stop_after = false) = 0;
virtual void Pause() = 0;
virtual void Unpause() = 0;
virtual void Seek(quint64 offset_nanosec) = 0;
virtual State state() const = 0;
virtual qint64 position_nanosec() const = 0;
virtual qint64 length_nanosec() const = 0;
// Subclasses should respect given markers (beginning and end) which are in miliseconds.
virtual bool Load(const QUrl &url, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
// Sets new values for the beginning and end markers of the currently playing song.
// This doesn't change the state of engine or the stream's current position.
virtual void RefreshMarkers(quint64 beginning_nanosec, qint64 end_nanosec) {
beginning_nanosec_ = beginning_nanosec;
end_nanosec_ = end_nanosec;
}
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
bool Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
void SetVolume(uint value);
// Simple accessors
EngineType type() const { return type_; }
inline uint volume() const { return volume_; }
virtual const Scope &scope(int chunk_length) { return scope_; }
bool is_fadeout_enabled() const { return fadeout_enabled_; }
bool is_crossfade_enabled() const { return crossfade_enabled_; }
bool is_autocrossfade_enabled() const { return autocrossfade_enabled_; }
bool crossfade_same_album() const { return crossfade_same_album_; }
static const int kScopeSize = 1024;
virtual void SetVolumeSW(uint percent) = 0;
static uint MakeVolumeLogarithmic(uint volume);
struct OutputDetails {
QString name;
QString description;
@ -102,9 +65,60 @@ class Base : public QObject {
};
typedef QList<OutputDetails> OutputDetailsList;
public slots:
virtual bool Init() = 0;
virtual State state() const = 0;
virtual void StartPreloading(const QUrl&, bool, qint64, qint64) {}
virtual bool Load(const QUrl &url, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
virtual bool Play(quint64 offset_nanosec) = 0;
virtual void Stop(bool stop_after = false) = 0;
virtual void Pause() = 0;
virtual void Unpause() = 0;
virtual void Seek(quint64 offset_nanosec) = 0;
virtual void SetVolumeSW(uint percent) = 0;
virtual qint64 position_nanosec() const = 0;
virtual qint64 length_nanosec() const = 0;
virtual const Scope &scope(int chunk_length) { return scope_; }
// Sets new values for the beginning and end markers of the currently playing song.
// This doesn't change the state of engine or the stream's current position.
virtual void RefreshMarkers(quint64 beginning_nanosec, qint64 end_nanosec) {
beginning_nanosec_ = beginning_nanosec;
end_nanosec_ = end_nanosec;
}
virtual OutputDetailsList GetOutputsList() const = 0;
virtual QString DefaultOutput() = 0;
virtual bool CustomDeviceSupport(const QString &name) = 0;
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
bool Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
void SetVolume(uint value);
static uint MakeVolumeLogarithmic(uint volume);
public slots:
virtual void ReloadSettings();
protected:
void EmitAboutToEnd();
public:
// Simple accessors
EngineType type() const { return type_; }
inline uint volume() const { return volume_; }
bool is_fadeout_enabled() const { return fadeout_enabled_; }
bool is_crossfade_enabled() const { return crossfade_enabled_; }
bool is_autocrossfade_enabled() const { return autocrossfade_enabled_; }
bool crossfade_same_album() const { return crossfade_same_album_; }
bool IsEqualizerEnabled() { return equalizer_enabled_; }
static const int kScopeSize = 1024;
public slots:
virtual void SetEqualizerEnabled(bool) {}
virtual void SetEqualizerParameters(int preamp, const QList<int> &bandGains) {}
virtual void SetStereoBalance(float value) {}
@ -132,28 +146,8 @@ signals:
// subsequent call to state() won't return a stale value.
void StateChanged(Engine::State);
protected:
Base();
protected:
void EmitAboutToEnd();
protected:
EngineType type_;
uint volume_;
quint64 beginning_nanosec_;
qint64 end_nanosec_;
QUrl url_;
Scope scope_;
bool fadeout_enabled_;
qint64 fadeout_duration_nanosec_;
bool crossfade_enabled_;
bool autocrossfade_enabled_;
bool crossfade_same_album_;
int next_background_stream_id_;
bool fadeout_pause_enabled_;
qint64 fadeout_pause_duration_nanosec_;
struct PluginDetails {
QString name;
QString description;
@ -161,7 +155,43 @@ signals:
};
typedef QList<PluginDetails> PluginDetailsList;
private:
EngineType type_;
uint volume_;
quint64 beginning_nanosec_;
qint64 end_nanosec_;
QUrl url_;
Scope scope_;
bool buffering_;
bool equalizer_enabled_;
// Settings
QString output_;
QVariant device_;
// ReplayGain
bool rg_enabled_;
int rg_mode_;
float rg_preamp_;
bool rg_compression_;
// Buffering
quint64 buffer_duration_nanosec_;
int buffer_min_fill_;
bool mono_playback_;
// Fadeout
bool fadeout_enabled_;
bool crossfade_enabled_;
bool autocrossfade_enabled_;
bool crossfade_same_album_;
bool fadeout_pause_enabled_;
qint64 fadeout_duration_;
qint64 fadeout_duration_nanosec_;
qint64 fadeout_pause_duration_;
qint64 fadeout_pause_duration_nanosec_;
private:
bool about_to_end_emitted_;
Q_DISABLE_COPY(Base);

View File

@ -26,18 +26,6 @@
namespace Engine {
QString EngineNameFromType(Engine::EngineType enginetype) {
switch (enginetype) {
case Engine::Xine: return QObject::tr("Xine");
case Engine::GStreamer: return QObject::tr("GStreamer");
case Engine::Phonon: return QObject::tr("Phonon");
case Engine::VLC: return QObject::tr("VLC");
case Engine::None:
default: return QObject::tr("None");
}
}
Engine::EngineType EngineTypeFromName(QString enginename) {
QString lower = enginename.toLower();
@ -50,4 +38,28 @@ Engine::EngineType EngineTypeFromName(QString enginename) {
}
QString EngineName(Engine::EngineType enginetype) {
switch (enginetype) {
case Engine::Xine: return QObject::tr("xine");
case Engine::GStreamer: return QObject::tr("gstreamer");
case Engine::Phonon: return QObject::tr("phonon");
case Engine::VLC: return QObject::tr("vlc");
case Engine::None:
default: return QObject::tr("None");
}
}
QString EngineDescription(Engine::EngineType enginetype) {
switch (enginetype) {
case Engine::Xine: return QObject::tr("Xine");
case Engine::GStreamer: return QObject::tr("GStreamer");
case Engine::Phonon: return QObject::tr("Phonon");
case Engine::VLC: return QObject::tr("VLC");
case Engine::None:
default: return QObject::tr("None");
}
}
}

View File

@ -35,8 +35,9 @@ enum EngineType {
Phonon
};
QString EngineNameFromType(Engine::EngineType enginetype);
Engine::EngineType EngineTypeFromName(QString enginename);
QString EngineName(Engine::EngineType enginetype);
QString EngineDescription(Engine::EngineType enginetype);
}
Q_DECLARE_METATYPE(Engine::EngineType);

View File

@ -1,4 +1,5 @@
/***************************************************************************
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* 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> *
@ -30,7 +31,6 @@
#include <string>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include <QtGlobal>
#include <QCoreApplication>
@ -90,15 +90,7 @@ GstEngine::GstEngine(TaskManager *task_manager)
task_manager_(task_manager),
buffering_task_id_(-1),
latest_buffer_(nullptr),
equalizer_enabled_(false),
stereo_balance_(0.0f),
rg_enabled_(false),
rg_mode_(0),
rg_preamp_(0.0),
rg_compression_(true),
buffer_duration_nanosec_(1 * kNsecPerSec), // 1s
buffer_min_fill_(33),
mono_playback_(false),
seek_timer_(new QTimer(this)),
timer_id_(-1),
next_element_id_(0),
@ -143,103 +135,6 @@ bool GstEngine::Init() {
}
void GstEngine::InitialiseGStreamer() {
gst_init(nullptr, nullptr);
gst_pb_utils_init();
#ifdef HAVE_IMOBILEDEVICE_ // FIXME
afcsrc_register_static();
#endif
}
void GstEngine::SetEnvironment() {
QString scanner_path;
QString plugin_path;
QString registry_filename;
// On windows and mac we bundle the gstreamer plugins with strawberry
#if defined(Q_OS_DARWIN)
scanner_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gst-plugin-scanner";
plugin_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gstreamer";
#elif defined(Q_OS_WIN32)
plugin_path = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + "/gstreamer-plugins");
#endif
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
registry_filename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QString("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
#endif
if (!scanner_path.isEmpty()) Utilities::SetEnv("GST_PLUGIN_SCANNER", scanner_path);
if (!plugin_path.isEmpty()) {
Utilities::SetEnv("GST_PLUGIN_PATH", plugin_path);
// Never load plugins from anywhere else.
Utilities::SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path);
}
if (!registry_filename.isEmpty()) {
Utilities::SetEnv("GST_REGISTRY", registry_filename);
}
#ifdef Q_OS_DARWIN
Utilities::SetEnv("GIO_EXTRA_MODULES", QCoreApplication::applicationDirPath() + "/../PlugIns/gio-modules");
#endif
Utilities::SetEnv("PULSE_PROP_media.role", "music");
}
void GstEngine::ReloadSettings() {
Engine::Base::ReloadSettings();
QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
sink_ = s.value("output", kAutoSink).toString();
device_ = s.value("device");
if (sink_.isEmpty()) sink_ = kAutoSink;
rg_enabled_ = s.value("rgenabled", false).toBool();
rg_mode_ = s.value("rgmode", 0).toInt();
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
rg_compression_ = s.value("rgcompression", true).toBool();
buffer_duration_nanosec_ = s.value("bufferduration", 4000).toLongLong() * kNsecPerMsec;
buffer_min_fill_ = s.value("bufferminfill", 33).toInt();
mono_playback_ = s.value("monoplayback", false).toBool();
}
qint64 GstEngine::position_nanosec() const {
if (!current_pipeline_) return 0;
const qint64 result = current_pipeline_->position() - beginning_nanosec_;
return qint64(qMax(0ll, result));
}
qint64 GstEngine::length_nanosec() const {
if (!current_pipeline_) return 0;
const qint64 result = end_nanosec_ - beginning_nanosec_;
if (result > 0) {
return result;
}
else {
// Get the length from the pipeline if we don't know.
return current_pipeline_->length();
}
}
Engine::State GstEngine::state() const {
if (!current_pipeline_) return url_.isEmpty() ? Engine::Empty : Engine::Idle;
@ -256,102 +151,6 @@ Engine::State GstEngine::state() const {
default:
return Engine::Empty;
}
}
void GstEngine::ConsumeBuffer(GstBuffer *buffer, int pipeline_id) {
// 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), Q_ARG(int, pipeline_id))) {
qLog(Warning) << "Failed to invoke AddBufferToScope on GstEngine";
}
}
void GstEngine::AddBufferToScope(GstBuffer *buf, int pipeline_id) {
if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) {
gst_buffer_unref(buf);
return;
}
if (latest_buffer_ != nullptr) {
gst_buffer_unref(latest_buffer_);
}
latest_buffer_ = buf;
have_new_buffer_ = true;
}
const Engine::Scope &GstEngine::scope(int chunk_length) {
// the new buffer could have a different size
if (have_new_buffer_) {
if (latest_buffer_ != nullptr) {
scope_chunks_ = ceil(((double)GST_BUFFER_DURATION(latest_buffer_) / (double)(chunk_length * kNsecPerMsec)));
}
// if the buffer is shorter than the chunk length
if (scope_chunks_ <= 0) {
scope_chunks_ = 1;
}
scope_chunk_ = 0;
have_new_buffer_ = false;
}
if (latest_buffer_ != nullptr) {
UpdateScope(chunk_length);
}
return scope_;
}
void GstEngine::UpdateScope(int chunk_length) {
typedef Engine::Scope::value_type sample_type;
// prevent dbz or invalid chunk size
if (!GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(latest_buffer_))) return;
if (GST_BUFFER_DURATION(latest_buffer_) == 0) return;
GstMapInfo map;
gst_buffer_map(latest_buffer_, &map, GST_MAP_READ);
// determine where to split the buffer
int chunk_density = (map.size * kNsecPerMsec) / GST_BUFFER_DURATION(latest_buffer_);
int chunk_size = chunk_length * chunk_density;
// in case a buffer doesn't arrive in time
if (scope_chunk_ >= scope_chunks_) {
scope_chunk_ = 0;
return;
}
const sample_type *source = reinterpret_cast<sample_type*>(map.data);
sample_type *dest = scope_.data();
source += (chunk_size / sizeof(sample_type)) * scope_chunk_;
int bytes = 0;
// make sure we don't go beyond the end of the buffer
if (scope_chunk_ == scope_chunks_ - 1) {
bytes = qMin(static_cast<Engine::Scope::size_type>(map.size - (chunk_size * scope_chunk_)), scope_.size() * sizeof(sample_type));
}
else {
bytes = qMin(static_cast<Engine::Scope::size_type>(chunk_size), scope_.size() * sizeof(sample_type));
}
scope_chunk_++;
memcpy(dest, source, bytes);
gst_buffer_unmap(latest_buffer_, &map);
if (scope_chunk_ == scope_chunks_) {
gst_buffer_unref(latest_buffer_);
latest_buffer_ = nullptr;
}
}
@ -367,42 +166,6 @@ void GstEngine::StartPreloading(const QUrl &url, bool force_stop_at_end, qint64
}
QByteArray GstEngine::FixupUrl(const QUrl &url) {
EnsureInitialised();
QByteArray uri;
// 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()) {
QString str = "//" + url.host() + url.path();
uri = str.toLocal8Bit();
}
else if (url.scheme() == "cdda") {
QString str;
if (url.path().isEmpty()) {
str = url.toString();
str.remove(str.lastIndexOf(QChar('a')), 1);
}
else {
// Currently, Gstreamer can't handle input CD devices inside cdda URL.
// So we handle them ourselve: we extract the track number and re-create an URL with only cdda:// + the track number (which can be handled by Gstreamer).
// We keep the device in mind, and we will set it later using SourceSetupCallback
QStringList path = url.path().split('/');
str = QString("cdda://%1a").arg(path.takeLast());
QString device = path.join("/");
current_pipeline_->SetSourceDevice(device);
}
uri = str.toLocal8Bit();
}
else {
uri = url.toEncoded();
}
return uri;
}
bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
EnsureInitialised();
@ -445,33 +208,6 @@ bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool forc
return true;
}
void GstEngine::StartFadeout() {
if (is_fading_out_to_pause_) return;
fadeout_pipeline_ = current_pipeline_;
disconnect(fadeout_pipeline_.get(), 0, 0, 0);
fadeout_pipeline_->RemoveAllBufferConsumers();
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
}
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;
}
bool GstEngine::Play(quint64 offset_nanosec) {
EnsureInitialised();
@ -489,44 +225,6 @@ bool GstEngine::Play(quint64 offset_nanosec) {
}
void GstEngine::PlayDone(QFuture<GstStateChangeReturn> future, const quint64 offset_nanosec, const int pipeline_id) {
GstStateChangeReturn ret = future.result();
if (!current_pipeline_ || pipeline_id != current_pipeline_->id()) {
return;
}
if (ret == GST_STATE_CHANGE_FAILURE) {
// Failure, but we got a redirection URL - try loading that instead
QByteArray redirect_url = current_pipeline_->redirect_url();
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) {
qLog(Info) << "Redirecting to" << redirect_url;
current_pipeline_ = CreatePipeline(redirect_url, end_nanosec_);
Play(offset_nanosec);
return;
}
// Failure - give up
qLog(Warning) << "Could not set thread to PLAYING.";
current_pipeline_.reset();
BufferingFinished();
return;
}
StartTimers();
// initial offset
if (offset_nanosec != 0 || beginning_nanosec_ != 0) {
Seek(offset_nanosec);
}
emit StateChanged(Engine::Playing);
// we've successfully started playing a media stream with this url
emit ValidSongRequested(url_);
}
void GstEngine::Stop(bool stop_after) {
StopTimers();
@ -552,27 +250,6 @@ void GstEngine::Stop(bool stop_after) {
}
void GstEngine::FadeoutFinished() {
fadeout_pipeline_.reset();
emit FadeoutFinishedSignal();
}
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();
}
void GstEngine::Pause() {
if (!current_pipeline_ || current_pipeline_->is_buffering()) return;
@ -634,16 +311,170 @@ void GstEngine::Seek(quint64 offset_nanosec) {
}
}
void GstEngine::SeekNow() {
void GstEngine::SetVolumeSW(uint percent) {
if (current_pipeline_) current_pipeline_->SetVolume(percent);
}
if (!waiting_to_seek_) return;
waiting_to_seek_ = false;
qint64 GstEngine::position_nanosec() const {
if (!current_pipeline_) return;
if (!current_pipeline_) return 0;
if (!current_pipeline_->Seek(seek_pos_)) {
qLog(Warning) << "Seek failed";
const qint64 result = current_pipeline_->position() - beginning_nanosec_;
return qint64(qMax(0ll, result));
}
qint64 GstEngine::length_nanosec() const {
if (!current_pipeline_) return 0;
const qint64 result = end_nanosec_ - beginning_nanosec_;
if (result > 0) {
return result;
}
else {
// Get the length from the pipeline if we don't know.
return current_pipeline_->length();
}
}
const Engine::Scope &GstEngine::scope(int chunk_length) {
// The new buffer could have a different size
if (have_new_buffer_) {
if (latest_buffer_) {
scope_chunks_ = ceil(((double)GST_BUFFER_DURATION(latest_buffer_) / (double)(chunk_length * kNsecPerMsec)));
}
// if the buffer is shorter than the chunk length
if (scope_chunks_ <= 0) {
scope_chunks_ = 1;
}
scope_chunk_ = 0;
have_new_buffer_ = false;
}
if (latest_buffer_) {
UpdateScope(chunk_length);
}
return scope_;
}
EngineBase::OutputDetailsList GstEngine::GetOutputsList() const {
EngineBase::OutputDetailsList ret;
PluginDetailsList plugins = GetPluginList("Sink/Audio");
for (const PluginDetails &plugin : plugins) {
OutputDetails output;
output.name = plugin.name;
output.description = plugin.description;
if (plugin.name == kAutoSink) output.iconname = "soundcard";
else if ((plugin.name == kALSASink) || (plugin.name == kOSS4Sink) || (plugin.name == kOSS4Sink)) output.iconname = "alsa";
else if (plugin.name== kJackAudioSink) output.iconname = "jack";
else if (plugin.name == kPulseSink) output.iconname = "pulseaudio";
else if ((plugin.name == kA2DPSink) || (plugin.name == kAVDTPSink)) output.iconname = "bluetooth";
else output.iconname = "soundcard";
ret.append(output);
}
return ret;
}
bool GstEngine::CustomDeviceSupport(const QString &name) {
return (name == kALSASink || name == kOpenALSASink || name == kOSSSink || name == kOSS4Sink || name == kPulseSink || name == kA2DPSink || name == kAVDTPSink);
}
void GstEngine::ReloadSettings() {
Engine::Base::ReloadSettings();
if (output_.isEmpty()) output_ = kAutoSink;
}
void GstEngine::InitialiseGStreamer() {
gst_init(nullptr, nullptr);
#ifdef HAVE_IMOBILEDEVICE_ // FIXME
afcsrc_register_static();
#endif
}
void GstEngine::SetEnvironment() {
QString scanner_path;
QString plugin_path;
QString registry_filename;
// On windows and mac we bundle the gstreamer plugins with strawberry
#if defined(Q_OS_DARWIN)
scanner_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gst-plugin-scanner";
plugin_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gstreamer";
#elif defined(Q_OS_WIN32)
plugin_path = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + "/gstreamer-plugins");
#endif
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
registry_filename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QString("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
#endif
if (!scanner_path.isEmpty()) Utilities::SetEnv("GST_PLUGIN_SCANNER", scanner_path);
if (!plugin_path.isEmpty()) {
Utilities::SetEnv("GST_PLUGIN_PATH", plugin_path);
// Never load plugins from anywhere else.
Utilities::SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path);
}
if (!registry_filename.isEmpty()) {
Utilities::SetEnv("GST_REGISTRY", registry_filename);
}
#ifdef Q_OS_DARWIN
Utilities::SetEnv("GIO_EXTRA_MODULES", QCoreApplication::applicationDirPath() + "/../PlugIns/gio-modules");
#endif
Utilities::SetEnv("PULSE_PROP_media.role", "music");
}
GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin, bool fatal, bool showerror) {
// Make a unique name
QString name = factoryName + "-" + QString::number(next_element_id_++);
GstElement *element = gst_element_factory_make(factoryName.toUtf8().constData(), name.toUtf8().constData());
if (!element) {
if (showerror)
emit Error(QString("GStreamer could not create the element: %1. Please make sure that you have installed all necessary GStreamer plugins").arg(factoryName));
else qLog(Error) << "GStreamer could not create the element:" << factoryName;
//if (fatal) gst_object_unref(GST_OBJECT(bin));
return nullptr;
}
if (bin) gst_bin_add(GST_BIN(bin), element);
return element;
}
void GstEngine::ConsumeBuffer(GstBuffer *buffer, int pipeline_id) {
// 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), Q_ARG(int, pipeline_id))) {
qLog(Warning) << "Failed to invoke AddBufferToScope on GstEngine";
}
}
void GstEngine::SetEqualizerEnabled(bool enabled) {
@ -669,21 +500,14 @@ void GstEngine::SetStereoBalance(float value) {
if (current_pipeline_) current_pipeline_->SetStereoBalance(value);
}
void GstEngine::SetVolumeSW(uint percent) {
if (current_pipeline_) current_pipeline_->SetVolume(percent);
void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) {
buffer_consumers_ << consumer;
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
}
void GstEngine::StartTimers() {
StopTimers();
timer_id_ = startTimer(kTimerIntervalNanosec / kNsecPerMsec);
}
void GstEngine::StopTimers() {
if (timer_id_ != -1) {
killTimer(timer_id_);
timer_id_ = -1;
}
void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) {
buffer_consumers_.removeAll(consumer);
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
}
void GstEngine::timerEvent(QTimerEvent *e) {
@ -710,6 +534,18 @@ void GstEngine::timerEvent(QTimerEvent *e) {
}
void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
return;
if (!has_next_track) {
current_pipeline_.reset();
BufferingFinished();
}
emit TrackEnded();
}
void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
@ -728,18 +564,6 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int
}
void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
return;
if (!has_next_track) {
current_pipeline_.reset();
BufferingFinished();
}
emit TrackEnded();
}
void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bundle) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
@ -748,24 +572,113 @@ void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bun
emit MetaData(bundle);
}
GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin, bool fatal, bool showerror) {
void GstEngine::AddBufferToScope(GstBuffer *buf, int pipeline_id) {
// Make a unique name
QString name = factoryName + "-" + QString::number(next_element_id_++);
GstElement *element = gst_element_factory_make(factoryName.toUtf8().constData(), name.toUtf8().constData());
if (!element) {
if (showerror)
emit Error(QString("GStreamer could not create the element: %1. Please make sure that you have installed all necessary GStreamer plugins").arg(factoryName));
else qLog(Error) << "GStreamer could not create the element:" << factoryName;
//if (fatal) gst_object_unref(GST_OBJECT(bin));
return nullptr;
if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) {
gst_buffer_unref(buf);
return;
}
if (bin) gst_bin_add(GST_BIN(bin), element);
if (latest_buffer_) {
gst_buffer_unref(latest_buffer_);
}
return element;
latest_buffer_ = buf;
have_new_buffer_ = true;
}
void GstEngine::FadeoutFinished() {
fadeout_pipeline_.reset();
emit FadeoutFinishedSignal();
}
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();
}
void GstEngine::SeekNow() {
if (!waiting_to_seek_) return;
waiting_to_seek_ = false;
if (!current_pipeline_) return;
if (!current_pipeline_->Seek(seek_pos_)) {
qLog(Warning) << "Seek failed";
}
}
void GstEngine::PlayDone(QFuture<GstStateChangeReturn> future, const quint64 offset_nanosec, const int pipeline_id) {
GstStateChangeReturn ret = future.result();
if (!current_pipeline_ || pipeline_id != current_pipeline_->id()) {
return;
}
if (ret == GST_STATE_CHANGE_FAILURE) {
// Failure, but we got a redirection URL - try loading that instead
QByteArray redirect_url = current_pipeline_->redirect_url();
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) {
qLog(Info) << "Redirecting to" << redirect_url;
current_pipeline_ = CreatePipeline(redirect_url, end_nanosec_);
Play(offset_nanosec);
return;
}
// Failure - give up
qLog(Warning) << "Could not set thread to PLAYING.";
current_pipeline_.reset();
BufferingFinished();
return;
}
StartTimers();
// initial offset
if (offset_nanosec != 0 || beginning_nanosec_ != 0) {
Seek(offset_nanosec);
}
emit StateChanged(Engine::Playing);
// we've successfully started playing a media stream with this url
emit ValidSongRequested(url_);
}
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;
}
}
GstEngine::PluginDetailsList GstEngine::GetPluginList(const QString &classname) const {
@ -793,12 +706,90 @@ GstEngine::PluginDetailsList GstEngine::GetPluginList(const QString &classname)
}
QByteArray GstEngine::FixupUrl(const QUrl &url) {
EnsureInitialised();
QByteArray uri;
// 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()) {
QString str = "//" + url.host() + url.path();
uri = str.toLocal8Bit();
}
else if (url.scheme() == "cdda") {
QString str;
if (url.path().isEmpty()) {
str = url.toString();
str.remove(str.lastIndexOf(QChar('a')), 1);
}
else {
// Currently, Gstreamer can't handle input CD devices inside cdda URL.
// So we handle them ourselve: we extract the track number and re-create an URL with only cdda:// + the track number (which can be handled by Gstreamer).
// We keep the device in mind, and we will set it later using SourceSetupCallback
QStringList path = url.path().split('/');
str = QString("cdda://%1a").arg(path.takeLast());
QString device = path.join("/");
current_pipeline_->SetSourceDevice(device);
}
uri = str.toLocal8Bit();
}
else {
uri = url.toEncoded();
}
return uri;
}
void GstEngine::StartFadeout() {
if (is_fading_out_to_pause_) return;
fadeout_pipeline_ = current_pipeline_;
disconnect(fadeout_pipeline_.get(), 0, 0, 0);
fadeout_pipeline_->RemoveAllBufferConsumers();
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
}
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;
}
void GstEngine::StartTimers() {
StopTimers();
timer_id_ = startTimer(kTimerIntervalNanosec / kNsecPerMsec);
}
void GstEngine::StopTimers() {
if (timer_id_ != -1) {
killTimer(timer_id_);
timer_id_ = -1;
}
}
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
EnsureInitialised();
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
ret->set_output_device(sink_, device_);
ret->set_output_device(output_, device_);
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
ret->set_buffer_min_fill(buffer_min_fill_);
@ -839,77 +830,50 @@ shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QByteArray &url, q
}
void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) {
buffer_consumers_ << consumer;
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
}
void GstEngine::UpdateScope(int chunk_length) {
void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) {
buffer_consumers_.removeAll(consumer);
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
}
typedef Engine::Scope::value_type sample_type;
void GstEngine::BufferingStarted() {
// Prevent dbz or invalid chunk size
if (!GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(latest_buffer_))) return;
if (GST_BUFFER_DURATION(latest_buffer_) == 0) return;
if (buffering_task_id_ != -1) {
task_manager_->SetTaskFinished(buffering_task_id_);
GstMapInfo map;
gst_buffer_map(latest_buffer_, &map, GST_MAP_READ);
// Determine where to split the buffer
int chunk_density = (map.size * kNsecPerMsec) / GST_BUFFER_DURATION(latest_buffer_);
int chunk_size = chunk_length * chunk_density;
// In case a buffer doesn't arrive in time
if (scope_chunk_ >= scope_chunks_) {
scope_chunk_ = 0;
return;
}
buffering_task_id_ = task_manager_->StartTask(tr("Buffering"));
task_manager_->SetTaskProgress(buffering_task_id_, 0, 100);
const sample_type *source = reinterpret_cast<sample_type*>(map.data);
sample_type *dest = scope_.data();
source += (chunk_size / sizeof(sample_type)) * scope_chunk_;
}
int bytes = 0;
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;
// Make sure we don't go beyond the end of the buffer
if (scope_chunk_ == scope_chunks_ - 1) {
bytes = qMin(static_cast<Engine::Scope::size_type>(map.size - (chunk_size * scope_chunk_)), scope_.size() * sizeof(sample_type));
}
}
EngineBase::OutputDetailsList GstEngine::GetOutputsList() const {
EngineBase::OutputDetailsList ret;
PluginDetailsList plugins = GetPluginList("Sink/Audio");
for (const PluginDetails &plugin : plugins) {
OutputDetails output;
output.name = plugin.name;
output.description = plugin.description;
if (plugin.name == kAutoSink) output.iconname = "soundcard";
else if ((plugin.name == kALSASink) || (plugin.name == kOSS4Sink) || (plugin.name == kOSS4Sink)) output.iconname = "alsa";
else if (plugin.name== kJackAudioSink) output.iconname = "jack";
else if (plugin.name == kPulseSink) output.iconname = "pulseaudio";
else if ((plugin.name == kA2DPSink) || (plugin.name == kAVDTPSink)) output.iconname = "bluetooth";
else output.iconname = "soundcard";
ret.append(output);
else {
bytes = qMin(static_cast<Engine::Scope::size_type>(chunk_size), scope_.size() * sizeof(sample_type));
}
return ret;
scope_chunk_++;
memcpy(dest, source, bytes);
gst_buffer_unmap(latest_buffer_, &map);
if (scope_chunk_ == scope_chunks_) {
gst_buffer_unref(latest_buffer_);
latest_buffer_ = nullptr;
}
}
bool GstEngine::ALSADeviceSupport(const QString &name) {
return (name == kALSASink);
}
bool GstEngine::PulseDeviceSupport(const QString &name) {
return (name == kPulseSink);
}
bool GstEngine::DirectSoundDeviceSupport(const QString &name) {
return (name == kDirectSoundSink);
}
bool GstEngine::OSXAudioDeviceSupport(const QString &name) {
return (name == kOSXAudioSink);
}
bool GstEngine::CustomDeviceSupport(const QString &name) {
return (name == kALSASink || name == kOpenALSASink || name == kOSSSink || name == kOSS4Sink || name == kA2DPSink || name == kAVDTPSink);
}

View File

@ -1,7 +1,8 @@
/***************************************************************************
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* 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> *
* 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 *
@ -65,34 +66,8 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
GstEngine(TaskManager *task_manager);
~GstEngine();
static const char *kAutoSink;
bool Init();
void EnsureInitialised() { initialising_.waitForFinished(); }
void InitialiseGStreamer();
void SetEnvironment();
OutputDetailsList GetOutputsList() const;
qint64 position_nanosec() const;
qint64 length_nanosec() const;
Engine::State state() const;
const Engine::Scope &scope(int chunk_length);
GstElement *CreateElement(const QString &factoryName, GstElement *bin = 0, bool fatal = true, bool showerror = true);
// BufferConsumer
void ConsumeBuffer(GstBuffer *buffer, int pipeline_id);
bool IsEqualizerEnabled() { return equalizer_enabled_; }
static bool ALSADeviceSupport(const QString &name);
static bool PulseDeviceSupport(const QString &name);
static bool DirectSoundDeviceSupport(const QString &name);
static bool OSXAudioDeviceSupport(const QString &name);
static bool CustomDeviceSupport(const QString &name);
public slots:
void StartPreloading(const QUrl &url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec);
bool Load(const QUrl &, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec);
@ -100,6 +75,28 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
void Pause();
void Unpause();
void Seek(quint64 offset_nanosec);
protected:
void SetVolumeSW(uint percent);
public:
qint64 position_nanosec() const;
qint64 length_nanosec() const;
const Engine::Scope &scope(int chunk_length);
OutputDetailsList GetOutputsList() const;
QString DefaultOutput() { return kAutoSink; }
bool CustomDeviceSupport(const QString &name);
void EnsureInitialised() { initialising_.waitForFinished(); }
void InitialiseGStreamer();
void SetEnvironment();
GstElement *CreateElement(const QString &factoryName, GstElement *bin = 0, bool fatal = true, bool showerror = true);
void ConsumeBuffer(GstBuffer *buffer, int pipeline_id);
public slots:
void ReloadSettings();
/** Set whether equalizer is enabled */
void SetEqualizerEnabled(bool);
@ -110,8 +107,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
/** Set Stereo balance, range -1.0f..1.0f */
void SetStereoBalance(float value);
void ReloadSettings();
void AddBufferConsumer(GstBufferConsumer *consumer);
void RemoveBufferConsumer(GstBufferConsumer *consumer);
@ -120,7 +115,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
#endif
protected:
void SetVolumeSW(uint percent);
void timerEvent(QTimerEvent*);
private slots:
@ -139,6 +133,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
private:
static const char *kAutoSink;
static const char *kALSASink;
static const char *kOpenALSASink;
static const char *kOSSSink;
@ -152,6 +147,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
static const char *kOSXAudioSink;
PluginDetailsList GetPluginList(const QString &classname) const;
QByteArray FixupUrl(const QUrl &url);
void StartFadeout();
void StartFadeoutPause();
@ -164,22 +160,17 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
std::shared_ptr<GstEnginePipeline> CreatePipeline(const QByteArray &url, qint64 end_nanosec);
void UpdateScope(int chunk_length);
QByteArray FixupUrl(const QUrl &url);
private:
static const qint64 kTimerIntervalNanosec = 1000 *kNsecPerMsec; // 1s
static const qint64 kPreloadGapNanosec = 3000 *kNsecPerMsec; // 3s
static const qint64 kSeekDelayNanosec = 100 *kNsecPerMsec; // 100msec
static const qint64 kTimerIntervalNanosec = 1000 * kNsecPerMsec; // 1s
static const qint64 kPreloadGapNanosec = 3000 * kNsecPerMsec; // 3s
static const qint64 kSeekDelayNanosec = 100 * kNsecPerMsec; // 100msec
TaskManager *task_manager_;
int buffering_task_id_;
QFuture<void> initialising_;
QString sink_;
QVariant device_;
std::shared_ptr<GstEnginePipeline> current_pipeline_;
std::shared_ptr<GstEnginePipeline> fadeout_pipeline_;
std::shared_ptr<GstEnginePipeline> fadeout_pause_pipeline_;
@ -189,22 +180,10 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
GstBuffer *latest_buffer_;
bool equalizer_enabled_;
int equalizer_preamp_;
QList<int> equalizer_gains_;
float stereo_balance_;
bool rg_enabled_;
int rg_mode_;
float rg_preamp_;
bool rg_compression_;
qint64 buffer_duration_nanosec_;
int buffer_min_fill_;
bool mono_playback_;
mutable bool can_decode_success_;
mutable bool can_decode_last_;

View File

@ -62,9 +62,8 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
engine_(engine),
id_(sId++),
valid_(false),
sink_(GstEngine::kAutoSink),
segment_start_(0),
segment_start_received_(false),
output_(""),
device_(""),
eq_enabled_(false),
eq_preamp_(0),
stereo_balance_(0.0f),
@ -76,6 +75,8 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
buffer_min_fill_(33),
buffering_(false),
mono_playback_(false),
segment_start_(0),
segment_start_received_(false),
end_offset_nanosec_(-1),
next_beginning_offset_nanosec_(-1),
next_end_offset_nanosec_(-1),
@ -108,9 +109,9 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
}
void GstEnginePipeline::set_output_device(const QString &sink, const QVariant &device) {
void GstEnginePipeline::set_output_device(const QString &output, const QVariant &device) {
sink_ = sink;
output_ = output;
device_ = device;
}
@ -194,12 +195,16 @@ bool GstEnginePipeline::InitAudioBin() {
// Audio bin
audiobin_ = gst_bin_new("audiobin");
if (!audiobin_) return false;
// Create the sink
audiosink_ = engine_->CreateElement(sink_, audiobin_);
if (!audiosink_) return false;
audiosink_ = engine_->CreateElement(output_, audiobin_);
if (!audiosink_) {
gst_object_unref(GST_OBJECT(audiobin_));
return false;
}
if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device") && device_.isValid()) {
if (device_.isValid() && g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device")) {
switch (device_.type()) {
case QVariant::Int:
g_object_set(G_OBJECT(audiosink_), "device", device_.toInt(), nullptr);
@ -238,6 +243,7 @@ bool GstEnginePipeline::InitAudioBin() {
convert = engine_->CreateElement("audioconvert", audiobin_);
if (!queue_ || !audioconvert_ || !tee || !probe_queue || !probe_converter || !probe_sink || !audio_queue || !volume_ || !audioscale_ || !convert) {
gst_object_unref(GST_OBJECT(audiobin_));
return false;
}

View File

@ -179,15 +179,9 @@ signals:
// General settings for the pipeline
bool valid_;
QString sink_;
QString output_;
QVariant device_;
// These get called when there is a new audio buffer available
QList<GstBufferConsumer*> buffer_consumers_;
QMutex buffer_consumers_mutex_;
qint64 segment_start_;
bool segment_start_received_;
// Equalizer
bool eq_enabled_;
int eq_preamp_;
@ -210,6 +204,12 @@ signals:
bool buffering_;
bool mono_playback_;
// These get called when there is a new audio buffer available
QList<GstBufferConsumer*> buffer_consumers_;
QMutex buffer_consumers_mutex_;
qint64 segment_start_;
bool segment_start_received_;
// The URL that is currently playing, and the URL that is to be preloaded when the current track is close to finishing.
QByteArray url_;

View File

@ -62,7 +62,7 @@ std::unique_ptr<T> GetProperty(const AudioDeviceID& device_id, const AudioObject
OsxDeviceFinder::OsxDeviceFinder()
: DeviceFinder("osxaudio", "osxaudiosink") {
: DeviceFinder("osxaudio", { "osxaudio", "osx", "osxaudiosink"} ) {
}
QList<DeviceFinder::Device> OsxDeviceFinder::ListDevices() {

View File

@ -1,29 +1,35 @@
/* This file is part of Strawberry.
Strawberry 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 3 of the License, or
(at your option) any later version.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Strawberry Music Player
* This file was part of Clementine
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QtGlobal>
#include <QString>
#include <QUrl>
#include <QTimer>
#include "phononengine.h"
#include "core/logging.h"
#include "core/timeconstants.h"
#include "core/taskmanager.h"
#include "core/logging.h"
PhononEngine::PhononEngine(TaskManager *task_manager)
: media_object_(new Phonon::MediaObject(this)),
@ -66,7 +72,7 @@ bool PhononEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool f
bool PhononEngine::Play(quint64 offset_nanosec) {
// The seek happens in PhononStateChanged - phonon doesn't seem to change currentTime() if we seek before we start playing :S
seek_offset_ = offset_nanosec;
seek_offset_ = (offset_nanosec / kNsecPerMsec);
media_object_->play();
return true;
@ -113,7 +119,8 @@ uint PhononEngine::length() const {
}
void PhononEngine::Seek(quint64 offset_nanosec) {
media_object_->seek(offset_nanosec);
int offset = (offset_nanosec / kNsecPerMsec);
media_object_->seek(offset);
}
void PhononEngine::SetVolumeSW(uint volume) {
@ -144,13 +151,34 @@ void PhononEngine::StateTimeoutExpired() {
}
qint64 PhononEngine::position_nanosec() const {
return 0;
if (state() == Engine::Empty) return 0;
const qint64 result = (position() * kNsecPerMsec);
return qint64(qMax(0ll, result));
}
qint64 PhononEngine::length_nanosec() const {
return 0;
if (state() == Engine::Empty) return 0;
const qint64 result = end_nanosec_ - beginning_nanosec_;
if (result > 0) {
return result;
}
else {
// Get the length from the pipeline if we don't know.
return (length() * kNsecPerMsec);
}
}
EngineBase::OutputDetailsList PhononEngine::GetOutputsList() const {
OutputDetailsList ret;
OutputDetails output;
output.name = "none";
output.description = "Configured by the system";
output.iconname = "soundcard";
ret << output;
return ret;
}
bool PhononEngine::CustomDeviceSupport(const QString &name) {
return false;
}

View File

@ -1,18 +1,22 @@
/* This file is part of Strawberry.
Strawberry 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 3 of the License, or
(at your option) any later version.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Strawberry Music Player
* This file was part of Clementine
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PHONONENGINE_H
#define PHONONENGINE_H
@ -39,6 +43,8 @@ class PhononEngine : public Engine::Base {
~PhononEngine();
bool Init();
OutputDetailsList GetOutputsList() const;
bool CanDecode(const QUrl &url);
@ -56,6 +62,9 @@ class PhononEngine : public Engine::Base {
qint64 position_nanosec() const;
qint64 length_nanosec() const;
QString DefaultOutput() { return ""; }
bool CustomDeviceSupport(const QString &name);
protected:
void SetVolumeSW( uint percent );

View File

@ -34,7 +34,7 @@
#include "devicefinder.h"
#include "pulsedevicefinder.h"
PulseDeviceFinder::PulseDeviceFinder() : DeviceFinder("pulseaudio", "pulsesink"), mainloop_(nullptr), context_(nullptr) {
PulseDeviceFinder::PulseDeviceFinder() : DeviceFinder("pulseaudio", {"pulseaudio", "pulse", "pulsesink"} ), mainloop_(nullptr), context_(nullptr) {
}
bool PulseDeviceFinder::Initialise() {

View File

@ -1,18 +1,22 @@
/* This file is part of Clementine.
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Strawberry Music Player
* This file was part of Clementine
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
@ -24,22 +28,42 @@
#include <QByteArray>
#include <QUrl>
#include "core/timeconstants.h"
#include "core/taskmanager.h"
#include "core/logging.h"
#include "engine_fwd.h"
#include "enginebase.h"
#include "enginetype.h"
#include "vlcengine.h"
#include "vlcscopedref.h"
VLCEngine *VLCEngine::sInstance = NULL;
VLCEngine *VLCEngine::sInstance = nullptr;
VLCEngine::VLCEngine(TaskManager *task_manager)
: instance_(NULL),
player_(NULL),
scope_data_(4096),
state_(Engine::Empty)
: instance_(nullptr),
player_(nullptr),
state_(Engine::Empty),
scope_data_(4096)
{
Init();
}
VLCEngine::~VLCEngine() {
#if 1
libvlc_media_player_stop(player_);
libvlc_media_player_release(player_);
libvlc_release(instance_);
HandleErrors();
}
bool VLCEngine::Init() {
type_ = Engine::VLC;
/* FIXME: Do we need this?
static const char *const args[] = {
"-I", "dummy", // Don't use any interface
"--ignore-config", // Don't use VLC's config
@ -47,22 +71,22 @@ VLCEngine::VLCEngine(TaskManager *task_manager)
"--verbose=2", // be much more verbose then normal for debugging purpose
// Our scope plugin
"--audio-filter=clementine_scope",
"--audio-filter=strawberry_scope",
"--no-plugins-cache",
// Try to stop audio stuttering
"--file-caching=500", // msec
"--http-caching=500",
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
#ifdef HAVE_ALSA_
"--aout=alsa", // The default, pulseaudio, is buggy
#endif
};
#endif
*/
// Create the VLC instance
//libvlc_exception_init(&exception_);
instance_ = libvlc_new(sizeof(args) / sizeof(args[0]), args);
instance_ = libvlc_new(0, nullptr);
//instance_ = libvlc_new(sizeof(args) / sizeof(args[0]), args);
HandleErrors();
// Create the media player
@ -84,17 +108,200 @@ VLCEngine::VLCEngine(TaskManager *task_manager)
HandleErrors();
sInstance = this;
return true;
}
VLCEngine::~VLCEngine() {
bool VLCEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
// Create the media object
VlcScopedRef<libvlc_media_t> media(libvlc_media_new_location(instance_, url.toEncoded().constData()));
libvlc_media_player_set_media(player_, media);
return true;
}
bool VLCEngine::Play(quint64 offset_nanosec) {
// Set audio output
if (!output_.isEmpty() || output_ != "auto") {
int result = libvlc_audio_output_set(player_, output_.toUtf8().constData());
if (result != 0) qLog(Error) << "Failed to set output.";
}
// Set audio device
if (device_.isValid() && device_.type() == QVariant::String && !device_.toString().isEmpty()) {
libvlc_audio_output_device_set(player_, nullptr, device_.toString().toLocal8Bit().data());
}
int result = libvlc_media_player_play(player_);
if (result != 0) return false;
Seek(offset_nanosec);
return true;
}
void VLCEngine::Stop(bool stop_after) {
libvlc_media_player_stop(player_);
libvlc_media_player_release(player_);
libvlc_release(instance_);
HandleErrors();
}
void VLCEngine::Pause() {
libvlc_media_player_pause(player_);
HandleErrors();
}
void VLCEngine::Unpause() {
libvlc_media_player_play(player_);
HandleErrors();
}
void VLCEngine::Seek(quint64 offset_nanosec) {
int offset = (offset_nanosec / kNsecPerMsec);
uint len = length();
if (len == 0) return;
float pos = float(offset) / len;
libvlc_media_player_set_position(player_, pos);
HandleErrors();
}
void VLCEngine::SetVolumeSW(uint percent) {
libvlc_audio_set_volume(player_, percent);
HandleErrors();
}
qint64 VLCEngine::position_nanosec() const {
if (state() == Engine::Empty) return 0;
const qint64 result = (position() * kNsecPerMsec);
return qint64(qMax(0ll, result));
}
qint64 VLCEngine::length_nanosec() const {
if (state() == Engine::Empty) return 0;
const qint64 result = (end_nanosec_ - beginning_nanosec_);
if (result > 0) {
return result;
}
else {
// Get the length from the pipeline if we don't know.
return (length() * kNsecPerMsec);
}
}
const Engine::Scope &VLCEngine::Scope() {
QMutexLocker l(&scope_mutex_);
// Leave the scope unchanged if there's not enough data
if (scope_data_.size() < uint(kScopeSize))
return scope_;
// Take the samples off the front of the circular buffer
for (uint i=0 ; i<uint(kScopeSize) ; ++i)
scope_[i] = scope_data_[i] * (1 << 15);
// Remove the samples from the buffer. Unfortunately I think this is O(n) :(
scope_data_.rresize(qMax(0, int(scope_data_.size()) - kScopeSize*2));
return scope_;
}
EngineBase::OutputDetailsList VLCEngine::GetOutputsList() const {
OutputDetailsList ret;
PluginDetailsList plugins = GetPluginList();
for (const PluginDetails &plugin : plugins) {
OutputDetails output;
output.name = plugin.name;
output.description = plugin.description;
if (plugin.name == "auto") output.iconname = "soundcard";
else if ((plugin.name == "alsa")||(plugin.name == "oss")) output.iconname = "alsa";
else if (plugin.name== "jack") output.iconname = "jack";
else if (plugin.name == "pulse") output.iconname = "pulseaudio";
else if (plugin.name == "afile") output.iconname = "document-new";
else output.iconname = "soundcard";
ret.append(output);
}
return ret;
}
bool VLCEngine::CustomDeviceSupport(const QString &name) {
return (name == "auto" ? false : true);
}
uint VLCEngine::position() const {
bool is_playing = libvlc_media_player_is_playing(player_);
HandleErrors();
if (!is_playing) return 0;
float pos = libvlc_media_player_get_position(player_);
HandleErrors();
return (pos * length());
}
uint VLCEngine::length() const {
bool is_playing = libvlc_media_player_is_playing(player_);
HandleErrors();
if (!is_playing) return 0;
libvlc_time_t len = libvlc_media_player_get_length(player_);
HandleErrors();
return len;
}
bool VLCEngine::CanDecode(const QUrl &url) {
// TODO
return true;
}
void VLCEngine::SetScopeData(float *data, int size) {
if (!sInstance) return;
QMutexLocker l(&sInstance->scope_mutex_);
// This gets called by our VLC plugin. Just push the data on to the end of the circular buffer and let it get consumed by scope()
for (int i=0 ; i<size ; ++i) {
sInstance->scope_data_.push_back(data[i]);
}
}
void VLCEngine::HandleErrors() const {
}
void VLCEngine::AttachCallback(libvlc_event_manager_t *em, libvlc_event_type_t type, libvlc_callback_t callback) {
libvlc_event_attach(em, type, callback, this);
@ -133,167 +340,37 @@ void VLCEngine::StateChangedCallback(const libvlc_event_t *e, void *data) {
}
bool VLCEngine::Init() {
EngineBase::PluginDetailsList VLCEngine::GetPluginList() const {
type_ = Engine::VLC;
return true;
}
PluginDetailsList ret;
libvlc_audio_output_t *audio_output_list = libvlc_audio_output_list_get(instance_);
bool VLCEngine::CanDecode(const QUrl &url) {
// TODO
return true;
}
bool VLCEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
// Create the media object
VlcScopedRef<libvlc_media_t> media(libvlc_media_new_location(instance_, url.toEncoded().constData()));
//if (libvlc_exception_raised(&exception_))
//return false;
libvlc_media_player_set_media(player_, media);
//if (libvlc_exception_raised(&exception_))
//return false;
return true;
}
bool VLCEngine::Play(quint64 offset_nanosec) {
libvlc_media_player_play(player_);
//if (libvlc_exception_raised(&exception_))
//return false;
Seek(offset_nanosec);
return true;
}
void VLCEngine::Stop(bool stop_after) {
libvlc_media_player_stop(player_);
HandleErrors();
}
void VLCEngine::Pause() {
libvlc_media_player_pause(player_);
HandleErrors();
}
void VLCEngine::Unpause() {
libvlc_media_player_play(player_);
HandleErrors();
}
uint VLCEngine::position() const {
//bool is_playing = libvlc_media_player_is_playing(player_, const_cast<libvlc_exception_t*>(&exception_));
bool is_playing = libvlc_media_player_is_playing(player_);
HandleErrors();
if (!is_playing)
return 0;
//float pos = libvlc_media_player_get_position(player_, const_cast<libvlc_exception_t*>(&exception_));
float pos = libvlc_media_player_get_position(player_);
HandleErrors();
return pos * length();
}
uint VLCEngine::length() const {
//bool is_playing = libvlc_media_player_is_playing(player_, const_cast<libvlc_exception_t*>(&exception_));
bool is_playing = libvlc_media_player_is_playing(player_);
HandleErrors();
if (!is_playing)
return 0;
//libvlc_time_t len = libvlc_media_player_get_length(player_, const_cast<libvlc_exception_t*>(&exception_));
libvlc_time_t len = libvlc_media_player_get_length(player_);
HandleErrors();
return len;
}
void VLCEngine::Seek(quint64 offset_nanosec) {
uint len = length();
if (len == 0)
return;
float pos = float(offset_nanosec) / len;
libvlc_media_player_set_position(player_, pos);
HandleErrors();
}
void VLCEngine::SetVolumeSW(uint percent) {
libvlc_audio_set_volume(player_, percent);
HandleErrors();
}
void VLCEngine::HandleErrors() const {
//if (libvlc_exception_raised(&exception_)) {
//qFatal("libvlc error: %s", libvlc_exception_get_message(&exception_));
//}
}
void VLCEngine::SetScopeData(float *data, int size) {
if (!sInstance)
return;
QMutexLocker l(&sInstance->scope_mutex_);
// This gets called by our VLC plugin. Just push the data on to the end of
// the circular buffer and let it get consumed by scope()
for (int i=0 ; i<size ; ++i) {
sInstance->scope_data_.push_back(data[i]);
{
PluginDetails details;
details.name = "auto";
details.description = "Automatically detected";
ret << details;
}
}
const Engine::Scope& VLCEngine::Scope() {
for (libvlc_audio_output_t *audio_output = audio_output_list ; audio_output ; audio_output = audio_output->p_next) {
PluginDetails details;
details.name = QString::fromUtf8(audio_output->psz_name);
details.description = QString::fromUtf8(audio_output->psz_description);
ret << details;
}
libvlc_audio_output_list_release(audio_output_list);
QMutexLocker l(&scope_mutex_);
// Leave the scope unchanged if there's not enough data
if (scope_data_.size() < uint(kScopeSize))
return scope_;
// Take the samples off the front of the circular buffer
for (uint i=0 ; i<uint(kScopeSize) ; ++i)
scope_[i] = scope_data_[i] * (1 << 15);
// Remove the samples from the buffer. Unfortunately I think this is O(n) :(
scope_data_.rresize(qMax(0, int(scope_data_.size()) - kScopeSize*2));
return scope_;
return ret;
}
qint64 VLCEngine::position_nanosec() const {
return 0;
}
qint64 VLCEngine::length_nanosec() const {
return 0;
void VLCEngine::GetDevicesList(QString output) const {
libvlc_audio_output_device_t *audio_output_device_list = libvlc_audio_output_device_list_get(instance_, output_.toUtf8().constData());
for (libvlc_audio_output_device_t *audio_device = audio_output_device_list ; audio_device ; audio_device = audio_device->p_next) {
qLog(Debug) << audio_device->psz_device << audio_device->psz_description;
}
libvlc_audio_output_device_list_release(audio_output_device_list);
}

View File

@ -1,18 +1,22 @@
/* This file is part of Clementine.
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Strawberry Music Player
* This file was part of Clementine
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef VLCENGINE_H
#define VLCENGINE_H
@ -44,49 +48,44 @@ class VLCEngine : public Engine::Base {
~VLCEngine();
bool Init();
virtual qint64 position_nanosec() const;
virtual qint64 length_nanosec() const;
bool CanDecode( const QUrl &url );
Engine::State state() const { return state_; }
bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec);
void Stop(bool stop_after = false);
void Pause();
void Unpause();
Engine::State state() const { return state_; }
uint position() const;
uint length() const;
void Seek(quint64 offset_nanosec);
static void SetScopeData(float* data, int size);
const Engine::Scope& Scope();
protected:
void SetVolumeSW(uint percent);
public:
virtual qint64 position_nanosec() const;
virtual qint64 length_nanosec() const;
const Engine::Scope& Scope();
OutputDetailsList GetOutputsList() const;
QString DefaultOutput() { return ""; }
bool CustomDeviceSupport(const QString &name);
private:
libvlc_instance_t *instance_;
libvlc_media_player_t *player_;
Engine::State state_;
boost::circular_buffer<float> scope_data_;
static VLCEngine *sInstance;
QMutex scope_mutex_;
uint position() const;
uint length() const;
bool CanDecode(const QUrl &url);
static void SetScopeData(float* data, int size);
void HandleErrors() const;
void AttachCallback(libvlc_event_manager_t* em, libvlc_event_type_t type, libvlc_callback_t callback);
static void StateChangedCallback(const libvlc_event_t* e, void* data);
private:
// The callbacks need access to this
static VLCEngine *sInstance;
PluginDetailsList GetPluginList() const;
void GetDevicesList(QString output) const;
// VLC bits and pieces
//libvlc_exception_t exception_;
libvlc_instance_t *instance_;
libvlc_media_player_t *player_;
// Our clementine_scope VLC plugin puts data in here
QMutex scope_mutex_;
boost::circular_buffer<float> scope_data_;
Engine::State state_;
};
#endif // VLCENGINE_H

View File

@ -1,18 +1,22 @@
/* This file is part of Clementine.
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Strawberry Music Player
* This file was part of Clementine
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef VLCSCOPEDREF_H
#define VLCSCOPEDREF_H

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,10 @@
/***************************************************************************
* Copyright (C) 2004,5 Max Howell <max.howell@methylblue.com> *
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* Copyright (C) 2005 Christophe Thommeret <hftom@free.fr> *
* (C) 2005 Ian Monroe <ian@monroe.nu> *
* (C) 2005-2006 Mark Kretschmann <markey@web.de> *
* (C) 2004-2005 Max Howell <max.howell@methylblue.com> *
* (C) 2003-2004 J. Kofler <kaffeine@gmx.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 *
@ -8,11 +13,17 @@
* *
***************************************************************************/
#ifndef XINE_ENGINE_H
#define XINE_ENGINE_H
#ifndef XINEENGINE_H
#define XINEENGINE_H
#include "config.h"
#include <memory>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include <xine.h>
#include <QtGlobal>
#include <QObject>
#include <QMutex>
@ -22,24 +33,20 @@
#include <QString>
#include <QUrl>
extern "C"
{
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <xine.h>
}
#include "engine_fwd.h"
#include "enginebase.h"
using std::shared_ptr;
class TaskManager;
class PruneScopeThread;
class XineFader;
class XineOutFader;
class XineEvent : public QEvent {
public:
enum EventType {
PlaybackFinished = QEvent::User + 1,
PlaybackFinished,
InfoMessage,
StatusMessage,
MetaInfoChanged,
@ -47,159 +54,119 @@ public:
LastFMTrackChanged,
};
XineEvent(EventType type, void* data = NULL) : QEvent(QEvent::Type(type)), data_(data) {}
XineEvent(EventType type, void* data = nullptr) : QEvent(QEvent::Type(type)), data_(data) {}
void setData(void* data) { data_ = data; }
void* data() const { return data_; }
void setData(void *data) { data_ = data; }
void *data() const { return data_; }
private:
void* data_;
void *data_;
};
class PruneScopeThread;
class XineEngine : public Engine::Base {
Q_OBJECT
public:
XineEngine(TaskManager *task_manager);
~XineEngine();
XineEngine(TaskManager *task_manager);
~XineEngine();
friend class Fader;
friend class OutFader;
friend class PruneScopeThread;
bool Init();
Engine::State state() const;
bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec);
void Stop(bool stop_after = false);
void Pause();
void Unpause();
void Seek(quint64 offset_nanosec);
void SetVolumeSW(uint );
virtual bool Init();
qint64 position_nanosec() const;
qint64 length_nanosec() const;
virtual qint64 position_nanosec() const;
virtual qint64 length_nanosec() const;
const Engine::Scope& scope(int chunk_length);
virtual bool CanDecode( const QUrl &);
virtual bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
virtual bool Play(quint64 offset_nanosec);
virtual void Stop(bool stop_after = false);
virtual void Pause();
virtual void Unpause();
virtual uint position() const;
virtual uint length() const;
virtual void Seek(quint64 offset_nanosec);
QString DefaultOutput() { return "auto"; }
OutputDetailsList GetOutputsList() const;
bool CustomDeviceSupport(const QString &name);
virtual bool metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b);
virtual bool getAudioCDContents(const QString &device, QList<QUrl> &urls);
virtual bool flushBuffer();
void ReloadSettings();
virtual Engine::State state() const;
virtual const Engine::Scope& scope(int chunk_length);
void SetEnvironment();
virtual void setEqualizerEnabled( bool );
virtual void setEqualizerParameters( int preamp, const QList<int>& );
virtual void SetVolumeSW( uint );
virtual void fadeOut( uint fadeLength, bool* terminate, bool exiting = false );
uint length() const;
uint position() const;
static void XineEventListener( void*, const xine_event_t* );
virtual bool event( QEvent* );
bool CanDecode(const QUrl &);
virtual void playlistChanged();
virtual void ReloadSettings();
bool MetaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b);
bool GetAudioCDContents(const QString &device, QList<QUrl> &urls);
bool FlushBuffer();
Engine::SimpleMetaBundle fetchMetaData() const;
void SetEqualizerEnabled(bool enabled);
void SetEqualizerParameters(int preamp, const QList<int>&);
virtual bool lastFmProxyRequired();
void FadeOut(uint fadeLength, bool* terminate, bool exiting = false);
bool makeNewStream();
bool ensureStream();
static void XineEventListener(void*, const xine_event_t*);
bool event(QEvent*);
void determineAndShowErrorMessage(); //call after failure to load/play
//static void SetOutput(QString output, QString device);
Engine::SimpleMetaBundle fetchMetaData() const;
xine_t *xine_;
xine_stream_t *stream_;
xine_audio_port_t *audioPort_;
xine_event_queue_t *eventQueue_;
xine_post_t *post_;
bool MakeNewStream();
bool EnsureStream();
int64_t currentVpts_;
float preamp_;
void DetermineAndShowErrorMessage(); //call after failure to load/play
bool stopFader_;
bool fadeOutRunning_;
// Simple accessors
QString currentAudioPlugin_; //to see if audio plugin has been changed need to save these for when the audio plugin is changed and xine reloaded
QString currentAudioDevice_;
bool equalizerEnabled_;
int intPreamp_;
QList<int> equalizerGains_;
QMutex initMutex_;
bool fadeoutOnExit_;
bool fadeoutEnabled_;
bool crossfadeEnabled_;
int fadeoutDuration_;
int xfadeLength_;
bool xfadeNextTrack_;
QUrl url_;
PruneScopeThread* prune_;
mutable Engine::SimpleMetaBundle currentBundle_;
XineEngine();
OutputDetailsList GetOutputsList() const;
PluginDetailsList GetPluginList() const;
static bool ALSADeviceSupport(const QString &name);
xine_stream_t *stream() { return stream_; }
float preamp() { return preamp_; }
bool stop_fader() { return stop_fader_; }
void set_stop_fader(bool stop_fader) { stop_fader_ = stop_fader; }
private:
static const char *kAutoOutput;
xine_t *xine_;
xine_stream_t *stream_;
xine_audio_port_t *audioport_;
xine_event_queue_t *eventqueue_;
xine_post_t *post_;
float preamp_;
bool stop_fader_;
bool fadeout_running_;
std::unique_ptr<PruneScopeThread> prune_;
QUrl url_;
static int last_error_;
static time_t last_error_time_;
uint log_buffer_count_ = 0;
uint log_scope_call_count_ = 1; // Prevent divideByZero
uint log_no_suitable_buffer_ = 0;
std::unique_ptr<XineFader> s_fader_;
std::unique_ptr<XineOutFader> s_outfader_;
int int_preamp_;
QMutex init_mutex_;
int64_t current_vpts_;
QList<int> equalizer_gains_;
int fade_length_;
bool fade_next_track_;
mutable Engine::SimpleMetaBundle current_bundle_;
PluginDetailsList GetPluginList() const;
private slots:
void PruneScope();
void PruneScope();
signals:
void resetConfig(xine_t *xine);
void InfoMessage(const QString&);
void LastFmTrackChange();
};
class Fader : public QThread {
XineEngine *engine_;
xine_t *xine_;
xine_stream_t *decrease_;
xine_stream_t *increase_;
xine_audio_port_t *port_;
xine_post_t *post_;
uint fadeLength_;
bool paused_;
bool terminated_;
virtual void run();
public:
Fader( XineEngine *, uint fadeLengthMs );
~Fader();
void pause();
void resume();
void finish();
};
class OutFader : public QThread {
XineEngine *engine_;
bool terminated_;
uint fadeLength_;
virtual void run();
public:
OutFader( XineEngine *, uint fadeLengthMs );
~OutFader();
void finish();
void InfoMessage(const QString&);
};
class PruneScopeThread : public QThread {
@ -207,10 +174,10 @@ public:
PruneScopeThread(XineEngine *parent);
protected:
virtual void run();
void run();
private:
XineEngine* engine_;
XineEngine *engine_;
};

150
src/engine/xinefader.cpp Normal file
View File

@ -0,0 +1,150 @@
/***************************************************************************
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* Copyright (C) 2005 Christophe Thommeret <hftom@free.fr> *
* (C) 2005 Ian Monroe <ian@monroe.nu> *
* (C) 2005-2006 Mark Kretschmann <markey@web.de> *
* (C) 2004-2005 Max Howell <max.howell@methylblue.com> *
* (C) 2003-2004 J. Kofler <kaffeine@gmx.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. *
* *
***************************************************************************/
#include "config.h"
#include <xine.h>
#include <QtGlobal>
#include <QThread>
#include "core/logging.h"
#include "xineengine.h"
#include "xinefader.h"
XineFader::XineFader(XineEngine *engine, xine_t *xine, xine_stream_t *stream, xine_audio_port_t *audioport, xine_post_t *post, uint fade_length)
: QThread(engine),
engine_(engine),
xine_(xine),
stream_(stream),
decrease_(stream),
increase_(nullptr),
port_(audioport),
post_(post),
fade_length_(fade_length),
paused_(false),
terminated_(false) {
if (engine->MakeNewStream()) {
increase_ = stream_;
xine_set_param(increase_, XINE_PARAM_AUDIO_AMP_LEVEL, 0);
}
else {
terminated_ = true;
}
}
XineFader::~XineFader() {
wait();
xine_close(decrease_);
xine_dispose(decrease_);
xine_close_audio_driver(xine_, port_);
if (post_) xine_post_dispose(xine_, post_);
if (!engine_->stop_fader())
engine_->SetVolume(engine_->volume());
engine_->set_stop_fader(false);
}
void XineFader::run() {
// Do a volume change in 100 steps (or every 10ms)
uint stepsCount = fade_length_ < 1000 ? fade_length_ / 10 : 100;
uint stepSizeUs = (int)(1000.0 * (float)fade_length_ / (float)stepsCount);
float mix = 0.0;
float elapsedUs = 0.0;
while (mix < 1.0) {
if (terminated_) break;
// Sleep a constant amount of time
QThread::usleep(stepSizeUs);
if (paused_)
continue;
elapsedUs += stepSizeUs;
// Get volume (amarok main * equalizer preamp)
float vol = Engine::Base::MakeVolumeLogarithmic(engine_->volume()) * engine_->preamp();
// Compute the mix factor as the percentage of time spent since fade begun
float mix = (elapsedUs / 1000.0) / (float)fade_length_;
if (mix > 1.0) {
if (increase_) xine_set_param(increase_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)vol);
break;
}
// Change volume of streams (using dj-like cross-fade profile)
if (decrease_) {
//xine_set_param(decrease_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(vol * (1.0 - mix))); // linear
float v = 4.0 * (1.0 - mix) / 3.0;
xine_set_param(decrease_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(v < 1.0 ? vol * v : vol));
}
if (increase_) {
// xine_set_param(increase_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(vol * mix)); //linear
float v = 4.0 * mix / 3.0;
xine_set_param(increase_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(v < 1.0 ? vol * v : vol));
}
}
// Stop using cpu!
xine_stop(decrease_);
}
void XineFader::pause() {
paused_ = true;
}
void XineFader::resume() {
paused_ = false;
}
void XineFader::finish() {
terminated_ = true;
}
XineOutFader::XineOutFader(XineEngine *engine, uint fadeLength)
: QThread(engine),
engine_(engine),
terminated_(false),
fade_length_(fadeLength)
{
}
XineOutFader::~XineOutFader() {
wait();
}
void XineOutFader::run() {
engine_->FadeOut(fade_length_, &terminated_);
xine_stop(engine_->stream());
xine_close(engine_->stream());
xine_set_param(engine_->stream(), XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
}
void XineOutFader::finish() {
terminated_ = true;
}

70
src/engine/xinefader.h Normal file
View File

@ -0,0 +1,70 @@
/***************************************************************************
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* Copyright (C) 2005 Christophe Thommeret <hftom@free.fr> *
* (C) 2005 Ian Monroe <ian@monroe.nu> *
* (C) 2005-2006 Mark Kretschmann <markey@web.de> *
* (C) 2004-2005 Max Howell <max.howell@methylblue.com> *
* (C) 2003-2004 J. Kofler <kaffeine@gmx.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. *
* *
***************************************************************************/
#ifndef XINEFADER_H
#define XINEFADER_H
#include "config.h"
#include <QtGlobal>
#include <QThread>
class XineFader : public QThread {
private:
XineEngine *engine_;
xine_t *xine_;
xine_stream_t *stream_;
xine_stream_t *decrease_;
xine_stream_t *increase_;
xine_audio_port_t *port_;
xine_post_t *post_;
uint fade_length_;
bool paused_;
bool terminated_;
bool stop_fader_;
void run();
public:
XineFader(XineEngine *engine, xine_t *xine, xine_stream_t *stream, xine_audio_port_t *audioport, xine_post_t *post, uint fadeMs);
~XineFader();
void pause();
void resume();
void finish();
};
class XineOutFader : public QThread {
private:
XineEngine *engine_;
bool terminated_;
uint fade_length_;
void run();
public:
XineOutFader(XineEngine *, uint fadeLengthMs );
~XineOutFader();
void finish();
};
#endif

View File

@ -11,6 +11,8 @@
#include "config.h"
#include <time.h>
#include "xinescope.h"
#include <xine/post.h>
#include <xine/xine_internal.h>
@ -29,54 +31,53 @@ struct scope_plugin_s {
* post plugin functions *
*************************/
static int scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode ) {
static int scope_port_open(xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode) {
#define port ((post_audio_port_t*)port_gen)
#define this ((scope_plugin_t*)((post_audio_port_t*)port_gen)->post)
_x_post_rewire( (post_plugin_t*)port->post );
_x_post_inc_usage( port );
_x_post_rewire((post_plugin_t*)port->post);
_x_post_inc_usage(port);
port->stream = stream;
port->bits = bits;
port->rate = rate;
port->mode = mode;
this->channels = _x_ao_mode2channels( mode );
this->channels = _x_ao_mode2channels(mode);
return port->original_port->open( port->original_port, stream, bits, rate, mode );
return port->original_port->open(port->original_port, stream, bits, rate, mode);
}
static void scope_port_close( xine_audio_port_t *port_gen, xine_stream_t *stream ) {
static void scope_port_close(xine_audio_port_t *port_gen, xine_stream_t *stream) {
MyNode *node;
/* ensure the buffers are deleted during the next XineEngine::timerEvent() */
for( node = this->list->next; node != this->list; node = node->next )
for(node = this->list->next; node != this->list; node = node->next)
node->vpts = node->vpts_end = -1;
port->stream = NULL;
port->original_port->close( port->original_port, stream );
port->original_port->close(port->original_port, stream);
_x_post_dec_usage( port );
_x_post_dec_usage(port);
}
static void scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream ) {
/* FIXME With 8-bit samples the scope won't work correctly. For a special 8-bit code path,
the sample size could be checked like this: if( port->bits == 8 ) */
static void scope_port_put_buffer(xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream) {
/* FIXME With 8-bit samples the scope won't work correctly. For a special 8-bit code path, the sample size could be checked like this: if(port->bits == 8) */
const int num_samples = buf->num_frames * this->channels;
metronom_t *myMetronom = &this->metronom;
MyNode *new_node;
/* I keep my own metronom because xine wouldn't for some reason */
memcpy( &this->metronom, stream->metronom, sizeof(metronom_t) );
memcpy(&this->metronom, stream->metronom, sizeof(metronom_t));
new_node = malloc( sizeof(MyNode) );
new_node->vpts = myMetronom->got_audio_samples( myMetronom, buf->vpts, buf->num_frames );
new_node = malloc(sizeof(MyNode));
new_node->vpts = myMetronom->got_audio_samples(myMetronom, buf->vpts, buf->num_frames);
new_node->num_frames = buf->num_frames;
new_node->mem = malloc( num_samples * 2 );
memcpy( new_node->mem, buf->mem, num_samples * 2 );
new_node->mem = malloc(num_samples * 2);
memcpy(new_node->mem, buf->mem, num_samples * 2);
{
int64_t
@ -88,7 +89,7 @@ static void scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *
new_node->vpts_end = K;
}
port->original_port->put_buffer( port->original_port, buf, stream );
port->original_port->put_buffer(port->original_port, buf, stream);
/* finally we should append the current buffer to the list
* this is thread-safe due to the way we handle the list in the GUI thread */
@ -99,7 +100,7 @@ static void scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *
#undef this
}
static void scope_dispose( post_plugin_t *this ) {
static void scope_dispose(post_plugin_t *this) {
MyNode *list = ((scope_plugin_t*)this)->list;
MyNode *prev;
MyNode *node = list;
@ -108,15 +109,15 @@ static void scope_dispose( post_plugin_t *this ) {
do {
prev = node->next;
free( node->mem );
free( node );
free(node->mem);
free(node);
node = prev;
}
while( node != list );
while(node != list);
free( this );
free(this);
}
@ -124,19 +125,19 @@ static void scope_dispose( post_plugin_t *this ) {
* plugin init function *
************************/
xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) {
xine_post_t* scope_plugin_new(xine_t *xine, xine_audio_port_t *audio_target) {
scope_plugin_t *scope_plugin = calloc( 1, sizeof(scope_plugin_t) );
post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin;
scope_plugin_t *scope_plugin = calloc(1, sizeof(scope_plugin_t));
post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin;
{
post_in_t *input;
post_out_t *output;
post_audio_port_t *port;
_x_post_init( post_plugin, 1, 0 );
_x_post_init(post_plugin, 1, 0);
port = _x_post_intercept_audio_port( post_plugin, audio_target, &input, &output );
port = _x_post_intercept_audio_port(post_plugin, audio_target, &input, &output);
port->new_port.open = scope_port_open;
port->new_port.close = scope_port_close;
port->new_port.put_buffer = scope_port_put_buffer;
@ -147,28 +148,26 @@ xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) {
post_plugin->dispose = scope_dispose;
}
/* code is straight from xine_init_post()
can't use that function as it only dlopens the plugins
and our plugin is statically linked in */
// code is straight from xine_init_post() can't use that function as it only dlopens the plugins and our plugin is statically linked in
post_plugin->running_ticket = xine->port_ticket;
post_plugin->xine = xine;
/* scope_plugin_t init */
scope_plugin->list = calloc( 1, sizeof(MyNode) );
scope_plugin->list = calloc(1, sizeof(MyNode));
scope_plugin->list->next = scope_plugin->list;
return &post_plugin->xine_post;
}
MyNode* scope_plugin_list( void *post ) {
MyNode* scope_plugin_list(void *post) {
return ((scope_plugin_t*)post)->list;
}
int scope_plugin_channels( void *post ) {
int scope_plugin_channels(void *post) {
return ((scope_plugin_t*)post)->channels;
}
metronom_t* scope_plugin_metronom( void *post ) {
metronom_t* scope_plugin_metronom(void *post) {
return &((scope_plugin_t*)post)->metronom;
}

View File

@ -133,7 +133,7 @@ void GlobalShortcuts::Register() {
}
bool GlobalShortcuts::IsMacAccessibilityEnabled() const {
#ifdef Q_OS_MAC
#ifdef Q_OS_MACOS
return static_cast<MacGlobalShortcutBackend*>(system_backend_)->IsAccessibilityEnabled();
#else
return true;
@ -141,7 +141,7 @@ bool GlobalShortcuts::IsMacAccessibilityEnabled() const {
}
void GlobalShortcuts::ShowMacAccessibilityDialog() {
#ifdef Q_OS_MAC
#ifdef Q_OS_MACOS
static_cast<MacGlobalShortcutBackend*>(system_backend_)->ShowAccessibilityDialog();
#endif
}

View File

@ -1127,7 +1127,7 @@ QString Playlist::column_name(Column column) {
case Column_Samplerate: return tr("Sample rate");
case Column_Bitdepth: return tr("Bit depth");
case Column_SamplerateBitdepth: return tr("Sample rate B");
case Column_Bitrate: return tr("Bit rate");
case Column_Bitrate: return tr("Bitrate");
case Column_Filename: return tr("File name");
case Column_BaseFilename: return tr("File name (without path)");

View File

@ -56,13 +56,17 @@ PlaylistFilter::PlaylistFilter(QObject *parent)
column_names_["genre"] = Playlist::Column_Genre;
column_names_["comment"] = Playlist::Column_Comment;
column_names_["bitrate"] = Playlist::Column_Bitrate;
column_names_["samplerate"] = Playlist::Column_Samplerate;
column_names_["bitdepth"] = Playlist::Column_Bitdepth;
column_names_["filename"] = Playlist::Column_Filename;
numerical_columns_ << Playlist::Column_Length
<< Playlist::Column_Track
<< Playlist::Column_Disc
<< Playlist::Column_Year
<< Playlist::Column_Bitrate;
<< Playlist::Column_Bitrate
<< Playlist::Column_Samplerate
<< Playlist::Column_Bitdepth;
}
PlaylistFilter::~PlaylistFilter() {

View File

@ -37,17 +37,12 @@
#include "core/application.h"
#include "core/iconloader.h"
#include "core/player.h"
#include "core/logging.h"
#include "engine/engine_fwd.h"
#include "engine/enginebase.h"
#include "engine/enginedevice.h"
#include "engine/enginetype.h"
#include "engine/devicefinder.h"
#ifdef HAVE_GSTREAMER
# include "engine/gstengine.h"
#endif
#ifdef HAVE_XINE
# include "engine/xineengine.h"
#endif
#include "widgets/lineedit.h"
#include "widgets/stickyslider.h"
#include "dialogs/errordialog.h"
@ -56,25 +51,13 @@
#include "ui_backendsettingspage.h"
const char *BackendSettingsPage::kSettingsGroup = "Backend";
const char *BackendSettingsPage::EngineText_Xine = "Xine";
const char *BackendSettingsPage::EngineText_GStreamer = "GStreamer";
const char *BackendSettingsPage::EngineText_Phonon = "Phonon";
const char *BackendSettingsPage::EngineText_VLC = "VLC";
BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog) : SettingsPage(dialog), ui_(new Ui_BackendSettingsPage) {
ui_->setupUi(this);
setWindowIcon(IconLoader::Load("soundcard"));
connect(ui_->combobox_engine, SIGNAL(currentIndexChanged(int)), SLOT(EngineChanged(int)));
connect(ui_->combobox_output, SIGNAL(currentIndexChanged(int)), SLOT(OutputChanged(int)));
connect(ui_->combobox_device, SIGNAL(currentIndexChanged(int)), SLOT(DeviceSelectionChanged(int)));
connect(ui_->lineedit_device, SIGNAL(textChanged(const QString &)), SLOT(DeviceStringChanged()));
connect(ui_->slider_bufferminfill, SIGNAL(valueChanged(int)), SLOT(BufferMinFillChanged(int)));
ui_->label_bufferminfillvalue->setMinimumWidth(QFontMetrics(ui_->label_bufferminfillvalue->font()).width("WW%"));
connect(ui_->stickslider_replaygainpreamp, SIGNAL(valueChanged(int)), SLOT(RgPreampChanged(int)));
ui_->label_replaygainpreamp->setMinimumWidth(QFontMetrics(ui_->label_replaygainpreamp->font()).width("-WW.W dB"));
RgPreampChanged(ui_->stickslider_replaygainpreamp->value());
@ -93,29 +76,29 @@ BackendSettingsPage::~BackendSettingsPage() {
void BackendSettingsPage::Load() {
configloaded_ = false;
engineloaded_ = Engine::None;
engineloaded_ = false;
xinewarning_ = false;
Engine::EngineType enginetype = Engine::EngineTypeFromName(s_.value("engine", EngineText_GStreamer).toString());
Engine::EngineType enginetype = Engine::EngineTypeFromName(s_.value("engine", EngineDescription(Engine::GStreamer)).toString());
ui_->combobox_engine->clear();
#ifdef HAVE_XINE
ui_->combobox_engine->addItem(IconLoader::Load("xine"), EngineText_Xine, Engine::Xine);
#endif
#ifdef HAVE_GSTREAMER
ui_->combobox_engine->addItem(IconLoader::Load("gstreamer"), EngineText_GStreamer, Engine::GStreamer);
ui_->combobox_engine->addItem(IconLoader::Load("gstreamer"), EngineDescription(Engine::GStreamer), Engine::GStreamer);
#endif
#ifdef HAVE_PHONON
ui_->combobox_engine->addItem(IconLoader::Load("speaker"), EngineText_Phonon, Engine::Phonon);
#ifdef HAVE_XINE
ui_->combobox_engine->addItem(IconLoader::Load("xine"), EngineDescription(Engine::Xine), Engine::Xine);
#endif
#ifdef HAVE_VLC
ui_->combobox_engine->addItem(IconLoader::Load("vlc"), EngineText_VLC, Engine::VLC);
ui_->combobox_engine->addItem(IconLoader::Load("vlc"), EngineDescription(Engine::VLC), Engine::VLC);
#endif
#ifdef HAVE_PHONON
ui_->combobox_engine->addItem(IconLoader::Load("speaker"), EngineDescription(Engine::Phonon), Engine::Phonon);
#endif
configloaded_ = true;
enginereset_ = false;
ui_->combobox_engine->setCurrentIndex(ui_->combobox_engine->findData(enginetype));
if (enginetype != engineloaded_) Load_Engine(enginetype);
if (EngineInitialised()) Load_Engine(enginetype);
ui_->spinbox_bufferduration->setValue(s_.value("bufferduration", 4000).toInt());
ui_->checkbox_monoplayback->setChecked(s_.value("monoplayback", false).toBool());
@ -126,16 +109,49 @@ void BackendSettingsPage::Load() {
ui_->stickslider_replaygainpreamp->setValue(s_.value("rgpreamp", 0.0).toDouble() * 10 + 150);
ui_->checkbox_replaygaincompression->setChecked(s_.value("rgcompression", true).toBool());
//if (dialog()->app()->player()->engine()->state() != Engine::Empty) ui_->combobox_engine->setEnabled(false);
if (!EngineInitialised()) return;
#ifdef Q_OS_WIN32
ui_->combobox_engine->setEnabled(false);
#endif
if (engine()->state() == Engine::Empty) {
if (ui_->combobox_engine->count() > 1) ui_->combobox_engine->setEnabled(true);
else ui_->combobox_engine->setEnabled(false);
ResetWarning();
}
else {
ui_->combobox_engine->setEnabled(false);
ShowWarning("Engine can't be switched while playing. Close settings and reopen to change engine.");
}
ConnectSignals();
configloaded_ = true;
}
void BackendSettingsPage::ConnectSignals() {
connect(ui_->combobox_engine, SIGNAL(currentIndexChanged(int)), SLOT(EngineChanged(int)));
connect(ui_->combobox_output, SIGNAL(currentIndexChanged(int)), SLOT(OutputChanged(int)));
connect(ui_->combobox_device, SIGNAL(currentIndexChanged(int)), SLOT(DeviceSelectionChanged(int)));
connect(ui_->lineedit_device, SIGNAL(textChanged(const QString &)), SLOT(DeviceStringChanged()));
connect(ui_->slider_bufferminfill, SIGNAL(valueChanged(int)), SLOT(BufferMinFillChanged(int)));
connect(ui_->stickslider_replaygainpreamp, SIGNAL(valueChanged(int)), SLOT(RgPreampChanged(int)));
}
bool BackendSettingsPage::EngineInitialised() {
if (!engine() || engine()->type() == Engine::None) {
errordialog_.ShowMessage("Engine is not initialized! Please restart.");
return false;
}
return true;
}
void BackendSettingsPage::Load_Engine(Engine::EngineType enginetype) {
if (!EngineInitialised()) return;
QString output = s_.value("output", "").toString();
QVariant device = s_.value("device", QVariant());
@ -150,84 +166,99 @@ void BackendSettingsPage::Load_Engine(Engine::EngineType enginetype) {
ui_->groupbox_replaygain->setEnabled(false);
// If a engine is loaded (!= Engine::None) AND engine has been switched reset output and device.
if ((engineloaded_ != Engine::None) && (engineloaded_ != enginetype)) {
output = "";
device = QVariant();
s_.setValue("output", "");
s_.setValue("device", QVariant());
}
if (dialog()->app()->player()->engine()->type() != enginetype) {
if (engine()->type() != enginetype) {
dialog()->app()->player()->CreateEngine(enginetype);
dialog()->app()->player()->ReloadSettings();
dialog()->app()->player()->Init();
}
switch(enginetype) {
#ifdef HAVE_XINE
case Engine::Xine:
Xine_Load(output, device);
break;
#endif
#ifdef HAVE_GSTREAMER
case Engine::GStreamer:
Gst_Load(output, device);
break;
#endif
#ifdef HAVE_PHONON
case Engine::Phonon:
Phonon_Load(output, device);
break;
#endif
#ifdef HAVE_VLC
case Engine::VLC:
VLC_Load(output, device);
break;
#endif
default:
QString msg = QString("Missing engine %1!").arg(Engine::EngineNameFromType(enginetype));
errordialog_.ShowMessage(msg);
return;
}
engineloaded_ = true;
Load_Output(output, device);
}
void BackendSettingsPage::Load_Device(QString output, QVariant device, bool alsa, bool pulseaudio, bool directsound, bool osxaudio, bool custom) {
void BackendSettingsPage::Load_Output(QString output, QVariant device) {
if (!EngineInitialised()) return;
if (output == "") output = engine()->DefaultOutput();
ui_->combobox_output->clear();
int i = 0;
for (const EngineBase::OutputDetails &o : engine()->GetOutputsList()) {
i++;
ui_->combobox_output->addItem(IconLoader::Load(o.iconname), o.description, QVariant::fromValue(o));
}
if (i > 0) ui_->combobox_output->setEnabled(true);
bool found(false);
for (int i = 0; i < ui_->combobox_output->count(); ++i) {
EngineBase::OutputDetails o = ui_->combobox_output->itemData(i).value<EngineBase::OutputDetails>();
if (o.name == output) {
ui_->combobox_output->setCurrentIndex(i);
found = true;
break;
}
}
if (!found) { // Output is invalid for this engine, reset to default output.
output = engine()->DefaultOutput();
for (int i = 0; i < ui_->combobox_output->count(); ++i) {
EngineBase::OutputDetails o = ui_->combobox_output->itemData(i).value<EngineBase::OutputDetails>();
if (o.name == output) {
ui_->combobox_output->setCurrentIndex(i);
break;
}
}
}
if (engine()->type() == Engine::GStreamer) ui_->groupbox_replaygain->setEnabled(true);
else ui_->groupbox_replaygain->setEnabled(false);
if (ui_->combobox_output->count() < 1) {
ShowWarning("Engine may take some time to initialize. Close settings and reopen to set output and devices.");
}
else Load_Device(output, device);
}
void BackendSettingsPage::Load_Device(QString output, QVariant device) {
if (!EngineInitialised()) return;
int devices = 0;
DeviceFinder::Device dfdevice;
DeviceFinder::Device df_device;
ui_->combobox_device->clear();
ui_->combobox_device->setEnabled(false);
ui_->lineedit_device->setText("");
#ifdef Q_OS_LINUX
ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Automatically select", "");
#ifndef Q_OS_WIN32
ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Automatically select", QVariant(""));
#endif
if (alsa) ui_->lineedit_device->setEnabled(true);
else ui_->lineedit_device->setEnabled(false);
for (DeviceFinder *f : dialog()->app()->enginedevice()->device_finders_) {
if (f->name() == "alsa" && !alsa) continue;
if (f->name() == "pulseaudio" && !pulseaudio) continue;
if (f->name() == "directsound" && !directsound) continue;
if (f->name() == "osxaudio" && !osxaudio) continue;
if (!f->outputs().contains(output)) continue;
for (const DeviceFinder::Device &d : f->ListDevices()) {
devices++;
ui_->combobox_device->addItem(IconLoader::Load(d.iconname), d.description, d.value);
if (d.value == device) { dfdevice = d; }
if (d.value == device) { df_device = d; }
}
}
if (devices > 0) ui_->combobox_device->setEnabled(true);
if (custom) ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Custom", QVariant(""));
if (engine()->CustomDeviceSupport(output)) {
ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Custom", QVariant(""));
ui_->lineedit_device->setEnabled(true);
}
else {
ui_->lineedit_device->setEnabled(false);
}
bool found = false;
if (custom || devices > 0) ui_->combobox_device->setEnabled(true);
bool found(false);
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
QVariant d = ui_->combobox_device->itemData(i).value<QVariant>();
if (dfdevice.value == d) {
if (df_device.value == d) {
ui_->combobox_device->setCurrentIndex(i);
found = true;
break;
@ -235,143 +266,45 @@ void BackendSettingsPage::Load_Device(QString output, QVariant device, bool alsa
}
// This allows a custom ALSA device string ie: "hw:0,0" even if it is not listed.
if (found == false && custom && device.type() == QVariant::String && !device.toString().isEmpty()) {
if (engine()->CustomDeviceSupport(output) && device.type() == QVariant::String && !device.toString().isEmpty()) {
ui_->lineedit_device->setText(device.toString());
}
}
#ifdef HAVE_GSTREAMER
void BackendSettingsPage::Gst_Load(QString output, QVariant device) {
if (output == "") output = GstEngine::kAutoSink;
if (dialog()->app()->player()->engine()->type() != Engine::GStreamer) {
errordialog_.ShowMessage("GStramer not initialized! Please restart.");
return;
}
GstEngine *gstengine = qobject_cast<GstEngine*>(dialog()->app()->player()->engine());
ui_->combobox_output->clear();
int i = 0;
for (const EngineBase::OutputDetails &o : gstengine->GetOutputsList()) {
i++;
ui_->combobox_output->addItem(IconLoader::Load(o.iconname), o.description, QVariant::fromValue(o));
}
if (i > 0) ui_->combobox_output->setEnabled(true);
for (int i = 0; i < ui_->combobox_output->count(); ++i) {
EngineBase::OutputDetails o = ui_->combobox_output->itemData(i).value<EngineBase::OutputDetails>();
if (o.name == output) {
ui_->combobox_output->setCurrentIndex(i);
break;
if (!found) {
bool have_custom(false);
int index_custom = 0;
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
if (ui_->combobox_device->itemText(i) == "Custom") {
have_custom = true;
index_custom = i;
if (ui_->combobox_device->currentText() != "Custom") ui_->combobox_device->setCurrentIndex(i);
break;
}
}
if (have_custom) ui_->combobox_device->setItemData(index_custom, QVariant(device.toString()));
}
}
engineloaded_=Engine::GStreamer;
ui_->groupbox_replaygain->setEnabled(true);
Load_Device(output, device, GstEngine::ALSADeviceSupport(output), GstEngine::PulseDeviceSupport(output), GstEngine::DirectSoundDeviceSupport(output), GstEngine::OSXAudioDeviceSupport(output), GstEngine::CustomDeviceSupport(output));
}
#endif
#ifdef HAVE_XINE
void BackendSettingsPage::Xine_Load(QString output, QVariant device) {
if (output == "") output = "auto";
if (dialog()->app()->player()->engine()->type() != Engine::Xine) {
errordialog_.ShowMessage("Xine not initialized! Please restart.");
return;
}
XineEngine *xineengine = qobject_cast<XineEngine*>(dialog()->app()->player()->engine());
ui_->combobox_output->clear();
int i = 0;
for (const EngineBase::OutputDetails &o : xineengine->GetOutputsList()) {
i++;
ui_->combobox_output->addItem(IconLoader::Load(o.iconname), o.description, QVariant::fromValue(o));
}
if (i > 0) ui_->combobox_output->setEnabled(true);
for (int i = 0; i < ui_->combobox_output->count(); ++i) {
EngineBase::OutputDetails o = ui_->combobox_output->itemData(i).value<EngineBase::OutputDetails>();
if (o.name == output) {
ui_->combobox_output->setCurrentIndex(i);
break;
}
}
engineloaded_=Engine::Xine;
Load_Device(output, device);
}
#endif
#ifdef HAVE_PHONON
void BackendSettingsPage::Phonon_Load(QString output, QVariant device) {
ui_->combobox_output->clear();
ui_->combobox_device->clear();
ui_->lineedit_device->setText("");
engineloaded_=Engine::Phonon;
Load_Device(output, device);
}
#endif
#ifdef HAVE_VLC
void BackendSettingsPage::VLC_Load(QString output, QVariant device) {
ui_->combobox_output->clear();
ui_->combobox_device->clear();
ui_->lineedit_device->setText("");
engineloaded_=Engine::VLC;
Load_Device(output, device);
}
#endif
void BackendSettingsPage::Save() {
s_.setValue("engine", ui_->combobox_engine->itemText(ui_->combobox_engine->currentIndex()).toLower());
QVariant myVariant = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex());
Engine::EngineType enginetype = myVariant.value<Engine::EngineType>();
if (!EngineInitialised()) return;
switch(enginetype) {
#ifdef HAVE_XINE
case Engine::Xine:
Xine_Save();
break;
#endif
#ifdef HAVE_GSTREAMER
case Engine::GStreamer:
Gst_Save();
break;
#endif
#ifdef HAVE_PHONON
case Engine::Phonon:
Phonon_Save();
break;
#endif
#ifdef HAVE_VLC
case Engine::VLC:
VLC_Save();
break;
#endif
default:
break;
QVariant enginetype_v = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex());
Engine::EngineType enginetype = enginetype_v.value<Engine::EngineType>();
QString output_name;
QVariant device_value;
if (ui_->combobox_output->currentText().isEmpty()) output_name = engine()->DefaultOutput();
else {
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
output_name = output.name;
}
if (ui_->combobox_device->currentText().isEmpty()) device_value = QVariant();
else device_value = ui_->combobox_device->itemData(ui_->combobox_device->currentIndex()).value<QVariant>();
s_.setValue("device", ui_->combobox_device->itemData(ui_->combobox_device->currentIndex()).value<QVariant>());
s_.setValue("engine", EngineName(enginetype));
s_.setValue("output", output_name);
s_.setValue("device", device_value);
s_.setValue("bufferduration", ui_->spinbox_bufferduration->value());
s_.setValue("monoplayback", ui_->checkbox_monoplayback->isChecked());
@ -380,166 +313,103 @@ void BackendSettingsPage::Save() {
s_.setValue("rgmode", ui_->combobox_replaygainmode->currentIndex());
s_.setValue("rgpreamp", float(ui_->stickslider_replaygainpreamp->value()) / 10 - 15);
s_.setValue("rgcompression", ui_->checkbox_replaygaincompression->isChecked());
}
#ifdef HAVE_XINE
void BackendSettingsPage::Xine_Save() {
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
s_.setValue("output", output.name);
}
#endif
#ifdef HAVE_GSTREAMER
void BackendSettingsPage::Gst_Save() {
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
s_.setValue("output", output.name);
}
#endif
#ifdef HAVE_PHONON
void BackendSettingsPage::Phonon_Save() {
}
#endif
#ifdef HAVE_VLC
void BackendSettingsPage::VLC_Save() {
}
#endif
void BackendSettingsPage::EngineChanged(int index) {
if (configloaded_ == false) return;
if ((engineloaded_ != Engine::None) && (dialog()->app()->player()->engine()->state() != Engine::Empty)) {
if (!configloaded_ || !EngineInitialised()) return;
if (engine()->state() != Engine::Empty) {
if (enginereset_ == true) { enginereset_ = false; return; }
errordialog_.ShowMessage("Can't switch engine while playing!");
enginereset_ = true;
ui_->combobox_engine->setCurrentIndex(ui_->combobox_engine->findData(engineloaded_));
return;
}
QVariant v = ui_->combobox_engine->itemData(index);
Engine::EngineType enginetype = v.value<Engine::EngineType>();
engineloaded_ = false;
xinewarning_ = false;
ResetWarning();
Load_Engine(enginetype);
}
void BackendSettingsPage::OutputChanged(int index) {
QVariant v = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex());
Engine::EngineType enginetype = v.value<Engine::EngineType>();
OutputChanged(index, enginetype);
}
void BackendSettingsPage::OutputChanged(int index, Engine::EngineType enginetype) {
switch(enginetype) {
case Engine::Xine:
#ifdef HAVE_XINE
Xine_OutputChanged(index);
break;
#endif
case Engine::GStreamer:
#ifdef HAVE_GSTREAMER
Gst_OutputChanged(index);
break;
#endif
case Engine::Phonon:
#ifdef HAVE_PHONON
Phonon_OutputChanged(index);
break;
#endif
case Engine::VLC:
#ifdef HAVE_VLC
VLC_OutputChanged(index);
break;
#endif
default:
break;
}
}
#ifdef HAVE_XINE
void BackendSettingsPage::Xine_OutputChanged(int index) {
if (!configloaded_ || !EngineInitialised()) return;
EngineBase::OutputDetails output = ui_->combobox_output->itemData(index).value<EngineBase::OutputDetails>();
Load_Device(output.name, QVariant());
}
#endif
#ifdef HAVE_GSTREAMER
void BackendSettingsPage::Gst_OutputChanged(int index) {
EngineBase::OutputDetails output = ui_->combobox_output->itemData(index).value<EngineBase::OutputDetails>();
Load_Device(output.name, QVariant(), GstEngine::ALSADeviceSupport(output.name), GstEngine::PulseDeviceSupport(output.name), GstEngine::DirectSoundDeviceSupport(output.name), GstEngine::OSXAudioDeviceSupport(output.name), GstEngine::CustomDeviceSupport(output.name));
if (engine()->type() == Engine::Xine) XineWarning();
}
#endif
#ifdef HAVE_PHONON
void BackendSettingsPage::Phonon_OutputChanged(int index) {
Load_Device("", QVariant());
}
#endif
#ifdef HAVE_VLC
void BackendSettingsPage::VLC_OutputChanged(int index) {
Load_Device("", QVariant());
}
#endif
void BackendSettingsPage::DeviceSelectionChanged(int index) {
if (ui_->combobox_device->currentText() == "Custom") {
ui_->lineedit_device->setEnabled(true);
ui_->combobox_device->setItemData(index, QVariant(ui_->lineedit_device->text()));
return;
}
if (!configloaded_ || !EngineInitialised()) return;
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
QVariant device = ui_->combobox_device->itemData(index).value<QVariant>();
if (device.type() == QVariant::String && device.toString().startsWith("hw:", Qt::CaseInsensitive)) {
if (engine()->CustomDeviceSupport(output.name)) {
ui_->lineedit_device->setEnabled(true);
ui_->lineedit_device->setText(device.toString());
return;
if (ui_->combobox_device->currentText() == "Custom") {
ui_->combobox_device->setItemData(index, QVariant(ui_->lineedit_device->text()));
}
else {
if (device.type() == QVariant::String) ui_->lineedit_device->setText(device.toString());
}
}
else {
ui_->lineedit_device->setEnabled(false);
if (!ui_->lineedit_device->text().isEmpty()) ui_->lineedit_device->setText("");
}
ui_->lineedit_device->setEnabled(false);
ui_->lineedit_device->setText("");
if (engine()->type() == Engine::Xine) XineWarning();
}
void BackendSettingsPage::DeviceStringChanged() {
if (!configloaded_ || !EngineInitialised()) return;
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
bool found(false);
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
QVariant v = ui_->combobox_device->itemData(i).value<QVariant>();
if (v.type() != QVariant::String) continue;
if (v.toString() == ui_->lineedit_device->text()) {
ui_->combobox_device->setCurrentIndex(i);
return;
QVariant device = ui_->combobox_device->itemData(i).value<QVariant>();
if (device.type() != QVariant::String) continue;
if (device.toString().isEmpty()) continue;
if (device.toString() == ui_->lineedit_device->text()) {
if (ui_->combobox_device->currentIndex() != i) ui_->combobox_device->setCurrentIndex(i);
found = true;
}
}
// Assume this is a custom alsa device string
if (ui_->combobox_device->currentText() != "Custom") {
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
if (ui_->combobox_device->itemText(i) == "Custom") {
ui_->combobox_device->setCurrentIndex(i);
break;
if (engine()->CustomDeviceSupport(output.name)) {
ui_->lineedit_device->setEnabled(true);
if ((!found) && (ui_->combobox_device->currentText() != "Custom")) {
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
if (ui_->combobox_device->itemText(i) == "Custom") {
ui_->combobox_device->setCurrentIndex(i);
break;
}
}
}
if (ui_->combobox_device->currentText() == "Custom") {
ui_->combobox_device->setItemData(ui_->combobox_device->currentIndex(), QVariant(ui_->lineedit_device->text()));
if ((ui_->lineedit_device->text().isEmpty()) && (ui_->combobox_device->count() > 0) && (ui_->combobox_device->currentIndex() != 0)) ui_->combobox_device->setCurrentIndex(0);
}
}
if (ui_->combobox_device->currentText() == "Custom") {
ui_->combobox_device->setItemData(ui_->combobox_device->currentIndex(), QVariant(ui_->lineedit_device->text()));
else {
ui_->lineedit_device->setEnabled(false);
if (!ui_->lineedit_device->text().isEmpty()) ui_->lineedit_device->setText("");
if ((!found) && (ui_->combobox_device->count() > 0) && (ui_->combobox_device->currentIndex() != 0)) ui_->combobox_device->setCurrentIndex(0);
}
}
@ -556,3 +426,41 @@ void BackendSettingsPage::RgPreampChanged(int value) {
void BackendSettingsPage::BufferMinFillChanged(int value) {
ui_->label_bufferminfillvalue->setText(QString::number(value) + "%");
}
void BackendSettingsPage::ShowWarning(QString text) {
QImage image_logo(":/icons/64x64/dialog-warning.png");
QPixmap pixmap_logo(QPixmap::fromImage(image_logo));
ui_->label_warn_logo->setPixmap(pixmap_logo);
ui_->label_warn_text->setStyleSheet("QLabel { color: red; }");
ui_->label_warn_text->setText("<b>" + text + "</b>");
ui_->label_warn_logo->setEnabled(true);
ui_->label_warn_text->setEnabled(true);
}
void BackendSettingsPage::ResetWarning() {
ui_->label_warn_logo->clear();
ui_->label_warn_text->clear();
ui_->label_warn_logo->setEnabled(false);
ui_->label_warn_text->setEnabled(false);
}
void BackendSettingsPage::XineWarning() {
if (!engineloaded_) return;
if (!configloaded_) return;
if (engine()->type() != Engine::Xine) return;
if (xinewarning_) return;
ShowWarning("You need to restart Strawberry for output/device changes to take affect for Xine.");
xinewarning_ = true;
}

View File

@ -32,6 +32,11 @@
#include "engine/enginetype.h"
#include "dialogs/errordialog.h"
#include "settingspage.h"
#include "settingsdialog.h"
#include "core/application.h"
#include "core/player.h"
#include "engine/enginebase.h"
class SettingsDialog;
class Ui_BackendSettingsPage;
@ -44,14 +49,12 @@ public:
~BackendSettingsPage();
static const char *kSettingsGroup;
static const char *EngineText_Xine;
static const char *EngineText_GStreamer;
static const char *EngineText_Phonon;
static const char *EngineText_VLC;
void Load();
void Save();
EngineBase *engine() const { return dialog()->app()->player()->engine(); }
private slots:
void EngineChanged(int index);
void OutputChanged(int index);
@ -63,41 +66,24 @@ public:
private:
Ui_BackendSettingsPage *ui_;
void ConnectSignals();
bool EngineInitialised();
void EngineChanged(Engine::EngineType enginetype);
void OutputChanged(int index, Engine::EngineType enginetype);
void Load_Engine(Engine::EngineType enginetype);
void Load_Device(QString output, QVariant device, bool alsa = false, bool pulseaudio = false, bool directsound = false, bool osxaudio = false, bool custom = false);
#ifdef HAVE_XINE
void Xine_Load(QString output, QVariant device);
void Xine_Save();
void Xine_OutputChanged(int index);
#endif
#ifdef HAVE_GSTREAMER
void Gst_Load(QString output, QVariant device);
void Gst_Save();
void Gst_OutputChanged(int index);
#endif
void Load_Output(QString output, QVariant device);
void Load_Device(QString output, QVariant device);
void ShowWarning(QString text);
void ResetWarning();
void XineWarning();
#ifdef HAVE_PHONON
void Phonon_Load(QString output, QVariant device);
void Phonon_Save();
void Phonon_OutputChanged(int index);
#endif
#ifdef HAVE_VLC
void VLC_Load(QString output, QVariant device);
void VLC_Save();
void VLC_OutputChanged(int index);
#endif
bool configloaded_;
Engine::EngineType engineloaded_;
QSettings s_;
bool configloaded_;
bool engineloaded_;
ErrorDialog errordialog_;
bool enginereset_;
bool xinewarning_;
};

View File

@ -25,6 +25,12 @@
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_engine">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Engine</string>
</property>
@ -38,6 +44,12 @@
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Output</string>
</property>
@ -55,6 +67,12 @@
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Device</string>
</property>
@ -76,7 +94,7 @@
</property>
<property name="maximumSize">
<size>
<width>80</width>
<width>160</width>
<height>16777215</height>
</size>
</property>
@ -102,14 +120,26 @@
</item>
</layout>
</item>
<item row="6" column="0">
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupbox_buffer">
<property name="title">
<string>Buffer</string>
</property>
<layout class="QFormLayout" name="formLayout_5">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_bufferduration">
<property name="text">
<string>Buffer duration</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="1" column="1">
<widget class="QSpinBox" name="spinbox_bufferduration">
<property name="suffix">
<string> ms</string>
@ -122,14 +152,14 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="2" column="0">
<widget class="QLabel" name="label_bufferminfill">
<property name="text">
<string>Minimum buffer fill</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_buffer">
<item>
<widget class="QLabel" name="label_bufferminfillvalue">
@ -159,16 +189,6 @@
</item>
</layout>
</item>
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="checkbox_monoplayback">
<property name="toolTip">
<string>Changing mono playback preference will be effective for the next playing songs</string>
</property>
<property name="text">
<string>Mono playback</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -273,6 +293,75 @@
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_monoplayback">
<property name="toolTip">
<string>Changing mono playback preference will be effective for the next playing songs</string>
</property>
<property name="text">
<string>Mono playback</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupbox_warning">
<property name="title">
<string/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_warn_logo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>70</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_warn_text">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>70</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>64</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

View File

@ -43,9 +43,9 @@ PlaybackSettingsPage::PlaybackSettingsPage(SettingsDialog *dialog) : SettingsPag
ui_->setupUi(this);
setWindowIcon(IconLoader::Load("media-play"));
connect(ui_->fading_cross, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
connect(ui_->fading_out, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
connect(ui_->fading_auto, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
connect(ui_->checkbox_fadeout_stop, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
connect(ui_->checkbox_fadeout_cross, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
connect(ui_->checkbox_fadeout_auto, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
}
@ -60,14 +60,14 @@ void PlaybackSettingsPage::Load() {
QSettings s;
s.beginGroup(kSettingsGroup);
ui_->current_glow->setChecked(s.value("glow_effect", true).toBool());
ui_->fading_out->setChecked(s.value("FadeoutEnabled", false).toBool());
ui_->fading_cross->setChecked(s.value("CrossfadeEnabled", false).toBool());
ui_->fading_auto->setChecked(s.value("AutoCrossfadeEnabled", false).toBool());
ui_->fading_duration->setValue(s.value("FadeoutDuration", 2000).toInt());
ui_->fading_samealbum->setChecked(s.value("NoCrossfadeSameAlbum", true).toBool());
ui_->fadeout_pause->setChecked(s.value("FadeoutPauseEnabled", false).toBool());
ui_->fading_pause_duration->setValue(s.value("FadeoutPauseDuration", 250).toInt());
ui_->checkbox_glowcurrenttrack->setChecked(s.value("glow_effect", true).toBool());
ui_->checkbox_fadeout_stop->setChecked(s.value("FadeoutEnabled", false).toBool());
ui_->checkbox_fadeout_cross->setChecked(s.value("CrossfadeEnabled", false).toBool());
ui_->checkbox_fadeout_auto->setChecked(s.value("AutoCrossfadeEnabled", false).toBool());
ui_->checkbox_fadeout_samealbum->setChecked(s.value("NoCrossfadeSameAlbum", true).toBool());
ui_->checkbox_fadeout_pauseresume->setChecked(s.value("FadeoutPauseEnabled", false).toBool());
ui_->spinbox_fadeduration->setValue(s.value("FadeoutDuration", 2000).toInt());
ui_->spinbox_fadeduration_pauseresume->setValue(s.value("FadeoutPauseDuration", 250).toInt());
s.endGroup();
}
@ -77,19 +77,19 @@ void PlaybackSettingsPage::Save() {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("glow_effect", ui_->current_glow->isChecked());
s.setValue("FadeoutEnabled", ui_->fading_out->isChecked());
s.setValue("FadeoutDuration", ui_->fading_duration->value());
s.setValue("CrossfadeEnabled", ui_->fading_cross->isChecked());
s.setValue("AutoCrossfadeEnabled", ui_->fading_auto->isChecked());
s.setValue("NoCrossfadeSameAlbum", ui_->fading_samealbum->isChecked());
s.setValue("FadeoutPauseEnabled", ui_->fadeout_pause->isChecked());
s.setValue("FadeoutPauseDuration", ui_->fading_pause_duration->value());
s.setValue("glow_effect", ui_->checkbox_glowcurrenttrack->isChecked());
s.setValue("FadeoutEnabled", ui_->checkbox_fadeout_stop->isChecked());
s.setValue("CrossfadeEnabled", ui_->checkbox_fadeout_cross->isChecked());
s.setValue("AutoCrossfadeEnabled", ui_->checkbox_fadeout_auto->isChecked());
s.setValue("NoCrossfadeSameAlbum", ui_->checkbox_fadeout_samealbum->isChecked());
s.setValue("FadeoutPauseEnabled", ui_->checkbox_fadeout_pauseresume->isChecked());
s.setValue("FadeoutDuration", ui_->spinbox_fadeduration->value());
s.setValue("FadeoutPauseDuration", ui_->spinbox_fadeduration_pauseresume->value());
s.endGroup();
}
void PlaybackSettingsPage::FadingOptionsChanged() {
ui_->fading_options->setEnabled(ui_->fading_out->isChecked() || ui_->fading_cross->isChecked() || ui_->fading_auto->isChecked());
ui_->widget_fading_options->setEnabled(ui_->checkbox_fadeout_stop->isChecked() || ui_->checkbox_fadeout_cross->isChecked() || ui_->checkbox_fadeout_auto->isChecked());
}

View File

@ -15,7 +15,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="current_glow">
<widget class="QCheckBox" name="checkbox_glowcurrenttrack">
<property name="text">
<string>Show a glowing animation on the current track</string>
</property>
@ -25,13 +25,13 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="groupbox_fading">
<property name="title">
<string>Fading</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="fading_out">
<widget class="QCheckBox" name="checkbox_fadeout_stop">
<property name="text">
<string>Fade out when stopping a track</string>
</property>
@ -41,7 +41,7 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="fading_cross">
<widget class="QCheckBox" name="checkbox_fadeout_cross">
<property name="text">
<string>Cross-fade when changing tracks manually</string>
</property>
@ -51,14 +51,14 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="fading_auto">
<widget class="QCheckBox" name="checkbox_fadeout_auto">
<property name="text">
<string>Cross-fade when changing tracks automatically</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="fading_samealbum">
<widget class="QCheckBox" name="checkbox_fadeout_samealbum">
<property name="enabled">
<bool>false</bool>
</property>
@ -68,13 +68,22 @@
</widget>
</item>
<item>
<widget class="QWidget" name="fading_options" native="true">
<widget class="QWidget" name="widget_fading_options" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<widget class="QLabel" name="label_fadingduration_1">
<property name="text">
<string>Fading duration</string>
</property>
@ -84,7 +93,7 @@
</widget>
</item>
<item>
<widget class="QSpinBox" name="fading_duration">
<widget class="QSpinBox" name="spinbox_fadeduration">
<property name="suffix">
<string> ms</string>
</property>
@ -100,7 +109,7 @@
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<spacer name="horizontalSpacer_1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -116,16 +125,16 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="fadeout_pause">
<widget class="QCheckBox" name="checkbox_fadeout_pauseresume">
<property name="text">
<string>Fade out on pause / fade in on resume</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QHBoxLayout" name="layout_fading">
<item>
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="label_fadingduration_2">
<property name="text">
<string>Fading duration</string>
</property>
@ -135,7 +144,7 @@
</widget>
</item>
<item>
<widget class="QSpinBox" name="fading_pause_duration">
<widget class="QSpinBox" name="spinbox_fadeduration_pauseresume">
<property name="suffix">
<string> ms</string>
</property>
@ -183,19 +192,12 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>StickySlider</class>
<extends>QSlider</extends>
<header>widgets/stickyslider.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>fading_auto</sender>
<sender>checkbox_fadeout_auto</sender>
<signal>toggled(bool)</signal>
<receiver>fading_samealbum</receiver>
<receiver>checkbox_fadeout_samealbum</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">

View File

@ -44,6 +44,8 @@
#include <QTreeWidget>
#include "core/application.h"
#include "core/player.h"
#include "engine/enginebase.h"
#include "widgets/groupediconview.h"
#include "collection/collectionmodel.h"
@ -94,17 +96,14 @@ void SettingsItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
}
SettingsDialog::SettingsDialog(Application *app, QWidget *parent)
: QDialog(parent),
app_(app),
//player_(app_->player()),
player_(app_->player()),
engine_(app_->player()->engine()),
model_(app_->collection_model()->directory_model()),
//gst_engine_(qobject_cast<GstEngine*>(app_->player()->engine())),
//engine_(app_->player()->engine()),
appearance_(app_->appearance()),
ui_(new Ui_SettingsDialog),
//mui_(parent),
loading_settings_(false) {
ui_->setupUi(this);

View File

@ -42,8 +42,9 @@
class QModelIndex;
class QShowEvent;
class Appearance;
class Application;
class Player;
class Appearance;
class CollectionDirectoryModel;
class GlobalShortcuts;
class SettingsPage;
@ -88,6 +89,8 @@ public:
bool is_loading_settings() const { return loading_settings_; }
Application *app() const { return app_; }
Player *player() const { return player_; }
EngineBase *engine() const { return engine_; }
CollectionDirectoryModel *collection_directory_model() const { return model_; }
GlobalShortcuts *global_shortcuts_manager() const { return manager_; }
Appearance *appearance() const { return appearance_; }
@ -122,6 +125,8 @@ private:
private:
Application *app_;
Player *player_;
EngineBase *engine_;
CollectionDirectoryModel *model_;
GlobalShortcuts *manager_;
Appearance *appearance_;

View File

@ -72,7 +72,7 @@ GlobalShortcutsSettingsPage::~GlobalShortcutsSettingsPage() { delete ui_; }
bool GlobalShortcutsSettingsPage::IsEnabled() const {
#ifdef Q_OS_MAC
#ifdef Q_OS_MACOS
qLog(Debug) << Utilities::GetMacVersion();
if (Utilities::GetMacVersion() < 6) { // Leopard and earlier.
return false;

View File

@ -298,7 +298,6 @@ void StatusView::UpdateSong() {
const QueryOptions opt;
CollectionBackend::AlbumList albumlist;
Engine::EngineType enginetype = app_->player()->engine()->type();
QString EngineName = EngineNameFromType(enginetype);
label_playing_top_->setText("");
label_playing_text_->setText("");
@ -317,7 +316,7 @@ void StatusView::UpdateSong() {
if (enginetype != Engine::EngineType::None) {
html += QString("<br />");
html += QString("Engine: %1<br />").arg(EngineName);
html += QString("Engine: %1<br />").arg(EngineName(enginetype));
}
html += QString("<br />");