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_AUDIO gstreamer-audio-1.0)
pkg_check_modules(GSTREAMER_APP gstreamer-app-1.0) pkg_check_modules(GSTREAMER_APP gstreamer-app-1.0)
pkg_check_modules(GSTREAMER_TAG gstreamer-tag-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(LIBXINE libxine)
pkg_check_modules(LIBVLC libvlc) pkg_check_modules(LIBVLC libvlc)
pkg_check_modules(PHONON phonon4qt5) 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-app-1.0" GSTREAMER_APP_FOUND
DEPENDS "gstreamer-audio-1.0" GSTREAMER_AUDIO_FOUND DEPENDS "gstreamer-audio-1.0" GSTREAMER_AUDIO_FOUND
DEPENDS "gstreamer-tag-1.0" GSTREAMER_TAG_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 DEPENDS "libxine" LIBXINE_FOUND
) )
optional_component(VLC OFF "Engine: VLC backend" optional_component(VLC ON "Engine: VLC backend"
DEPENDS "libvlc" LIBVLC_FOUND DEPENDS "libvlc" LIBVLC_FOUND
) )

View File

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

View File

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

View File

@ -53,7 +53,6 @@ if(HAVE_GSTREAMER)
include_directories(${GSTREAMER_AUDIO_INCLUDE_DIRS}) include_directories(${GSTREAMER_AUDIO_INCLUDE_DIRS})
include_directories(${GSTREAMER_BASE_INCLUDE_DIRS}) include_directories(${GSTREAMER_BASE_INCLUDE_DIRS})
include_directories(${GSTREAMER_TAG_INCLUDE_DIRS}) include_directories(${GSTREAMER_TAG_INCLUDE_DIRS})
include_directories(${GSTREAMER_PBUTILS_INCLUDE_DIRS})
endif() endif()
if(HAVE_PHONON) if(HAVE_PHONON)
@ -503,7 +502,7 @@ optional_source(HAVE_GSTREAMER
# Xine # Xine
optional_source(HAVE_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 HEADERS engine/xineengine.h
) )
@ -863,7 +862,7 @@ if(HAVE_ALSA)
endif(HAVE_ALSA) endif(HAVE_ALSA)
if(HAVE_GSTREAMER) 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() endif()
if(HAVE_XINE) if(HAVE_XINE)

View File

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

View File

@ -125,21 +125,4 @@ void Collection::CurrentSongChanged(const Song &song) {
connect(reply, SIGNAL(Finished(bool)), reply, SLOT(deleteLater())); 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 CurrentSongChanged(const Song &song);
void Stopped(); void Stopped();
private:
SongList FilterCurrentWMASong(SongList songs, Song* queued);
private: private:
Application *app_; Application *app_;
CollectionBackend *backend_; CollectionBackend *backend_;
@ -84,10 +81,6 @@ class Collection : public QObject {
CollectionWatcher *watcher_; CollectionWatcher *watcher_;
Thread *watcher_thread_; 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). // DB schema versions which should trigger a full collection rescan (each of those with a short reason why).
QHash<int, QString> full_rescan_revisions_; 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_Genre: key = song.genre(); break;
case GroupBy_AlbumArtist: key = song.effective_albumartist(); break; case GroupBy_AlbumArtist: key = song.effective_albumartist(); break;
case GroupBy_Year: case GroupBy_Year:
key = QString::number(qMax(0, song.year())); break; key = QString::number(qMax(0, song.year()));
break;
case GroupBy_OriginalYear: case GroupBy_OriginalYear:
key = QString::number(qMax(0, song.effective_originalyear())); key = QString::number(qMax(0, song.effective_originalyear()));
break; break;
@ -227,8 +228,7 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
key = PrettyYearAlbum(qMax(0, song.year()), song.album()); key = PrettyYearAlbum(qMax(0, song.year()), song.album());
break; break;
case GroupBy_OriginalYearAlbum: case GroupBy_OriginalYearAlbum:
key = PrettyYearAlbum(qMax(0, song.effective_originalyear()), key = PrettyYearAlbum(qMax(0, song.effective_originalyear()), song.album());
song.album());
break; break;
case GroupBy_FileType: case GroupBy_FileType:
key = song.filetype(); key = song.filetype();
@ -236,6 +236,12 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
case GroupBy_Bitrate: case GroupBy_Bitrate:
key = song.bitrate(); key = song.bitrate();
break; break;
case GroupBy_Samplerate:
key = song.samplerate();
break;
case GroupBy_Bitdepth:
key = song.bitdepth();
break;
case GroupBy_None: case GroupBy_None:
qLog(Error) << "GroupBy_None"; qLog(Error) << "GroupBy_None";
break; break;
@ -325,6 +331,12 @@ QString CollectionModel::DividerKey(GroupBy type, CollectionItem *item) const {
case GroupBy_Bitrate: case GroupBy_Bitrate:
return SortTextForNumber(item->metadata.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: case GroupBy_None:
return QString(); return QString();
@ -364,6 +376,14 @@ QString CollectionModel::DividerDisplayText(GroupBy type, const QString &key) co
case GroupBy_Bitrate: case GroupBy_Bitrate:
if (key == "000") return tr("Unknown"); if (key == "000") return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s 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: case GroupBy_None:
// fallthrough // fallthrough
@ -836,6 +856,12 @@ void CollectionModel::InitQuery(GroupBy type, CollectionQuery *q) {
case GroupBy_Bitrate: case GroupBy_Bitrate:
q->SetColumnSpec("DISTINCT bitrate"); q->SetColumnSpec("DISTINCT bitrate");
break; break;
case GroupBy_Samplerate:
q->SetColumnSpec("DISTINCT samplerate");
break;
case GroupBy_Bitdepth:
q->SetColumnSpec("DISTINCT bitdepth");
break;
case GroupBy_None: case GroupBy_None:
q->SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); q->SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
break; break;
@ -911,6 +937,12 @@ void CollectionModel::FilterQuery(GroupBy type, CollectionItem *item, Collection
case GroupBy_Bitrate: case GroupBy_Bitrate:
q->AddWhere("bitrate", item->key); q->AddWhere("bitrate", item->key);
break; break;
case GroupBy_Samplerate:
q->AddWhere("samplerate", item->key);
break;
case GroupBy_Bitdepth:
q->AddWhere("bitdepth", item->key);
break;
case GroupBy_None: case GroupBy_None:
qLog(Error) << "Unknown GroupBy type" << type << "used in filter"; qLog(Error) << "Unknown GroupBy type" << type << "used in filter";
break; 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 *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); CollectionItem *item = InitItem(type, signal, parent, container_level);
int year = 0; int year(0), effective_originalyear(0), disc(0), bitrate(0), samplerate(0), bitdepth(0);
int effective_originalyear = 0;
int bitrate = 0;
int disc = 0;
switch (type) { switch (type) {
case GroupBy_Artist: case GroupBy_Artist:
@ -972,13 +1001,11 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
item->key = QString::number(year); item->key = QString::number(year);
item->sort_text = SortTextForNumber(year) + " "; item->sort_text = SortTextForNumber(year) + " ";
break; break;
case GroupBy_OriginalYear: case GroupBy_OriginalYear:
year = qMax(0, row.value(0).toInt()); year = qMax(0, row.value(0).toInt());
item->key = QString::number(year); item->key = QString::number(year);
item->sort_text = SortTextForNumber(year) + " "; item->sort_text = SortTextForNumber(year) + " ";
break; break;
case GroupBy_Composer: case GroupBy_Composer:
case GroupBy_Performer: case GroupBy_Performer:
case GroupBy_Grouping: case GroupBy_Grouping:
@ -1006,6 +1033,18 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
item->key = QString::number(bitrate); item->key = QString::number(bitrate);
item->sort_text = SortTextForNumber(bitrate) + " "; item->sort_text = SortTextForNumber(bitrate) + " ";
break; 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: case GroupBy_None:
item->metadata.InitFromQuery(row, true); 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 *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); CollectionItem *item = InitItem(type, signal, parent, container_level);
int year = 0; int year(0), originalyear(0), effective_originalyear(0), bitrate(0), samplerate(0), bitdepth(0);
int originalyear = 0;
int effective_originalyear = 0;
int bitrate = 0;
switch (type) { switch (type) {
case GroupBy_Artist: case GroupBy_Artist:
@ -1092,6 +1128,18 @@ CollectionItem *CollectionModel::ItemFromSong(GroupBy type, bool signal, bool cr
item->key = QString::number(bitrate); item->key = QString::number(bitrate);
item->sort_text = SortTextForNumber(bitrate) + " "; item->sort_text = SortTextForNumber(bitrate) + " ";
break; 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: case GroupBy_None:
item->metadata = s; item->metadata = s;

View File

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

View File

@ -230,7 +230,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
ScanTransaction transaction(this, dir.id, true); ScanTransaction transaction(this, dir.id, true);
transaction.SetKnownSubdirs(subdirs); transaction.SetKnownSubdirs(subdirs);
transaction.AddToProgressMax(subdirs.count()); transaction.AddToProgressMax(subdirs.count());
for (const Subdirectory& subdir : subdirs) { for (const Subdirectory &subdir : subdirs) {
if (stop_requested_) return; if (stop_requested_) return;
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, &transaction); if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, &transaction);
@ -742,9 +742,9 @@ void CollectionWatcher::ReloadSettings() {
} }
else if (monitor_ && !was_monitoring_before) { else if (monitor_ && !was_monitoring_before) {
// Add all directories to all QFileSystemWatchers again // 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); SubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
for (const Subdirectory& subdir : subdirs) { for (const Subdirectory &subdir : subdirs) {
AddWatch(dir, subdir.path); AddWatch(dir, subdir.path);
} }
} }
@ -783,12 +783,12 @@ void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
void CollectionWatcher::PerformScan(bool incremental, bool ignore_mtimes) { 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); ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes);
SubdirectoryList subdirs(transaction.GetAllSubdirs()); SubdirectoryList subdirs(transaction.GetAllSubdirs());
transaction.AddToProgressMax(subdirs.count()); transaction.AddToProgressMax(subdirs.count());
for (const Subdirectory & subdir : subdirs) { for (const Subdirectory &subdir : subdirs) {
if (stop_requested_) return; if (stop_requested_) return;
ScanSubdirectory(subdir.path, subdir, &transaction); 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_YearAlbum, 9));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYearAlbum, 10)); p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYearAlbum, 10));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitrate, 11)); p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitrate, 11));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 12)); p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Samplerate, 12));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 13)); p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitdepth, 13));
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 14)); 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()); resize(sizeHint());
} }
@ -108,23 +110,23 @@ GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_Grou
GroupByDialog::~GroupByDialog() {} GroupByDialog::~GroupByDialog() {}
void GroupByDialog::Reset() { void GroupByDialog::Reset() {
ui_->first->setCurrentIndex(2); // Artist ui_->combobox_first->setCurrentIndex(2); // Artist
ui_->second->setCurrentIndex(1); // Album ui_->combobox_second->setCurrentIndex(1); // Album
ui_->third->setCurrentIndex(0); // None ui_->combobox_third->setCurrentIndex(0); // None
} }
void GroupByDialog::accept() { void GroupByDialog::accept() {
emit Accepted(CollectionModel::Grouping( emit Accepted(CollectionModel::Grouping(
p_->mapping_.get<tag_index>().find(ui_->first->currentIndex())->group_by, p_->mapping_.get<tag_index>().find(ui_->combobox_first->currentIndex())->group_by,
p_->mapping_.get<tag_index>().find(ui_->second->currentIndex())->group_by, p_->mapping_.get<tag_index>().find(ui_->combobox_second->currentIndex())->group_by,
p_->mapping_.get<tag_index>().find(ui_->third->currentIndex())->group_by) p_->mapping_.get<tag_index>().find(ui_->combobox_third->currentIndex())->group_by)
); );
QDialog::accept(); QDialog::accept();
} }
void GroupByDialog::CollectionGroupingChanged(const CollectionModel::Grouping &g) { void GroupByDialog::CollectionGroupingChanged(const CollectionModel::Grouping &g) {
ui_->first->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[0])->combo_box_index); ui_->combobox_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_->combobox_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_third->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[2])->combo_box_index);
} }

View File

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

View File

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

View File

@ -2,6 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * 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 * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -60,6 +61,8 @@
#include <iostream> #include <iostream>
#endif // Q_OS_WIN32 #endif // Q_OS_WIN32
#include "main.h"
#include "core/logging.h" #include "core/logging.h"
#include "qtsingleapplication.h" #include "qtsingleapplication.h"
@ -132,6 +135,7 @@ int main(int argc, char* argv[]) {
qLog(Info) << "Strawberry is already running - activating existing window"; qLog(Info) << "Strawberry is already running - activating existing window";
} }
if (a.sendMessage(options.Serialize(), 5000)) { if (a.sendMessage(options.Serialize(), 5000)) {
main_exit_safe(0);
return 0; return 0;
} }
// Couldn't send the message so start anyway // Couldn't send the message so start anyway
@ -175,8 +179,8 @@ int main(int argc, char* argv[]) {
QCoreApplication::setAttribute(Qt::AA_NativeWindows, true); QCoreApplication::setAttribute(Qt::AA_NativeWindows, true);
#endif #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. // 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. // On Windows these are stored in the registry instead.
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
{ {
QSettings s; QSettings s;
@ -226,17 +230,44 @@ int main(int argc, char* argv[]) {
int ret = a.exec(); int ret = a.exec();
#ifdef Q_OS_LINUX main_exit_safe(ret);
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
return 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(); app_->player()->ReloadSettings();
osd_->ReloadSettings(); osd_->ReloadSettings();
collection_view_->ReloadSettings(); collection_view_->ReloadSettings();
app_->player()->engine()->ReloadSettings();
ui_->playlist->view()->ReloadSettings(); ui_->playlist->view()->ReloadSettings();
} }

View File

@ -75,8 +75,6 @@ using std::shared_ptr;
Player::Player(Application *app, QObject *parent) Player::Player(Application *app, QObject *parent)
: PlayerInterface(parent), : PlayerInterface(parent),
app_(app), app_(app),
//engine_(new GstEngine(app_->task_manager())),
//engine_(CreateEngine()),
stream_change_type_(Engine::First), stream_change_type_(Engine::First),
last_state_(Engine::Empty), last_state_(Engine::Empty),
nb_errors_received_(0), nb_errors_received_(0),
@ -88,7 +86,7 @@ Player::Player(Application *app, QObject *parent)
QSettings s; QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup); 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(); s.endGroup();
CreateEngine(enginetype); CreateEngine(enginetype);
@ -129,19 +127,19 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
enginebase = new XineEngine(app_->task_manager()); enginebase = new XineEngine(app_->task_manager());
break; break;
#endif #endif
#ifdef HAVE_PHONON
case Engine::Phonon:
engine=true;
enginetype=Engine::Phonon;
enginebase = new PhononEngine(app_->task_manager());
break;
#endif
#ifdef HAVE_VLC #ifdef HAVE_VLC
case Engine::VLC: case Engine::VLC:
engine=true; engine=true;
enginetype=Engine::VLC; enginetype=Engine::VLC;
enginebase = new VLCEngine(app_->task_manager()); enginebase = new VLCEngine(app_->task_manager());
break; break;
#endif
#ifdef HAVE_PHONON
case Engine::Phonon:
engine=true;
enginetype=Engine::Phonon;
enginebase = new PhononEngine(app_->task_manager());
break;
#endif #endif
default: default:
if (i > 1) { qFatal("No engine available!"); return nullptr; } if (i > 1) { qFatal("No engine available!"); return nullptr; }
@ -149,7 +147,7 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
s.beginGroup(BackendSettingsPage::kSettingsGroup); s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.setValue("engine", ""); s.setValue("engine", "");
s.setValue("output", ""); s.setValue("output", "");
s.setValue("device", ""); s.setValue("device", QVariant(""));
s.endGroup(); s.endGroup();
enginetype = Engine::None; enginetype = Engine::None;
break; break;
@ -158,7 +156,7 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
QSettings s; QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup); s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.setValue("engine", Engine::EngineNameFromType(enginetype)); s.setValue("engine", Engine::EngineName(enginetype));
s.endGroup(); s.endGroup();
if (enginebase == nullptr) { if (enginebase == nullptr) {
@ -226,7 +224,7 @@ void Player::ReloadSettings() {
seek_step_sec_ = s.value("seek_step_sec", 10).toInt(); seek_step_sec_ = s.value("seek_step_sec", 10).toInt();
s.endGroup(); 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 return !(status.BatteryFlag & 128); // 128 = no system battery
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
return !QDir("/proc/acpi/battery").entryList(QDir::Dirs | QDir::NoDotAndDotDot).isEmpty(); 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<CFTypeRef> power_sources(IOPSCopyPowerSourcesInfo());
ScopedCFTypeRef<CFArrayRef> power_source_list(IOPSCopyPowerSourcesList(power_sources.get())); ScopedCFTypeRef<CFArrayRef> power_source_list(IOPSCopyPowerSourcesList(power_sources.get()));
for (CFIndex i = 0; i < CFArrayGetCount(power_source_list.get()); ++i) { 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: case IDEVICE_DEVICE_REMOVE:
me->DeviceRemovedCallback(uuid); me->DeviceRemovedCallback(uuid);
break; break;
default:
break;
} }
} }

View File

@ -96,7 +96,8 @@ OrganiseDialog::OrganiseDialog(TaskManager *task_manager, QWidget *parent)
tags[tr("Comment")] = "comment"; tags[tr("Comment")] = "comment";
tags[tr("Length")] = "length"; tags[tr("Length")] = "length";
tags[tr("Bitrate", "Refers to bitrate in file organise dialog.")] = "bitrate"; 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"; tags[tr("File extension")] = "extension";
// Naming scheme input field // Naming scheme input field

View File

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

View File

@ -23,7 +23,7 @@
#include "devicefinder.h" #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) { QString DeviceFinder::GuessIconName(const QString &description) {

View File

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

View File

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

View File

@ -1,9 +1,10 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* This file was part of Amarok / Clementine. * This file was part of Amarok / Clementine
* Copyright 2003 Mark Kretschmann * Copyright 2003 Mark Kretschmann
* Copyright 2004, 2005 Max Howell, <max.howell@methylblue.com> * Copyright 2004 - 2005 Max Howell, <max.howell@methylblue.com>
* Copyright 2010, David Sansome <me@davidsansome.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 * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -25,12 +26,14 @@
#include <cmath> #include <cmath>
#include <QtGlobal> #include <QtGlobal>
#include <QVariant>
#include <QUrl> #include <QUrl>
#include <QSettings> #include <QSettings>
#include "core/timeconstants.h" #include "core/timeconstants.h"
#include "engine_fwd.h" #include "engine_fwd.h"
#include "enginebase.h" #include "enginebase.h"
#include "settings/backendsettingspage.h"
#include "settings/playbacksettingspage.h" #include "settings/playbacksettingspage.h"
Engine::Base::Base() Engine::Base::Base()
@ -38,12 +41,21 @@ Engine::Base::Base()
beginning_nanosec_(0), beginning_nanosec_(0),
end_nanosec_(0), end_nanosec_(0),
scope_(kScopeSize), 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_enabled_(true),
fadeout_duration_nanosec_(2 * kNsecPerSec), // 2s
crossfade_enabled_(true), crossfade_enabled_(true),
autocrossfade_enabled_(false), autocrossfade_enabled_(false),
crossfade_same_album_(false), crossfade_same_album_(false),
next_background_stream_id_(0), fadeout_duration_(2),
fadeout_duration_nanosec_(2 * kNsecPerSec),
about_to_end_emitted_(false) {} about_to_end_emitted_(false) {}
Engine::Base::~Base() {} 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) { void Engine::Base::SetVolume(uint value) {
volume_ = value; volume_ = value;
@ -77,15 +97,30 @@ uint Engine::Base::MakeVolumeLogarithmic(uint volume) {
void Engine::Base::ReloadSettings() { void Engine::Base::ReloadSettings() {
QSettings s; 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_enabled_ = s.value("FadeoutEnabled", false).toBool();
fadeout_duration_nanosec_ = s.value("FadeoutDuration", 2000).toLongLong() * kNsecPerMsec;
crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool(); crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool();
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool(); autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
crossfade_same_album_ = !s.value("NoCrossfadeSameAlbum", true).toBool(); crossfade_same_album_ = !s.value("NoCrossfadeSameAlbum", true).toBool();
fadeout_pause_enabled_ = s.value("FadeoutPauseEnabled", false).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; about_to_end_emitted_ = true;
emit TrackAboutToEnd(); 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 * Strawberry Music Player
* This file was part of Amarok / Clementine. * This file was part of Amarok / Clementine
* Copyright 2003 Mark Kretschmann * Copyright 2003 Mark Kretschmann
* Copyright 2004, 2005 Max Howell, <max.howell@methylblue.com> * Copyright 2004 - 2005 Max Howell, <max.howell@methylblue.com>
* Copyright 2010, David Sansome <me@davidsansome.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 * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -34,6 +35,7 @@
#include <QObject> #include <QObject>
#include <QList> #include <QList>
#include <QMetaType> #include <QMetaType>
#include <QVariant>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
@ -49,52 +51,13 @@ typedef std::vector<int16_t> Scope;
class Base : public QObject { class Base : public QObject {
Q_OBJECT Q_OBJECT
public: protected:
Base();
public:
virtual ~Base(); 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 { struct OutputDetails {
QString name; QString name;
QString description; QString description;
@ -102,9 +65,60 @@ class Base : public QObject {
}; };
typedef QList<OutputDetails> OutputDetailsList; 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(); 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 SetEqualizerEnabled(bool) {}
virtual void SetEqualizerParameters(int preamp, const QList<int> &bandGains) {} virtual void SetEqualizerParameters(int preamp, const QList<int> &bandGains) {}
virtual void SetStereoBalance(float value) {} virtual void SetStereoBalance(float value) {}
@ -132,28 +146,8 @@ signals:
// subsequent call to state() won't return a stale value. // subsequent call to state() won't return a stale value.
void StateChanged(Engine::State); void StateChanged(Engine::State);
protected: protected:
Base();
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 { struct PluginDetails {
QString name; QString name;
QString description; QString description;
@ -161,7 +155,43 @@ signals:
}; };
typedef QList<PluginDetails> PluginDetailsList; 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_; bool about_to_end_emitted_;
Q_DISABLE_COPY(Base); Q_DISABLE_COPY(Base);

View File

@ -26,18 +26,6 @@
namespace Engine { 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) { Engine::EngineType EngineTypeFromName(QString enginename) {
QString lower = enginename.toLower(); 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 Phonon
}; };
QString EngineNameFromType(Engine::EngineType enginetype);
Engine::EngineType EngineTypeFromName(QString enginename); Engine::EngineType EngineTypeFromName(QString enginename);
QString EngineName(Engine::EngineType enginetype);
QString EngineDescription(Engine::EngineType enginetype);
} }
Q_DECLARE_METATYPE(Engine::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) 2003-2005 by Mark Kretschmann <markey@web.de> *
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> * * 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> *
@ -30,7 +31,6 @@
#include <string> #include <string>
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include <QtGlobal> #include <QtGlobal>
#include <QCoreApplication> #include <QCoreApplication>
@ -90,15 +90,7 @@ GstEngine::GstEngine(TaskManager *task_manager)
task_manager_(task_manager), task_manager_(task_manager),
buffering_task_id_(-1), buffering_task_id_(-1),
latest_buffer_(nullptr), latest_buffer_(nullptr),
equalizer_enabled_(false),
stereo_balance_(0.0f), 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)), seek_timer_(new QTimer(this)),
timer_id_(-1), timer_id_(-1),
next_element_id_(0), 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 { Engine::State GstEngine::state() const {
if (!current_pipeline_) return url_.isEmpty() ? Engine::Empty : Engine::Idle; if (!current_pipeline_) return url_.isEmpty() ? Engine::Empty : Engine::Idle;
@ -256,102 +151,6 @@ Engine::State GstEngine::state() const {
default: default:
return Engine::Empty; 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) { bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
EnsureInitialised(); EnsureInitialised();
@ -445,33 +208,6 @@ bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool forc
return true; 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) { bool GstEngine::Play(quint64 offset_nanosec) {
EnsureInitialised(); 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) { void GstEngine::Stop(bool stop_after) {
StopTimers(); 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() { void GstEngine::Pause() {
if (!current_pipeline_ || current_pipeline_->is_buffering()) return; 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; qint64 GstEngine::position_nanosec() const {
waiting_to_seek_ = false;
if (!current_pipeline_) return; if (!current_pipeline_) return 0;
if (!current_pipeline_->Seek(seek_pos_)) { const qint64 result = current_pipeline_->position() - beginning_nanosec_;
qLog(Warning) << "Seek failed"; 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) { void GstEngine::SetEqualizerEnabled(bool enabled) {
@ -669,21 +500,14 @@ void GstEngine::SetStereoBalance(float value) {
if (current_pipeline_) current_pipeline_->SetStereoBalance(value); if (current_pipeline_) current_pipeline_->SetStereoBalance(value);
} }
void GstEngine::SetVolumeSW(uint percent) { void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) {
if (current_pipeline_) current_pipeline_->SetVolume(percent); buffer_consumers_ << consumer;
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
} }
void GstEngine::StartTimers() { void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) {
StopTimers(); buffer_consumers_.removeAll(consumer);
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
timer_id_ = startTimer(kTimerIntervalNanosec / kNsecPerMsec);
}
void GstEngine::StopTimers() {
if (timer_id_ != -1) {
killTimer(timer_id_);
timer_id_ = -1;
}
} }
void GstEngine::timerEvent(QTimerEvent *e) { 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) { void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id) 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) { void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bundle) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id) 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); 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 if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) {
QString name = factoryName + "-" + QString::number(next_element_id_++); gst_buffer_unref(buf);
return;
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); 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 { 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() { shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
EnsureInitialised(); EnsureInitialised();
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this)); 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_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_); ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
ret->set_buffer_min_fill(buffer_min_fill_); 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) { void GstEngine::UpdateScope(int chunk_length) {
buffer_consumers_ << consumer;
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
}
void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) { typedef Engine::Scope::value_type sample_type;
buffer_consumers_.removeAll(consumer);
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
}
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) { GstMapInfo map;
task_manager_->SetTaskFinished(buffering_task_id_); 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")); const sample_type *source = reinterpret_cast<sample_type*>(map.data);
task_manager_->SetTaskProgress(buffering_task_id_, 0, 100); sample_type *dest = scope_.data();
source += (chunk_size / sizeof(sample_type)) * scope_chunk_;
} int bytes = 0;
void GstEngine::BufferingProgress(int percent) { // Make sure we don't go beyond the end of the buffer
task_manager_->SetTaskProgress(buffering_task_id_, percent, 100); 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));
void GstEngine::BufferingFinished() {
if (buffering_task_id_ != -1) {
task_manager_->SetTaskFinished(buffering_task_id_);
buffering_task_id_ = -1;
} }
} else {
bytes = qMin(static_cast<Engine::Scope::size_type>(chunk_size), 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);
} }
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) 2003-2005 by Mark Kretschmann <markey@web.de> *
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> * * 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 * * 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 * * 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(TaskManager *task_manager);
~GstEngine(); ~GstEngine();
static const char *kAutoSink;
bool Init(); 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; 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); 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 Load(const QUrl &, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec); bool Play(quint64 offset_nanosec);
@ -100,6 +75,28 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
void Pause(); void Pause();
void Unpause(); void Unpause();
void Seek(quint64 offset_nanosec); 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 */ /** Set whether equalizer is enabled */
void SetEqualizerEnabled(bool); void SetEqualizerEnabled(bool);
@ -110,8 +107,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
/** Set Stereo balance, range -1.0f..1.0f */ /** Set Stereo balance, range -1.0f..1.0f */
void SetStereoBalance(float value); void SetStereoBalance(float value);
void ReloadSettings();
void AddBufferConsumer(GstBufferConsumer *consumer); void AddBufferConsumer(GstBufferConsumer *consumer);
void RemoveBufferConsumer(GstBufferConsumer *consumer); void RemoveBufferConsumer(GstBufferConsumer *consumer);
@ -120,7 +115,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
#endif #endif
protected: protected:
void SetVolumeSW(uint percent);
void timerEvent(QTimerEvent*); void timerEvent(QTimerEvent*);
private slots: private slots:
@ -139,6 +133,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
private: private:
static const char *kAutoSink;
static const char *kALSASink; static const char *kALSASink;
static const char *kOpenALSASink; static const char *kOpenALSASink;
static const char *kOSSSink; static const char *kOSSSink;
@ -152,6 +147,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
static const char *kOSXAudioSink; static const char *kOSXAudioSink;
PluginDetailsList GetPluginList(const QString &classname) const; PluginDetailsList GetPluginList(const QString &classname) const;
QByteArray FixupUrl(const QUrl &url);
void StartFadeout(); void StartFadeout();
void StartFadeoutPause(); void StartFadeoutPause();
@ -164,22 +160,17 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
std::shared_ptr<GstEnginePipeline> CreatePipeline(const QByteArray &url, qint64 end_nanosec); std::shared_ptr<GstEnginePipeline> CreatePipeline(const QByteArray &url, qint64 end_nanosec);
void UpdateScope(int chunk_length); void UpdateScope(int chunk_length);
QByteArray FixupUrl(const QUrl &url);
private: private:
static const qint64 kTimerIntervalNanosec = 1000 *kNsecPerMsec; // 1s static const qint64 kTimerIntervalNanosec = 1000 * kNsecPerMsec; // 1s
static const qint64 kPreloadGapNanosec = 3000 *kNsecPerMsec; // 3s static const qint64 kPreloadGapNanosec = 3000 * kNsecPerMsec; // 3s
static const qint64 kSeekDelayNanosec = 100 *kNsecPerMsec; // 100msec static const qint64 kSeekDelayNanosec = 100 * kNsecPerMsec; // 100msec
TaskManager *task_manager_; TaskManager *task_manager_;
int buffering_task_id_; int buffering_task_id_;
QFuture<void> initialising_; QFuture<void> initialising_;
QString sink_;
QVariant device_;
std::shared_ptr<GstEnginePipeline> current_pipeline_; std::shared_ptr<GstEnginePipeline> current_pipeline_;
std::shared_ptr<GstEnginePipeline> fadeout_pipeline_; std::shared_ptr<GstEnginePipeline> fadeout_pipeline_;
std::shared_ptr<GstEnginePipeline> fadeout_pause_pipeline_; std::shared_ptr<GstEnginePipeline> fadeout_pause_pipeline_;
@ -189,22 +180,10 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
GstBuffer *latest_buffer_; GstBuffer *latest_buffer_;
bool equalizer_enabled_;
int equalizer_preamp_; int equalizer_preamp_;
QList<int> equalizer_gains_; QList<int> equalizer_gains_;
float stereo_balance_; 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_success_;
mutable bool can_decode_last_; mutable bool can_decode_last_;

View File

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

View File

@ -179,15 +179,9 @@ signals:
// General settings for the pipeline // General settings for the pipeline
bool valid_; bool valid_;
QString sink_; QString output_;
QVariant device_; 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 // Equalizer
bool eq_enabled_; bool eq_enabled_;
int eq_preamp_; int eq_preamp_;
@ -210,6 +204,12 @@ signals:
bool buffering_; bool buffering_;
bool mono_playback_; 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. // The URL that is currently playing, and the URL that is to be preloaded when the current track is close to finishing.
QByteArray url_; QByteArray url_;

View File

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

View File

@ -1,29 +1,35 @@
/* This file is part of Strawberry. /*
* Strawberry Music Player
Strawberry is free software: you can redistribute it and/or modify * This file was part of Clementine
it under the terms of the GNU General Public License as published by * Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
the Free Software Foundation, either version 3 of the License, or *
(at your option) any later version. * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Strawberry is distributed in the hope that it will be useful, * the Free Software Foundation, either version 3 of the License, or
but WITHOUT ANY WARRANTY; without even the implied warranty of * (at your option) any later version.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
GNU General Public License for more details. * Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
You should have received a copy of the GNU General Public License * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
along with Strawberry. If not, see <http://www.gnu.org/licenses/>. * 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 "config.h"
#include <QtGlobal> #include <QtGlobal>
#include <QString>
#include <QUrl> #include <QUrl>
#include <QTimer> #include <QTimer>
#include "phononengine.h" #include "phononengine.h"
#include "core/logging.h" #include "core/timeconstants.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "core/logging.h"
PhononEngine::PhononEngine(TaskManager *task_manager) PhononEngine::PhononEngine(TaskManager *task_manager)
: media_object_(new Phonon::MediaObject(this)), : 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) { 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 // 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(); media_object_->play();
return true; return true;
@ -113,7 +119,8 @@ uint PhononEngine::length() const {
} }
void PhononEngine::Seek(quint64 offset_nanosec) { 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) { void PhononEngine::SetVolumeSW(uint volume) {
@ -144,13 +151,34 @@ void PhononEngine::StateTimeoutExpired() {
} }
qint64 PhononEngine::position_nanosec() const { qint64 PhononEngine::position_nanosec() const {
if (state() == Engine::Empty) return 0;
return 0; const qint64 result = (position() * kNsecPerMsec);
return qint64(qMax(0ll, result));
} }
qint64 PhononEngine::length_nanosec() const { qint64 PhononEngine::length_nanosec() const {
if (state() == Engine::Empty) return 0;
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 Music Player
Strawberry is free software: you can redistribute it and/or modify * This file was part of Clementine
it under the terms of the GNU General Public License as published by * Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
the Free Software Foundation, either version 3 of the License, or *
(at your option) any later version. * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Strawberry is distributed in the hope that it will be useful, * the Free Software Foundation, either version 3 of the License, or
but WITHOUT ANY WARRANTY; without even the implied warranty of * (at your option) any later version.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
GNU General Public License for more details. * Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
You should have received a copy of the GNU General Public License * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
along with Strawberry. If not, see <http://www.gnu.org/licenses/>. * 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 #ifndef PHONONENGINE_H
#define PHONONENGINE_H #define PHONONENGINE_H
@ -39,6 +43,8 @@ class PhononEngine : public Engine::Base {
~PhononEngine(); ~PhononEngine();
bool Init(); bool Init();
OutputDetailsList GetOutputsList() const;
bool CanDecode(const QUrl &url); bool CanDecode(const QUrl &url);
@ -56,6 +62,9 @@ class PhononEngine : public Engine::Base {
qint64 position_nanosec() const; qint64 position_nanosec() const;
qint64 length_nanosec() const; qint64 length_nanosec() const;
QString DefaultOutput() { return ""; }
bool CustomDeviceSupport(const QString &name);
protected: protected:
void SetVolumeSW( uint percent ); void SetVolumeSW( uint percent );

View File

@ -34,7 +34,7 @@
#include "devicefinder.h" #include "devicefinder.h"
#include "pulsedevicefinder.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() { bool PulseDeviceFinder::Initialise() {

View File

@ -1,18 +1,22 @@
/* This file is part of Clementine. /*
* Strawberry Music Player
Clementine is free software: you can redistribute it and/or modify * This file was part of Clementine
it under the terms of the GNU General Public License as published by * Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
the Free Software Foundation, either version 3 of the License, or *
(at your option) any later version. * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Clementine is distributed in the hope that it will be useful, * the Free Software Foundation, either version 3 of the License, or
but WITHOUT ANY WARRANTY; without even the implied warranty of * (at your option) any later version.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
GNU General Public License for more details. * Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
You should have received a copy of the GNU General Public License * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
along with Clementine. If not, see <http://www.gnu.org/licenses/>. * 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 "config.h"
@ -24,22 +28,42 @@
#include <QByteArray> #include <QByteArray>
#include <QUrl> #include <QUrl>
#include "core/timeconstants.h"
#include "core/taskmanager.h"
#include "core/logging.h"
#include "engine_fwd.h" #include "engine_fwd.h"
#include "enginebase.h" #include "enginebase.h"
#include "enginetype.h" #include "enginetype.h"
#include "vlcengine.h" #include "vlcengine.h"
#include "vlcscopedref.h" #include "vlcscopedref.h"
VLCEngine *VLCEngine::sInstance = NULL; VLCEngine *VLCEngine::sInstance = nullptr;
VLCEngine::VLCEngine(TaskManager *task_manager) VLCEngine::VLCEngine(TaskManager *task_manager)
: instance_(NULL), : instance_(nullptr),
player_(NULL), player_(nullptr),
scope_data_(4096), state_(Engine::Empty),
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[] = { static const char *const args[] = {
"-I", "dummy", // Don't use any interface "-I", "dummy", // Don't use any interface
"--ignore-config", // Don't use VLC's config "--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 "--verbose=2", // be much more verbose then normal for debugging purpose
// Our scope plugin // Our scope plugin
"--audio-filter=clementine_scope", "--audio-filter=strawberry_scope",
"--no-plugins-cache", "--no-plugins-cache",
// Try to stop audio stuttering // Try to stop audio stuttering
"--file-caching=500", // msec "--file-caching=500", // msec
"--http-caching=500", "--http-caching=500",
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) #ifdef HAVE_ALSA_
"--aout=alsa", // The default, pulseaudio, is buggy "--aout=alsa", // The default, pulseaudio, is buggy
#endif #endif
}; };
#endif */
// Create the VLC instance // Create the VLC instance
//libvlc_exception_init(&exception_); instance_ = libvlc_new(0, nullptr);
instance_ = libvlc_new(sizeof(args) / sizeof(args[0]), args); //instance_ = libvlc_new(sizeof(args) / sizeof(args[0]), args);
HandleErrors(); HandleErrors();
// Create the media player // Create the media player
@ -84,17 +108,200 @@ VLCEngine::VLCEngine(TaskManager *task_manager)
HandleErrors(); HandleErrors();
sInstance = this; 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_stop(player_);
libvlc_media_player_release(player_);
libvlc_release(instance_);
HandleErrors(); 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) { void VLCEngine::AttachCallback(libvlc_event_manager_t *em, libvlc_event_type_t type, libvlc_callback_t callback) {
libvlc_event_attach(em, type, callback, this); 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; PluginDetailsList ret;
libvlc_audio_output_t *audio_output_list = libvlc_audio_output_list_get(instance_);
return true;
}
bool VLCEngine::CanDecode(const QUrl &url) { {
PluginDetails details;
// TODO details.name = "auto";
return true; details.description = "Automatically detected";
} ret << details;
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]);
} }
}
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_); return ret;
// 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_;
} }
qint64 VLCEngine::position_nanosec() const { void VLCEngine::GetDevicesList(QString output) const {
return 0; 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;
}
qint64 VLCEngine::length_nanosec() const { libvlc_audio_output_device_list_release(audio_output_device_list);
return 0;
} }

View File

@ -1,18 +1,22 @@
/* This file is part of Clementine. /*
* Strawberry Music Player
Clementine is free software: you can redistribute it and/or modify * This file was part of Clementine
it under the terms of the GNU General Public License as published by * Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
the Free Software Foundation, either version 3 of the License, or *
(at your option) any later version. * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Clementine is distributed in the hope that it will be useful, * the Free Software Foundation, either version 3 of the License, or
but WITHOUT ANY WARRANTY; without even the implied warranty of * (at your option) any later version.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
GNU General Public License for more details. * Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
You should have received a copy of the GNU General Public License * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
along with Clementine. If not, see <http://www.gnu.org/licenses/>. * 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 #ifndef VLCENGINE_H
#define VLCENGINE_H #define VLCENGINE_H
@ -44,49 +48,44 @@ class VLCEngine : public Engine::Base {
~VLCEngine(); ~VLCEngine();
bool Init(); bool Init();
Engine::State state() const { return state_; }
virtual qint64 position_nanosec() const;
virtual qint64 length_nanosec() const;
bool CanDecode( const QUrl &url );
bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec); bool Play(quint64 offset_nanosec);
void Stop(bool stop_after = false); void Stop(bool stop_after = false);
void Pause(); void Pause();
void Unpause(); void Unpause();
Engine::State state() const { return state_; }
uint position() const;
uint length() const;
void Seek(quint64 offset_nanosec); void Seek(quint64 offset_nanosec);
static void SetScopeData(float* data, int size);
const Engine::Scope& Scope();
protected: protected:
void SetVolumeSW(uint percent); 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: 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 HandleErrors() const;
void AttachCallback(libvlc_event_manager_t* em, libvlc_event_type_t type, libvlc_callback_t callback); 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); static void StateChangedCallback(const libvlc_event_t* e, void* data);
private: PluginDetailsList GetPluginList() const;
// The callbacks need access to this void GetDevicesList(QString output) const;
static VLCEngine *sInstance;
// 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 #endif // VLCENGINE_H

View File

@ -1,18 +1,22 @@
/* This file is part of Clementine. /*
* Strawberry Music Player
Clementine is free software: you can redistribute it and/or modify * This file was part of Clementine
it under the terms of the GNU General Public License as published by * Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
the Free Software Foundation, either version 3 of the License, or *
(at your option) any later version. * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Clementine is distributed in the hope that it will be useful, * the Free Software Foundation, either version 3 of the License, or
but WITHOUT ANY WARRANTY; without even the implied warranty of * (at your option) any later version.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
GNU General Public License for more details. * Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
You should have received a copy of the GNU General Public License * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
along with Clementine. If not, see <http://www.gnu.org/licenses/>. * 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 #ifndef VLCSCOPEDREF_H
#define 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 * * 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 * * it under the terms of the GNU General Public License as published by *
@ -8,11 +13,17 @@
* * * *
***************************************************************************/ ***************************************************************************/
#ifndef XINE_ENGINE_H #ifndef XINEENGINE_H
#define XINE_ENGINE_H #define XINEENGINE_H
#include "config.h" #include "config.h"
#include <memory>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include <xine.h>
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
#include <QMutex> #include <QMutex>
@ -22,24 +33,20 @@
#include <QString> #include <QString>
#include <QUrl> #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 "engine_fwd.h"
#include "enginebase.h" #include "enginebase.h"
using std::shared_ptr;
class TaskManager; class TaskManager;
class PruneScopeThread;
class XineFader;
class XineOutFader;
class XineEvent : public QEvent { class XineEvent : public QEvent {
public: public:
enum EventType { enum EventType {
PlaybackFinished = QEvent::User + 1, PlaybackFinished,
InfoMessage, InfoMessage,
StatusMessage, StatusMessage,
MetaInfoChanged, MetaInfoChanged,
@ -47,159 +54,119 @@ public:
LastFMTrackChanged, 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 setData(void *data) { data_ = data; }
void* data() const { return data_; } void *data() const { return data_; }
private: private:
void* data_; void *data_;
}; };
class PruneScopeThread;
class XineEngine : public Engine::Base { class XineEngine : public Engine::Base {
Q_OBJECT Q_OBJECT
public: public:
XineEngine(TaskManager *task_manager); XineEngine(TaskManager *task_manager);
~XineEngine(); ~XineEngine();
friend class Fader; bool Init();
friend class OutFader; Engine::State state() const;
friend class PruneScopeThread; 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; const Engine::Scope& scope(int chunk_length);
virtual qint64 length_nanosec() const;
virtual bool CanDecode( const QUrl &); QString DefaultOutput() { return "auto"; }
virtual bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); OutputDetailsList GetOutputsList() const;
virtual bool Play(quint64 offset_nanosec); bool CustomDeviceSupport(const QString &name);
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);
virtual bool metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b); void ReloadSettings();
virtual bool getAudioCDContents(const QString &device, QList<QUrl> &urls);
virtual bool flushBuffer();
virtual Engine::State state() const; void SetEnvironment();
virtual const Engine::Scope& scope(int chunk_length);
virtual void setEqualizerEnabled( bool ); uint length() const;
virtual void setEqualizerParameters( int preamp, const QList<int>& ); uint position() const;
virtual void SetVolumeSW( uint );
virtual void fadeOut( uint fadeLength, bool* terminate, bool exiting = false );
static void XineEventListener( void*, const xine_event_t* ); bool CanDecode(const QUrl &);
virtual bool event( QEvent* );
virtual void playlistChanged(); bool MetaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b);
virtual void ReloadSettings(); 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(); static void XineEventListener(void*, const xine_event_t*);
bool ensureStream(); bool event(QEvent*);
void determineAndShowErrorMessage(); //call after failure to load/play Engine::SimpleMetaBundle fetchMetaData() const;
//static void SetOutput(QString output, QString device);
xine_t *xine_; bool MakeNewStream();
xine_stream_t *stream_; bool EnsureStream();
xine_audio_port_t *audioPort_;
xine_event_queue_t *eventQueue_;
xine_post_t *post_;
int64_t currentVpts_; void DetermineAndShowErrorMessage(); //call after failure to load/play
float preamp_;
bool stopFader_; // Simple accessors
bool fadeOutRunning_;
QString currentAudioPlugin_; //to see if audio plugin has been changed need to save these for when the audio plugin is changed and xine reloaded xine_stream_t *stream() { return stream_; }
QString currentAudioDevice_; float preamp() { return preamp_; }
bool equalizerEnabled_; bool stop_fader() { return stop_fader_; }
int intPreamp_; void set_stop_fader(bool stop_fader) { stop_fader_ = stop_fader; }
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);
private: 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: private slots:
void PruneScope(); void PruneScope();
signals: signals:
void resetConfig(xine_t *xine); void InfoMessage(const QString&);
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();
}; };
class PruneScopeThread : public QThread { class PruneScopeThread : public QThread {
@ -207,10 +174,10 @@ public:
PruneScopeThread(XineEngine *parent); PruneScopeThread(XineEngine *parent);
protected: protected:
virtual void run(); void run();
private: 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 "config.h"
#include <time.h>
#include "xinescope.h" #include "xinescope.h"
#include <xine/post.h> #include <xine/post.h>
#include <xine/xine_internal.h> #include <xine/xine_internal.h>
@ -29,54 +31,53 @@ struct scope_plugin_s {
* post plugin functions * * 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 port ((post_audio_port_t*)port_gen)
#define this ((scope_plugin_t*)((post_audio_port_t*)port_gen)->post) #define this ((scope_plugin_t*)((post_audio_port_t*)port_gen)->post)
_x_post_rewire( (post_plugin_t*)port->post ); _x_post_rewire((post_plugin_t*)port->post);
_x_post_inc_usage( port ); _x_post_inc_usage(port);
port->stream = stream; port->stream = stream;
port->bits = bits; port->bits = bits;
port->rate = rate; port->rate = rate;
port->mode = mode; 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; MyNode *node;
/* ensure the buffers are deleted during the next XineEngine::timerEvent() */ /* 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; node->vpts = node->vpts_end = -1;
port->stream = NULL; 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 ) { 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, /* 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) */
the sample size could be checked like this: if( port->bits == 8 ) */
const int num_samples = buf->num_frames * this->channels; const int num_samples = buf->num_frames * this->channels;
metronom_t *myMetronom = &this->metronom; metronom_t *myMetronom = &this->metronom;
MyNode *new_node; MyNode *new_node;
/* I keep my own metronom because xine wouldn't for some reason */ /* 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 = malloc(sizeof(MyNode));
new_node->vpts = myMetronom->got_audio_samples( myMetronom, buf->vpts, buf->num_frames ); new_node->vpts = myMetronom->got_audio_samples(myMetronom, buf->vpts, buf->num_frames);
new_node->num_frames = buf->num_frames; new_node->num_frames = buf->num_frames;
new_node->mem = malloc( num_samples * 2 ); new_node->mem = malloc(num_samples * 2);
memcpy( new_node->mem, buf->mem, num_samples * 2 ); memcpy(new_node->mem, buf->mem, num_samples * 2);
{ {
int64_t 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; 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 /* 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 */ * 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 #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 *list = ((scope_plugin_t*)this)->list;
MyNode *prev; MyNode *prev;
MyNode *node = list; MyNode *node = list;
@ -108,15 +109,15 @@ static void scope_dispose( post_plugin_t *this ) {
do { do {
prev = node->next; prev = node->next;
free( node->mem ); free(node->mem);
free( node ); free(node);
node = prev; 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 * * 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) ); scope_plugin_t *scope_plugin = calloc(1, sizeof(scope_plugin_t));
post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin; post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin;
{ {
post_in_t *input; post_in_t *input;
post_out_t *output; post_out_t *output;
post_audio_port_t *port; 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.open = scope_port_open;
port->new_port.close = scope_port_close; port->new_port.close = scope_port_close;
port->new_port.put_buffer = scope_port_put_buffer; 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; post_plugin->dispose = scope_dispose;
} }
/* code is straight from xine_init_post() // 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
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->running_ticket = xine->port_ticket;
post_plugin->xine = xine; post_plugin->xine = xine;
/* scope_plugin_t init */ /* 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; scope_plugin->list->next = scope_plugin->list;
return &post_plugin->xine_post; return &post_plugin->xine_post;
} }
MyNode* scope_plugin_list( void *post ) { MyNode* scope_plugin_list(void *post) {
return ((scope_plugin_t*)post)->list; return ((scope_plugin_t*)post)->list;
} }
int scope_plugin_channels( void *post ) { int scope_plugin_channels(void *post) {
return ((scope_plugin_t*)post)->channels; 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; return &((scope_plugin_t*)post)->metronom;
} }

View File

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

View File

@ -1127,7 +1127,7 @@ QString Playlist::column_name(Column column) {
case Column_Samplerate: return tr("Sample rate"); case Column_Samplerate: return tr("Sample rate");
case Column_Bitdepth: return tr("Bit depth"); case Column_Bitdepth: return tr("Bit depth");
case Column_SamplerateBitdepth: return tr("Sample rate B"); 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_Filename: return tr("File name");
case Column_BaseFilename: return tr("File name (without path)"); 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_["genre"] = Playlist::Column_Genre;
column_names_["comment"] = Playlist::Column_Comment; column_names_["comment"] = Playlist::Column_Comment;
column_names_["bitrate"] = Playlist::Column_Bitrate; column_names_["bitrate"] = Playlist::Column_Bitrate;
column_names_["samplerate"] = Playlist::Column_Samplerate;
column_names_["bitdepth"] = Playlist::Column_Bitdepth;
column_names_["filename"] = Playlist::Column_Filename; column_names_["filename"] = Playlist::Column_Filename;
numerical_columns_ << Playlist::Column_Length numerical_columns_ << Playlist::Column_Length
<< Playlist::Column_Track << Playlist::Column_Track
<< Playlist::Column_Disc << Playlist::Column_Disc
<< Playlist::Column_Year << Playlist::Column_Year
<< Playlist::Column_Bitrate; << Playlist::Column_Bitrate
<< Playlist::Column_Samplerate
<< Playlist::Column_Bitdepth;
} }
PlaylistFilter::~PlaylistFilter() { PlaylistFilter::~PlaylistFilter() {

View File

@ -37,17 +37,12 @@
#include "core/application.h" #include "core/application.h"
#include "core/iconloader.h" #include "core/iconloader.h"
#include "core/player.h" #include "core/player.h"
#include "core/logging.h"
#include "engine/engine_fwd.h" #include "engine/engine_fwd.h"
#include "engine/enginebase.h" #include "engine/enginebase.h"
#include "engine/enginedevice.h" #include "engine/enginedevice.h"
#include "engine/enginetype.h" #include "engine/enginetype.h"
#include "engine/devicefinder.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/lineedit.h"
#include "widgets/stickyslider.h" #include "widgets/stickyslider.h"
#include "dialogs/errordialog.h" #include "dialogs/errordialog.h"
@ -56,25 +51,13 @@
#include "ui_backendsettingspage.h" #include "ui_backendsettingspage.h"
const char *BackendSettingsPage::kSettingsGroup = "Backend"; 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) { BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog) : SettingsPage(dialog), ui_(new Ui_BackendSettingsPage) {
ui_->setupUi(this); ui_->setupUi(this);
setWindowIcon(IconLoader::Load("soundcard")); 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%")); 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")); ui_->label_replaygainpreamp->setMinimumWidth(QFontMetrics(ui_->label_replaygainpreamp->font()).width("-WW.W dB"));
RgPreampChanged(ui_->stickslider_replaygainpreamp->value()); RgPreampChanged(ui_->stickslider_replaygainpreamp->value());
@ -93,29 +76,29 @@ BackendSettingsPage::~BackendSettingsPage() {
void BackendSettingsPage::Load() { void BackendSettingsPage::Load() {
configloaded_ = false; 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(); ui_->combobox_engine->clear();
#ifdef HAVE_XINE
ui_->combobox_engine->addItem(IconLoader::Load("xine"), EngineText_Xine, Engine::Xine);
#endif
#ifdef HAVE_GSTREAMER #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 #endif
#ifdef HAVE_PHONON #ifdef HAVE_XINE
ui_->combobox_engine->addItem(IconLoader::Load("speaker"), EngineText_Phonon, Engine::Phonon); ui_->combobox_engine->addItem(IconLoader::Load("xine"), EngineDescription(Engine::Xine), Engine::Xine);
#endif #endif
#ifdef HAVE_VLC #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 #endif
configloaded_ = true;
enginereset_ = false; enginereset_ = false;
ui_->combobox_engine->setCurrentIndex(ui_->combobox_engine->findData(enginetype)); 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_->spinbox_bufferduration->setValue(s_.value("bufferduration", 4000).toInt());
ui_->checkbox_monoplayback->setChecked(s_.value("monoplayback", false).toBool()); 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_->stickslider_replaygainpreamp->setValue(s_.value("rgpreamp", 0.0).toDouble() * 10 + 150);
ui_->checkbox_replaygaincompression->setChecked(s_.value("rgcompression", true).toBool()); 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 if (engine()->state() == Engine::Empty) {
ui_->combobox_engine->setEnabled(false); if (ui_->combobox_engine->count() > 1) ui_->combobox_engine->setEnabled(true);
#endif 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) { void BackendSettingsPage::Load_Engine(Engine::EngineType enginetype) {
if (!EngineInitialised()) return;
QString output = s_.value("output", "").toString(); QString output = s_.value("output", "").toString();
QVariant device = s_.value("device", QVariant()); QVariant device = s_.value("device", QVariant());
@ -150,84 +166,99 @@ void BackendSettingsPage::Load_Engine(Engine::EngineType enginetype) {
ui_->groupbox_replaygain->setEnabled(false); ui_->groupbox_replaygain->setEnabled(false);
// If a engine is loaded (!= Engine::None) AND engine has been switched reset output and device. if (engine()->type() != enginetype) {
if ((engineloaded_ != Engine::None) && (engineloaded_ != enginetype)) {
output = "";
device = QVariant();
s_.setValue("output", "");
s_.setValue("device", QVariant());
}
if (dialog()->app()->player()->engine()->type() != enginetype) {
dialog()->app()->player()->CreateEngine(enginetype); dialog()->app()->player()->CreateEngine(enginetype);
dialog()->app()->player()->ReloadSettings(); dialog()->app()->player()->ReloadSettings();
dialog()->app()->player()->Init(); dialog()->app()->player()->Init();
} }
switch(enginetype) { engineloaded_ = true;
#ifdef HAVE_XINE
case Engine::Xine: Load_Output(output, device);
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;
}
} }
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; int devices = 0;
DeviceFinder::Device dfdevice; DeviceFinder::Device df_device;
ui_->combobox_device->clear(); ui_->combobox_device->clear();
ui_->combobox_device->setEnabled(false); ui_->combobox_device->setEnabled(false);
ui_->lineedit_device->setText(""); ui_->lineedit_device->setText("");
#ifdef Q_OS_LINUX #ifndef Q_OS_WIN32
ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Automatically select", ""); ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Automatically select", QVariant(""));
#endif #endif
if (alsa) ui_->lineedit_device->setEnabled(true);
else ui_->lineedit_device->setEnabled(false);
for (DeviceFinder *f : dialog()->app()->enginedevice()->device_finders_) { for (DeviceFinder *f : dialog()->app()->enginedevice()->device_finders_) {
if (f->name() == "alsa" && !alsa) continue; if (!f->outputs().contains(output)) continue;
if (f->name() == "pulseaudio" && !pulseaudio) continue;
if (f->name() == "directsound" && !directsound) continue;
if (f->name() == "osxaudio" && !osxaudio) continue;
for (const DeviceFinder::Device &d : f->ListDevices()) { for (const DeviceFinder::Device &d : f->ListDevices()) {
devices++; devices++;
ui_->combobox_device->addItem(IconLoader::Load(d.iconname), d.description, d.value); 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; bool found(false);
if (custom || devices > 0) ui_->combobox_device->setEnabled(true);
for (int i = 0; i < ui_->combobox_device->count(); ++i) { for (int i = 0; i < ui_->combobox_device->count(); ++i) {
QVariant d = ui_->combobox_device->itemData(i).value<QVariant>(); QVariant d = ui_->combobox_device->itemData(i).value<QVariant>();
if (dfdevice.value == d) { if (df_device.value == d) {
ui_->combobox_device->setCurrentIndex(i); ui_->combobox_device->setCurrentIndex(i);
found = true; found = true;
break; 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. // 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()); ui_->lineedit_device->setText(device.toString());
} if (!found) {
bool have_custom(false);
} int index_custom = 0;
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
#ifdef HAVE_GSTREAMER if (ui_->combobox_device->itemText(i) == "Custom") {
void BackendSettingsPage::Gst_Load(QString output, QVariant device) { have_custom = true;
index_custom = i;
if (output == "") output = GstEngine::kAutoSink; if (ui_->combobox_device->currentText() != "Custom") ui_->combobox_device->setCurrentIndex(i);
break;
if (dialog()->app()->player()->engine()->type() != Engine::GStreamer) { }
errordialog_.ShowMessage("GStramer not initialized! Please restart."); }
return; if (have_custom) ui_->combobox_device->setItemData(index_custom, QVariant(device.toString()));
}
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;
} }
} }
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() { void BackendSettingsPage::Save() {
s_.setValue("engine", ui_->combobox_engine->itemText(ui_->combobox_engine->currentIndex()).toLower()); if (!EngineInitialised()) return;
QVariant myVariant = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex());
Engine::EngineType enginetype = myVariant.value<Engine::EngineType>();
switch(enginetype) { QVariant enginetype_v = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex());
#ifdef HAVE_XINE Engine::EngineType enginetype = enginetype_v.value<Engine::EngineType>();
case Engine::Xine: QString output_name;
Xine_Save(); QVariant device_value;
break;
#endif if (ui_->combobox_output->currentText().isEmpty()) output_name = engine()->DefaultOutput();
#ifdef HAVE_GSTREAMER else {
case Engine::GStreamer: EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
Gst_Save(); output_name = output.name;
break;
#endif
#ifdef HAVE_PHONON
case Engine::Phonon:
Phonon_Save();
break;
#endif
#ifdef HAVE_VLC
case Engine::VLC:
VLC_Save();
break;
#endif
default:
break;
} }
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("bufferduration", ui_->spinbox_bufferduration->value());
s_.setValue("monoplayback", ui_->checkbox_monoplayback->isChecked()); s_.setValue("monoplayback", ui_->checkbox_monoplayback->isChecked());
@ -380,166 +313,103 @@ void BackendSettingsPage::Save() {
s_.setValue("rgmode", ui_->combobox_replaygainmode->currentIndex()); s_.setValue("rgmode", ui_->combobox_replaygainmode->currentIndex());
s_.setValue("rgpreamp", float(ui_->stickslider_replaygainpreamp->value()) / 10 - 15); s_.setValue("rgpreamp", float(ui_->stickslider_replaygainpreamp->value()) / 10 - 15);
s_.setValue("rgcompression", ui_->checkbox_replaygaincompression->isChecked()); 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) { void BackendSettingsPage::EngineChanged(int index) {
if (configloaded_ == false) return; if (!configloaded_ || !EngineInitialised()) return;
if ((engineloaded_ != Engine::None) && (dialog()->app()->player()->engine()->state() != Engine::Empty)) { if (engine()->state() != Engine::Empty) {
if (enginereset_ == true) { enginereset_ = false; return; } if (enginereset_ == true) { enginereset_ = false; return; }
errordialog_.ShowMessage("Can't switch engine while playing!"); errordialog_.ShowMessage("Can't switch engine while playing!");
enginereset_ = true; enginereset_ = true;
ui_->combobox_engine->setCurrentIndex(ui_->combobox_engine->findData(engineloaded_)); ui_->combobox_engine->setCurrentIndex(ui_->combobox_engine->findData(engineloaded_));
return; return;
} }
QVariant v = ui_->combobox_engine->itemData(index); QVariant v = ui_->combobox_engine->itemData(index);
Engine::EngineType enginetype = v.value<Engine::EngineType>(); Engine::EngineType enginetype = v.value<Engine::EngineType>();
engineloaded_ = false;
xinewarning_ = false;
ResetWarning();
Load_Engine(enginetype); Load_Engine(enginetype);
} }
void BackendSettingsPage::OutputChanged(int index) { void BackendSettingsPage::OutputChanged(int index) {
QVariant v = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex()); if (!configloaded_ || !EngineInitialised()) return;
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) {
EngineBase::OutputDetails output = ui_->combobox_output->itemData(index).value<EngineBase::OutputDetails>(); EngineBase::OutputDetails output = ui_->combobox_output->itemData(index).value<EngineBase::OutputDetails>();
Load_Device(output.name, QVariant()); Load_Device(output.name, QVariant());
} if (engine()->type() == Engine::Xine) XineWarning();
#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));
} }
#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) { void BackendSettingsPage::DeviceSelectionChanged(int index) {
if (ui_->combobox_device->currentText() == "Custom") { if (!configloaded_ || !EngineInitialised()) return;
ui_->lineedit_device->setEnabled(true);
ui_->combobox_device->setItemData(index, QVariant(ui_->lineedit_device->text()));
return;
}
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
QVariant device = ui_->combobox_device->itemData(index).value<QVariant>(); 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->setEnabled(true);
ui_->lineedit_device->setText(device.toString()); if (ui_->combobox_device->currentText() == "Custom") {
return; 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); if (engine()->type() == Engine::Xine) XineWarning();
ui_->lineedit_device->setText("");
} }
void BackendSettingsPage::DeviceStringChanged() { 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) { for (int i = 0; i < ui_->combobox_device->count(); ++i) {
QVariant v = ui_->combobox_device->itemData(i).value<QVariant>(); QVariant device = ui_->combobox_device->itemData(i).value<QVariant>();
if (v.type() != QVariant::String) continue; if (device.type() != QVariant::String) continue;
if (v.toString() == ui_->lineedit_device->text()) { if (device.toString().isEmpty()) continue;
ui_->combobox_device->setCurrentIndex(i); if (device.toString() == ui_->lineedit_device->text()) {
return; if (ui_->combobox_device->currentIndex() != i) ui_->combobox_device->setCurrentIndex(i);
found = true;
} }
} }
// Assume this is a custom alsa device string if (engine()->CustomDeviceSupport(output.name)) {
ui_->lineedit_device->setEnabled(true);
if (ui_->combobox_device->currentText() != "Custom") { if ((!found) && (ui_->combobox_device->currentText() != "Custom")) {
for (int i = 0; i < ui_->combobox_device->count(); ++i) { for (int i = 0; i < ui_->combobox_device->count(); ++i) {
if (ui_->combobox_device->itemText(i) == "Custom") { if (ui_->combobox_device->itemText(i) == "Custom") {
ui_->combobox_device->setCurrentIndex(i); ui_->combobox_device->setCurrentIndex(i);
break; 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") { else {
ui_->combobox_device->setItemData(ui_->combobox_device->currentIndex(), QVariant(ui_->lineedit_device->text())); 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) { void BackendSettingsPage::BufferMinFillChanged(int value) {
ui_->label_bufferminfillvalue->setText(QString::number(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 "engine/enginetype.h"
#include "dialogs/errordialog.h" #include "dialogs/errordialog.h"
#include "settingspage.h" #include "settingspage.h"
#include "settingsdialog.h"
#include "core/application.h"
#include "core/player.h"
#include "engine/enginebase.h"
class SettingsDialog; class SettingsDialog;
class Ui_BackendSettingsPage; class Ui_BackendSettingsPage;
@ -44,14 +49,12 @@ public:
~BackendSettingsPage(); ~BackendSettingsPage();
static const char *kSettingsGroup; 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 Load();
void Save(); void Save();
EngineBase *engine() const { return dialog()->app()->player()->engine(); }
private slots: private slots:
void EngineChanged(int index); void EngineChanged(int index);
void OutputChanged(int index); void OutputChanged(int index);
@ -63,41 +66,24 @@ public:
private: private:
Ui_BackendSettingsPage *ui_; Ui_BackendSettingsPage *ui_;
void ConnectSignals();
bool EngineInitialised();
void EngineChanged(Engine::EngineType enginetype); void EngineChanged(Engine::EngineType enginetype);
void OutputChanged(int index, Engine::EngineType enginetype);
void Load_Engine(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); void Load_Output(QString output, QVariant device);
void Load_Device(QString output, QVariant device);
#ifdef HAVE_XINE void ShowWarning(QString text);
void Xine_Load(QString output, QVariant device); void ResetWarning();
void Xine_Save(); void XineWarning();
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
#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_; QSettings s_;
bool configloaded_;
bool engineloaded_;
ErrorDialog errordialog_; ErrorDialog errordialog_;
bool enginereset_; bool enginereset_;
bool xinewarning_;
}; };

View File

@ -25,6 +25,12 @@
</property> </property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_engine"> <widget class="QLabel" name="label_engine">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text"> <property name="text">
<string>Engine</string> <string>Engine</string>
</property> </property>
@ -38,6 +44,12 @@
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text"> <property name="text">
<string>Output</string> <string>Output</string>
</property> </property>
@ -55,6 +67,12 @@
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text"> <property name="text">
<string>Device</string> <string>Device</string>
</property> </property>
@ -76,7 +94,7 @@
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>80</width> <width>160</width>
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
@ -102,14 +120,26 @@
</item> </item>
</layout> </layout>
</item> </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"> <widget class="QLabel" name="label_bufferduration">
<property name="text"> <property name="text">
<string>Buffer duration</string> <string>Buffer duration</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="spinbox_bufferduration"> <widget class="QSpinBox" name="spinbox_bufferduration">
<property name="suffix"> <property name="suffix">
<string> ms</string> <string> ms</string>
@ -122,14 +152,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_bufferminfill"> <widget class="QLabel" name="label_bufferminfill">
<property name="text"> <property name="text">
<string>Minimum buffer fill</string> <string>Minimum buffer fill</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_buffer"> <layout class="QHBoxLayout" name="horizontalLayout_buffer">
<item> <item>
<widget class="QLabel" name="label_bufferminfillvalue"> <widget class="QLabel" name="label_bufferminfillvalue">
@ -159,16 +189,6 @@
</item> </item>
</layout> </layout>
</item> </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> </layout>
</widget> </widget>
</item> </item>
@ -273,6 +293,75 @@
</layout> </layout>
</widget> </widget>
</item> </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> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -43,9 +43,9 @@ PlaybackSettingsPage::PlaybackSettingsPage(SettingsDialog *dialog) : SettingsPag
ui_->setupUi(this); ui_->setupUi(this);
setWindowIcon(IconLoader::Load("media-play")); setWindowIcon(IconLoader::Load("media-play"));
connect(ui_->fading_cross, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged())); connect(ui_->checkbox_fadeout_stop, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
connect(ui_->fading_out, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged())); connect(ui_->checkbox_fadeout_cross, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
connect(ui_->fading_auto, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged())); connect(ui_->checkbox_fadeout_auto, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
} }
@ -60,14 +60,14 @@ void PlaybackSettingsPage::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
ui_->current_glow->setChecked(s.value("glow_effect", true).toBool()); ui_->checkbox_glowcurrenttrack->setChecked(s.value("glow_effect", true).toBool());
ui_->fading_out->setChecked(s.value("FadeoutEnabled", false).toBool()); ui_->checkbox_fadeout_stop->setChecked(s.value("FadeoutEnabled", false).toBool());
ui_->fading_cross->setChecked(s.value("CrossfadeEnabled", false).toBool()); ui_->checkbox_fadeout_cross->setChecked(s.value("CrossfadeEnabled", false).toBool());
ui_->fading_auto->setChecked(s.value("AutoCrossfadeEnabled", false).toBool()); ui_->checkbox_fadeout_auto->setChecked(s.value("AutoCrossfadeEnabled", false).toBool());
ui_->fading_duration->setValue(s.value("FadeoutDuration", 2000).toInt()); ui_->checkbox_fadeout_samealbum->setChecked(s.value("NoCrossfadeSameAlbum", true).toBool());
ui_->fading_samealbum->setChecked(s.value("NoCrossfadeSameAlbum", true).toBool()); ui_->checkbox_fadeout_pauseresume->setChecked(s.value("FadeoutPauseEnabled", false).toBool());
ui_->fadeout_pause->setChecked(s.value("FadeoutPauseEnabled", false).toBool()); ui_->spinbox_fadeduration->setValue(s.value("FadeoutDuration", 2000).toInt());
ui_->fading_pause_duration->setValue(s.value("FadeoutPauseDuration", 250).toInt()); ui_->spinbox_fadeduration_pauseresume->setValue(s.value("FadeoutPauseDuration", 250).toInt());
s.endGroup(); s.endGroup();
} }
@ -77,19 +77,19 @@ void PlaybackSettingsPage::Save() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
s.setValue("glow_effect", ui_->current_glow->isChecked()); s.setValue("glow_effect", ui_->checkbox_glowcurrenttrack->isChecked());
s.setValue("FadeoutEnabled", ui_->fading_out->isChecked()); s.setValue("FadeoutEnabled", ui_->checkbox_fadeout_stop->isChecked());
s.setValue("FadeoutDuration", ui_->fading_duration->value()); s.setValue("CrossfadeEnabled", ui_->checkbox_fadeout_cross->isChecked());
s.setValue("CrossfadeEnabled", ui_->fading_cross->isChecked()); s.setValue("AutoCrossfadeEnabled", ui_->checkbox_fadeout_auto->isChecked());
s.setValue("AutoCrossfadeEnabled", ui_->fading_auto->isChecked()); s.setValue("NoCrossfadeSameAlbum", ui_->checkbox_fadeout_samealbum->isChecked());
s.setValue("NoCrossfadeSameAlbum", ui_->fading_samealbum->isChecked()); s.setValue("FadeoutPauseEnabled", ui_->checkbox_fadeout_pauseresume->isChecked());
s.setValue("FadeoutPauseEnabled", ui_->fadeout_pause->isChecked()); s.setValue("FadeoutDuration", ui_->spinbox_fadeduration->value());
s.setValue("FadeoutPauseDuration", ui_->fading_pause_duration->value()); s.setValue("FadeoutPauseDuration", ui_->spinbox_fadeduration_pauseresume->value());
s.endGroup(); s.endGroup();
} }
void PlaybackSettingsPage::FadingOptionsChanged() { 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> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<widget class="QCheckBox" name="current_glow"> <widget class="QCheckBox" name="checkbox_glowcurrenttrack">
<property name="text"> <property name="text">
<string>Show a glowing animation on the current track</string> <string>Show a glowing animation on the current track</string>
</property> </property>
@ -25,13 +25,13 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupbox_fading">
<property name="title"> <property name="title">
<string>Fading</string> <string>Fading</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QCheckBox" name="fading_out"> <widget class="QCheckBox" name="checkbox_fadeout_stop">
<property name="text"> <property name="text">
<string>Fade out when stopping a track</string> <string>Fade out when stopping a track</string>
</property> </property>
@ -41,7 +41,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="fading_cross"> <widget class="QCheckBox" name="checkbox_fadeout_cross">
<property name="text"> <property name="text">
<string>Cross-fade when changing tracks manually</string> <string>Cross-fade when changing tracks manually</string>
</property> </property>
@ -51,14 +51,14 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="fading_auto"> <widget class="QCheckBox" name="checkbox_fadeout_auto">
<property name="text"> <property name="text">
<string>Cross-fade when changing tracks automatically</string> <string>Cross-fade when changing tracks automatically</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="fading_samealbum"> <widget class="QCheckBox" name="checkbox_fadeout_samealbum">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
@ -68,13 +68,22 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QWidget" name="fading_options" native="true"> <widget class="QWidget" name="widget_fading_options" native="true">
<layout class="QHBoxLayout" name="horizontalLayout"> <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> <number>0</number>
</property> </property>
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label_fadingduration_1">
<property name="text"> <property name="text">
<string>Fading duration</string> <string>Fading duration</string>
</property> </property>
@ -84,7 +93,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="fading_duration"> <widget class="QSpinBox" name="spinbox_fadeduration">
<property name="suffix"> <property name="suffix">
<string> ms</string> <string> ms</string>
</property> </property>
@ -100,7 +109,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer_1">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
@ -116,16 +125,16 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="fadeout_pause"> <widget class="QCheckBox" name="checkbox_fadeout_pauseresume">
<property name="text"> <property name="text">
<string>Fade out on pause / fade in on resume</string> <string>Fade out on pause / fade in on resume</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="layout_fading">
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_fadingduration_2">
<property name="text"> <property name="text">
<string>Fading duration</string> <string>Fading duration</string>
</property> </property>
@ -135,7 +144,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="fading_pause_duration"> <widget class="QSpinBox" name="spinbox_fadeduration_pauseresume">
<property name="suffix"> <property name="suffix">
<string> ms</string> <string> ms</string>
</property> </property>
@ -183,19 +192,12 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>StickySlider</class>
<extends>QSlider</extends>
<header>widgets/stickyslider.h</header>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>
<sender>fading_auto</sender> <sender>checkbox_fadeout_auto</sender>
<signal>toggled(bool)</signal> <signal>toggled(bool)</signal>
<receiver>fading_samealbum</receiver> <receiver>checkbox_fadeout_samealbum</receiver>
<slot>setEnabled(bool)</slot> <slot>setEnabled(bool)</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">

View File

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

View File

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

View File

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

View File

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