mirror of
https://github.com/strawberrymusicplayer/strawberry
synced 2025-01-27 07:46:19 +01:00
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:
parent
6978983dd3
commit
985b91e5f4
@ -93,7 +93,6 @@ pkg_check_modules(GSTREAMER_BASE gstreamer-base-1.0)
|
||||
pkg_check_modules(GSTREAMER_AUDIO gstreamer-audio-1.0)
|
||||
pkg_check_modules(GSTREAMER_APP gstreamer-app-1.0)
|
||||
pkg_check_modules(GSTREAMER_TAG gstreamer-tag-1.0)
|
||||
pkg_check_modules(GSTREAMER_PBUTILS gstreamer-pbutils-1.0)
|
||||
pkg_check_modules(LIBXINE libxine)
|
||||
pkg_check_modules(LIBVLC libvlc)
|
||||
pkg_check_modules(PHONON phonon4qt5)
|
||||
@ -265,14 +264,13 @@ optional_component(GSTREAMER ON "Engine: GStreamer backend"
|
||||
DEPENDS "gstreamer-app-1.0" GSTREAMER_APP_FOUND
|
||||
DEPENDS "gstreamer-audio-1.0" GSTREAMER_AUDIO_FOUND
|
||||
DEPENDS "gstreamer-tag-1.0" GSTREAMER_TAG_FOUND
|
||||
DEPENDS "gstreamer-pbutils-1.0" GSTREAMER_PBUTILS_FOUND
|
||||
)
|
||||
|
||||
optional_component(XINE OFF "Engine: Xine backend"
|
||||
optional_component(XINE ON "Engine: Xine backend"
|
||||
DEPENDS "libxine" LIBXINE_FOUND
|
||||
)
|
||||
|
||||
optional_component(VLC OFF "Engine: VLC backend"
|
||||
optional_component(VLC ON "Engine: VLC backend"
|
||||
DEPENDS "libvlc" LIBVLC_FOUND
|
||||
)
|
||||
|
||||
|
@ -44,6 +44,7 @@ static Level sDefaultLevel = Level_Debug;
|
||||
static QMap<QString, Level>* sClassLevels = nullptr;
|
||||
static QIODevice *sNullDevice = nullptr;
|
||||
|
||||
//const char* kDefaultLogLevels = "*:3";
|
||||
const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3";
|
||||
|
||||
static const char *kMessageHandlerMagic = "__logging_message__";
|
||||
|
@ -228,7 +228,7 @@ void WorkerPool<HandlerType>::DoStart() {
|
||||
|
||||
QStringList search_path;
|
||||
search_path << qApp->applicationDirPath();
|
||||
#ifdef Q_OS_MAC
|
||||
#ifdef Q_OS_MACOS
|
||||
search_path << qApp->applicationDirPath() + "/../PlugIns";
|
||||
#endif
|
||||
|
||||
|
@ -53,7 +53,6 @@ if(HAVE_GSTREAMER)
|
||||
include_directories(${GSTREAMER_AUDIO_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_BASE_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_TAG_INCLUDE_DIRS})
|
||||
include_directories(${GSTREAMER_PBUTILS_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_PHONON)
|
||||
@ -503,7 +502,7 @@ optional_source(HAVE_GSTREAMER
|
||||
|
||||
# Xine
|
||||
optional_source(HAVE_XINE
|
||||
SOURCES engine/xineengine.cpp engine/xinescope.c
|
||||
SOURCES engine/xineengine.cpp engine/xinescope.c engine/xinefader.cpp
|
||||
HEADERS engine/xineengine.h
|
||||
)
|
||||
|
||||
@ -863,7 +862,7 @@ if(HAVE_ALSA)
|
||||
endif(HAVE_ALSA)
|
||||
|
||||
if(HAVE_GSTREAMER)
|
||||
target_link_libraries(strawberry_lib ${GSTREAMER_LIBRARIES} ${GSTREAMER_BASE_LIBRARIES} ${GSTREAMER_AUDIO_LIBRARIES} ${GSTREAMER_APP_LIBRARIES} ${GSTREAMER_TAG_LIBRARIES} ${GSTREAMER_PBUTILS_LIBRARIES})
|
||||
target_link_libraries(strawberry_lib ${GSTREAMER_LIBRARIES} ${GSTREAMER_BASE_LIBRARIES} ${GSTREAMER_AUDIO_LIBRARIES} ${GSTREAMER_APP_LIBRARIES} ${GSTREAMER_TAG_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_XINE)
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef Q_WS_MACX
|
||||
#ifdef Q_OS_MACOS
|
||||
#include <OpenGL/gl.h> //included for convenience
|
||||
#include <OpenGL/glu.h> //included for convenience
|
||||
#else
|
||||
|
@ -125,21 +125,4 @@ void Collection::CurrentSongChanged(const Song &song) {
|
||||
connect(reply, SIGNAL(Finished(bool)), reply, SLOT(deleteLater()));
|
||||
}
|
||||
|
||||
if (song.filetype() == Song::Type_Asf) {
|
||||
current_wma_song_url_ = song.url();
|
||||
}
|
||||
}
|
||||
|
||||
SongList Collection::FilterCurrentWMASong(SongList songs, Song* queued) {
|
||||
|
||||
for (SongList::iterator it = songs.begin(); it != songs.end(); ) {
|
||||
if (it->url() == current_wma_song_url_) {
|
||||
*queued = *it;
|
||||
it = songs.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return songs;
|
||||
}
|
||||
|
@ -73,9 +73,6 @@ class Collection : public QObject {
|
||||
void CurrentSongChanged(const Song &song);
|
||||
void Stopped();
|
||||
|
||||
private:
|
||||
SongList FilterCurrentWMASong(SongList songs, Song* queued);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
CollectionBackend *backend_;
|
||||
@ -84,10 +81,6 @@ class Collection : public QObject {
|
||||
CollectionWatcher *watcher_;
|
||||
Thread *watcher_thread_;
|
||||
|
||||
// Hack: Gstreamer doesn't cope well with WMA files being rewritten while being played,
|
||||
// so we delay statistics and rating changes until the current song has finished playing.
|
||||
QUrl current_wma_song_url_;
|
||||
|
||||
// DB schema versions which should trigger a full collection rescan (each of those with a short reason why).
|
||||
QHash<int, QString> full_rescan_revisions_;
|
||||
};
|
||||
|
@ -219,7 +219,8 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
|
||||
case GroupBy_Genre: key = song.genre(); break;
|
||||
case GroupBy_AlbumArtist: key = song.effective_albumartist(); break;
|
||||
case GroupBy_Year:
|
||||
key = QString::number(qMax(0, song.year())); break;
|
||||
key = QString::number(qMax(0, song.year()));
|
||||
break;
|
||||
case GroupBy_OriginalYear:
|
||||
key = QString::number(qMax(0, song.effective_originalyear()));
|
||||
break;
|
||||
@ -227,8 +228,7 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
|
||||
key = PrettyYearAlbum(qMax(0, song.year()), song.album());
|
||||
break;
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
key = PrettyYearAlbum(qMax(0, song.effective_originalyear()),
|
||||
song.album());
|
||||
key = PrettyYearAlbum(qMax(0, song.effective_originalyear()), song.album());
|
||||
break;
|
||||
case GroupBy_FileType:
|
||||
key = song.filetype();
|
||||
@ -236,6 +236,12 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
|
||||
case GroupBy_Bitrate:
|
||||
key = song.bitrate();
|
||||
break;
|
||||
case GroupBy_Samplerate:
|
||||
key = song.samplerate();
|
||||
break;
|
||||
case GroupBy_Bitdepth:
|
||||
key = song.bitdepth();
|
||||
break;
|
||||
case GroupBy_None:
|
||||
qLog(Error) << "GroupBy_None";
|
||||
break;
|
||||
@ -325,6 +331,12 @@ QString CollectionModel::DividerKey(GroupBy type, CollectionItem *item) const {
|
||||
|
||||
case GroupBy_Bitrate:
|
||||
return SortTextForNumber(item->metadata.bitrate());
|
||||
|
||||
case GroupBy_Samplerate:
|
||||
return SortTextForNumber(item->metadata.samplerate());
|
||||
|
||||
case GroupBy_Bitdepth:
|
||||
return SortTextForNumber(item->metadata.bitdepth());
|
||||
|
||||
case GroupBy_None:
|
||||
return QString();
|
||||
@ -364,6 +376,14 @@ QString CollectionModel::DividerDisplayText(GroupBy type, const QString &key) co
|
||||
case GroupBy_Bitrate:
|
||||
if (key == "000") return tr("Unknown");
|
||||
return QString::number(key.toInt()); // To remove leading 0s
|
||||
|
||||
case GroupBy_Samplerate:
|
||||
if (key == "000") return tr("Unknown");
|
||||
return QString::number(key.toInt()); // To remove leading 0s
|
||||
|
||||
case GroupBy_Bitdepth:
|
||||
if (key == "000") return tr("Unknown");
|
||||
return QString::number(key.toInt()); // To remove leading 0s
|
||||
|
||||
case GroupBy_None:
|
||||
// fallthrough
|
||||
@ -836,6 +856,12 @@ void CollectionModel::InitQuery(GroupBy type, CollectionQuery *q) {
|
||||
case GroupBy_Bitrate:
|
||||
q->SetColumnSpec("DISTINCT bitrate");
|
||||
break;
|
||||
case GroupBy_Samplerate:
|
||||
q->SetColumnSpec("DISTINCT samplerate");
|
||||
break;
|
||||
case GroupBy_Bitdepth:
|
||||
q->SetColumnSpec("DISTINCT bitdepth");
|
||||
break;
|
||||
case GroupBy_None:
|
||||
q->SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||
break;
|
||||
@ -911,6 +937,12 @@ void CollectionModel::FilterQuery(GroupBy type, CollectionItem *item, Collection
|
||||
case GroupBy_Bitrate:
|
||||
q->AddWhere("bitrate", item->key);
|
||||
break;
|
||||
case GroupBy_Samplerate:
|
||||
q->AddWhere("samplerate", item->key);
|
||||
break;
|
||||
case GroupBy_Bitdepth:
|
||||
q->AddWhere("bitdepth", item->key);
|
||||
break;
|
||||
case GroupBy_None:
|
||||
qLog(Error) << "Unknown GroupBy type" << type << "used in filter";
|
||||
break;
|
||||
@ -936,10 +968,7 @@ CollectionItem *CollectionModel::InitItem(GroupBy type, bool signal, CollectionI
|
||||
CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const SqlRow &row, int container_level) {
|
||||
|
||||
CollectionItem *item = InitItem(type, signal, parent, container_level);
|
||||
int year = 0;
|
||||
int effective_originalyear = 0;
|
||||
int bitrate = 0;
|
||||
int disc = 0;
|
||||
int year(0), effective_originalyear(0), disc(0), bitrate(0), samplerate(0), bitdepth(0);
|
||||
|
||||
switch (type) {
|
||||
case GroupBy_Artist:
|
||||
@ -972,13 +1001,11 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
|
||||
item->key = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_OriginalYear:
|
||||
year = qMax(0, row.value(0).toInt());
|
||||
item->key = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_Composer:
|
||||
case GroupBy_Performer:
|
||||
case GroupBy_Grouping:
|
||||
@ -1006,6 +1033,18 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
|
||||
item->key = QString::number(bitrate);
|
||||
item->sort_text = SortTextForNumber(bitrate) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_Samplerate:
|
||||
samplerate = qMax(0, row.value(0).toInt());
|
||||
item->key = QString::number(samplerate);
|
||||
item->sort_text = SortTextForNumber(samplerate) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_Bitdepth:
|
||||
bitdepth = qMax(0, row.value(0).toInt());
|
||||
item->key = QString::number(bitdepth);
|
||||
item->sort_text = SortTextForNumber(bitdepth) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_None:
|
||||
item->metadata.InitFromQuery(row, true);
|
||||
@ -1024,10 +1063,7 @@ CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool c
|
||||
CollectionItem *CollectionModel::ItemFromSong(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const Song &s, int container_level) {
|
||||
|
||||
CollectionItem *item = InitItem(type, signal, parent, container_level);
|
||||
int year = 0;
|
||||
int originalyear = 0;
|
||||
int effective_originalyear = 0;
|
||||
int bitrate = 0;
|
||||
int year(0), originalyear(0), effective_originalyear(0), bitrate(0), samplerate(0), bitdepth(0);
|
||||
|
||||
switch (type) {
|
||||
case GroupBy_Artist:
|
||||
@ -1092,6 +1128,18 @@ CollectionItem *CollectionModel::ItemFromSong(GroupBy type, bool signal, bool cr
|
||||
item->key = QString::number(bitrate);
|
||||
item->sort_text = SortTextForNumber(bitrate) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_Samplerate:
|
||||
samplerate = qMax(0, s.samplerate());
|
||||
item->key = QString::number(samplerate);
|
||||
item->sort_text = SortTextForNumber(samplerate) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_Bitdepth:
|
||||
bitdepth = qMax(0, s.bitdepth());
|
||||
item->key = QString::number(bitdepth);
|
||||
item->sort_text = SortTextForNumber(bitdepth) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_None:
|
||||
item->metadata = s;
|
||||
|
@ -99,6 +99,8 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
GroupBy_Disc = 12,
|
||||
GroupBy_OriginalYearAlbum = 13,
|
||||
GroupBy_OriginalYear = 14,
|
||||
GroupBy_Samplerate = 15,
|
||||
GroupBy_Bitdepth = 16
|
||||
};
|
||||
|
||||
struct Grouping {
|
||||
|
@ -230,7 +230,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
||||
ScanTransaction transaction(this, dir.id, true);
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(subdirs.count());
|
||||
for (const Subdirectory& subdir : subdirs) {
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
if (stop_requested_) return;
|
||||
|
||||
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, &transaction);
|
||||
@ -742,9 +742,9 @@ void CollectionWatcher::ReloadSettings() {
|
||||
}
|
||||
else if (monitor_ && !was_monitoring_before) {
|
||||
// Add all directories to all QFileSystemWatchers again
|
||||
for (const Directory& dir : watched_dirs_.values()) {
|
||||
for (const Directory &dir : watched_dirs_.values()) {
|
||||
SubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
||||
for (const Subdirectory& subdir : subdirs) {
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
AddWatch(dir, subdir.path);
|
||||
}
|
||||
}
|
||||
@ -783,12 +783,12 @@ void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
|
||||
|
||||
void CollectionWatcher::PerformScan(bool incremental, bool ignore_mtimes) {
|
||||
|
||||
for (const Directory & dir : watched_dirs_.values()) {
|
||||
for (const Directory &dir : watched_dirs_.values()) {
|
||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes);
|
||||
SubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||
transaction.AddToProgressMax(subdirs.count());
|
||||
|
||||
for (const Subdirectory & subdir : subdirs) {
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
if (stop_requested_) return;
|
||||
|
||||
ScanSubdirectory(subdir.path, subdir, &transaction);
|
||||
|
@ -96,11 +96,13 @@ GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_Grou
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbum, 9));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYearAlbum, 10));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitrate, 11));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 12));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 13));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 14));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Samplerate, 12));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitdepth, 13));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 14));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 15));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 16));
|
||||
|
||||
connect(ui_->button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()), SLOT(Reset()));
|
||||
connect(ui_->buttonbox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), SLOT(Reset()));
|
||||
|
||||
resize(sizeHint());
|
||||
}
|
||||
@ -108,23 +110,23 @@ GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_Grou
|
||||
GroupByDialog::~GroupByDialog() {}
|
||||
|
||||
void GroupByDialog::Reset() {
|
||||
ui_->first->setCurrentIndex(2); // Artist
|
||||
ui_->second->setCurrentIndex(1); // Album
|
||||
ui_->third->setCurrentIndex(0); // None
|
||||
ui_->combobox_first->setCurrentIndex(2); // Artist
|
||||
ui_->combobox_second->setCurrentIndex(1); // Album
|
||||
ui_->combobox_third->setCurrentIndex(0); // None
|
||||
}
|
||||
|
||||
void GroupByDialog::accept() {
|
||||
emit Accepted(CollectionModel::Grouping(
|
||||
p_->mapping_.get<tag_index>().find(ui_->first->currentIndex())->group_by,
|
||||
p_->mapping_.get<tag_index>().find(ui_->second->currentIndex())->group_by,
|
||||
p_->mapping_.get<tag_index>().find(ui_->third->currentIndex())->group_by)
|
||||
p_->mapping_.get<tag_index>().find(ui_->combobox_first->currentIndex())->group_by,
|
||||
p_->mapping_.get<tag_index>().find(ui_->combobox_second->currentIndex())->group_by,
|
||||
p_->mapping_.get<tag_index>().find(ui_->combobox_third->currentIndex())->group_by)
|
||||
);
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void GroupByDialog::CollectionGroupingChanged(const CollectionModel::Grouping &g) {
|
||||
ui_->first->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[0])->combo_box_index);
|
||||
ui_->second->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[1])->combo_box_index);
|
||||
ui_->third->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[2])->combo_box_index);
|
||||
ui_->combobox_first->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[0])->combo_box_index);
|
||||
ui_->combobox_second->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[1])->combo_box_index);
|
||||
ui_->combobox_third->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[2])->combo_box_index);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>354</width>
|
||||
<height>236</height>
|
||||
<height>246</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -19,7 +19,7 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>You can change the way the songs in the collection are organised.</string>
|
||||
</property>
|
||||
@ -35,14 +35,14 @@
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="label_first">
|
||||
<property name="text">
|
||||
<string>First level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="first">
|
||||
<widget class="QComboBox" name="combobox_first">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
@ -103,6 +103,16 @@
|
||||
<string>Bitrate</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sample rate</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bit depth</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disc</string>
|
||||
@ -121,14 +131,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<widget class="QLabel" name="label_second">
|
||||
<property name="text">
|
||||
<string>Second level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="second">
|
||||
<widget class="QComboBox" name="combobox_second">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
@ -189,6 +199,16 @@
|
||||
<string>Bitrate</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sample rate</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bit depth</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disc</string>
|
||||
@ -207,14 +227,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<widget class="QLabel" name="label_third">
|
||||
<property name="text">
|
||||
<string>Third level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="third">
|
||||
<widget class="QComboBox" name="combobox_third">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
@ -275,6 +295,16 @@
|
||||
<string>Bitrate</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sample rate</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bit depth</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disc</string>
|
||||
@ -309,7 +339,7 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="button_box">
|
||||
<widget class="QDialogButtonBox" name="buttonbox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
@ -321,17 +351,17 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>first</tabstop>
|
||||
<tabstop>second</tabstop>
|
||||
<tabstop>third</tabstop>
|
||||
<tabstop>button_box</tabstop>
|
||||
<tabstop>combobox_first</tabstop>
|
||||
<tabstop>combobox_second</tabstop>
|
||||
<tabstop>combobox_third</tabstop>
|
||||
<tabstop>buttonbox</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<sender>buttonbox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>GroupByDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
@ -347,7 +377,7 @@
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<sender>buttonbox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>GroupByDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
|
@ -110,6 +110,12 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g)
|
||||
case CollectionModel::GroupBy_Bitrate: {
|
||||
return tr("Bitrate");
|
||||
}
|
||||
case CollectionModel::GroupBy_Samplerate: {
|
||||
return tr("Sample rate");
|
||||
}
|
||||
case CollectionModel::GroupBy_Bitdepth: {
|
||||
return tr("Bit depth");
|
||||
}
|
||||
case CollectionModel::GroupBy_Disc: {
|
||||
return tr("Disc");
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -60,6 +61,8 @@
|
||||
#include <iostream>
|
||||
#endif // Q_OS_WIN32
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "qtsingleapplication.h"
|
||||
@ -132,6 +135,7 @@ int main(int argc, char* argv[]) {
|
||||
qLog(Info) << "Strawberry is already running - activating existing window";
|
||||
}
|
||||
if (a.sendMessage(options.Serialize(), 5000)) {
|
||||
main_exit_safe(0);
|
||||
return 0;
|
||||
}
|
||||
// Couldn't send the message so start anyway
|
||||
@ -175,8 +179,8 @@ int main(int argc, char* argv[]) {
|
||||
QCoreApplication::setAttribute(Qt::AA_NativeWindows, true);
|
||||
#endif
|
||||
|
||||
// Set the permissions on the config file on Unix - it can contain passwords for internet services so it's important that other users can't read it.
|
||||
// On Windows these are stored in the registry instead.
|
||||
// Set the permissions on the config file on Unix - it can contain passwords for internet services so it's important that other users can't read it.
|
||||
// On Windows these are stored in the registry instead.
|
||||
#ifdef Q_OS_UNIX
|
||||
{
|
||||
QSettings s;
|
||||
@ -226,17 +230,44 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
int ret = a.exec();
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
QFile self_maps("/proc/self/maps");
|
||||
if (self_maps.open(QIODevice::ReadOnly)) {
|
||||
QByteArray data = self_maps.readAll();
|
||||
if (data.contains("libnvidia-tls.so.")) {
|
||||
qLog(Warning) << "Exiting immediately to work around NVIDIA driver bug";
|
||||
_exit(ret);
|
||||
}
|
||||
self_maps.close();
|
||||
}
|
||||
#endif
|
||||
main_exit_safe(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void main_exit_safe(int ret) {
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
bool have_nvidia = false;
|
||||
|
||||
QFile proc_modules("/proc/modules");
|
||||
if (proc_modules.open(QIODevice::ReadOnly)) {
|
||||
forever {
|
||||
QByteArray line = proc_modules.readLine();
|
||||
if (line.startsWith("nvidia ") || line.startsWith("nvidia_")) {
|
||||
have_nvidia = true;
|
||||
}
|
||||
if (proc_modules.atEnd()) break;
|
||||
}
|
||||
proc_modules.close();
|
||||
}
|
||||
|
||||
QFile self_maps("/proc/self/maps");
|
||||
if (self_maps.open(QIODevice::ReadOnly)) {
|
||||
forever {
|
||||
QByteArray line = self_maps.readLine();
|
||||
if (line.startsWith("libnvidia-")) {
|
||||
have_nvidia = true;
|
||||
}
|
||||
if (self_maps.atEnd()) break;
|
||||
}
|
||||
self_maps.close();
|
||||
}
|
||||
|
||||
if (have_nvidia) {
|
||||
qLog(Warning) << "Exiting immediately to work around NVIDIA driver bug.";
|
||||
_exit(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
28
src/core/main.h
Normal file
28
src/core/main.h
Normal 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
|
@ -741,7 +741,6 @@ void MainWindow::ReloadAllSettings() {
|
||||
app_->player()->ReloadSettings();
|
||||
osd_->ReloadSettings();
|
||||
collection_view_->ReloadSettings();
|
||||
app_->player()->engine()->ReloadSettings();
|
||||
ui_->playlist->view()->ReloadSettings();
|
||||
|
||||
}
|
||||
|
@ -75,8 +75,6 @@ using std::shared_ptr;
|
||||
Player::Player(Application *app, QObject *parent)
|
||||
: PlayerInterface(parent),
|
||||
app_(app),
|
||||
//engine_(new GstEngine(app_->task_manager())),
|
||||
//engine_(CreateEngine()),
|
||||
stream_change_type_(Engine::First),
|
||||
last_state_(Engine::Empty),
|
||||
nb_errors_received_(0),
|
||||
@ -88,7 +86,7 @@ Player::Player(Application *app, QObject *parent)
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", BackendSettingsPage::EngineText_GStreamer).toString().toLower());
|
||||
Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", EngineName(Engine::GStreamer)).toString().toLower());
|
||||
s.endGroup();
|
||||
|
||||
CreateEngine(enginetype);
|
||||
@ -129,19 +127,19 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
|
||||
enginebase = new XineEngine(app_->task_manager());
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_PHONON
|
||||
case Engine::Phonon:
|
||||
engine=true;
|
||||
enginetype=Engine::Phonon;
|
||||
enginebase = new PhononEngine(app_->task_manager());
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_VLC
|
||||
case Engine::VLC:
|
||||
engine=true;
|
||||
enginetype=Engine::VLC;
|
||||
enginebase = new VLCEngine(app_->task_manager());
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_PHONON
|
||||
case Engine::Phonon:
|
||||
engine=true;
|
||||
enginetype=Engine::Phonon;
|
||||
enginebase = new PhononEngine(app_->task_manager());
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
if (i > 1) { qFatal("No engine available!"); return nullptr; }
|
||||
@ -149,7 +147,7 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
s.setValue("engine", "");
|
||||
s.setValue("output", "");
|
||||
s.setValue("device", "");
|
||||
s.setValue("device", QVariant(""));
|
||||
s.endGroup();
|
||||
enginetype = Engine::None;
|
||||
break;
|
||||
@ -158,7 +156,7 @@ EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
s.setValue("engine", Engine::EngineNameFromType(enginetype));
|
||||
s.setValue("engine", Engine::EngineName(enginetype));
|
||||
s.endGroup();
|
||||
|
||||
if (enginebase == nullptr) {
|
||||
@ -226,7 +224,7 @@ void Player::ReloadSettings() {
|
||||
seek_step_sec_ = s.value("seek_step_sec", 10).toInt();
|
||||
s.endGroup();
|
||||
|
||||
engine_->ReloadSettings();
|
||||
if (engine_.get()) engine_->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
|
@ -671,7 +671,7 @@ bool IsLaptop() {
|
||||
return !(status.BatteryFlag & 128); // 128 = no system battery
|
||||
#elif defined(Q_OS_LINUX)
|
||||
return !QDir("/proc/acpi/battery").entryList(QDir::Dirs | QDir::NoDotAndDotDot).isEmpty();
|
||||
#elif defined(Q_OS_MAC)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
ScopedCFTypeRef<CFTypeRef> power_sources(IOPSCopyPowerSourcesInfo());
|
||||
ScopedCFTypeRef<CFArrayRef> power_source_list(IOPSCopyPowerSourcesList(power_sources.get()));
|
||||
for (CFIndex i = 0; i < CFArrayGetCount(power_source_list.get()); ++i) {
|
||||
|
@ -58,6 +58,8 @@ void iLister::EventCallback(const idevice_event_t *event, void *context) {
|
||||
case IDEVICE_DEVICE_REMOVE:
|
||||
me->DeviceRemovedCallback(uuid);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,8 @@ OrganiseDialog::OrganiseDialog(TaskManager *task_manager, QWidget *parent)
|
||||
tags[tr("Comment")] = "comment";
|
||||
tags[tr("Length")] = "length";
|
||||
tags[tr("Bitrate", "Refers to bitrate in file organise dialog.")] = "bitrate";
|
||||
tags[tr("Samplerate")] = "samplerate";
|
||||
tags[tr("Sample rate")] = "samplerate";
|
||||
tags[tr("Bit depth")] = "bitdepth";
|
||||
tags[tr("File extension")] = "extension";
|
||||
|
||||
// Naming scheme input field
|
||||
|
@ -35,8 +35,7 @@
|
||||
#include "alsadevicefinder.h"
|
||||
|
||||
AlsaDeviceFinder::AlsaDeviceFinder()
|
||||
: DeviceFinder("alsa", "alsasink") {
|
||||
|
||||
: DeviceFinder("alsa", {"alsa","alsasink"}) {
|
||||
}
|
||||
|
||||
QList<DeviceFinder::Device> AlsaDeviceFinder::ListDevices() {
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
#include "devicefinder.h"
|
||||
|
||||
DeviceFinder::DeviceFinder(const QString &name, const QString &gstsink): name_(name), gstsink_(gstsink) {
|
||||
DeviceFinder::DeviceFinder(const QString &name, const QStringList &outputs): name_(name), outputs_(outputs) {
|
||||
}
|
||||
|
||||
QString DeviceFinder::GuessIconName(const QString &description) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2014, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2017, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -41,8 +42,9 @@ class DeviceFinder {
|
||||
|
||||
virtual ~DeviceFinder() {}
|
||||
|
||||
// The name of the gstreamer sink element that devices found by this class can be used with.
|
||||
QString name() const { return name_; }
|
||||
QStringList outputs() const { return outputs_; }
|
||||
void add_output(const QString output) { outputs_.append(output); }
|
||||
|
||||
// Does any necessary setup, returning false if this DeviceFinder cannot be used.
|
||||
virtual bool Initialise() = 0;
|
||||
@ -51,13 +53,13 @@ class DeviceFinder {
|
||||
virtual QList<Device> ListDevices() = 0;
|
||||
|
||||
protected:
|
||||
explicit DeviceFinder(const QString &name, const QString &gstsink);
|
||||
explicit DeviceFinder(const QString &name, const QStringList &outputs);
|
||||
|
||||
static QString GuessIconName(const QString &description);
|
||||
|
||||
private:
|
||||
QString name_;
|
||||
QString gstsink_;
|
||||
QStringList outputs_;
|
||||
|
||||
};
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
#include "core/logging.h"
|
||||
|
||||
DirectSoundDeviceFinder::DirectSoundDeviceFinder()
|
||||
: DeviceFinder("directsound", "directsoundsink") {
|
||||
: DeviceFinder("directsound", { "directsound", "dsound", "directsoundsink" }) {
|
||||
}
|
||||
|
||||
QList<DeviceFinder::Device> DirectSoundDeviceFinder::ListDevices() {
|
||||
|
@ -1,9 +1,10 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Amarok / Clementine.
|
||||
* This file was part of Amarok / Clementine
|
||||
* Copyright 2003 Mark Kretschmann
|
||||
* Copyright 2004, 2005 Max Howell, <max.howell@methylblue.com>
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2004 - 2005 Max Howell, <max.howell@methylblue.com>
|
||||
* Copyright 2010 David Sansome <me@davidsansome.com>
|
||||
* Copyright 2017 - 2018 Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -25,12 +26,14 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QVariant>
|
||||
#include <QUrl>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/timeconstants.h"
|
||||
#include "engine_fwd.h"
|
||||
#include "enginebase.h"
|
||||
#include "settings/backendsettingspage.h"
|
||||
#include "settings/playbacksettingspage.h"
|
||||
|
||||
Engine::Base::Base()
|
||||
@ -38,12 +41,21 @@ Engine::Base::Base()
|
||||
beginning_nanosec_(0),
|
||||
end_nanosec_(0),
|
||||
scope_(kScopeSize),
|
||||
output_(""),
|
||||
device_(QVariant("")),
|
||||
rg_enabled_(false),
|
||||
rg_mode_(0),
|
||||
rg_preamp_(0),
|
||||
rg_compression_(true),
|
||||
buffer_duration_nanosec_(4000),
|
||||
buffer_min_fill_(33),
|
||||
mono_playback_(false),
|
||||
fadeout_enabled_(true),
|
||||
fadeout_duration_nanosec_(2 * kNsecPerSec), // 2s
|
||||
crossfade_enabled_(true),
|
||||
autocrossfade_enabled_(false),
|
||||
crossfade_same_album_(false),
|
||||
next_background_stream_id_(0),
|
||||
fadeout_duration_(2),
|
||||
fadeout_duration_nanosec_(2 * kNsecPerSec),
|
||||
about_to_end_emitted_(false) {}
|
||||
|
||||
Engine::Base::~Base() {}
|
||||
@ -61,6 +73,14 @@ bool Engine::Base::Load(const QUrl &url, TrackChangeFlags, bool force_stop_at_en
|
||||
|
||||
}
|
||||
|
||||
bool Engine::Base::Play(const QUrl &url, TrackChangeFlags flags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
if (!Load(url, flags, force_stop_at_end, beginning_nanosec, end_nanosec))
|
||||
return false;
|
||||
|
||||
return Play(0);
|
||||
}
|
||||
|
||||
void Engine::Base::SetVolume(uint value) {
|
||||
|
||||
volume_ = value;
|
||||
@ -77,15 +97,30 @@ uint Engine::Base::MakeVolumeLogarithmic(uint volume) {
|
||||
void Engine::Base::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(PlaybackSettingsPage::kSettingsGroup);
|
||||
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
output_ = s.value("output").toString();
|
||||
device_ = s.value("device");
|
||||
rg_enabled_ = s.value("rgenabled", false).toBool();
|
||||
rg_mode_ = s.value("rgmode", 0).toInt();
|
||||
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
|
||||
rg_compression_ = s.value("rgcompression", true).toBool();
|
||||
buffer_duration_nanosec_ = s.value("bufferduration", 4000).toLongLong() * kNsecPerMsec;
|
||||
buffer_min_fill_ = s.value("bufferminfill", 33).toInt();
|
||||
mono_playback_ = s.value("monoplayback", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
s.beginGroup(PlaybackSettingsPage::kSettingsGroup);
|
||||
fadeout_enabled_ = s.value("FadeoutEnabled", false).toBool();
|
||||
fadeout_duration_nanosec_ = s.value("FadeoutDuration", 2000).toLongLong() * kNsecPerMsec;
|
||||
crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool();
|
||||
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
|
||||
crossfade_same_album_ = !s.value("NoCrossfadeSameAlbum", true).toBool();
|
||||
fadeout_pause_enabled_ = s.value("FadeoutPauseEnabled", false).toBool();
|
||||
fadeout_pause_duration_nanosec_ = s.value("FadeoutPauseDuration", 250).toLongLong() * kNsecPerMsec;
|
||||
fadeout_duration_ = s.value("FadeoutDuration", 2000).toLongLong();
|
||||
fadeout_duration_nanosec_ = (fadeout_duration_ * kNsecPerMsec);
|
||||
fadeout_pause_duration_ = s.value("FadeoutPauseDuration", 250).toLongLong();
|
||||
fadeout_pause_duration_nanosec_ = (fadeout_pause_duration_ * kNsecPerMsec);
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
@ -97,11 +132,3 @@ void Engine::Base::EmitAboutToEnd() {
|
||||
about_to_end_emitted_ = true;
|
||||
emit TrackAboutToEnd();
|
||||
}
|
||||
|
||||
bool Engine::Base::Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
if (!Load(u, c, force_stop_at_end, beginning_nanosec, end_nanosec))
|
||||
return false;
|
||||
|
||||
return Play(0);
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Amarok / Clementine.
|
||||
* This file was part of Amarok / Clementine
|
||||
* Copyright 2003 Mark Kretschmann
|
||||
* Copyright 2004, 2005 Max Howell, <max.howell@methylblue.com>
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2004 - 2005 Max Howell, <max.howell@methylblue.com>
|
||||
* Copyright 2010 David Sansome <me@davidsansome.com>
|
||||
* Copyright 2017 - 2018 Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -34,6 +35,7 @@
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMetaType>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
@ -49,52 +51,13 @@ typedef std::vector<int16_t> Scope;
|
||||
class Base : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
protected:
|
||||
Base();
|
||||
|
||||
public:
|
||||
|
||||
virtual ~Base();
|
||||
|
||||
virtual bool Init() = 0;
|
||||
|
||||
virtual void StartPreloading(const QUrl&, bool, qint64, qint64) {}
|
||||
virtual bool Play(quint64 offset_nanosec) = 0;
|
||||
virtual void Stop(bool stop_after = false) = 0;
|
||||
virtual void Pause() = 0;
|
||||
virtual void Unpause() = 0;
|
||||
virtual void Seek(quint64 offset_nanosec) = 0;
|
||||
|
||||
virtual State state() const = 0;
|
||||
virtual qint64 position_nanosec() const = 0;
|
||||
virtual qint64 length_nanosec() const = 0;
|
||||
|
||||
// Subclasses should respect given markers (beginning and end) which are in miliseconds.
|
||||
virtual bool Load(const QUrl &url, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
// Sets new values for the beginning and end markers of the currently playing song.
|
||||
// This doesn't change the state of engine or the stream's current position.
|
||||
virtual void RefreshMarkers(quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
beginning_nanosec_ = beginning_nanosec;
|
||||
end_nanosec_ = end_nanosec;
|
||||
}
|
||||
|
||||
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
|
||||
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
|
||||
bool Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
|
||||
void SetVolume(uint value);
|
||||
|
||||
// Simple accessors
|
||||
EngineType type() const { return type_; }
|
||||
inline uint volume() const { return volume_; }
|
||||
virtual const Scope &scope(int chunk_length) { return scope_; }
|
||||
bool is_fadeout_enabled() const { return fadeout_enabled_; }
|
||||
bool is_crossfade_enabled() const { return crossfade_enabled_; }
|
||||
bool is_autocrossfade_enabled() const { return autocrossfade_enabled_; }
|
||||
bool crossfade_same_album() const { return crossfade_same_album_; }
|
||||
|
||||
static const int kScopeSize = 1024;
|
||||
|
||||
virtual void SetVolumeSW(uint percent) = 0;
|
||||
static uint MakeVolumeLogarithmic(uint volume);
|
||||
|
||||
struct OutputDetails {
|
||||
QString name;
|
||||
QString description;
|
||||
@ -102,9 +65,60 @@ class Base : public QObject {
|
||||
};
|
||||
typedef QList<OutputDetails> OutputDetailsList;
|
||||
|
||||
public slots:
|
||||
virtual bool Init() = 0;
|
||||
virtual State state() const = 0;
|
||||
virtual void StartPreloading(const QUrl&, bool, qint64, qint64) {}
|
||||
virtual bool Load(const QUrl &url, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
virtual bool Play(quint64 offset_nanosec) = 0;
|
||||
virtual void Stop(bool stop_after = false) = 0;
|
||||
virtual void Pause() = 0;
|
||||
virtual void Unpause() = 0;
|
||||
virtual void Seek(quint64 offset_nanosec) = 0;
|
||||
virtual void SetVolumeSW(uint percent) = 0;
|
||||
|
||||
virtual qint64 position_nanosec() const = 0;
|
||||
virtual qint64 length_nanosec() const = 0;
|
||||
|
||||
virtual const Scope &scope(int chunk_length) { return scope_; }
|
||||
|
||||
// Sets new values for the beginning and end markers of the currently playing song.
|
||||
// This doesn't change the state of engine or the stream's current position.
|
||||
virtual void RefreshMarkers(quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
beginning_nanosec_ = beginning_nanosec;
|
||||
end_nanosec_ = end_nanosec;
|
||||
}
|
||||
|
||||
virtual OutputDetailsList GetOutputsList() const = 0;
|
||||
virtual QString DefaultOutput() = 0;
|
||||
virtual bool CustomDeviceSupport(const QString &name) = 0;
|
||||
|
||||
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
|
||||
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
|
||||
bool Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
void SetVolume(uint value);
|
||||
static uint MakeVolumeLogarithmic(uint volume);
|
||||
|
||||
public slots:
|
||||
virtual void ReloadSettings();
|
||||
|
||||
protected:
|
||||
void EmitAboutToEnd();
|
||||
|
||||
public:
|
||||
|
||||
// Simple accessors
|
||||
EngineType type() const { return type_; }
|
||||
inline uint volume() const { return volume_; }
|
||||
|
||||
bool is_fadeout_enabled() const { return fadeout_enabled_; }
|
||||
bool is_crossfade_enabled() const { return crossfade_enabled_; }
|
||||
bool is_autocrossfade_enabled() const { return autocrossfade_enabled_; }
|
||||
bool crossfade_same_album() const { return crossfade_same_album_; }
|
||||
bool IsEqualizerEnabled() { return equalizer_enabled_; }
|
||||
|
||||
static const int kScopeSize = 1024;
|
||||
|
||||
public slots:
|
||||
virtual void SetEqualizerEnabled(bool) {}
|
||||
virtual void SetEqualizerParameters(int preamp, const QList<int> &bandGains) {}
|
||||
virtual void SetStereoBalance(float value) {}
|
||||
@ -132,28 +146,8 @@ signals:
|
||||
// subsequent call to state() won't return a stale value.
|
||||
void StateChanged(Engine::State);
|
||||
|
||||
protected:
|
||||
Base();
|
||||
protected:
|
||||
|
||||
void EmitAboutToEnd();
|
||||
|
||||
protected:
|
||||
EngineType type_;
|
||||
uint volume_;
|
||||
quint64 beginning_nanosec_;
|
||||
qint64 end_nanosec_;
|
||||
QUrl url_;
|
||||
Scope scope_;
|
||||
|
||||
bool fadeout_enabled_;
|
||||
qint64 fadeout_duration_nanosec_;
|
||||
bool crossfade_enabled_;
|
||||
bool autocrossfade_enabled_;
|
||||
bool crossfade_same_album_;
|
||||
int next_background_stream_id_;
|
||||
bool fadeout_pause_enabled_;
|
||||
qint64 fadeout_pause_duration_nanosec_;
|
||||
|
||||
struct PluginDetails {
|
||||
QString name;
|
||||
QString description;
|
||||
@ -161,7 +155,43 @@ signals:
|
||||
};
|
||||
typedef QList<PluginDetails> PluginDetailsList;
|
||||
|
||||
private:
|
||||
EngineType type_;
|
||||
uint volume_;
|
||||
quint64 beginning_nanosec_;
|
||||
qint64 end_nanosec_;
|
||||
QUrl url_;
|
||||
Scope scope_;
|
||||
bool buffering_;
|
||||
bool equalizer_enabled_;
|
||||
|
||||
// Settings
|
||||
QString output_;
|
||||
QVariant device_;
|
||||
|
||||
// ReplayGain
|
||||
bool rg_enabled_;
|
||||
int rg_mode_;
|
||||
float rg_preamp_;
|
||||
bool rg_compression_;
|
||||
|
||||
// Buffering
|
||||
quint64 buffer_duration_nanosec_;
|
||||
int buffer_min_fill_;
|
||||
|
||||
bool mono_playback_;
|
||||
|
||||
// Fadeout
|
||||
bool fadeout_enabled_;
|
||||
bool crossfade_enabled_;
|
||||
bool autocrossfade_enabled_;
|
||||
bool crossfade_same_album_;
|
||||
bool fadeout_pause_enabled_;
|
||||
qint64 fadeout_duration_;
|
||||
qint64 fadeout_duration_nanosec_;
|
||||
qint64 fadeout_pause_duration_;
|
||||
qint64 fadeout_pause_duration_nanosec_;
|
||||
|
||||
private:
|
||||
bool about_to_end_emitted_;
|
||||
Q_DISABLE_COPY(Base);
|
||||
|
||||
|
@ -26,18 +26,6 @@
|
||||
|
||||
namespace Engine {
|
||||
|
||||
QString EngineNameFromType(Engine::EngineType enginetype) {
|
||||
switch (enginetype) {
|
||||
case Engine::Xine: return QObject::tr("Xine");
|
||||
case Engine::GStreamer: return QObject::tr("GStreamer");
|
||||
case Engine::Phonon: return QObject::tr("Phonon");
|
||||
case Engine::VLC: return QObject::tr("VLC");
|
||||
case Engine::None:
|
||||
default: return QObject::tr("None");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Engine::EngineType EngineTypeFromName(QString enginename) {
|
||||
|
||||
QString lower = enginename.toLower();
|
||||
@ -50,4 +38,28 @@ Engine::EngineType EngineTypeFromName(QString enginename) {
|
||||
|
||||
}
|
||||
|
||||
QString EngineName(Engine::EngineType enginetype) {
|
||||
switch (enginetype) {
|
||||
case Engine::Xine: return QObject::tr("xine");
|
||||
case Engine::GStreamer: return QObject::tr("gstreamer");
|
||||
case Engine::Phonon: return QObject::tr("phonon");
|
||||
case Engine::VLC: return QObject::tr("vlc");
|
||||
case Engine::None:
|
||||
default: return QObject::tr("None");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
QString EngineDescription(Engine::EngineType enginetype) {
|
||||
switch (enginetype) {
|
||||
case Engine::Xine: return QObject::tr("Xine");
|
||||
case Engine::GStreamer: return QObject::tr("GStreamer");
|
||||
case Engine::Phonon: return QObject::tr("Phonon");
|
||||
case Engine::VLC: return QObject::tr("VLC");
|
||||
case Engine::None:
|
||||
default: return QObject::tr("None");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,8 +35,9 @@ enum EngineType {
|
||||
Phonon
|
||||
};
|
||||
|
||||
QString EngineNameFromType(Engine::EngineType enginetype);
|
||||
Engine::EngineType EngineTypeFromName(QString enginename);
|
||||
QString EngineName(Engine::EngineType enginetype);
|
||||
QString EngineDescription(Engine::EngineType enginetype);
|
||||
|
||||
}
|
||||
Q_DECLARE_METATYPE(Engine::EngineType);
|
||||
|
@ -1,4 +1,5 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
|
||||
* Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> *
|
||||
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> *
|
||||
* Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> *
|
||||
@ -30,7 +31,6 @@
|
||||
#include <string>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QCoreApplication>
|
||||
@ -90,15 +90,7 @@ GstEngine::GstEngine(TaskManager *task_manager)
|
||||
task_manager_(task_manager),
|
||||
buffering_task_id_(-1),
|
||||
latest_buffer_(nullptr),
|
||||
equalizer_enabled_(false),
|
||||
stereo_balance_(0.0f),
|
||||
rg_enabled_(false),
|
||||
rg_mode_(0),
|
||||
rg_preamp_(0.0),
|
||||
rg_compression_(true),
|
||||
buffer_duration_nanosec_(1 * kNsecPerSec), // 1s
|
||||
buffer_min_fill_(33),
|
||||
mono_playback_(false),
|
||||
seek_timer_(new QTimer(this)),
|
||||
timer_id_(-1),
|
||||
next_element_id_(0),
|
||||
@ -143,103 +135,6 @@ bool GstEngine::Init() {
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::InitialiseGStreamer() {
|
||||
|
||||
gst_init(nullptr, nullptr);
|
||||
gst_pb_utils_init();
|
||||
|
||||
#ifdef HAVE_IMOBILEDEVICE_ // FIXME
|
||||
afcsrc_register_static();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::SetEnvironment() {
|
||||
|
||||
QString scanner_path;
|
||||
QString plugin_path;
|
||||
QString registry_filename;
|
||||
|
||||
// On windows and mac we bundle the gstreamer plugins with strawberry
|
||||
#if defined(Q_OS_DARWIN)
|
||||
scanner_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gst-plugin-scanner";
|
||||
plugin_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gstreamer";
|
||||
#elif defined(Q_OS_WIN32)
|
||||
plugin_path = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + "/gstreamer-plugins");
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
||||
registry_filename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QString("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
|
||||
#endif
|
||||
|
||||
if (!scanner_path.isEmpty()) Utilities::SetEnv("GST_PLUGIN_SCANNER", scanner_path);
|
||||
|
||||
if (!plugin_path.isEmpty()) {
|
||||
Utilities::SetEnv("GST_PLUGIN_PATH", plugin_path);
|
||||
// Never load plugins from anywhere else.
|
||||
Utilities::SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path);
|
||||
}
|
||||
|
||||
if (!registry_filename.isEmpty()) {
|
||||
Utilities::SetEnv("GST_REGISTRY", registry_filename);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
Utilities::SetEnv("GIO_EXTRA_MODULES", QCoreApplication::applicationDirPath() + "/../PlugIns/gio-modules");
|
||||
#endif
|
||||
|
||||
Utilities::SetEnv("PULSE_PROP_media.role", "music");
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::ReloadSettings() {
|
||||
|
||||
Engine::Base::ReloadSettings();
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
|
||||
sink_ = s.value("output", kAutoSink).toString();
|
||||
device_ = s.value("device");
|
||||
|
||||
if (sink_.isEmpty()) sink_ = kAutoSink;
|
||||
|
||||
rg_enabled_ = s.value("rgenabled", false).toBool();
|
||||
rg_mode_ = s.value("rgmode", 0).toInt();
|
||||
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
|
||||
rg_compression_ = s.value("rgcompression", true).toBool();
|
||||
|
||||
buffer_duration_nanosec_ = s.value("bufferduration", 4000).toLongLong() * kNsecPerMsec;
|
||||
buffer_min_fill_ = s.value("bufferminfill", 33).toInt();
|
||||
mono_playback_ = s.value("monoplayback", false).toBool();
|
||||
|
||||
}
|
||||
|
||||
qint64 GstEngine::position_nanosec() const {
|
||||
|
||||
if (!current_pipeline_) return 0;
|
||||
|
||||
const qint64 result = current_pipeline_->position() - beginning_nanosec_;
|
||||
return qint64(qMax(0ll, result));
|
||||
|
||||
}
|
||||
|
||||
qint64 GstEngine::length_nanosec() const {
|
||||
|
||||
if (!current_pipeline_) return 0;
|
||||
|
||||
const qint64 result = end_nanosec_ - beginning_nanosec_;
|
||||
|
||||
if (result > 0) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
// Get the length from the pipeline if we don't know.
|
||||
return current_pipeline_->length();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Engine::State GstEngine::state() const {
|
||||
|
||||
if (!current_pipeline_) return url_.isEmpty() ? Engine::Empty : Engine::Idle;
|
||||
@ -256,102 +151,6 @@ Engine::State GstEngine::state() const {
|
||||
default:
|
||||
return Engine::Empty;
|
||||
}
|
||||
}
|
||||
|
||||
void GstEngine::ConsumeBuffer(GstBuffer *buffer, int pipeline_id) {
|
||||
|
||||
// Schedule this to run in the GUI thread. The buffer gets added to the queue and unreffed by UpdateScope.
|
||||
if (!QMetaObject::invokeMethod(this, "AddBufferToScope", Q_ARG(GstBuffer*, buffer), Q_ARG(int, pipeline_id))) {
|
||||
qLog(Warning) << "Failed to invoke AddBufferToScope on GstEngine";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::AddBufferToScope(GstBuffer *buf, int pipeline_id) {
|
||||
|
||||
if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) {
|
||||
gst_buffer_unref(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (latest_buffer_ != nullptr) {
|
||||
gst_buffer_unref(latest_buffer_);
|
||||
}
|
||||
|
||||
latest_buffer_ = buf;
|
||||
have_new_buffer_ = true;
|
||||
}
|
||||
|
||||
const Engine::Scope &GstEngine::scope(int chunk_length) {
|
||||
|
||||
// the new buffer could have a different size
|
||||
if (have_new_buffer_) {
|
||||
if (latest_buffer_ != nullptr) {
|
||||
scope_chunks_ = ceil(((double)GST_BUFFER_DURATION(latest_buffer_) / (double)(chunk_length * kNsecPerMsec)));
|
||||
}
|
||||
|
||||
// if the buffer is shorter than the chunk length
|
||||
if (scope_chunks_ <= 0) {
|
||||
scope_chunks_ = 1;
|
||||
}
|
||||
|
||||
scope_chunk_ = 0;
|
||||
have_new_buffer_ = false;
|
||||
}
|
||||
|
||||
if (latest_buffer_ != nullptr) {
|
||||
UpdateScope(chunk_length);
|
||||
}
|
||||
|
||||
return scope_;
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::UpdateScope(int chunk_length) {
|
||||
|
||||
typedef Engine::Scope::value_type sample_type;
|
||||
|
||||
// prevent dbz or invalid chunk size
|
||||
if (!GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(latest_buffer_))) return;
|
||||
if (GST_BUFFER_DURATION(latest_buffer_) == 0) return;
|
||||
|
||||
GstMapInfo map;
|
||||
gst_buffer_map(latest_buffer_, &map, GST_MAP_READ);
|
||||
|
||||
// determine where to split the buffer
|
||||
int chunk_density = (map.size * kNsecPerMsec) / GST_BUFFER_DURATION(latest_buffer_);
|
||||
|
||||
int chunk_size = chunk_length * chunk_density;
|
||||
|
||||
// in case a buffer doesn't arrive in time
|
||||
if (scope_chunk_ >= scope_chunks_) {
|
||||
scope_chunk_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const sample_type *source = reinterpret_cast<sample_type*>(map.data);
|
||||
sample_type *dest = scope_.data();
|
||||
source += (chunk_size / sizeof(sample_type)) * scope_chunk_;
|
||||
|
||||
int bytes = 0;
|
||||
|
||||
// make sure we don't go beyond the end of the buffer
|
||||
if (scope_chunk_ == scope_chunks_ - 1) {
|
||||
bytes = qMin(static_cast<Engine::Scope::size_type>(map.size - (chunk_size * scope_chunk_)), scope_.size() * sizeof(sample_type));
|
||||
}
|
||||
else {
|
||||
bytes = qMin(static_cast<Engine::Scope::size_type>(chunk_size), scope_.size() * sizeof(sample_type));
|
||||
}
|
||||
|
||||
scope_chunk_++;
|
||||
memcpy(dest, source, bytes);
|
||||
|
||||
gst_buffer_unmap(latest_buffer_, &map);
|
||||
|
||||
if (scope_chunk_ == scope_chunks_) {
|
||||
gst_buffer_unref(latest_buffer_);
|
||||
latest_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -367,42 +166,6 @@ void GstEngine::StartPreloading(const QUrl &url, bool force_stop_at_end, qint64
|
||||
|
||||
}
|
||||
|
||||
QByteArray GstEngine::FixupUrl(const QUrl &url) {
|
||||
|
||||
EnsureInitialised();
|
||||
|
||||
QByteArray uri;
|
||||
|
||||
// It's a file:// url with a hostname set. QUrl::fromLocalFile does this when given a \\host\share\file path on Windows. Munge it back into a path that gstreamer will recognise.
|
||||
if (url.scheme() == "file" && !url.host().isEmpty()) {
|
||||
QString str = "//" + url.host() + url.path();
|
||||
uri = str.toLocal8Bit();
|
||||
}
|
||||
else if (url.scheme() == "cdda") {
|
||||
QString str;
|
||||
if (url.path().isEmpty()) {
|
||||
str = url.toString();
|
||||
str.remove(str.lastIndexOf(QChar('a')), 1);
|
||||
}
|
||||
else {
|
||||
// Currently, Gstreamer can't handle input CD devices inside cdda URL.
|
||||
// So we handle them ourselve: we extract the track number and re-create an URL with only cdda:// + the track number (which can be handled by Gstreamer).
|
||||
// We keep the device in mind, and we will set it later using SourceSetupCallback
|
||||
QStringList path = url.path().split('/');
|
||||
str = QString("cdda://%1a").arg(path.takeLast());
|
||||
QString device = path.join("/");
|
||||
current_pipeline_->SetSourceDevice(device);
|
||||
}
|
||||
uri = str.toLocal8Bit();
|
||||
}
|
||||
else {
|
||||
uri = url.toEncoded();
|
||||
}
|
||||
|
||||
return uri;
|
||||
|
||||
}
|
||||
|
||||
bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
EnsureInitialised();
|
||||
@ -445,33 +208,6 @@ bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool forc
|
||||
return true;
|
||||
}
|
||||
|
||||
void GstEngine::StartFadeout() {
|
||||
|
||||
if (is_fading_out_to_pause_) return;
|
||||
|
||||
fadeout_pipeline_ = current_pipeline_;
|
||||
disconnect(fadeout_pipeline_.get(), 0, 0, 0);
|
||||
fadeout_pipeline_->RemoveAllBufferConsumers();
|
||||
|
||||
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
|
||||
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::StartFadeoutPause() {
|
||||
|
||||
fadeout_pause_pipeline_ = current_pipeline_;
|
||||
disconnect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
|
||||
|
||||
fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QTimeLine::EaseInOutCurve, false);
|
||||
if (fadeout_pipeline_ && fadeout_pipeline_->state() == GST_STATE_PLAYING) {
|
||||
fadeout_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QTimeLine::LinearCurve, false);
|
||||
}
|
||||
connect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutPauseFinished()));
|
||||
is_fading_out_to_pause_ = true;
|
||||
|
||||
}
|
||||
|
||||
bool GstEngine::Play(quint64 offset_nanosec) {
|
||||
|
||||
EnsureInitialised();
|
||||
@ -489,44 +225,6 @@ bool GstEngine::Play(quint64 offset_nanosec) {
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::PlayDone(QFuture<GstStateChangeReturn> future, const quint64 offset_nanosec, const int pipeline_id) {
|
||||
|
||||
GstStateChangeReturn ret = future.result();
|
||||
|
||||
if (!current_pipeline_ || pipeline_id != current_pipeline_->id()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
// Failure, but we got a redirection URL - try loading that instead
|
||||
QByteArray redirect_url = current_pipeline_->redirect_url();
|
||||
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) {
|
||||
qLog(Info) << "Redirecting to" << redirect_url;
|
||||
current_pipeline_ = CreatePipeline(redirect_url, end_nanosec_);
|
||||
Play(offset_nanosec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Failure - give up
|
||||
qLog(Warning) << "Could not set thread to PLAYING.";
|
||||
current_pipeline_.reset();
|
||||
BufferingFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimers();
|
||||
|
||||
// initial offset
|
||||
if (offset_nanosec != 0 || beginning_nanosec_ != 0) {
|
||||
Seek(offset_nanosec);
|
||||
}
|
||||
|
||||
emit StateChanged(Engine::Playing);
|
||||
// we've successfully started playing a media stream with this url
|
||||
emit ValidSongRequested(url_);
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::Stop(bool stop_after) {
|
||||
|
||||
StopTimers();
|
||||
@ -552,27 +250,6 @@ void GstEngine::Stop(bool stop_after) {
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::FadeoutFinished() {
|
||||
fadeout_pipeline_.reset();
|
||||
emit FadeoutFinishedSignal();
|
||||
}
|
||||
|
||||
void GstEngine::FadeoutPauseFinished() {
|
||||
|
||||
fadeout_pause_pipeline_->SetState(GST_STATE_PAUSED);
|
||||
current_pipeline_->SetState(GST_STATE_PAUSED);
|
||||
emit StateChanged(Engine::Paused);
|
||||
StopTimers();
|
||||
|
||||
is_fading_out_to_pause_ = false;
|
||||
has_faded_out_ = true;
|
||||
fadeout_pause_pipeline_.reset();
|
||||
fadeout_pipeline_.reset();
|
||||
|
||||
emit FadeoutFinishedSignal();
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::Pause() {
|
||||
|
||||
if (!current_pipeline_ || current_pipeline_->is_buffering()) return;
|
||||
@ -634,16 +311,170 @@ void GstEngine::Seek(quint64 offset_nanosec) {
|
||||
}
|
||||
}
|
||||
|
||||
void GstEngine::SeekNow() {
|
||||
void GstEngine::SetVolumeSW(uint percent) {
|
||||
if (current_pipeline_) current_pipeline_->SetVolume(percent);
|
||||
}
|
||||
|
||||
if (!waiting_to_seek_) return;
|
||||
waiting_to_seek_ = false;
|
||||
qint64 GstEngine::position_nanosec() const {
|
||||
|
||||
if (!current_pipeline_) return;
|
||||
if (!current_pipeline_) return 0;
|
||||
|
||||
if (!current_pipeline_->Seek(seek_pos_)) {
|
||||
qLog(Warning) << "Seek failed";
|
||||
const qint64 result = current_pipeline_->position() - beginning_nanosec_;
|
||||
return qint64(qMax(0ll, result));
|
||||
|
||||
}
|
||||
|
||||
qint64 GstEngine::length_nanosec() const {
|
||||
|
||||
if (!current_pipeline_) return 0;
|
||||
|
||||
const qint64 result = end_nanosec_ - beginning_nanosec_;
|
||||
|
||||
if (result > 0) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
// Get the length from the pipeline if we don't know.
|
||||
return current_pipeline_->length();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const Engine::Scope &GstEngine::scope(int chunk_length) {
|
||||
|
||||
// The new buffer could have a different size
|
||||
if (have_new_buffer_) {
|
||||
if (latest_buffer_) {
|
||||
scope_chunks_ = ceil(((double)GST_BUFFER_DURATION(latest_buffer_) / (double)(chunk_length * kNsecPerMsec)));
|
||||
}
|
||||
|
||||
// if the buffer is shorter than the chunk length
|
||||
if (scope_chunks_ <= 0) {
|
||||
scope_chunks_ = 1;
|
||||
}
|
||||
|
||||
scope_chunk_ = 0;
|
||||
have_new_buffer_ = false;
|
||||
}
|
||||
|
||||
if (latest_buffer_) {
|
||||
UpdateScope(chunk_length);
|
||||
}
|
||||
|
||||
return scope_;
|
||||
|
||||
}
|
||||
|
||||
EngineBase::OutputDetailsList GstEngine::GetOutputsList() const {
|
||||
|
||||
EngineBase::OutputDetailsList ret;
|
||||
|
||||
PluginDetailsList plugins = GetPluginList("Sink/Audio");
|
||||
|
||||
for (const PluginDetails &plugin : plugins) {
|
||||
OutputDetails output;
|
||||
output.name = plugin.name;
|
||||
output.description = plugin.description;
|
||||
if (plugin.name == kAutoSink) output.iconname = "soundcard";
|
||||
else if ((plugin.name == kALSASink) || (plugin.name == kOSS4Sink) || (plugin.name == kOSS4Sink)) output.iconname = "alsa";
|
||||
else if (plugin.name== kJackAudioSink) output.iconname = "jack";
|
||||
else if (plugin.name == kPulseSink) output.iconname = "pulseaudio";
|
||||
else if ((plugin.name == kA2DPSink) || (plugin.name == kAVDTPSink)) output.iconname = "bluetooth";
|
||||
else output.iconname = "soundcard";
|
||||
ret.append(output);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
bool GstEngine::CustomDeviceSupport(const QString &name) {
|
||||
return (name == kALSASink || name == kOpenALSASink || name == kOSSSink || name == kOSS4Sink || name == kPulseSink || name == kA2DPSink || name == kAVDTPSink);
|
||||
}
|
||||
|
||||
void GstEngine::ReloadSettings() {
|
||||
|
||||
Engine::Base::ReloadSettings();
|
||||
|
||||
if (output_.isEmpty()) output_ = kAutoSink;
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::InitialiseGStreamer() {
|
||||
|
||||
gst_init(nullptr, nullptr);
|
||||
|
||||
#ifdef HAVE_IMOBILEDEVICE_ // FIXME
|
||||
afcsrc_register_static();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::SetEnvironment() {
|
||||
|
||||
QString scanner_path;
|
||||
QString plugin_path;
|
||||
QString registry_filename;
|
||||
|
||||
// On windows and mac we bundle the gstreamer plugins with strawberry
|
||||
#if defined(Q_OS_DARWIN)
|
||||
scanner_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gst-plugin-scanner";
|
||||
plugin_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gstreamer";
|
||||
#elif defined(Q_OS_WIN32)
|
||||
plugin_path = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + "/gstreamer-plugins");
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
||||
registry_filename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QString("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
|
||||
#endif
|
||||
|
||||
if (!scanner_path.isEmpty()) Utilities::SetEnv("GST_PLUGIN_SCANNER", scanner_path);
|
||||
|
||||
if (!plugin_path.isEmpty()) {
|
||||
Utilities::SetEnv("GST_PLUGIN_PATH", plugin_path);
|
||||
// Never load plugins from anywhere else.
|
||||
Utilities::SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path);
|
||||
}
|
||||
|
||||
if (!registry_filename.isEmpty()) {
|
||||
Utilities::SetEnv("GST_REGISTRY", registry_filename);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
Utilities::SetEnv("GIO_EXTRA_MODULES", QCoreApplication::applicationDirPath() + "/../PlugIns/gio-modules");
|
||||
#endif
|
||||
|
||||
Utilities::SetEnv("PULSE_PROP_media.role", "music");
|
||||
|
||||
}
|
||||
|
||||
GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin, bool fatal, bool showerror) {
|
||||
|
||||
// Make a unique name
|
||||
QString name = factoryName + "-" + QString::number(next_element_id_++);
|
||||
|
||||
GstElement *element = gst_element_factory_make(factoryName.toUtf8().constData(), name.toUtf8().constData());
|
||||
|
||||
if (!element) {
|
||||
if (showerror)
|
||||
emit Error(QString("GStreamer could not create the element: %1. Please make sure that you have installed all necessary GStreamer plugins").arg(factoryName));
|
||||
else qLog(Error) << "GStreamer could not create the element:" << factoryName;
|
||||
//if (fatal) gst_object_unref(GST_OBJECT(bin));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (bin) gst_bin_add(GST_BIN(bin), element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void GstEngine::ConsumeBuffer(GstBuffer *buffer, int pipeline_id) {
|
||||
|
||||
// Schedule this to run in the GUI thread. The buffer gets added to the queue and unreffed by UpdateScope.
|
||||
if (!QMetaObject::invokeMethod(this, "AddBufferToScope", Q_ARG(GstBuffer*, buffer), Q_ARG(int, pipeline_id))) {
|
||||
qLog(Warning) << "Failed to invoke AddBufferToScope on GstEngine";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::SetEqualizerEnabled(bool enabled) {
|
||||
@ -669,21 +500,14 @@ void GstEngine::SetStereoBalance(float value) {
|
||||
if (current_pipeline_) current_pipeline_->SetStereoBalance(value);
|
||||
}
|
||||
|
||||
void GstEngine::SetVolumeSW(uint percent) {
|
||||
if (current_pipeline_) current_pipeline_->SetVolume(percent);
|
||||
void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) {
|
||||
buffer_consumers_ << consumer;
|
||||
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
|
||||
}
|
||||
|
||||
void GstEngine::StartTimers() {
|
||||
StopTimers();
|
||||
|
||||
timer_id_ = startTimer(kTimerIntervalNanosec / kNsecPerMsec);
|
||||
}
|
||||
|
||||
void GstEngine::StopTimers() {
|
||||
if (timer_id_ != -1) {
|
||||
killTimer(timer_id_);
|
||||
timer_id_ = -1;
|
||||
}
|
||||
void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) {
|
||||
buffer_consumers_.removeAll(consumer);
|
||||
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
|
||||
}
|
||||
|
||||
void GstEngine::timerEvent(QTimerEvent *e) {
|
||||
@ -710,6 +534,18 @@ void GstEngine::timerEvent(QTimerEvent *e) {
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
|
||||
|
||||
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
||||
return;
|
||||
|
||||
if (!has_next_track) {
|
||||
current_pipeline_.reset();
|
||||
BufferingFinished();
|
||||
}
|
||||
emit TrackEnded();
|
||||
}
|
||||
|
||||
void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code) {
|
||||
|
||||
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
||||
@ -728,18 +564,6 @@ void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
|
||||
|
||||
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
||||
return;
|
||||
|
||||
if (!has_next_track) {
|
||||
current_pipeline_.reset();
|
||||
BufferingFinished();
|
||||
}
|
||||
emit TrackEnded();
|
||||
}
|
||||
|
||||
void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bundle) {
|
||||
|
||||
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
||||
@ -748,24 +572,113 @@ void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bun
|
||||
emit MetaData(bundle);
|
||||
}
|
||||
|
||||
GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin, bool fatal, bool showerror) {
|
||||
void GstEngine::AddBufferToScope(GstBuffer *buf, int pipeline_id) {
|
||||
|
||||
// Make a unique name
|
||||
QString name = factoryName + "-" + QString::number(next_element_id_++);
|
||||
|
||||
GstElement *element = gst_element_factory_make(factoryName.toUtf8().constData(), name.toUtf8().constData());
|
||||
|
||||
if (!element) {
|
||||
if (showerror)
|
||||
emit Error(QString("GStreamer could not create the element: %1. Please make sure that you have installed all necessary GStreamer plugins").arg(factoryName));
|
||||
else qLog(Error) << "GStreamer could not create the element:" << factoryName;
|
||||
//if (fatal) gst_object_unref(GST_OBJECT(bin));
|
||||
return nullptr;
|
||||
if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) {
|
||||
gst_buffer_unref(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bin) gst_bin_add(GST_BIN(bin), element);
|
||||
if (latest_buffer_) {
|
||||
gst_buffer_unref(latest_buffer_);
|
||||
}
|
||||
|
||||
return element;
|
||||
latest_buffer_ = buf;
|
||||
have_new_buffer_ = true;
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::FadeoutFinished() {
|
||||
fadeout_pipeline_.reset();
|
||||
emit FadeoutFinishedSignal();
|
||||
}
|
||||
|
||||
void GstEngine::FadeoutPauseFinished() {
|
||||
|
||||
fadeout_pause_pipeline_->SetState(GST_STATE_PAUSED);
|
||||
current_pipeline_->SetState(GST_STATE_PAUSED);
|
||||
emit StateChanged(Engine::Paused);
|
||||
StopTimers();
|
||||
|
||||
is_fading_out_to_pause_ = false;
|
||||
has_faded_out_ = true;
|
||||
fadeout_pause_pipeline_.reset();
|
||||
fadeout_pipeline_.reset();
|
||||
|
||||
emit FadeoutFinishedSignal();
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::SeekNow() {
|
||||
|
||||
if (!waiting_to_seek_) return;
|
||||
waiting_to_seek_ = false;
|
||||
|
||||
if (!current_pipeline_) return;
|
||||
|
||||
if (!current_pipeline_->Seek(seek_pos_)) {
|
||||
qLog(Warning) << "Seek failed";
|
||||
}
|
||||
}
|
||||
|
||||
void GstEngine::PlayDone(QFuture<GstStateChangeReturn> future, const quint64 offset_nanosec, const int pipeline_id) {
|
||||
|
||||
GstStateChangeReturn ret = future.result();
|
||||
|
||||
if (!current_pipeline_ || pipeline_id != current_pipeline_->id()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
// Failure, but we got a redirection URL - try loading that instead
|
||||
QByteArray redirect_url = current_pipeline_->redirect_url();
|
||||
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) {
|
||||
qLog(Info) << "Redirecting to" << redirect_url;
|
||||
current_pipeline_ = CreatePipeline(redirect_url, end_nanosec_);
|
||||
Play(offset_nanosec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Failure - give up
|
||||
qLog(Warning) << "Could not set thread to PLAYING.";
|
||||
current_pipeline_.reset();
|
||||
BufferingFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimers();
|
||||
|
||||
// initial offset
|
||||
if (offset_nanosec != 0 || beginning_nanosec_ != 0) {
|
||||
Seek(offset_nanosec);
|
||||
}
|
||||
|
||||
emit StateChanged(Engine::Playing);
|
||||
// we've successfully started playing a media stream with this url
|
||||
emit ValidSongRequested(url_);
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::BufferingStarted() {
|
||||
|
||||
if (buffering_task_id_ != -1) {
|
||||
task_manager_->SetTaskFinished(buffering_task_id_);
|
||||
}
|
||||
|
||||
buffering_task_id_ = task_manager_->StartTask(tr("Buffering"));
|
||||
task_manager_->SetTaskProgress(buffering_task_id_, 0, 100);
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::BufferingProgress(int percent) {
|
||||
task_manager_->SetTaskProgress(buffering_task_id_, percent, 100);
|
||||
}
|
||||
|
||||
void GstEngine::BufferingFinished() {
|
||||
if (buffering_task_id_ != -1) {
|
||||
task_manager_->SetTaskFinished(buffering_task_id_);
|
||||
buffering_task_id_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
GstEngine::PluginDetailsList GstEngine::GetPluginList(const QString &classname) const {
|
||||
@ -793,12 +706,90 @@ GstEngine::PluginDetailsList GstEngine::GetPluginList(const QString &classname)
|
||||
|
||||
}
|
||||
|
||||
QByteArray GstEngine::FixupUrl(const QUrl &url) {
|
||||
|
||||
EnsureInitialised();
|
||||
|
||||
QByteArray uri;
|
||||
|
||||
// It's a file:// url with a hostname set.
|
||||
// QUrl::fromLocalFile does this when given a \\host\share\file path on Windows.
|
||||
// Munge it back into a path that gstreamer will recognise.
|
||||
if (url.scheme() == "file" && !url.host().isEmpty()) {
|
||||
QString str = "//" + url.host() + url.path();
|
||||
uri = str.toLocal8Bit();
|
||||
}
|
||||
else if (url.scheme() == "cdda") {
|
||||
QString str;
|
||||
if (url.path().isEmpty()) {
|
||||
str = url.toString();
|
||||
str.remove(str.lastIndexOf(QChar('a')), 1);
|
||||
}
|
||||
else {
|
||||
// Currently, Gstreamer can't handle input CD devices inside cdda URL.
|
||||
// So we handle them ourselve: we extract the track number and re-create an URL with only cdda:// + the track number (which can be handled by Gstreamer).
|
||||
// We keep the device in mind, and we will set it later using SourceSetupCallback
|
||||
QStringList path = url.path().split('/');
|
||||
str = QString("cdda://%1a").arg(path.takeLast());
|
||||
QString device = path.join("/");
|
||||
current_pipeline_->SetSourceDevice(device);
|
||||
}
|
||||
uri = str.toLocal8Bit();
|
||||
}
|
||||
else {
|
||||
uri = url.toEncoded();
|
||||
}
|
||||
|
||||
return uri;
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::StartFadeout() {
|
||||
|
||||
if (is_fading_out_to_pause_) return;
|
||||
|
||||
fadeout_pipeline_ = current_pipeline_;
|
||||
disconnect(fadeout_pipeline_.get(), 0, 0, 0);
|
||||
fadeout_pipeline_->RemoveAllBufferConsumers();
|
||||
|
||||
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
|
||||
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::StartFadeoutPause() {
|
||||
|
||||
fadeout_pause_pipeline_ = current_pipeline_;
|
||||
disconnect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
|
||||
|
||||
fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QTimeLine::EaseInOutCurve, false);
|
||||
if (fadeout_pipeline_ && fadeout_pipeline_->state() == GST_STATE_PLAYING) {
|
||||
fadeout_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QTimeLine::LinearCurve, false);
|
||||
}
|
||||
connect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutPauseFinished()));
|
||||
is_fading_out_to_pause_ = true;
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::StartTimers() {
|
||||
StopTimers();
|
||||
|
||||
timer_id_ = startTimer(kTimerIntervalNanosec / kNsecPerMsec);
|
||||
}
|
||||
|
||||
void GstEngine::StopTimers() {
|
||||
if (timer_id_ != -1) {
|
||||
killTimer(timer_id_);
|
||||
timer_id_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
||||
|
||||
EnsureInitialised();
|
||||
|
||||
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
|
||||
ret->set_output_device(sink_, device_);
|
||||
ret->set_output_device(output_, device_);
|
||||
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
|
||||
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
|
||||
ret->set_buffer_min_fill(buffer_min_fill_);
|
||||
@ -839,77 +830,50 @@ shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QByteArray &url, q
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::AddBufferConsumer(GstBufferConsumer *consumer) {
|
||||
buffer_consumers_ << consumer;
|
||||
if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer);
|
||||
}
|
||||
void GstEngine::UpdateScope(int chunk_length) {
|
||||
|
||||
void GstEngine::RemoveBufferConsumer(GstBufferConsumer *consumer) {
|
||||
buffer_consumers_.removeAll(consumer);
|
||||
if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer);
|
||||
}
|
||||
typedef Engine::Scope::value_type sample_type;
|
||||
|
||||
void GstEngine::BufferingStarted() {
|
||||
// Prevent dbz or invalid chunk size
|
||||
if (!GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(latest_buffer_))) return;
|
||||
if (GST_BUFFER_DURATION(latest_buffer_) == 0) return;
|
||||
|
||||
if (buffering_task_id_ != -1) {
|
||||
task_manager_->SetTaskFinished(buffering_task_id_);
|
||||
GstMapInfo map;
|
||||
gst_buffer_map(latest_buffer_, &map, GST_MAP_READ);
|
||||
|
||||
// Determine where to split the buffer
|
||||
int chunk_density = (map.size * kNsecPerMsec) / GST_BUFFER_DURATION(latest_buffer_);
|
||||
|
||||
int chunk_size = chunk_length * chunk_density;
|
||||
|
||||
// In case a buffer doesn't arrive in time
|
||||
if (scope_chunk_ >= scope_chunks_) {
|
||||
scope_chunk_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
buffering_task_id_ = task_manager_->StartTask(tr("Buffering"));
|
||||
task_manager_->SetTaskProgress(buffering_task_id_, 0, 100);
|
||||
const sample_type *source = reinterpret_cast<sample_type*>(map.data);
|
||||
sample_type *dest = scope_.data();
|
||||
source += (chunk_size / sizeof(sample_type)) * scope_chunk_;
|
||||
|
||||
}
|
||||
int bytes = 0;
|
||||
|
||||
void GstEngine::BufferingProgress(int percent) {
|
||||
task_manager_->SetTaskProgress(buffering_task_id_, percent, 100);
|
||||
}
|
||||
|
||||
void GstEngine::BufferingFinished() {
|
||||
if (buffering_task_id_ != -1) {
|
||||
task_manager_->SetTaskFinished(buffering_task_id_);
|
||||
buffering_task_id_ = -1;
|
||||
// Make sure we don't go beyond the end of the buffer
|
||||
if (scope_chunk_ == scope_chunks_ - 1) {
|
||||
bytes = qMin(static_cast<Engine::Scope::size_type>(map.size - (chunk_size * scope_chunk_)), scope_.size() * sizeof(sample_type));
|
||||
}
|
||||
}
|
||||
|
||||
EngineBase::OutputDetailsList GstEngine::GetOutputsList() const {
|
||||
|
||||
EngineBase::OutputDetailsList ret;
|
||||
|
||||
PluginDetailsList plugins = GetPluginList("Sink/Audio");
|
||||
|
||||
for (const PluginDetails &plugin : plugins) {
|
||||
OutputDetails output;
|
||||
output.name = plugin.name;
|
||||
output.description = plugin.description;
|
||||
if (plugin.name == kAutoSink) output.iconname = "soundcard";
|
||||
else if ((plugin.name == kALSASink) || (plugin.name == kOSS4Sink) || (plugin.name == kOSS4Sink)) output.iconname = "alsa";
|
||||
else if (plugin.name== kJackAudioSink) output.iconname = "jack";
|
||||
else if (plugin.name == kPulseSink) output.iconname = "pulseaudio";
|
||||
else if ((plugin.name == kA2DPSink) || (plugin.name == kAVDTPSink)) output.iconname = "bluetooth";
|
||||
else output.iconname = "soundcard";
|
||||
ret.append(output);
|
||||
else {
|
||||
bytes = qMin(static_cast<Engine::Scope::size_type>(chunk_size), scope_.size() * sizeof(sample_type));
|
||||
}
|
||||
|
||||
return ret;
|
||||
scope_chunk_++;
|
||||
memcpy(dest, source, bytes);
|
||||
|
||||
gst_buffer_unmap(latest_buffer_, &map);
|
||||
|
||||
if (scope_chunk_ == scope_chunks_) {
|
||||
gst_buffer_unref(latest_buffer_);
|
||||
latest_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool GstEngine::ALSADeviceSupport(const QString &name) {
|
||||
return (name == kALSASink);
|
||||
}
|
||||
|
||||
bool GstEngine::PulseDeviceSupport(const QString &name) {
|
||||
return (name == kPulseSink);
|
||||
}
|
||||
|
||||
bool GstEngine::DirectSoundDeviceSupport(const QString &name) {
|
||||
return (name == kDirectSoundSink);
|
||||
}
|
||||
|
||||
bool GstEngine::OSXAudioDeviceSupport(const QString &name) {
|
||||
return (name == kOSXAudioSink);
|
||||
}
|
||||
bool GstEngine::CustomDeviceSupport(const QString &name) {
|
||||
return (name == kALSASink || name == kOpenALSASink || name == kOSSSink || name == kOSS4Sink || name == kA2DPSink || name == kAVDTPSink);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
|
||||
* Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> *
|
||||
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> *
|
||||
* Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> *
|
||||
* Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
@ -65,34 +66,8 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
GstEngine(TaskManager *task_manager);
|
||||
~GstEngine();
|
||||
|
||||
static const char *kAutoSink;
|
||||
|
||||
bool Init();
|
||||
void EnsureInitialised() { initialising_.waitForFinished(); }
|
||||
void InitialiseGStreamer();
|
||||
void SetEnvironment();
|
||||
|
||||
OutputDetailsList GetOutputsList() const;
|
||||
|
||||
qint64 position_nanosec() const;
|
||||
qint64 length_nanosec() const;
|
||||
Engine::State state() const;
|
||||
const Engine::Scope &scope(int chunk_length);
|
||||
|
||||
GstElement *CreateElement(const QString &factoryName, GstElement *bin = 0, bool fatal = true, bool showerror = true);
|
||||
|
||||
// BufferConsumer
|
||||
void ConsumeBuffer(GstBuffer *buffer, int pipeline_id);
|
||||
|
||||
bool IsEqualizerEnabled() { return equalizer_enabled_; }
|
||||
|
||||
static bool ALSADeviceSupport(const QString &name);
|
||||
static bool PulseDeviceSupport(const QString &name);
|
||||
static bool DirectSoundDeviceSupport(const QString &name);
|
||||
static bool OSXAudioDeviceSupport(const QString &name);
|
||||
static bool CustomDeviceSupport(const QString &name);
|
||||
|
||||
public slots:
|
||||
void StartPreloading(const QUrl &url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Load(const QUrl &, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(quint64 offset_nanosec);
|
||||
@ -100,6 +75,28 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
void Pause();
|
||||
void Unpause();
|
||||
void Seek(quint64 offset_nanosec);
|
||||
protected:
|
||||
void SetVolumeSW(uint percent);
|
||||
|
||||
public:
|
||||
qint64 position_nanosec() const;
|
||||
qint64 length_nanosec() const;
|
||||
const Engine::Scope &scope(int chunk_length);
|
||||
|
||||
OutputDetailsList GetOutputsList() const;
|
||||
QString DefaultOutput() { return kAutoSink; }
|
||||
bool CustomDeviceSupport(const QString &name);
|
||||
|
||||
void EnsureInitialised() { initialising_.waitForFinished(); }
|
||||
void InitialiseGStreamer();
|
||||
void SetEnvironment();
|
||||
|
||||
GstElement *CreateElement(const QString &factoryName, GstElement *bin = 0, bool fatal = true, bool showerror = true);
|
||||
void ConsumeBuffer(GstBuffer *buffer, int pipeline_id);
|
||||
|
||||
public slots:
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
/** Set whether equalizer is enabled */
|
||||
void SetEqualizerEnabled(bool);
|
||||
@ -110,8 +107,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
/** Set Stereo balance, range -1.0f..1.0f */
|
||||
void SetStereoBalance(float value);
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
void AddBufferConsumer(GstBufferConsumer *consumer);
|
||||
void RemoveBufferConsumer(GstBufferConsumer *consumer);
|
||||
|
||||
@ -120,7 +115,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void SetVolumeSW(uint percent);
|
||||
void timerEvent(QTimerEvent*);
|
||||
|
||||
private slots:
|
||||
@ -139,6 +133,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
|
||||
private:
|
||||
|
||||
static const char *kAutoSink;
|
||||
static const char *kALSASink;
|
||||
static const char *kOpenALSASink;
|
||||
static const char *kOSSSink;
|
||||
@ -152,6 +147,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
static const char *kOSXAudioSink;
|
||||
|
||||
PluginDetailsList GetPluginList(const QString &classname) const;
|
||||
QByteArray FixupUrl(const QUrl &url);
|
||||
|
||||
void StartFadeout();
|
||||
void StartFadeoutPause();
|
||||
@ -164,22 +160,17 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
std::shared_ptr<GstEnginePipeline> CreatePipeline(const QByteArray &url, qint64 end_nanosec);
|
||||
|
||||
void UpdateScope(int chunk_length);
|
||||
|
||||
QByteArray FixupUrl(const QUrl &url);
|
||||
|
||||
private:
|
||||
static const qint64 kTimerIntervalNanosec = 1000 *kNsecPerMsec; // 1s
|
||||
static const qint64 kPreloadGapNanosec = 3000 *kNsecPerMsec; // 3s
|
||||
static const qint64 kSeekDelayNanosec = 100 *kNsecPerMsec; // 100msec
|
||||
static const qint64 kTimerIntervalNanosec = 1000 * kNsecPerMsec; // 1s
|
||||
static const qint64 kPreloadGapNanosec = 3000 * kNsecPerMsec; // 3s
|
||||
static const qint64 kSeekDelayNanosec = 100 * kNsecPerMsec; // 100msec
|
||||
|
||||
TaskManager *task_manager_;
|
||||
int buffering_task_id_;
|
||||
|
||||
QFuture<void> initialising_;
|
||||
|
||||
QString sink_;
|
||||
QVariant device_;
|
||||
|
||||
std::shared_ptr<GstEnginePipeline> current_pipeline_;
|
||||
std::shared_ptr<GstEnginePipeline> fadeout_pipeline_;
|
||||
std::shared_ptr<GstEnginePipeline> fadeout_pause_pipeline_;
|
||||
@ -189,22 +180,10 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
|
||||
|
||||
GstBuffer *latest_buffer_;
|
||||
|
||||
bool equalizer_enabled_;
|
||||
int equalizer_preamp_;
|
||||
QList<int> equalizer_gains_;
|
||||
float stereo_balance_;
|
||||
|
||||
bool rg_enabled_;
|
||||
int rg_mode_;
|
||||
float rg_preamp_;
|
||||
bool rg_compression_;
|
||||
|
||||
qint64 buffer_duration_nanosec_;
|
||||
|
||||
int buffer_min_fill_;
|
||||
|
||||
bool mono_playback_;
|
||||
|
||||
mutable bool can_decode_success_;
|
||||
mutable bool can_decode_last_;
|
||||
|
||||
|
@ -62,9 +62,8 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
||||
engine_(engine),
|
||||
id_(sId++),
|
||||
valid_(false),
|
||||
sink_(GstEngine::kAutoSink),
|
||||
segment_start_(0),
|
||||
segment_start_received_(false),
|
||||
output_(""),
|
||||
device_(""),
|
||||
eq_enabled_(false),
|
||||
eq_preamp_(0),
|
||||
stereo_balance_(0.0f),
|
||||
@ -76,6 +75,8 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
||||
buffer_min_fill_(33),
|
||||
buffering_(false),
|
||||
mono_playback_(false),
|
||||
segment_start_(0),
|
||||
segment_start_received_(false),
|
||||
end_offset_nanosec_(-1),
|
||||
next_beginning_offset_nanosec_(-1),
|
||||
next_end_offset_nanosec_(-1),
|
||||
@ -108,9 +109,9 @@ GstEnginePipeline::GstEnginePipeline(GstEngine *engine)
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::set_output_device(const QString &sink, const QVariant &device) {
|
||||
void GstEnginePipeline::set_output_device(const QString &output, const QVariant &device) {
|
||||
|
||||
sink_ = sink;
|
||||
output_ = output;
|
||||
device_ = device;
|
||||
|
||||
}
|
||||
@ -194,12 +195,16 @@ bool GstEnginePipeline::InitAudioBin() {
|
||||
|
||||
// Audio bin
|
||||
audiobin_ = gst_bin_new("audiobin");
|
||||
if (!audiobin_) return false;
|
||||
|
||||
// Create the sink
|
||||
audiosink_ = engine_->CreateElement(sink_, audiobin_);
|
||||
if (!audiosink_) return false;
|
||||
audiosink_ = engine_->CreateElement(output_, audiobin_);
|
||||
if (!audiosink_) {
|
||||
gst_object_unref(GST_OBJECT(audiobin_));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device") && device_.isValid()) {
|
||||
if (device_.isValid() && g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device")) {
|
||||
switch (device_.type()) {
|
||||
case QVariant::Int:
|
||||
g_object_set(G_OBJECT(audiosink_), "device", device_.toInt(), nullptr);
|
||||
@ -238,6 +243,7 @@ bool GstEnginePipeline::InitAudioBin() {
|
||||
convert = engine_->CreateElement("audioconvert", audiobin_);
|
||||
|
||||
if (!queue_ || !audioconvert_ || !tee || !probe_queue || !probe_converter || !probe_sink || !audio_queue || !volume_ || !audioscale_ || !convert) {
|
||||
gst_object_unref(GST_OBJECT(audiobin_));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -179,15 +179,9 @@ signals:
|
||||
|
||||
// General settings for the pipeline
|
||||
bool valid_;
|
||||
QString sink_;
|
||||
QString output_;
|
||||
QVariant device_;
|
||||
|
||||
// These get called when there is a new audio buffer available
|
||||
QList<GstBufferConsumer*> buffer_consumers_;
|
||||
QMutex buffer_consumers_mutex_;
|
||||
qint64 segment_start_;
|
||||
bool segment_start_received_;
|
||||
|
||||
// Equalizer
|
||||
bool eq_enabled_;
|
||||
int eq_preamp_;
|
||||
@ -210,6 +204,12 @@ signals:
|
||||
bool buffering_;
|
||||
|
||||
bool mono_playback_;
|
||||
|
||||
// These get called when there is a new audio buffer available
|
||||
QList<GstBufferConsumer*> buffer_consumers_;
|
||||
QMutex buffer_consumers_mutex_;
|
||||
qint64 segment_start_;
|
||||
bool segment_start_received_;
|
||||
|
||||
// The URL that is currently playing, and the URL that is to be preloaded when the current track is close to finishing.
|
||||
QByteArray url_;
|
||||
|
@ -62,7 +62,7 @@ std::unique_ptr<T> GetProperty(const AudioDeviceID& device_id, const AudioObject
|
||||
|
||||
|
||||
OsxDeviceFinder::OsxDeviceFinder()
|
||||
: DeviceFinder("osxaudio", "osxaudiosink") {
|
||||
: DeviceFinder("osxaudio", { "osxaudio", "osx", "osxaudiosink"} ) {
|
||||
}
|
||||
|
||||
QList<DeviceFinder::Device> OsxDeviceFinder::ListDevices() {
|
||||
|
@ -1,29 +1,35 @@
|
||||
/* This file is part of Strawberry.
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine
|
||||
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QTimer>
|
||||
|
||||
#include "phononengine.h"
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
PhononEngine::PhononEngine(TaskManager *task_manager)
|
||||
: media_object_(new Phonon::MediaObject(this)),
|
||||
@ -66,7 +72,7 @@ bool PhononEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool f
|
||||
bool PhononEngine::Play(quint64 offset_nanosec) {
|
||||
|
||||
// The seek happens in PhononStateChanged - phonon doesn't seem to change currentTime() if we seek before we start playing :S
|
||||
seek_offset_ = offset_nanosec;
|
||||
seek_offset_ = (offset_nanosec / kNsecPerMsec);
|
||||
|
||||
media_object_->play();
|
||||
return true;
|
||||
@ -113,7 +119,8 @@ uint PhononEngine::length() const {
|
||||
}
|
||||
|
||||
void PhononEngine::Seek(quint64 offset_nanosec) {
|
||||
media_object_->seek(offset_nanosec);
|
||||
int offset = (offset_nanosec / kNsecPerMsec);
|
||||
media_object_->seek(offset);
|
||||
}
|
||||
|
||||
void PhononEngine::SetVolumeSW(uint volume) {
|
||||
@ -144,13 +151,34 @@ void PhononEngine::StateTimeoutExpired() {
|
||||
}
|
||||
|
||||
qint64 PhononEngine::position_nanosec() const {
|
||||
|
||||
return 0;
|
||||
if (state() == Engine::Empty) return 0;
|
||||
const qint64 result = (position() * kNsecPerMsec);
|
||||
return qint64(qMax(0ll, result));
|
||||
|
||||
}
|
||||
|
||||
qint64 PhononEngine::length_nanosec() const {
|
||||
|
||||
return 0;
|
||||
|
||||
if (state() == Engine::Empty) return 0;
|
||||
const qint64 result = end_nanosec_ - beginning_nanosec_;
|
||||
if (result > 0) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
// Get the length from the pipeline if we don't know.
|
||||
return (length() * kNsecPerMsec);
|
||||
}
|
||||
}
|
||||
|
||||
EngineBase::OutputDetailsList PhononEngine::GetOutputsList() const {
|
||||
OutputDetailsList ret;
|
||||
OutputDetails output;
|
||||
output.name = "none";
|
||||
output.description = "Configured by the system";
|
||||
output.iconname = "soundcard";
|
||||
ret << output;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool PhononEngine::CustomDeviceSupport(const QString &name) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
/* This file is part of Strawberry.
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine
|
||||
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PHONONENGINE_H
|
||||
#define PHONONENGINE_H
|
||||
@ -39,6 +43,8 @@ class PhononEngine : public Engine::Base {
|
||||
~PhononEngine();
|
||||
|
||||
bool Init();
|
||||
|
||||
OutputDetailsList GetOutputsList() const;
|
||||
|
||||
bool CanDecode(const QUrl &url);
|
||||
|
||||
@ -56,6 +62,9 @@ class PhononEngine : public Engine::Base {
|
||||
|
||||
qint64 position_nanosec() const;
|
||||
qint64 length_nanosec() const;
|
||||
|
||||
QString DefaultOutput() { return ""; }
|
||||
bool CustomDeviceSupport(const QString &name);
|
||||
|
||||
protected:
|
||||
void SetVolumeSW( uint percent );
|
||||
|
@ -34,7 +34,7 @@
|
||||
#include "devicefinder.h"
|
||||
#include "pulsedevicefinder.h"
|
||||
|
||||
PulseDeviceFinder::PulseDeviceFinder() : DeviceFinder("pulseaudio", "pulsesink"), mainloop_(nullptr), context_(nullptr) {
|
||||
PulseDeviceFinder::PulseDeviceFinder() : DeviceFinder("pulseaudio", {"pulseaudio", "pulse", "pulsesink"} ), mainloop_(nullptr), context_(nullptr) {
|
||||
}
|
||||
|
||||
bool PulseDeviceFinder::Initialise() {
|
||||
|
@ -1,18 +1,22 @@
|
||||
/* This file is part of Clementine.
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine
|
||||
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@ -24,22 +28,42 @@
|
||||
#include <QByteArray>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/logging.h"
|
||||
#include "engine_fwd.h"
|
||||
#include "enginebase.h"
|
||||
#include "enginetype.h"
|
||||
#include "vlcengine.h"
|
||||
#include "vlcscopedref.h"
|
||||
|
||||
VLCEngine *VLCEngine::sInstance = NULL;
|
||||
VLCEngine *VLCEngine::sInstance = nullptr;
|
||||
|
||||
VLCEngine::VLCEngine(TaskManager *task_manager)
|
||||
: instance_(NULL),
|
||||
player_(NULL),
|
||||
scope_data_(4096),
|
||||
state_(Engine::Empty)
|
||||
: instance_(nullptr),
|
||||
player_(nullptr),
|
||||
state_(Engine::Empty),
|
||||
scope_data_(4096)
|
||||
{
|
||||
|
||||
Init();
|
||||
|
||||
}
|
||||
|
||||
VLCEngine::~VLCEngine() {
|
||||
|
||||
#if 1
|
||||
libvlc_media_player_stop(player_);
|
||||
libvlc_media_player_release(player_);
|
||||
libvlc_release(instance_);
|
||||
HandleErrors();
|
||||
|
||||
}
|
||||
|
||||
bool VLCEngine::Init() {
|
||||
|
||||
type_ = Engine::VLC;
|
||||
|
||||
/* FIXME: Do we need this?
|
||||
static const char *const args[] = {
|
||||
"-I", "dummy", // Don't use any interface
|
||||
"--ignore-config", // Don't use VLC's config
|
||||
@ -47,22 +71,22 @@ VLCEngine::VLCEngine(TaskManager *task_manager)
|
||||
"--verbose=2", // be much more verbose then normal for debugging purpose
|
||||
|
||||
// Our scope plugin
|
||||
"--audio-filter=clementine_scope",
|
||||
"--audio-filter=strawberry_scope",
|
||||
"--no-plugins-cache",
|
||||
|
||||
// Try to stop audio stuttering
|
||||
"--file-caching=500", // msec
|
||||
"--http-caching=500",
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||
#ifdef HAVE_ALSA_
|
||||
"--aout=alsa", // The default, pulseaudio, is buggy
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
*/
|
||||
|
||||
// Create the VLC instance
|
||||
//libvlc_exception_init(&exception_);
|
||||
instance_ = libvlc_new(sizeof(args) / sizeof(args[0]), args);
|
||||
instance_ = libvlc_new(0, nullptr);
|
||||
//instance_ = libvlc_new(sizeof(args) / sizeof(args[0]), args);
|
||||
HandleErrors();
|
||||
|
||||
// Create the media player
|
||||
@ -84,17 +108,200 @@ VLCEngine::VLCEngine(TaskManager *task_manager)
|
||||
HandleErrors();
|
||||
|
||||
sInstance = this;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
VLCEngine::~VLCEngine() {
|
||||
|
||||
bool VLCEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
// Create the media object
|
||||
VlcScopedRef<libvlc_media_t> media(libvlc_media_new_location(instance_, url.toEncoded().constData()));
|
||||
|
||||
libvlc_media_player_set_media(player_, media);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool VLCEngine::Play(quint64 offset_nanosec) {
|
||||
|
||||
// Set audio output
|
||||
if (!output_.isEmpty() || output_ != "auto") {
|
||||
int result = libvlc_audio_output_set(player_, output_.toUtf8().constData());
|
||||
if (result != 0) qLog(Error) << "Failed to set output.";
|
||||
}
|
||||
|
||||
// Set audio device
|
||||
if (device_.isValid() && device_.type() == QVariant::String && !device_.toString().isEmpty()) {
|
||||
libvlc_audio_output_device_set(player_, nullptr, device_.toString().toLocal8Bit().data());
|
||||
}
|
||||
|
||||
int result = libvlc_media_player_play(player_);
|
||||
if (result != 0) return false;
|
||||
|
||||
Seek(offset_nanosec);
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::Stop(bool stop_after) {
|
||||
|
||||
libvlc_media_player_stop(player_);
|
||||
libvlc_media_player_release(player_);
|
||||
libvlc_release(instance_);
|
||||
HandleErrors();
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::Pause() {
|
||||
|
||||
libvlc_media_player_pause(player_);
|
||||
HandleErrors();
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::Unpause() {
|
||||
|
||||
libvlc_media_player_play(player_);
|
||||
HandleErrors();
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::Seek(quint64 offset_nanosec) {
|
||||
|
||||
int offset = (offset_nanosec / kNsecPerMsec);
|
||||
|
||||
uint len = length();
|
||||
if (len == 0) return;
|
||||
|
||||
float pos = float(offset) / len;
|
||||
|
||||
libvlc_media_player_set_position(player_, pos);
|
||||
HandleErrors();
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::SetVolumeSW(uint percent) {
|
||||
|
||||
libvlc_audio_set_volume(player_, percent);
|
||||
HandleErrors();
|
||||
}
|
||||
|
||||
qint64 VLCEngine::position_nanosec() const {
|
||||
if (state() == Engine::Empty) return 0;
|
||||
const qint64 result = (position() * kNsecPerMsec);
|
||||
return qint64(qMax(0ll, result));
|
||||
|
||||
}
|
||||
|
||||
qint64 VLCEngine::length_nanosec() const {
|
||||
if (state() == Engine::Empty) return 0;
|
||||
const qint64 result = (end_nanosec_ - beginning_nanosec_);
|
||||
if (result > 0) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
// Get the length from the pipeline if we don't know.
|
||||
return (length() * kNsecPerMsec);
|
||||
}
|
||||
}
|
||||
|
||||
const Engine::Scope &VLCEngine::Scope() {
|
||||
|
||||
QMutexLocker l(&scope_mutex_);
|
||||
|
||||
// Leave the scope unchanged if there's not enough data
|
||||
if (scope_data_.size() < uint(kScopeSize))
|
||||
return scope_;
|
||||
|
||||
// Take the samples off the front of the circular buffer
|
||||
for (uint i=0 ; i<uint(kScopeSize) ; ++i)
|
||||
scope_[i] = scope_data_[i] * (1 << 15);
|
||||
|
||||
// Remove the samples from the buffer. Unfortunately I think this is O(n) :(
|
||||
scope_data_.rresize(qMax(0, int(scope_data_.size()) - kScopeSize*2));
|
||||
|
||||
return scope_;
|
||||
|
||||
}
|
||||
|
||||
EngineBase::OutputDetailsList VLCEngine::GetOutputsList() const {
|
||||
|
||||
OutputDetailsList ret;
|
||||
|
||||
PluginDetailsList plugins = GetPluginList();
|
||||
for (const PluginDetails &plugin : plugins) {
|
||||
OutputDetails output;
|
||||
output.name = plugin.name;
|
||||
output.description = plugin.description;
|
||||
if (plugin.name == "auto") output.iconname = "soundcard";
|
||||
else if ((plugin.name == "alsa")||(plugin.name == "oss")) output.iconname = "alsa";
|
||||
else if (plugin.name== "jack") output.iconname = "jack";
|
||||
else if (plugin.name == "pulse") output.iconname = "pulseaudio";
|
||||
else if (plugin.name == "afile") output.iconname = "document-new";
|
||||
else output.iconname = "soundcard";
|
||||
ret.append(output);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
bool VLCEngine::CustomDeviceSupport(const QString &name) {
|
||||
return (name == "auto" ? false : true);
|
||||
}
|
||||
|
||||
uint VLCEngine::position() const {
|
||||
|
||||
bool is_playing = libvlc_media_player_is_playing(player_);
|
||||
HandleErrors();
|
||||
|
||||
if (!is_playing) return 0;
|
||||
|
||||
float pos = libvlc_media_player_get_position(player_);
|
||||
HandleErrors();
|
||||
|
||||
return (pos * length());
|
||||
|
||||
}
|
||||
|
||||
uint VLCEngine::length() const {
|
||||
|
||||
bool is_playing = libvlc_media_player_is_playing(player_);
|
||||
HandleErrors();
|
||||
|
||||
if (!is_playing) return 0;
|
||||
|
||||
libvlc_time_t len = libvlc_media_player_get_length(player_);
|
||||
HandleErrors();
|
||||
|
||||
return len;
|
||||
|
||||
}
|
||||
|
||||
bool VLCEngine::CanDecode(const QUrl &url) {
|
||||
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
void VLCEngine::SetScopeData(float *data, int size) {
|
||||
|
||||
if (!sInstance) return;
|
||||
|
||||
QMutexLocker l(&sInstance->scope_mutex_);
|
||||
|
||||
// This gets called by our VLC plugin. Just push the data on to the end of the circular buffer and let it get consumed by scope()
|
||||
for (int i=0 ; i<size ; ++i) {
|
||||
sInstance->scope_data_.push_back(data[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::HandleErrors() const {
|
||||
}
|
||||
|
||||
void VLCEngine::AttachCallback(libvlc_event_manager_t *em, libvlc_event_type_t type, libvlc_callback_t callback) {
|
||||
|
||||
libvlc_event_attach(em, type, callback, this);
|
||||
@ -133,167 +340,37 @@ void VLCEngine::StateChangedCallback(const libvlc_event_t *e, void *data) {
|
||||
|
||||
}
|
||||
|
||||
bool VLCEngine::Init() {
|
||||
EngineBase::PluginDetailsList VLCEngine::GetPluginList() const {
|
||||
|
||||
type_ = Engine::VLC;
|
||||
|
||||
return true;
|
||||
}
|
||||
PluginDetailsList ret;
|
||||
libvlc_audio_output_t *audio_output_list = libvlc_audio_output_list_get(instance_);
|
||||
|
||||
bool VLCEngine::CanDecode(const QUrl &url) {
|
||||
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VLCEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
|
||||
|
||||
// Create the media object
|
||||
VlcScopedRef<libvlc_media_t> media(libvlc_media_new_location(instance_, url.toEncoded().constData()));
|
||||
//if (libvlc_exception_raised(&exception_))
|
||||
//return false;
|
||||
|
||||
libvlc_media_player_set_media(player_, media);
|
||||
//if (libvlc_exception_raised(&exception_))
|
||||
//return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool VLCEngine::Play(quint64 offset_nanosec) {
|
||||
|
||||
libvlc_media_player_play(player_);
|
||||
//if (libvlc_exception_raised(&exception_))
|
||||
//return false;
|
||||
|
||||
Seek(offset_nanosec);
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::Stop(bool stop_after) {
|
||||
|
||||
libvlc_media_player_stop(player_);
|
||||
HandleErrors();
|
||||
}
|
||||
|
||||
void VLCEngine::Pause() {
|
||||
|
||||
libvlc_media_player_pause(player_);
|
||||
HandleErrors();
|
||||
}
|
||||
|
||||
void VLCEngine::Unpause() {
|
||||
|
||||
libvlc_media_player_play(player_);
|
||||
HandleErrors();
|
||||
}
|
||||
|
||||
uint VLCEngine::position() const {
|
||||
|
||||
//bool is_playing = libvlc_media_player_is_playing(player_, const_cast<libvlc_exception_t*>(&exception_));
|
||||
bool is_playing = libvlc_media_player_is_playing(player_);
|
||||
HandleErrors();
|
||||
|
||||
if (!is_playing)
|
||||
return 0;
|
||||
|
||||
//float pos = libvlc_media_player_get_position(player_, const_cast<libvlc_exception_t*>(&exception_));
|
||||
float pos = libvlc_media_player_get_position(player_);
|
||||
HandleErrors();
|
||||
|
||||
return pos * length();
|
||||
|
||||
}
|
||||
|
||||
uint VLCEngine::length() const {
|
||||
|
||||
//bool is_playing = libvlc_media_player_is_playing(player_, const_cast<libvlc_exception_t*>(&exception_));
|
||||
bool is_playing = libvlc_media_player_is_playing(player_);
|
||||
HandleErrors();
|
||||
|
||||
if (!is_playing)
|
||||
return 0;
|
||||
|
||||
//libvlc_time_t len = libvlc_media_player_get_length(player_, const_cast<libvlc_exception_t*>(&exception_));
|
||||
libvlc_time_t len = libvlc_media_player_get_length(player_);
|
||||
HandleErrors();
|
||||
|
||||
return len;
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::Seek(quint64 offset_nanosec) {
|
||||
|
||||
uint len = length();
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
float pos = float(offset_nanosec) / len;
|
||||
|
||||
libvlc_media_player_set_position(player_, pos);
|
||||
HandleErrors();
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::SetVolumeSW(uint percent) {
|
||||
|
||||
libvlc_audio_set_volume(player_, percent);
|
||||
HandleErrors();
|
||||
}
|
||||
|
||||
void VLCEngine::HandleErrors() const {
|
||||
|
||||
//if (libvlc_exception_raised(&exception_)) {
|
||||
//qFatal("libvlc error: %s", libvlc_exception_get_message(&exception_));
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
void VLCEngine::SetScopeData(float *data, int size) {
|
||||
|
||||
if (!sInstance)
|
||||
return;
|
||||
|
||||
QMutexLocker l(&sInstance->scope_mutex_);
|
||||
|
||||
// This gets called by our VLC plugin. Just push the data on to the end of
|
||||
// the circular buffer and let it get consumed by scope()
|
||||
for (int i=0 ; i<size ; ++i) {
|
||||
sInstance->scope_data_.push_back(data[i]);
|
||||
{
|
||||
PluginDetails details;
|
||||
details.name = "auto";
|
||||
details.description = "Automatically detected";
|
||||
ret << details;
|
||||
}
|
||||
}
|
||||
|
||||
const Engine::Scope& VLCEngine::Scope() {
|
||||
for (libvlc_audio_output_t *audio_output = audio_output_list ; audio_output ; audio_output = audio_output->p_next) {
|
||||
PluginDetails details;
|
||||
details.name = QString::fromUtf8(audio_output->psz_name);
|
||||
details.description = QString::fromUtf8(audio_output->psz_description);
|
||||
ret << details;
|
||||
}
|
||||
|
||||
libvlc_audio_output_list_release(audio_output_list);
|
||||
|
||||
QMutexLocker l(&scope_mutex_);
|
||||
|
||||
// Leave the scope unchanged if there's not enough data
|
||||
if (scope_data_.size() < uint(kScopeSize))
|
||||
return scope_;
|
||||
|
||||
// Take the samples off the front of the circular buffer
|
||||
for (uint i=0 ; i<uint(kScopeSize) ; ++i)
|
||||
scope_[i] = scope_data_[i] * (1 << 15);
|
||||
|
||||
// Remove the samples from the buffer. Unfortunately I think this is O(n) :(
|
||||
scope_data_.rresize(qMax(0, int(scope_data_.size()) - kScopeSize*2));
|
||||
|
||||
return scope_;
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
qint64 VLCEngine::position_nanosec() const {
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
qint64 VLCEngine::length_nanosec() const {
|
||||
|
||||
return 0;
|
||||
void VLCEngine::GetDevicesList(QString output) const {
|
||||
|
||||
libvlc_audio_output_device_t *audio_output_device_list = libvlc_audio_output_device_list_get(instance_, output_.toUtf8().constData());
|
||||
for (libvlc_audio_output_device_t *audio_device = audio_output_device_list ; audio_device ; audio_device = audio_device->p_next) {
|
||||
qLog(Debug) << audio_device->psz_device << audio_device->psz_description;
|
||||
}
|
||||
libvlc_audio_output_device_list_release(audio_output_device_list);
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
/* This file is part of Clementine.
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine
|
||||
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef VLCENGINE_H
|
||||
#define VLCENGINE_H
|
||||
@ -44,49 +48,44 @@ class VLCEngine : public Engine::Base {
|
||||
~VLCEngine();
|
||||
|
||||
bool Init();
|
||||
|
||||
virtual qint64 position_nanosec() const;
|
||||
virtual qint64 length_nanosec() const;
|
||||
|
||||
bool CanDecode( const QUrl &url );
|
||||
|
||||
Engine::State state() const { return state_; }
|
||||
bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(quint64 offset_nanosec);
|
||||
void Stop(bool stop_after = false);
|
||||
void Pause();
|
||||
void Unpause();
|
||||
|
||||
Engine::State state() const { return state_; }
|
||||
uint position() const;
|
||||
uint length() const;
|
||||
|
||||
void Seek(quint64 offset_nanosec);
|
||||
|
||||
static void SetScopeData(float* data, int size);
|
||||
const Engine::Scope& Scope();
|
||||
|
||||
protected:
|
||||
void SetVolumeSW(uint percent);
|
||||
public:
|
||||
virtual qint64 position_nanosec() const;
|
||||
virtual qint64 length_nanosec() const;
|
||||
const Engine::Scope& Scope();
|
||||
|
||||
OutputDetailsList GetOutputsList() const;
|
||||
QString DefaultOutput() { return ""; }
|
||||
bool CustomDeviceSupport(const QString &name);
|
||||
|
||||
private:
|
||||
libvlc_instance_t *instance_;
|
||||
libvlc_media_player_t *player_;
|
||||
Engine::State state_;
|
||||
boost::circular_buffer<float> scope_data_;
|
||||
|
||||
static VLCEngine *sInstance;
|
||||
QMutex scope_mutex_;
|
||||
|
||||
uint position() const;
|
||||
uint length() const;
|
||||
bool CanDecode(const QUrl &url);
|
||||
static void SetScopeData(float* data, int size);
|
||||
void HandleErrors() const;
|
||||
void AttachCallback(libvlc_event_manager_t* em, libvlc_event_type_t type, libvlc_callback_t callback);
|
||||
static void StateChangedCallback(const libvlc_event_t* e, void* data);
|
||||
|
||||
private:
|
||||
// The callbacks need access to this
|
||||
static VLCEngine *sInstance;
|
||||
PluginDetailsList GetPluginList() const;
|
||||
void GetDevicesList(QString output) const;
|
||||
|
||||
// VLC bits and pieces
|
||||
//libvlc_exception_t exception_;
|
||||
libvlc_instance_t *instance_;
|
||||
libvlc_media_player_t *player_;
|
||||
|
||||
// Our clementine_scope VLC plugin puts data in here
|
||||
QMutex scope_mutex_;
|
||||
boost::circular_buffer<float> scope_data_;
|
||||
|
||||
Engine::State state_;
|
||||
};
|
||||
|
||||
#endif // VLCENGINE_H
|
||||
|
@ -1,18 +1,22 @@
|
||||
/* This file is part of Clementine.
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine
|
||||
* Copyright 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef VLCSCOPEDREF_H
|
||||
#define VLCSCOPEDREF_H
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,10 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2004,5 Max Howell <max.howell@methylblue.com> *
|
||||
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
|
||||
* Copyright (C) 2005 Christophe Thommeret <hftom@free.fr> *
|
||||
* (C) 2005 Ian Monroe <ian@monroe.nu> *
|
||||
* (C) 2005-2006 Mark Kretschmann <markey@web.de> *
|
||||
* (C) 2004-2005 Max Howell <max.howell@methylblue.com> *
|
||||
* (C) 2003-2004 J. Kofler <kaffeine@gmx.net> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
@ -8,11 +13,17 @@
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef XINE_ENGINE_H
|
||||
#define XINE_ENGINE_H
|
||||
#ifndef XINEENGINE_H
|
||||
#define XINEENGINE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <xine.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
@ -22,24 +33,20 @@
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <xine.h>
|
||||
}
|
||||
|
||||
#include "engine_fwd.h"
|
||||
#include "enginebase.h"
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
class TaskManager;
|
||||
class PruneScopeThread;
|
||||
class XineFader;
|
||||
class XineOutFader;
|
||||
|
||||
class XineEvent : public QEvent {
|
||||
public:
|
||||
enum EventType {
|
||||
PlaybackFinished = QEvent::User + 1,
|
||||
PlaybackFinished,
|
||||
InfoMessage,
|
||||
StatusMessage,
|
||||
MetaInfoChanged,
|
||||
@ -47,159 +54,119 @@ public:
|
||||
LastFMTrackChanged,
|
||||
};
|
||||
|
||||
XineEvent(EventType type, void* data = NULL) : QEvent(QEvent::Type(type)), data_(data) {}
|
||||
XineEvent(EventType type, void* data = nullptr) : QEvent(QEvent::Type(type)), data_(data) {}
|
||||
|
||||
void setData(void* data) { data_ = data; }
|
||||
void* data() const { return data_; }
|
||||
void setData(void *data) { data_ = data; }
|
||||
void *data() const { return data_; }
|
||||
|
||||
private:
|
||||
void* data_;
|
||||
void *data_;
|
||||
};
|
||||
|
||||
class PruneScopeThread;
|
||||
|
||||
class XineEngine : public Engine::Base {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
XineEngine(TaskManager *task_manager);
|
||||
~XineEngine();
|
||||
XineEngine(TaskManager *task_manager);
|
||||
~XineEngine();
|
||||
|
||||
friend class Fader;
|
||||
friend class OutFader;
|
||||
friend class PruneScopeThread;
|
||||
bool Init();
|
||||
Engine::State state() const;
|
||||
bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
bool Play(quint64 offset_nanosec);
|
||||
void Stop(bool stop_after = false);
|
||||
void Pause();
|
||||
void Unpause();
|
||||
void Seek(quint64 offset_nanosec);
|
||||
void SetVolumeSW(uint );
|
||||
|
||||
virtual bool Init();
|
||||
qint64 position_nanosec() const;
|
||||
qint64 length_nanosec() const;
|
||||
|
||||
virtual qint64 position_nanosec() const;
|
||||
virtual qint64 length_nanosec() const;
|
||||
const Engine::Scope& scope(int chunk_length);
|
||||
|
||||
virtual bool CanDecode( const QUrl &);
|
||||
virtual bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
|
||||
virtual bool Play(quint64 offset_nanosec);
|
||||
virtual void Stop(bool stop_after = false);
|
||||
virtual void Pause();
|
||||
virtual void Unpause();
|
||||
virtual uint position() const;
|
||||
virtual uint length() const;
|
||||
virtual void Seek(quint64 offset_nanosec);
|
||||
QString DefaultOutput() { return "auto"; }
|
||||
OutputDetailsList GetOutputsList() const;
|
||||
bool CustomDeviceSupport(const QString &name);
|
||||
|
||||
virtual bool metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b);
|
||||
virtual bool getAudioCDContents(const QString &device, QList<QUrl> &urls);
|
||||
virtual bool flushBuffer();
|
||||
void ReloadSettings();
|
||||
|
||||
virtual Engine::State state() const;
|
||||
virtual const Engine::Scope& scope(int chunk_length);
|
||||
void SetEnvironment();
|
||||
|
||||
virtual void setEqualizerEnabled( bool );
|
||||
virtual void setEqualizerParameters( int preamp, const QList<int>& );
|
||||
virtual void SetVolumeSW( uint );
|
||||
virtual void fadeOut( uint fadeLength, bool* terminate, bool exiting = false );
|
||||
uint length() const;
|
||||
uint position() const;
|
||||
|
||||
static void XineEventListener( void*, const xine_event_t* );
|
||||
virtual bool event( QEvent* );
|
||||
bool CanDecode(const QUrl &);
|
||||
|
||||
virtual void playlistChanged();
|
||||
virtual void ReloadSettings();
|
||||
bool MetaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b);
|
||||
bool GetAudioCDContents(const QString &device, QList<QUrl> &urls);
|
||||
bool FlushBuffer();
|
||||
|
||||
Engine::SimpleMetaBundle fetchMetaData() const;
|
||||
void SetEqualizerEnabled(bool enabled);
|
||||
void SetEqualizerParameters(int preamp, const QList<int>&);
|
||||
|
||||
virtual bool lastFmProxyRequired();
|
||||
void FadeOut(uint fadeLength, bool* terminate, bool exiting = false);
|
||||
|
||||
bool makeNewStream();
|
||||
bool ensureStream();
|
||||
static void XineEventListener(void*, const xine_event_t*);
|
||||
bool event(QEvent*);
|
||||
|
||||
void determineAndShowErrorMessage(); //call after failure to load/play
|
||||
|
||||
//static void SetOutput(QString output, QString device);
|
||||
Engine::SimpleMetaBundle fetchMetaData() const;
|
||||
|
||||
xine_t *xine_;
|
||||
xine_stream_t *stream_;
|
||||
xine_audio_port_t *audioPort_;
|
||||
xine_event_queue_t *eventQueue_;
|
||||
xine_post_t *post_;
|
||||
bool MakeNewStream();
|
||||
bool EnsureStream();
|
||||
|
||||
int64_t currentVpts_;
|
||||
float preamp_;
|
||||
void DetermineAndShowErrorMessage(); //call after failure to load/play
|
||||
|
||||
bool stopFader_;
|
||||
bool fadeOutRunning_;
|
||||
// Simple accessors
|
||||
|
||||
QString currentAudioPlugin_; //to see if audio plugin has been changed need to save these for when the audio plugin is changed and xine reloaded
|
||||
QString currentAudioDevice_;
|
||||
bool equalizerEnabled_;
|
||||
int intPreamp_;
|
||||
QList<int> equalizerGains_;
|
||||
|
||||
QMutex initMutex_;
|
||||
|
||||
bool fadeoutOnExit_;
|
||||
bool fadeoutEnabled_;
|
||||
bool crossfadeEnabled_;
|
||||
int fadeoutDuration_;
|
||||
int xfadeLength_;
|
||||
bool xfadeNextTrack_;
|
||||
QUrl url_;
|
||||
|
||||
PruneScopeThread* prune_;
|
||||
|
||||
mutable Engine::SimpleMetaBundle currentBundle_;
|
||||
|
||||
XineEngine();
|
||||
|
||||
OutputDetailsList GetOutputsList() const;
|
||||
PluginDetailsList GetPluginList() const;
|
||||
static bool ALSADeviceSupport(const QString &name);
|
||||
xine_stream_t *stream() { return stream_; }
|
||||
float preamp() { return preamp_; }
|
||||
bool stop_fader() { return stop_fader_; }
|
||||
void set_stop_fader(bool stop_fader) { stop_fader_ = stop_fader; }
|
||||
|
||||
private:
|
||||
|
||||
|
||||
static const char *kAutoOutput;
|
||||
|
||||
xine_t *xine_;
|
||||
xine_stream_t *stream_;
|
||||
xine_audio_port_t *audioport_;
|
||||
xine_event_queue_t *eventqueue_;
|
||||
xine_post_t *post_;
|
||||
float preamp_;
|
||||
bool stop_fader_;
|
||||
bool fadeout_running_;
|
||||
std::unique_ptr<PruneScopeThread> prune_;
|
||||
|
||||
QUrl url_;
|
||||
|
||||
static int last_error_;
|
||||
static time_t last_error_time_;
|
||||
|
||||
uint log_buffer_count_ = 0;
|
||||
uint log_scope_call_count_ = 1; // Prevent divideByZero
|
||||
uint log_no_suitable_buffer_ = 0;
|
||||
|
||||
std::unique_ptr<XineFader> s_fader_;
|
||||
std::unique_ptr<XineOutFader> s_outfader_;
|
||||
|
||||
int int_preamp_;
|
||||
QMutex init_mutex_;
|
||||
int64_t current_vpts_;
|
||||
QList<int> equalizer_gains_;
|
||||
int fade_length_;
|
||||
bool fade_next_track_;
|
||||
|
||||
mutable Engine::SimpleMetaBundle current_bundle_;
|
||||
|
||||
PluginDetailsList GetPluginList() const;
|
||||
|
||||
private slots:
|
||||
void PruneScope();
|
||||
void PruneScope();
|
||||
|
||||
signals:
|
||||
void resetConfig(xine_t *xine);
|
||||
void InfoMessage(const QString&);
|
||||
void LastFmTrackChange();
|
||||
};
|
||||
|
||||
class Fader : public QThread {
|
||||
|
||||
XineEngine *engine_;
|
||||
xine_t *xine_;
|
||||
xine_stream_t *decrease_;
|
||||
xine_stream_t *increase_;
|
||||
xine_audio_port_t *port_;
|
||||
xine_post_t *post_;
|
||||
uint fadeLength_;
|
||||
bool paused_;
|
||||
bool terminated_;
|
||||
|
||||
virtual void run();
|
||||
|
||||
public:
|
||||
Fader( XineEngine *, uint fadeLengthMs );
|
||||
~Fader();
|
||||
void pause();
|
||||
void resume();
|
||||
void finish();
|
||||
};
|
||||
|
||||
class OutFader : public QThread {
|
||||
|
||||
XineEngine *engine_;
|
||||
bool terminated_;
|
||||
uint fadeLength_;
|
||||
|
||||
virtual void run();
|
||||
|
||||
public:
|
||||
OutFader( XineEngine *, uint fadeLengthMs );
|
||||
~OutFader();
|
||||
|
||||
void finish();
|
||||
void InfoMessage(const QString&);
|
||||
};
|
||||
|
||||
class PruneScopeThread : public QThread {
|
||||
@ -207,10 +174,10 @@ public:
|
||||
PruneScopeThread(XineEngine *parent);
|
||||
|
||||
protected:
|
||||
virtual void run();
|
||||
void run();
|
||||
|
||||
private:
|
||||
XineEngine* engine_;
|
||||
XineEngine *engine_;
|
||||
|
||||
};
|
||||
|
||||
|
150
src/engine/xinefader.cpp
Normal file
150
src/engine/xinefader.cpp
Normal 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
70
src/engine/xinefader.h
Normal 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
|
@ -11,6 +11,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "xinescope.h"
|
||||
#include <xine/post.h>
|
||||
#include <xine/xine_internal.h>
|
||||
@ -29,54 +31,53 @@ struct scope_plugin_s {
|
||||
* post plugin functions *
|
||||
*************************/
|
||||
|
||||
static int scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode ) {
|
||||
static int scope_port_open(xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode) {
|
||||
|
||||
#define port ((post_audio_port_t*)port_gen)
|
||||
#define this ((scope_plugin_t*)((post_audio_port_t*)port_gen)->post)
|
||||
|
||||
_x_post_rewire( (post_plugin_t*)port->post );
|
||||
_x_post_inc_usage( port );
|
||||
_x_post_rewire((post_plugin_t*)port->post);
|
||||
_x_post_inc_usage(port);
|
||||
|
||||
port->stream = stream;
|
||||
port->bits = bits;
|
||||
port->rate = rate;
|
||||
port->mode = mode;
|
||||
|
||||
this->channels = _x_ao_mode2channels( mode );
|
||||
this->channels = _x_ao_mode2channels(mode);
|
||||
|
||||
return port->original_port->open( port->original_port, stream, bits, rate, mode );
|
||||
return port->original_port->open(port->original_port, stream, bits, rate, mode);
|
||||
}
|
||||
|
||||
static void scope_port_close( xine_audio_port_t *port_gen, xine_stream_t *stream ) {
|
||||
static void scope_port_close(xine_audio_port_t *port_gen, xine_stream_t *stream) {
|
||||
|
||||
MyNode *node;
|
||||
|
||||
/* ensure the buffers are deleted during the next XineEngine::timerEvent() */
|
||||
for( node = this->list->next; node != this->list; node = node->next )
|
||||
for(node = this->list->next; node != this->list; node = node->next)
|
||||
node->vpts = node->vpts_end = -1;
|
||||
|
||||
port->stream = NULL;
|
||||
port->original_port->close( port->original_port, stream );
|
||||
port->original_port->close(port->original_port, stream);
|
||||
|
||||
_x_post_dec_usage( port );
|
||||
_x_post_dec_usage(port);
|
||||
}
|
||||
|
||||
static void scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream ) {
|
||||
/* FIXME With 8-bit samples the scope won't work correctly. For a special 8-bit code path,
|
||||
the sample size could be checked like this: if( port->bits == 8 ) */
|
||||
static void scope_port_put_buffer(xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream) {
|
||||
/* FIXME With 8-bit samples the scope won't work correctly. For a special 8-bit code path, the sample size could be checked like this: if(port->bits == 8) */
|
||||
|
||||
const int num_samples = buf->num_frames * this->channels;
|
||||
metronom_t *myMetronom = &this->metronom;
|
||||
MyNode *new_node;
|
||||
|
||||
/* I keep my own metronom because xine wouldn't for some reason */
|
||||
memcpy( &this->metronom, stream->metronom, sizeof(metronom_t) );
|
||||
memcpy(&this->metronom, stream->metronom, sizeof(metronom_t));
|
||||
|
||||
new_node = malloc( sizeof(MyNode) );
|
||||
new_node->vpts = myMetronom->got_audio_samples( myMetronom, buf->vpts, buf->num_frames );
|
||||
new_node = malloc(sizeof(MyNode));
|
||||
new_node->vpts = myMetronom->got_audio_samples(myMetronom, buf->vpts, buf->num_frames);
|
||||
new_node->num_frames = buf->num_frames;
|
||||
new_node->mem = malloc( num_samples * 2 );
|
||||
memcpy( new_node->mem, buf->mem, num_samples * 2 );
|
||||
new_node->mem = malloc(num_samples * 2);
|
||||
memcpy(new_node->mem, buf->mem, num_samples * 2);
|
||||
|
||||
{
|
||||
int64_t
|
||||
@ -88,7 +89,7 @@ static void scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *
|
||||
new_node->vpts_end = K;
|
||||
}
|
||||
|
||||
port->original_port->put_buffer( port->original_port, buf, stream );
|
||||
port->original_port->put_buffer(port->original_port, buf, stream);
|
||||
|
||||
/* finally we should append the current buffer to the list
|
||||
* this is thread-safe due to the way we handle the list in the GUI thread */
|
||||
@ -99,7 +100,7 @@ static void scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *
|
||||
#undef this
|
||||
}
|
||||
|
||||
static void scope_dispose( post_plugin_t *this ) {
|
||||
static void scope_dispose(post_plugin_t *this) {
|
||||
MyNode *list = ((scope_plugin_t*)this)->list;
|
||||
MyNode *prev;
|
||||
MyNode *node = list;
|
||||
@ -108,15 +109,15 @@ static void scope_dispose( post_plugin_t *this ) {
|
||||
do {
|
||||
prev = node->next;
|
||||
|
||||
free( node->mem );
|
||||
free( node );
|
||||
free(node->mem);
|
||||
free(node);
|
||||
|
||||
node = prev;
|
||||
}
|
||||
while( node != list );
|
||||
while(node != list);
|
||||
|
||||
|
||||
free( this );
|
||||
free(this);
|
||||
}
|
||||
|
||||
|
||||
@ -124,19 +125,19 @@ static void scope_dispose( post_plugin_t *this ) {
|
||||
* plugin init function *
|
||||
************************/
|
||||
|
||||
xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) {
|
||||
xine_post_t* scope_plugin_new(xine_t *xine, xine_audio_port_t *audio_target) {
|
||||
|
||||
scope_plugin_t *scope_plugin = calloc( 1, sizeof(scope_plugin_t) );
|
||||
post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin;
|
||||
scope_plugin_t *scope_plugin = calloc(1, sizeof(scope_plugin_t));
|
||||
post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin;
|
||||
|
||||
{
|
||||
post_in_t *input;
|
||||
post_out_t *output;
|
||||
post_audio_port_t *port;
|
||||
|
||||
_x_post_init( post_plugin, 1, 0 );
|
||||
_x_post_init(post_plugin, 1, 0);
|
||||
|
||||
port = _x_post_intercept_audio_port( post_plugin, audio_target, &input, &output );
|
||||
port = _x_post_intercept_audio_port(post_plugin, audio_target, &input, &output);
|
||||
port->new_port.open = scope_port_open;
|
||||
port->new_port.close = scope_port_close;
|
||||
port->new_port.put_buffer = scope_port_put_buffer;
|
||||
@ -147,28 +148,26 @@ xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) {
|
||||
post_plugin->dispose = scope_dispose;
|
||||
}
|
||||
|
||||
/* code is straight from xine_init_post()
|
||||
can't use that function as it only dlopens the plugins
|
||||
and our plugin is statically linked in */
|
||||
// code is straight from xine_init_post() can't use that function as it only dlopens the plugins and our plugin is statically linked in
|
||||
|
||||
post_plugin->running_ticket = xine->port_ticket;
|
||||
post_plugin->xine = xine;
|
||||
|
||||
/* scope_plugin_t init */
|
||||
scope_plugin->list = calloc( 1, sizeof(MyNode) );
|
||||
scope_plugin->list = calloc(1, sizeof(MyNode));
|
||||
scope_plugin->list->next = scope_plugin->list;
|
||||
|
||||
return &post_plugin->xine_post;
|
||||
}
|
||||
|
||||
MyNode* scope_plugin_list( void *post ) {
|
||||
MyNode* scope_plugin_list(void *post) {
|
||||
return ((scope_plugin_t*)post)->list;
|
||||
}
|
||||
|
||||
int scope_plugin_channels( void *post ) {
|
||||
int scope_plugin_channels(void *post) {
|
||||
return ((scope_plugin_t*)post)->channels;
|
||||
}
|
||||
|
||||
metronom_t* scope_plugin_metronom( void *post ) {
|
||||
metronom_t* scope_plugin_metronom(void *post) {
|
||||
return &((scope_plugin_t*)post)->metronom;
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ void GlobalShortcuts::Register() {
|
||||
}
|
||||
|
||||
bool GlobalShortcuts::IsMacAccessibilityEnabled() const {
|
||||
#ifdef Q_OS_MAC
|
||||
#ifdef Q_OS_MACOS
|
||||
return static_cast<MacGlobalShortcutBackend*>(system_backend_)->IsAccessibilityEnabled();
|
||||
#else
|
||||
return true;
|
||||
@ -141,7 +141,7 @@ bool GlobalShortcuts::IsMacAccessibilityEnabled() const {
|
||||
}
|
||||
|
||||
void GlobalShortcuts::ShowMacAccessibilityDialog() {
|
||||
#ifdef Q_OS_MAC
|
||||
#ifdef Q_OS_MACOS
|
||||
static_cast<MacGlobalShortcutBackend*>(system_backend_)->ShowAccessibilityDialog();
|
||||
#endif
|
||||
}
|
||||
|
@ -1127,7 +1127,7 @@ QString Playlist::column_name(Column column) {
|
||||
case Column_Samplerate: return tr("Sample rate");
|
||||
case Column_Bitdepth: return tr("Bit depth");
|
||||
case Column_SamplerateBitdepth: return tr("Sample rate B");
|
||||
case Column_Bitrate: return tr("Bit rate");
|
||||
case Column_Bitrate: return tr("Bitrate");
|
||||
|
||||
case Column_Filename: return tr("File name");
|
||||
case Column_BaseFilename: return tr("File name (without path)");
|
||||
|
@ -56,13 +56,17 @@ PlaylistFilter::PlaylistFilter(QObject *parent)
|
||||
column_names_["genre"] = Playlist::Column_Genre;
|
||||
column_names_["comment"] = Playlist::Column_Comment;
|
||||
column_names_["bitrate"] = Playlist::Column_Bitrate;
|
||||
column_names_["samplerate"] = Playlist::Column_Samplerate;
|
||||
column_names_["bitdepth"] = Playlist::Column_Bitdepth;
|
||||
column_names_["filename"] = Playlist::Column_Filename;
|
||||
|
||||
numerical_columns_ << Playlist::Column_Length
|
||||
<< Playlist::Column_Track
|
||||
<< Playlist::Column_Disc
|
||||
<< Playlist::Column_Year
|
||||
<< Playlist::Column_Bitrate;
|
||||
<< Playlist::Column_Bitrate
|
||||
<< Playlist::Column_Samplerate
|
||||
<< Playlist::Column_Bitdepth;
|
||||
}
|
||||
|
||||
PlaylistFilter::~PlaylistFilter() {
|
||||
|
@ -37,17 +37,12 @@
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/player.h"
|
||||
#include "core/logging.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "engine/enginedevice.h"
|
||||
#include "engine/enginetype.h"
|
||||
#include "engine/devicefinder.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
# include "engine/gstengine.h"
|
||||
#endif
|
||||
#ifdef HAVE_XINE
|
||||
# include "engine/xineengine.h"
|
||||
#endif
|
||||
#include "widgets/lineedit.h"
|
||||
#include "widgets/stickyslider.h"
|
||||
#include "dialogs/errordialog.h"
|
||||
@ -56,25 +51,13 @@
|
||||
#include "ui_backendsettingspage.h"
|
||||
|
||||
const char *BackendSettingsPage::kSettingsGroup = "Backend";
|
||||
const char *BackendSettingsPage::EngineText_Xine = "Xine";
|
||||
const char *BackendSettingsPage::EngineText_GStreamer = "GStreamer";
|
||||
const char *BackendSettingsPage::EngineText_Phonon = "Phonon";
|
||||
const char *BackendSettingsPage::EngineText_VLC = "VLC";
|
||||
|
||||
BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog) : SettingsPage(dialog), ui_(new Ui_BackendSettingsPage) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
setWindowIcon(IconLoader::Load("soundcard"));
|
||||
|
||||
connect(ui_->combobox_engine, SIGNAL(currentIndexChanged(int)), SLOT(EngineChanged(int)));
|
||||
connect(ui_->combobox_output, SIGNAL(currentIndexChanged(int)), SLOT(OutputChanged(int)));
|
||||
connect(ui_->combobox_device, SIGNAL(currentIndexChanged(int)), SLOT(DeviceSelectionChanged(int)));
|
||||
connect(ui_->lineedit_device, SIGNAL(textChanged(const QString &)), SLOT(DeviceStringChanged()));
|
||||
|
||||
connect(ui_->slider_bufferminfill, SIGNAL(valueChanged(int)), SLOT(BufferMinFillChanged(int)));
|
||||
ui_->label_bufferminfillvalue->setMinimumWidth(QFontMetrics(ui_->label_bufferminfillvalue->font()).width("WW%"));
|
||||
|
||||
connect(ui_->stickslider_replaygainpreamp, SIGNAL(valueChanged(int)), SLOT(RgPreampChanged(int)));
|
||||
ui_->label_replaygainpreamp->setMinimumWidth(QFontMetrics(ui_->label_replaygainpreamp->font()).width("-WW.W dB"));
|
||||
|
||||
RgPreampChanged(ui_->stickslider_replaygainpreamp->value());
|
||||
@ -93,29 +76,29 @@ BackendSettingsPage::~BackendSettingsPage() {
|
||||
void BackendSettingsPage::Load() {
|
||||
|
||||
configloaded_ = false;
|
||||
engineloaded_ = Engine::None;
|
||||
engineloaded_ = false;
|
||||
xinewarning_ = false;
|
||||
|
||||
Engine::EngineType enginetype = Engine::EngineTypeFromName(s_.value("engine", EngineText_GStreamer).toString());
|
||||
Engine::EngineType enginetype = Engine::EngineTypeFromName(s_.value("engine", EngineDescription(Engine::GStreamer)).toString());
|
||||
|
||||
ui_->combobox_engine->clear();
|
||||
#ifdef HAVE_XINE
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("xine"), EngineText_Xine, Engine::Xine);
|
||||
#endif
|
||||
#ifdef HAVE_GSTREAMER
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("gstreamer"), EngineText_GStreamer, Engine::GStreamer);
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("gstreamer"), EngineDescription(Engine::GStreamer), Engine::GStreamer);
|
||||
#endif
|
||||
#ifdef HAVE_PHONON
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("speaker"), EngineText_Phonon, Engine::Phonon);
|
||||
#ifdef HAVE_XINE
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("xine"), EngineDescription(Engine::Xine), Engine::Xine);
|
||||
#endif
|
||||
#ifdef HAVE_VLC
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("vlc"), EngineText_VLC, Engine::VLC);
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("vlc"), EngineDescription(Engine::VLC), Engine::VLC);
|
||||
#endif
|
||||
#ifdef HAVE_PHONON
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("speaker"), EngineDescription(Engine::Phonon), Engine::Phonon);
|
||||
#endif
|
||||
|
||||
configloaded_ = true;
|
||||
enginereset_ = false;
|
||||
|
||||
ui_->combobox_engine->setCurrentIndex(ui_->combobox_engine->findData(enginetype));
|
||||
if (enginetype != engineloaded_) Load_Engine(enginetype);
|
||||
if (EngineInitialised()) Load_Engine(enginetype);
|
||||
|
||||
ui_->spinbox_bufferduration->setValue(s_.value("bufferduration", 4000).toInt());
|
||||
ui_->checkbox_monoplayback->setChecked(s_.value("monoplayback", false).toBool());
|
||||
@ -126,16 +109,49 @@ void BackendSettingsPage::Load() {
|
||||
ui_->stickslider_replaygainpreamp->setValue(s_.value("rgpreamp", 0.0).toDouble() * 10 + 150);
|
||||
ui_->checkbox_replaygaincompression->setChecked(s_.value("rgcompression", true).toBool());
|
||||
|
||||
//if (dialog()->app()->player()->engine()->state() != Engine::Empty) ui_->combobox_engine->setEnabled(false);
|
||||
if (!EngineInitialised()) return;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
ui_->combobox_engine->setEnabled(false);
|
||||
#endif
|
||||
if (engine()->state() == Engine::Empty) {
|
||||
if (ui_->combobox_engine->count() > 1) ui_->combobox_engine->setEnabled(true);
|
||||
else ui_->combobox_engine->setEnabled(false);
|
||||
ResetWarning();
|
||||
}
|
||||
else {
|
||||
ui_->combobox_engine->setEnabled(false);
|
||||
ShowWarning("Engine can't be switched while playing. Close settings and reopen to change engine.");
|
||||
}
|
||||
|
||||
ConnectSignals();
|
||||
|
||||
configloaded_ = true;
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::ConnectSignals() {
|
||||
|
||||
connect(ui_->combobox_engine, SIGNAL(currentIndexChanged(int)), SLOT(EngineChanged(int)));
|
||||
connect(ui_->combobox_output, SIGNAL(currentIndexChanged(int)), SLOT(OutputChanged(int)));
|
||||
connect(ui_->combobox_device, SIGNAL(currentIndexChanged(int)), SLOT(DeviceSelectionChanged(int)));
|
||||
connect(ui_->lineedit_device, SIGNAL(textChanged(const QString &)), SLOT(DeviceStringChanged()));
|
||||
connect(ui_->slider_bufferminfill, SIGNAL(valueChanged(int)), SLOT(BufferMinFillChanged(int)));
|
||||
connect(ui_->stickslider_replaygainpreamp, SIGNAL(valueChanged(int)), SLOT(RgPreampChanged(int)));
|
||||
|
||||
}
|
||||
|
||||
bool BackendSettingsPage::EngineInitialised() {
|
||||
|
||||
if (!engine() || engine()->type() == Engine::None) {
|
||||
errordialog_.ShowMessage("Engine is not initialized! Please restart.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::Load_Engine(Engine::EngineType enginetype) {
|
||||
|
||||
if (!EngineInitialised()) return;
|
||||
|
||||
QString output = s_.value("output", "").toString();
|
||||
QVariant device = s_.value("device", QVariant());
|
||||
|
||||
@ -150,84 +166,99 @@ void BackendSettingsPage::Load_Engine(Engine::EngineType enginetype) {
|
||||
|
||||
ui_->groupbox_replaygain->setEnabled(false);
|
||||
|
||||
// If a engine is loaded (!= Engine::None) AND engine has been switched reset output and device.
|
||||
if ((engineloaded_ != Engine::None) && (engineloaded_ != enginetype)) {
|
||||
output = "";
|
||||
device = QVariant();
|
||||
s_.setValue("output", "");
|
||||
s_.setValue("device", QVariant());
|
||||
}
|
||||
|
||||
if (dialog()->app()->player()->engine()->type() != enginetype) {
|
||||
if (engine()->type() != enginetype) {
|
||||
dialog()->app()->player()->CreateEngine(enginetype);
|
||||
dialog()->app()->player()->ReloadSettings();
|
||||
dialog()->app()->player()->Init();
|
||||
}
|
||||
|
||||
switch(enginetype) {
|
||||
#ifdef HAVE_XINE
|
||||
case Engine::Xine:
|
||||
Xine_Load(output, device);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_GSTREAMER
|
||||
case Engine::GStreamer:
|
||||
Gst_Load(output, device);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_PHONON
|
||||
case Engine::Phonon:
|
||||
Phonon_Load(output, device);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_VLC
|
||||
case Engine::VLC:
|
||||
VLC_Load(output, device);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
QString msg = QString("Missing engine %1!").arg(Engine::EngineNameFromType(enginetype));
|
||||
errordialog_.ShowMessage(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
engineloaded_ = true;
|
||||
|
||||
Load_Output(output, device);
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::Load_Device(QString output, QVariant device, bool alsa, bool pulseaudio, bool directsound, bool osxaudio, bool custom) {
|
||||
void BackendSettingsPage::Load_Output(QString output, QVariant device) {
|
||||
|
||||
if (!EngineInitialised()) return;
|
||||
|
||||
if (output == "") output = engine()->DefaultOutput();
|
||||
|
||||
ui_->combobox_output->clear();
|
||||
int i = 0;
|
||||
for (const EngineBase::OutputDetails &o : engine()->GetOutputsList()) {
|
||||
i++;
|
||||
ui_->combobox_output->addItem(IconLoader::Load(o.iconname), o.description, QVariant::fromValue(o));
|
||||
}
|
||||
if (i > 0) ui_->combobox_output->setEnabled(true);
|
||||
|
||||
bool found(false);
|
||||
for (int i = 0; i < ui_->combobox_output->count(); ++i) {
|
||||
EngineBase::OutputDetails o = ui_->combobox_output->itemData(i).value<EngineBase::OutputDetails>();
|
||||
if (o.name == output) {
|
||||
ui_->combobox_output->setCurrentIndex(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) { // Output is invalid for this engine, reset to default output.
|
||||
output = engine()->DefaultOutput();
|
||||
for (int i = 0; i < ui_->combobox_output->count(); ++i) {
|
||||
EngineBase::OutputDetails o = ui_->combobox_output->itemData(i).value<EngineBase::OutputDetails>();
|
||||
if (o.name == output) {
|
||||
ui_->combobox_output->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (engine()->type() == Engine::GStreamer) ui_->groupbox_replaygain->setEnabled(true);
|
||||
else ui_->groupbox_replaygain->setEnabled(false);
|
||||
|
||||
if (ui_->combobox_output->count() < 1) {
|
||||
ShowWarning("Engine may take some time to initialize. Close settings and reopen to set output and devices.");
|
||||
}
|
||||
else Load_Device(output, device);
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::Load_Device(QString output, QVariant device) {
|
||||
|
||||
if (!EngineInitialised()) return;
|
||||
|
||||
int devices = 0;
|
||||
DeviceFinder::Device dfdevice;
|
||||
DeviceFinder::Device df_device;
|
||||
|
||||
ui_->combobox_device->clear();
|
||||
ui_->combobox_device->setEnabled(false);
|
||||
ui_->lineedit_device->setText("");
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Automatically select", "");
|
||||
#ifndef Q_OS_WIN32
|
||||
ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Automatically select", QVariant(""));
|
||||
#endif
|
||||
|
||||
if (alsa) ui_->lineedit_device->setEnabled(true);
|
||||
else ui_->lineedit_device->setEnabled(false);
|
||||
|
||||
for (DeviceFinder *f : dialog()->app()->enginedevice()->device_finders_) {
|
||||
if (f->name() == "alsa" && !alsa) continue;
|
||||
if (f->name() == "pulseaudio" && !pulseaudio) continue;
|
||||
if (f->name() == "directsound" && !directsound) continue;
|
||||
if (f->name() == "osxaudio" && !osxaudio) continue;
|
||||
if (!f->outputs().contains(output)) continue;
|
||||
for (const DeviceFinder::Device &d : f->ListDevices()) {
|
||||
devices++;
|
||||
ui_->combobox_device->addItem(IconLoader::Load(d.iconname), d.description, d.value);
|
||||
if (d.value == device) { dfdevice = d; }
|
||||
if (d.value == device) { df_device = d; }
|
||||
}
|
||||
}
|
||||
if (devices > 0) ui_->combobox_device->setEnabled(true);
|
||||
|
||||
if (custom) ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Custom", QVariant(""));
|
||||
if (engine()->CustomDeviceSupport(output)) {
|
||||
ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Custom", QVariant(""));
|
||||
ui_->lineedit_device->setEnabled(true);
|
||||
}
|
||||
else {
|
||||
ui_->lineedit_device->setEnabled(false);
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
if (custom || devices > 0) ui_->combobox_device->setEnabled(true);
|
||||
bool found(false);
|
||||
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
|
||||
QVariant d = ui_->combobox_device->itemData(i).value<QVariant>();
|
||||
if (dfdevice.value == d) {
|
||||
if (df_device.value == d) {
|
||||
ui_->combobox_device->setCurrentIndex(i);
|
||||
found = true;
|
||||
break;
|
||||
@ -235,143 +266,45 @@ void BackendSettingsPage::Load_Device(QString output, QVariant device, bool alsa
|
||||
}
|
||||
|
||||
// This allows a custom ALSA device string ie: "hw:0,0" even if it is not listed.
|
||||
if (found == false && custom && device.type() == QVariant::String && !device.toString().isEmpty()) {
|
||||
if (engine()->CustomDeviceSupport(output) && device.type() == QVariant::String && !device.toString().isEmpty()) {
|
||||
ui_->lineedit_device->setText(device.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void BackendSettingsPage::Gst_Load(QString output, QVariant device) {
|
||||
|
||||
if (output == "") output = GstEngine::kAutoSink;
|
||||
|
||||
if (dialog()->app()->player()->engine()->type() != Engine::GStreamer) {
|
||||
errordialog_.ShowMessage("GStramer not initialized! Please restart.");
|
||||
return;
|
||||
}
|
||||
|
||||
GstEngine *gstengine = qobject_cast<GstEngine*>(dialog()->app()->player()->engine());
|
||||
|
||||
ui_->combobox_output->clear();
|
||||
int i = 0;
|
||||
for (const EngineBase::OutputDetails &o : gstengine->GetOutputsList()) {
|
||||
i++;
|
||||
ui_->combobox_output->addItem(IconLoader::Load(o.iconname), o.description, QVariant::fromValue(o));
|
||||
}
|
||||
if (i > 0) ui_->combobox_output->setEnabled(true);
|
||||
|
||||
for (int i = 0; i < ui_->combobox_output->count(); ++i) {
|
||||
EngineBase::OutputDetails o = ui_->combobox_output->itemData(i).value<EngineBase::OutputDetails>();
|
||||
if (o.name == output) {
|
||||
ui_->combobox_output->setCurrentIndex(i);
|
||||
break;
|
||||
if (!found) {
|
||||
bool have_custom(false);
|
||||
int index_custom = 0;
|
||||
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
|
||||
if (ui_->combobox_device->itemText(i) == "Custom") {
|
||||
have_custom = true;
|
||||
index_custom = i;
|
||||
if (ui_->combobox_device->currentText() != "Custom") ui_->combobox_device->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (have_custom) ui_->combobox_device->setItemData(index_custom, QVariant(device.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
engineloaded_=Engine::GStreamer;
|
||||
ui_->groupbox_replaygain->setEnabled(true);
|
||||
|
||||
Load_Device(output, device, GstEngine::ALSADeviceSupport(output), GstEngine::PulseDeviceSupport(output), GstEngine::DirectSoundDeviceSupport(output), GstEngine::OSXAudioDeviceSupport(output), GstEngine::CustomDeviceSupport(output));
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XINE
|
||||
void BackendSettingsPage::Xine_Load(QString output, QVariant device) {
|
||||
|
||||
if (output == "") output = "auto";
|
||||
|
||||
if (dialog()->app()->player()->engine()->type() != Engine::Xine) {
|
||||
errordialog_.ShowMessage("Xine not initialized! Please restart.");
|
||||
return;
|
||||
}
|
||||
XineEngine *xineengine = qobject_cast<XineEngine*>(dialog()->app()->player()->engine());
|
||||
|
||||
ui_->combobox_output->clear();
|
||||
int i = 0;
|
||||
for (const EngineBase::OutputDetails &o : xineengine->GetOutputsList()) {
|
||||
i++;
|
||||
ui_->combobox_output->addItem(IconLoader::Load(o.iconname), o.description, QVariant::fromValue(o));
|
||||
}
|
||||
if (i > 0) ui_->combobox_output->setEnabled(true);
|
||||
|
||||
for (int i = 0; i < ui_->combobox_output->count(); ++i) {
|
||||
EngineBase::OutputDetails o = ui_->combobox_output->itemData(i).value<EngineBase::OutputDetails>();
|
||||
if (o.name == output) {
|
||||
ui_->combobox_output->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
engineloaded_=Engine::Xine;
|
||||
|
||||
Load_Device(output, device);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PHONON
|
||||
void BackendSettingsPage::Phonon_Load(QString output, QVariant device) {
|
||||
|
||||
ui_->combobox_output->clear();
|
||||
ui_->combobox_device->clear();
|
||||
ui_->lineedit_device->setText("");
|
||||
|
||||
engineloaded_=Engine::Phonon;
|
||||
|
||||
Load_Device(output, device);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_VLC
|
||||
void BackendSettingsPage::VLC_Load(QString output, QVariant device) {
|
||||
|
||||
ui_->combobox_output->clear();
|
||||
ui_->combobox_device->clear();
|
||||
ui_->lineedit_device->setText("");
|
||||
|
||||
engineloaded_=Engine::VLC;
|
||||
|
||||
Load_Device(output, device);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
void BackendSettingsPage::Save() {
|
||||
|
||||
s_.setValue("engine", ui_->combobox_engine->itemText(ui_->combobox_engine->currentIndex()).toLower());
|
||||
|
||||
QVariant myVariant = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex());
|
||||
Engine::EngineType enginetype = myVariant.value<Engine::EngineType>();
|
||||
if (!EngineInitialised()) return;
|
||||
|
||||
switch(enginetype) {
|
||||
#ifdef HAVE_XINE
|
||||
case Engine::Xine:
|
||||
Xine_Save();
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_GSTREAMER
|
||||
case Engine::GStreamer:
|
||||
Gst_Save();
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_PHONON
|
||||
case Engine::Phonon:
|
||||
Phonon_Save();
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_VLC
|
||||
case Engine::VLC:
|
||||
VLC_Save();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
QVariant enginetype_v = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex());
|
||||
Engine::EngineType enginetype = enginetype_v.value<Engine::EngineType>();
|
||||
QString output_name;
|
||||
QVariant device_value;
|
||||
|
||||
if (ui_->combobox_output->currentText().isEmpty()) output_name = engine()->DefaultOutput();
|
||||
else {
|
||||
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
|
||||
output_name = output.name;
|
||||
}
|
||||
if (ui_->combobox_device->currentText().isEmpty()) device_value = QVariant();
|
||||
else device_value = ui_->combobox_device->itemData(ui_->combobox_device->currentIndex()).value<QVariant>();
|
||||
|
||||
s_.setValue("device", ui_->combobox_device->itemData(ui_->combobox_device->currentIndex()).value<QVariant>());
|
||||
s_.setValue("engine", EngineName(enginetype));
|
||||
s_.setValue("output", output_name);
|
||||
s_.setValue("device", device_value);
|
||||
|
||||
s_.setValue("bufferduration", ui_->spinbox_bufferduration->value());
|
||||
s_.setValue("monoplayback", ui_->checkbox_monoplayback->isChecked());
|
||||
@ -380,166 +313,103 @@ void BackendSettingsPage::Save() {
|
||||
s_.setValue("rgmode", ui_->combobox_replaygainmode->currentIndex());
|
||||
s_.setValue("rgpreamp", float(ui_->stickslider_replaygainpreamp->value()) / 10 - 15);
|
||||
s_.setValue("rgcompression", ui_->checkbox_replaygaincompression->isChecked());
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_XINE
|
||||
void BackendSettingsPage::Xine_Save() {
|
||||
|
||||
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
|
||||
s_.setValue("output", output.name);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void BackendSettingsPage::Gst_Save() {
|
||||
|
||||
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
|
||||
s_.setValue("output", output.name);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PHONON
|
||||
void BackendSettingsPage::Phonon_Save() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_VLC
|
||||
void BackendSettingsPage::VLC_Save() {
|
||||
}
|
||||
#endif
|
||||
|
||||
void BackendSettingsPage::EngineChanged(int index) {
|
||||
|
||||
if (configloaded_ == false) return;
|
||||
|
||||
if ((engineloaded_ != Engine::None) && (dialog()->app()->player()->engine()->state() != Engine::Empty)) {
|
||||
if (!configloaded_ || !EngineInitialised()) return;
|
||||
|
||||
if (engine()->state() != Engine::Empty) {
|
||||
if (enginereset_ == true) { enginereset_ = false; return; }
|
||||
errordialog_.ShowMessage("Can't switch engine while playing!");
|
||||
enginereset_ = true;
|
||||
ui_->combobox_engine->setCurrentIndex(ui_->combobox_engine->findData(engineloaded_));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QVariant v = ui_->combobox_engine->itemData(index);
|
||||
Engine::EngineType enginetype = v.value<Engine::EngineType>();
|
||||
|
||||
engineloaded_ = false;
|
||||
xinewarning_ = false;
|
||||
ResetWarning();
|
||||
Load_Engine(enginetype);
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::OutputChanged(int index) {
|
||||
|
||||
QVariant v = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex());
|
||||
Engine::EngineType enginetype = v.value<Engine::EngineType>();
|
||||
OutputChanged(index, enginetype);
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::OutputChanged(int index, Engine::EngineType enginetype) {
|
||||
|
||||
switch(enginetype) {
|
||||
case Engine::Xine:
|
||||
#ifdef HAVE_XINE
|
||||
Xine_OutputChanged(index);
|
||||
break;
|
||||
#endif
|
||||
case Engine::GStreamer:
|
||||
#ifdef HAVE_GSTREAMER
|
||||
Gst_OutputChanged(index);
|
||||
break;
|
||||
#endif
|
||||
case Engine::Phonon:
|
||||
#ifdef HAVE_PHONON
|
||||
Phonon_OutputChanged(index);
|
||||
break;
|
||||
#endif
|
||||
case Engine::VLC:
|
||||
#ifdef HAVE_VLC
|
||||
VLC_OutputChanged(index);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_XINE
|
||||
void BackendSettingsPage::Xine_OutputChanged(int index) {
|
||||
if (!configloaded_ || !EngineInitialised()) return;
|
||||
|
||||
EngineBase::OutputDetails output = ui_->combobox_output->itemData(index).value<EngineBase::OutputDetails>();
|
||||
Load_Device(output.name, QVariant());
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void BackendSettingsPage::Gst_OutputChanged(int index) {
|
||||
|
||||
EngineBase::OutputDetails output = ui_->combobox_output->itemData(index).value<EngineBase::OutputDetails>();
|
||||
Load_Device(output.name, QVariant(), GstEngine::ALSADeviceSupport(output.name), GstEngine::PulseDeviceSupport(output.name), GstEngine::DirectSoundDeviceSupport(output.name), GstEngine::OSXAudioDeviceSupport(output.name), GstEngine::CustomDeviceSupport(output.name));
|
||||
if (engine()->type() == Engine::Xine) XineWarning();
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PHONON
|
||||
void BackendSettingsPage::Phonon_OutputChanged(int index) {
|
||||
Load_Device("", QVariant());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_VLC
|
||||
void BackendSettingsPage::VLC_OutputChanged(int index) {
|
||||
Load_Device("", QVariant());
|
||||
}
|
||||
#endif
|
||||
|
||||
void BackendSettingsPage::DeviceSelectionChanged(int index) {
|
||||
|
||||
if (ui_->combobox_device->currentText() == "Custom") {
|
||||
ui_->lineedit_device->setEnabled(true);
|
||||
ui_->combobox_device->setItemData(index, QVariant(ui_->lineedit_device->text()));
|
||||
return;
|
||||
}
|
||||
if (!configloaded_ || !EngineInitialised()) return;
|
||||
|
||||
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
|
||||
QVariant device = ui_->combobox_device->itemData(index).value<QVariant>();
|
||||
if (device.type() == QVariant::String && device.toString().startsWith("hw:", Qt::CaseInsensitive)) {
|
||||
|
||||
if (engine()->CustomDeviceSupport(output.name)) {
|
||||
ui_->lineedit_device->setEnabled(true);
|
||||
ui_->lineedit_device->setText(device.toString());
|
||||
return;
|
||||
if (ui_->combobox_device->currentText() == "Custom") {
|
||||
ui_->combobox_device->setItemData(index, QVariant(ui_->lineedit_device->text()));
|
||||
}
|
||||
else {
|
||||
if (device.type() == QVariant::String) ui_->lineedit_device->setText(device.toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
ui_->lineedit_device->setEnabled(false);
|
||||
if (!ui_->lineedit_device->text().isEmpty()) ui_->lineedit_device->setText("");
|
||||
}
|
||||
|
||||
ui_->lineedit_device->setEnabled(false);
|
||||
ui_->lineedit_device->setText("");
|
||||
if (engine()->type() == Engine::Xine) XineWarning();
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::DeviceStringChanged() {
|
||||
|
||||
if (!configloaded_ || !EngineInitialised()) return;
|
||||
|
||||
EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value<EngineBase::OutputDetails>();
|
||||
bool found(false);
|
||||
|
||||
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
|
||||
QVariant v = ui_->combobox_device->itemData(i).value<QVariant>();
|
||||
if (v.type() != QVariant::String) continue;
|
||||
if (v.toString() == ui_->lineedit_device->text()) {
|
||||
ui_->combobox_device->setCurrentIndex(i);
|
||||
return;
|
||||
QVariant device = ui_->combobox_device->itemData(i).value<QVariant>();
|
||||
if (device.type() != QVariant::String) continue;
|
||||
if (device.toString().isEmpty()) continue;
|
||||
if (device.toString() == ui_->lineedit_device->text()) {
|
||||
if (ui_->combobox_device->currentIndex() != i) ui_->combobox_device->setCurrentIndex(i);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Assume this is a custom alsa device string
|
||||
|
||||
if (ui_->combobox_device->currentText() != "Custom") {
|
||||
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
|
||||
if (ui_->combobox_device->itemText(i) == "Custom") {
|
||||
ui_->combobox_device->setCurrentIndex(i);
|
||||
break;
|
||||
if (engine()->CustomDeviceSupport(output.name)) {
|
||||
ui_->lineedit_device->setEnabled(true);
|
||||
if ((!found) && (ui_->combobox_device->currentText() != "Custom")) {
|
||||
for (int i = 0; i < ui_->combobox_device->count(); ++i) {
|
||||
if (ui_->combobox_device->itemText(i) == "Custom") {
|
||||
ui_->combobox_device->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ui_->combobox_device->currentText() == "Custom") {
|
||||
ui_->combobox_device->setItemData(ui_->combobox_device->currentIndex(), QVariant(ui_->lineedit_device->text()));
|
||||
if ((ui_->lineedit_device->text().isEmpty()) && (ui_->combobox_device->count() > 0) && (ui_->combobox_device->currentIndex() != 0)) ui_->combobox_device->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
if (ui_->combobox_device->currentText() == "Custom") {
|
||||
ui_->combobox_device->setItemData(ui_->combobox_device->currentIndex(), QVariant(ui_->lineedit_device->text()));
|
||||
else {
|
||||
ui_->lineedit_device->setEnabled(false);
|
||||
if (!ui_->lineedit_device->text().isEmpty()) ui_->lineedit_device->setText("");
|
||||
if ((!found) && (ui_->combobox_device->count() > 0) && (ui_->combobox_device->currentIndex() != 0)) ui_->combobox_device->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
}
|
||||
@ -556,3 +426,41 @@ void BackendSettingsPage::RgPreampChanged(int value) {
|
||||
void BackendSettingsPage::BufferMinFillChanged(int value) {
|
||||
ui_->label_bufferminfillvalue->setText(QString::number(value) + "%");
|
||||
}
|
||||
|
||||
void BackendSettingsPage::ShowWarning(QString text) {
|
||||
|
||||
QImage image_logo(":/icons/64x64/dialog-warning.png");
|
||||
QPixmap pixmap_logo(QPixmap::fromImage(image_logo));
|
||||
|
||||
ui_->label_warn_logo->setPixmap(pixmap_logo);
|
||||
|
||||
ui_->label_warn_text->setStyleSheet("QLabel { color: red; }");
|
||||
ui_->label_warn_text->setText("<b>" + text + "</b>");
|
||||
|
||||
ui_->label_warn_logo->setEnabled(true);
|
||||
ui_->label_warn_text->setEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::ResetWarning() {
|
||||
|
||||
ui_->label_warn_logo->clear();
|
||||
ui_->label_warn_text->clear();
|
||||
|
||||
ui_->label_warn_logo->setEnabled(false);
|
||||
ui_->label_warn_text->setEnabled(false);
|
||||
|
||||
}
|
||||
|
||||
void BackendSettingsPage::XineWarning() {
|
||||
|
||||
if (!engineloaded_) return;
|
||||
if (!configloaded_) return;
|
||||
|
||||
if (engine()->type() != Engine::Xine) return;
|
||||
if (xinewarning_) return;
|
||||
|
||||
ShowWarning("You need to restart Strawberry for output/device changes to take affect for Xine.");
|
||||
xinewarning_ = true;
|
||||
|
||||
}
|
||||
|
@ -32,6 +32,11 @@
|
||||
#include "engine/enginetype.h"
|
||||
#include "dialogs/errordialog.h"
|
||||
#include "settingspage.h"
|
||||
#include "settingsdialog.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/player.h"
|
||||
#include "engine/enginebase.h"
|
||||
|
||||
class SettingsDialog;
|
||||
class Ui_BackendSettingsPage;
|
||||
@ -44,14 +49,12 @@ public:
|
||||
~BackendSettingsPage();
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
static const char *EngineText_Xine;
|
||||
static const char *EngineText_GStreamer;
|
||||
static const char *EngineText_Phonon;
|
||||
static const char *EngineText_VLC;
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
EngineBase *engine() const { return dialog()->app()->player()->engine(); }
|
||||
|
||||
private slots:
|
||||
void EngineChanged(int index);
|
||||
void OutputChanged(int index);
|
||||
@ -63,41 +66,24 @@ public:
|
||||
private:
|
||||
Ui_BackendSettingsPage *ui_;
|
||||
|
||||
void ConnectSignals();
|
||||
bool EngineInitialised();
|
||||
|
||||
void EngineChanged(Engine::EngineType enginetype);
|
||||
void OutputChanged(int index, Engine::EngineType enginetype);
|
||||
|
||||
void Load_Engine(Engine::EngineType enginetype);
|
||||
void Load_Device(QString output, QVariant device, bool alsa = false, bool pulseaudio = false, bool directsound = false, bool osxaudio = false, bool custom = false);
|
||||
|
||||
#ifdef HAVE_XINE
|
||||
void Xine_Load(QString output, QVariant device);
|
||||
void Xine_Save();
|
||||
void Xine_OutputChanged(int index);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void Gst_Load(QString output, QVariant device);
|
||||
void Gst_Save();
|
||||
void Gst_OutputChanged(int index);
|
||||
#endif
|
||||
void Load_Output(QString output, QVariant device);
|
||||
void Load_Device(QString output, QVariant device);
|
||||
void ShowWarning(QString text);
|
||||
void ResetWarning();
|
||||
void XineWarning();
|
||||
|
||||
#ifdef HAVE_PHONON
|
||||
void Phonon_Load(QString output, QVariant device);
|
||||
void Phonon_Save();
|
||||
void Phonon_OutputChanged(int index);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_VLC
|
||||
void VLC_Load(QString output, QVariant device);
|
||||
void VLC_Save();
|
||||
void VLC_OutputChanged(int index);
|
||||
#endif
|
||||
|
||||
bool configloaded_;
|
||||
Engine::EngineType engineloaded_;
|
||||
QSettings s_;
|
||||
bool configloaded_;
|
||||
bool engineloaded_;
|
||||
ErrorDialog errordialog_;
|
||||
bool enginereset_;
|
||||
bool xinewarning_;
|
||||
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,12 @@
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_engine">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Engine</string>
|
||||
</property>
|
||||
@ -38,6 +44,12 @@
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Output</string>
|
||||
</property>
|
||||
@ -55,6 +67,12 @@
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Device</string>
|
||||
</property>
|
||||
@ -76,7 +94,7 @@
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -102,14 +120,26 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupbox_buffer">
|
||||
<property name="title">
|
||||
<string>Buffer</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_5">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_bufferduration">
|
||||
<property name="text">
|
||||
<string>Buffer duration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="spinbox_bufferduration">
|
||||
<property name="suffix">
|
||||
<string> ms</string>
|
||||
@ -122,14 +152,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_bufferminfill">
|
||||
<property name="text">
|
||||
<string>Minimum buffer fill</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_buffer">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_bufferminfillvalue">
|
||||
@ -159,16 +189,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="checkbox_monoplayback">
|
||||
<property name="toolTip">
|
||||
<string>Changing mono playback preference will be effective for the next playing songs</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Mono playback</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -273,6 +293,75 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_monoplayback">
|
||||
<property name="toolTip">
|
||||
<string>Changing mono playback preference will be effective for the next playing songs</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Mono playback</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupbox_warning">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_warn_logo">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>70</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_warn_text">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>70</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
@ -43,9 +43,9 @@ PlaybackSettingsPage::PlaybackSettingsPage(SettingsDialog *dialog) : SettingsPag
|
||||
ui_->setupUi(this);
|
||||
setWindowIcon(IconLoader::Load("media-play"));
|
||||
|
||||
connect(ui_->fading_cross, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
|
||||
connect(ui_->fading_out, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
|
||||
connect(ui_->fading_auto, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
|
||||
connect(ui_->checkbox_fadeout_stop, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
|
||||
connect(ui_->checkbox_fadeout_cross, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
|
||||
connect(ui_->checkbox_fadeout_auto, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged()));
|
||||
|
||||
}
|
||||
|
||||
@ -60,14 +60,14 @@ void PlaybackSettingsPage::Load() {
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(kSettingsGroup);
|
||||
ui_->current_glow->setChecked(s.value("glow_effect", true).toBool());
|
||||
ui_->fading_out->setChecked(s.value("FadeoutEnabled", false).toBool());
|
||||
ui_->fading_cross->setChecked(s.value("CrossfadeEnabled", false).toBool());
|
||||
ui_->fading_auto->setChecked(s.value("AutoCrossfadeEnabled", false).toBool());
|
||||
ui_->fading_duration->setValue(s.value("FadeoutDuration", 2000).toInt());
|
||||
ui_->fading_samealbum->setChecked(s.value("NoCrossfadeSameAlbum", true).toBool());
|
||||
ui_->fadeout_pause->setChecked(s.value("FadeoutPauseEnabled", false).toBool());
|
||||
ui_->fading_pause_duration->setValue(s.value("FadeoutPauseDuration", 250).toInt());
|
||||
ui_->checkbox_glowcurrenttrack->setChecked(s.value("glow_effect", true).toBool());
|
||||
ui_->checkbox_fadeout_stop->setChecked(s.value("FadeoutEnabled", false).toBool());
|
||||
ui_->checkbox_fadeout_cross->setChecked(s.value("CrossfadeEnabled", false).toBool());
|
||||
ui_->checkbox_fadeout_auto->setChecked(s.value("AutoCrossfadeEnabled", false).toBool());
|
||||
ui_->checkbox_fadeout_samealbum->setChecked(s.value("NoCrossfadeSameAlbum", true).toBool());
|
||||
ui_->checkbox_fadeout_pauseresume->setChecked(s.value("FadeoutPauseEnabled", false).toBool());
|
||||
ui_->spinbox_fadeduration->setValue(s.value("FadeoutDuration", 2000).toInt());
|
||||
ui_->spinbox_fadeduration_pauseresume->setValue(s.value("FadeoutPauseDuration", 250).toInt());
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
@ -77,19 +77,19 @@ void PlaybackSettingsPage::Save() {
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("glow_effect", ui_->current_glow->isChecked());
|
||||
s.setValue("FadeoutEnabled", ui_->fading_out->isChecked());
|
||||
s.setValue("FadeoutDuration", ui_->fading_duration->value());
|
||||
s.setValue("CrossfadeEnabled", ui_->fading_cross->isChecked());
|
||||
s.setValue("AutoCrossfadeEnabled", ui_->fading_auto->isChecked());
|
||||
s.setValue("NoCrossfadeSameAlbum", ui_->fading_samealbum->isChecked());
|
||||
s.setValue("FadeoutPauseEnabled", ui_->fadeout_pause->isChecked());
|
||||
s.setValue("FadeoutPauseDuration", ui_->fading_pause_duration->value());
|
||||
s.setValue("glow_effect", ui_->checkbox_glowcurrenttrack->isChecked());
|
||||
s.setValue("FadeoutEnabled", ui_->checkbox_fadeout_stop->isChecked());
|
||||
s.setValue("CrossfadeEnabled", ui_->checkbox_fadeout_cross->isChecked());
|
||||
s.setValue("AutoCrossfadeEnabled", ui_->checkbox_fadeout_auto->isChecked());
|
||||
s.setValue("NoCrossfadeSameAlbum", ui_->checkbox_fadeout_samealbum->isChecked());
|
||||
s.setValue("FadeoutPauseEnabled", ui_->checkbox_fadeout_pauseresume->isChecked());
|
||||
s.setValue("FadeoutDuration", ui_->spinbox_fadeduration->value());
|
||||
s.setValue("FadeoutPauseDuration", ui_->spinbox_fadeduration_pauseresume->value());
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
void PlaybackSettingsPage::FadingOptionsChanged() {
|
||||
|
||||
ui_->fading_options->setEnabled(ui_->fading_out->isChecked() || ui_->fading_cross->isChecked() || ui_->fading_auto->isChecked());
|
||||
ui_->widget_fading_options->setEnabled(ui_->checkbox_fadeout_stop->isChecked() || ui_->checkbox_fadeout_cross->isChecked() || ui_->checkbox_fadeout_auto->isChecked());
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="current_glow">
|
||||
<widget class="QCheckBox" name="checkbox_glowcurrenttrack">
|
||||
<property name="text">
|
||||
<string>Show a glowing animation on the current track</string>
|
||||
</property>
|
||||
@ -25,13 +25,13 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<widget class="QGroupBox" name="groupbox_fading">
|
||||
<property name="title">
|
||||
<string>Fading</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fading_out">
|
||||
<widget class="QCheckBox" name="checkbox_fadeout_stop">
|
||||
<property name="text">
|
||||
<string>Fade out when stopping a track</string>
|
||||
</property>
|
||||
@ -41,7 +41,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fading_cross">
|
||||
<widget class="QCheckBox" name="checkbox_fadeout_cross">
|
||||
<property name="text">
|
||||
<string>Cross-fade when changing tracks manually</string>
|
||||
</property>
|
||||
@ -51,14 +51,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fading_auto">
|
||||
<widget class="QCheckBox" name="checkbox_fadeout_auto">
|
||||
<property name="text">
|
||||
<string>Cross-fade when changing tracks automatically</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fading_samealbum">
|
||||
<widget class="QCheckBox" name="checkbox_fadeout_samealbum">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
@ -68,13 +68,22 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="fading_options" native="true">
|
||||
<widget class="QWidget" name="widget_fading_options" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="label_fadingduration_1">
|
||||
<property name="text">
|
||||
<string>Fading duration</string>
|
||||
</property>
|
||||
@ -84,7 +93,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="fading_duration">
|
||||
<widget class="QSpinBox" name="spinbox_fadeduration">
|
||||
<property name="suffix">
|
||||
<string> ms</string>
|
||||
</property>
|
||||
@ -100,7 +109,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<spacer name="horizontalSpacer_1">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
@ -116,16 +125,16 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fadeout_pause">
|
||||
<widget class="QCheckBox" name="checkbox_fadeout_pauseresume">
|
||||
<property name="text">
|
||||
<string>Fade out on pause / fade in on resume</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<layout class="QHBoxLayout" name="layout_fading">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<widget class="QLabel" name="label_fadingduration_2">
|
||||
<property name="text">
|
||||
<string>Fading duration</string>
|
||||
</property>
|
||||
@ -135,7 +144,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="fading_pause_duration">
|
||||
<widget class="QSpinBox" name="spinbox_fadeduration_pauseresume">
|
||||
<property name="suffix">
|
||||
<string> ms</string>
|
||||
</property>
|
||||
@ -183,19 +192,12 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>StickySlider</class>
|
||||
<extends>QSlider</extends>
|
||||
<header>widgets/stickyslider.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>fading_auto</sender>
|
||||
<sender>checkbox_fadeout_auto</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>fading_samealbum</receiver>
|
||||
<receiver>checkbox_fadeout_samealbum</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
|
@ -44,6 +44,8 @@
|
||||
#include <QTreeWidget>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/player.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "widgets/groupediconview.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
|
||||
@ -94,17 +96,14 @@ void SettingsItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
|
||||
}
|
||||
|
||||
|
||||
SettingsDialog::SettingsDialog(Application *app, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
app_(app),
|
||||
//player_(app_->player()),
|
||||
player_(app_->player()),
|
||||
engine_(app_->player()->engine()),
|
||||
model_(app_->collection_model()->directory_model()),
|
||||
//gst_engine_(qobject_cast<GstEngine*>(app_->player()->engine())),
|
||||
//engine_(app_->player()->engine()),
|
||||
appearance_(app_->appearance()),
|
||||
ui_(new Ui_SettingsDialog),
|
||||
//mui_(parent),
|
||||
loading_settings_(false) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
@ -42,8 +42,9 @@
|
||||
class QModelIndex;
|
||||
class QShowEvent;
|
||||
|
||||
class Appearance;
|
||||
class Application;
|
||||
class Player;
|
||||
class Appearance;
|
||||
class CollectionDirectoryModel;
|
||||
class GlobalShortcuts;
|
||||
class SettingsPage;
|
||||
@ -88,6 +89,8 @@ public:
|
||||
bool is_loading_settings() const { return loading_settings_; }
|
||||
|
||||
Application *app() const { return app_; }
|
||||
Player *player() const { return player_; }
|
||||
EngineBase *engine() const { return engine_; }
|
||||
CollectionDirectoryModel *collection_directory_model() const { return model_; }
|
||||
GlobalShortcuts *global_shortcuts_manager() const { return manager_; }
|
||||
Appearance *appearance() const { return appearance_; }
|
||||
@ -122,6 +125,8 @@ private:
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
Player *player_;
|
||||
EngineBase *engine_;
|
||||
CollectionDirectoryModel *model_;
|
||||
GlobalShortcuts *manager_;
|
||||
Appearance *appearance_;
|
||||
|
@ -72,7 +72,7 @@ GlobalShortcutsSettingsPage::~GlobalShortcutsSettingsPage() { delete ui_; }
|
||||
|
||||
bool GlobalShortcutsSettingsPage::IsEnabled() const {
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#ifdef Q_OS_MACOS
|
||||
qLog(Debug) << Utilities::GetMacVersion();
|
||||
if (Utilities::GetMacVersion() < 6) { // Leopard and earlier.
|
||||
return false;
|
||||
|
@ -298,7 +298,6 @@ void StatusView::UpdateSong() {
|
||||
const QueryOptions opt;
|
||||
CollectionBackend::AlbumList albumlist;
|
||||
Engine::EngineType enginetype = app_->player()->engine()->type();
|
||||
QString EngineName = EngineNameFromType(enginetype);
|
||||
|
||||
label_playing_top_->setText("");
|
||||
label_playing_text_->setText("");
|
||||
@ -317,7 +316,7 @@ void StatusView::UpdateSong() {
|
||||
|
||||
if (enginetype != Engine::EngineType::None) {
|
||||
html += QString("<br />");
|
||||
html += QString("Engine: %1<br />").arg(EngineName);
|
||||
html += QString("Engine: %1<br />").arg(EngineName(enginetype));
|
||||
}
|
||||
|
||||
html += QString("<br />");
|
||||
|
Loading…
Reference in New Issue
Block a user