diff --git a/CMakeLists.txt b/CMakeLists.txt index 1731af97..02c9dde6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/ext/libstrawberry-common/core/logging.cpp b/ext/libstrawberry-common/core/logging.cpp index 66ae3b13..eed1a15c 100644 --- a/ext/libstrawberry-common/core/logging.cpp +++ b/ext/libstrawberry-common/core/logging.cpp @@ -44,6 +44,7 @@ static Level sDefaultLevel = Level_Debug; static QMap* sClassLevels = nullptr; static QIODevice *sNullDevice = nullptr; +//const char* kDefaultLogLevels = "*:3"; const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3"; static const char *kMessageHandlerMagic = "__logging_message__"; diff --git a/ext/libstrawberry-common/core/workerpool.h b/ext/libstrawberry-common/core/workerpool.h index 4ed982cd..5d8154c3 100644 --- a/ext/libstrawberry-common/core/workerpool.h +++ b/ext/libstrawberry-common/core/workerpool.h @@ -228,7 +228,7 @@ void WorkerPool::DoStart() { QStringList search_path; search_path << qApp->applicationDirPath(); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS search_path << qApp->applicationDirPath() + "/../PlugIns"; #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e8aeadab..48f336ae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/analyzer/analyzerbase.h b/src/analyzer/analyzerbase.h index bdca9147..051d1651 100644 --- a/src/analyzer/analyzerbase.h +++ b/src/analyzer/analyzerbase.h @@ -13,7 +13,7 @@ #include #include -#ifdef Q_WS_MACX +#ifdef Q_OS_MACOS #include //included for convenience #include //included for convenience #else diff --git a/src/collection/collection.cpp b/src/collection/collection.cpp index b456a3f9..e0e3e2c0 100644 --- a/src/collection/collection.cpp +++ b/src/collection/collection.cpp @@ -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; } diff --git a/src/collection/collection.h b/src/collection/collection.h index 9db2e102..422415c9 100644 --- a/src/collection/collection.h +++ b/src/collection/collection.h @@ -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 full_rescan_revisions_; }; diff --git a/src/collection/collectionmodel.cpp b/src/collection/collectionmodel.cpp index f1530aa5..6fae73dd 100644 --- a/src/collection/collectionmodel.cpp +++ b/src/collection/collectionmodel.cpp @@ -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; diff --git a/src/collection/collectionmodel.h b/src/collection/collectionmodel.h index c7fbcc76..02e5f7f5 100644 --- a/src/collection/collectionmodel.h +++ b/src/collection/collectionmodel.h @@ -99,6 +99,8 @@ class CollectionModel : public SimpleTreeModel { GroupBy_Disc = 12, GroupBy_OriginalYearAlbum = 13, GroupBy_OriginalYear = 14, + GroupBy_Samplerate = 15, + GroupBy_Bitdepth = 16 }; struct Grouping { diff --git a/src/collection/collectionwatcher.cpp b/src/collection/collectionwatcher.cpp index a7a1c26d..01ea85f7 100644 --- a/src/collection/collectionwatcher.cpp +++ b/src/collection/collectionwatcher.cpp @@ -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); diff --git a/src/collection/groupbydialog.cpp b/src/collection/groupbydialog.cpp index 8d24a066..e8ae41ad 100644 --- a/src/collection/groupbydialog.cpp +++ b/src/collection/groupbydialog.cpp @@ -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().find(ui_->first->currentIndex())->group_by, - p_->mapping_.get().find(ui_->second->currentIndex())->group_by, - p_->mapping_.get().find(ui_->third->currentIndex())->group_by) + p_->mapping_.get().find(ui_->combobox_first->currentIndex())->group_by, + p_->mapping_.get().find(ui_->combobox_second->currentIndex())->group_by, + p_->mapping_.get().find(ui_->combobox_third->currentIndex())->group_by) ); QDialog::accept(); } void GroupByDialog::CollectionGroupingChanged(const CollectionModel::Grouping &g) { - ui_->first->setCurrentIndex(p_->mapping_.get().find(g[0])->combo_box_index); - ui_->second->setCurrentIndex(p_->mapping_.get().find(g[1])->combo_box_index); - ui_->third->setCurrentIndex(p_->mapping_.get().find(g[2])->combo_box_index); + ui_->combobox_first->setCurrentIndex(p_->mapping_.get().find(g[0])->combo_box_index); + ui_->combobox_second->setCurrentIndex(p_->mapping_.get().find(g[1])->combo_box_index); + ui_->combobox_third->setCurrentIndex(p_->mapping_.get().find(g[2])->combo_box_index); } diff --git a/src/collection/groupbydialog.ui b/src/collection/groupbydialog.ui index f112437e..b81a999d 100644 --- a/src/collection/groupbydialog.ui +++ b/src/collection/groupbydialog.ui @@ -7,7 +7,7 @@ 0 0 354 - 236 + 246 @@ -19,7 +19,7 @@ - + You can change the way the songs in the collection are organised. @@ -35,14 +35,14 @@ - + First level - + None @@ -103,6 +103,16 @@ Bitrate + + + Sample rate + + + + + Bit depth + + Disc @@ -121,14 +131,14 @@ - + Second level - + None @@ -189,6 +199,16 @@ Bitrate + + + Sample rate + + + + + Bit depth + + Disc @@ -207,14 +227,14 @@ - + Third level - + None @@ -275,6 +295,16 @@ Bitrate + + + Sample rate + + + + + Bit depth + + Disc @@ -309,7 +339,7 @@ - + Qt::Horizontal @@ -321,17 +351,17 @@ - first - second - third - button_box + combobox_first + combobox_second + combobox_third + buttonbox - button_box + buttonbox accepted() GroupByDialog accept() @@ -347,7 +377,7 @@ - button_box + buttonbox rejected() GroupByDialog reject() diff --git a/src/collection/savedgroupingmanager.cpp b/src/collection/savedgroupingmanager.cpp index fe227e56..410f9775 100644 --- a/src/collection/savedgroupingmanager.cpp +++ b/src/collection/savedgroupingmanager.cpp @@ -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"); } diff --git a/src/core/main.cpp b/src/core/main.cpp index 2aaf29f8..42bf54e5 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * 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 #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 + +} diff --git a/src/core/main.h b/src/core/main.h new file mode 100644 index 00000000..eed4f5b1 --- /dev/null +++ b/src/core/main.h @@ -0,0 +1,28 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef MAIN_H +#define MAIN_H + +#include "config.h" + +int main(int argc, char* argv[]); +void main_exit_safe(int ret); + +#endif // MAIN_H diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index f227af35..07e8e973 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -741,7 +741,6 @@ void MainWindow::ReloadAllSettings() { app_->player()->ReloadSettings(); osd_->ReloadSettings(); collection_view_->ReloadSettings(); - app_->player()->engine()->ReloadSettings(); ui_->playlist->view()->ReloadSettings(); } diff --git a/src/core/player.cpp b/src/core/player.cpp index 58fbaf5f..0949f4ae 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -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(); } diff --git a/src/core/utilities.cpp b/src/core/utilities.cpp index fded4840..82d4e319 100644 --- a/src/core/utilities.cpp +++ b/src/core/utilities.cpp @@ -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 power_sources(IOPSCopyPowerSourcesInfo()); ScopedCFTypeRef power_source_list(IOPSCopyPowerSourcesList(power_sources.get())); for (CFIndex i = 0; i < CFArrayGetCount(power_source_list.get()); ++i) { diff --git a/src/device/ilister.cpp b/src/device/ilister.cpp index 38110b22..ed2c2f61 100644 --- a/src/device/ilister.cpp +++ b/src/device/ilister.cpp @@ -58,6 +58,8 @@ void iLister::EventCallback(const idevice_event_t *event, void *context) { case IDEVICE_DEVICE_REMOVE: me->DeviceRemovedCallback(uuid); break; + default: + break; } } diff --git a/src/dialogs/organisedialog.cpp b/src/dialogs/organisedialog.cpp index 50edb1be..4848b466 100644 --- a/src/dialogs/organisedialog.cpp +++ b/src/dialogs/organisedialog.cpp @@ -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 diff --git a/src/engine/alsadevicefinder.cpp b/src/engine/alsadevicefinder.cpp index 2a7e63e3..75483da7 100644 --- a/src/engine/alsadevicefinder.cpp +++ b/src/engine/alsadevicefinder.cpp @@ -35,8 +35,7 @@ #include "alsadevicefinder.h" AlsaDeviceFinder::AlsaDeviceFinder() - : DeviceFinder("alsa", "alsasink") { - + : DeviceFinder("alsa", {"alsa","alsasink"}) { } QList AlsaDeviceFinder::ListDevices() { diff --git a/src/engine/devicefinder.cpp b/src/engine/devicefinder.cpp index 363104ff..6bb2968c 100644 --- a/src/engine/devicefinder.cpp +++ b/src/engine/devicefinder.cpp @@ -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) { diff --git a/src/engine/devicefinder.h b/src/engine/devicefinder.h index eb806c24..10f3aec0 100644 --- a/src/engine/devicefinder.h +++ b/src/engine/devicefinder.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2014, David Sansome + * Copyright 2017, Jonas Kvinge * * 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 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_; }; diff --git a/src/engine/directsounddevicefinder.cpp b/src/engine/directsounddevicefinder.cpp index bdec613f..9b2a2018 100644 --- a/src/engine/directsounddevicefinder.cpp +++ b/src/engine/directsounddevicefinder.cpp @@ -35,7 +35,7 @@ #include "core/logging.h" DirectSoundDeviceFinder::DirectSoundDeviceFinder() - : DeviceFinder("directsound", "directsoundsink") { + : DeviceFinder("directsound", { "directsound", "dsound", "directsoundsink" }) { } QList DirectSoundDeviceFinder::ListDevices() { diff --git a/src/engine/enginebase.cpp b/src/engine/enginebase.cpp index 09462086..c27b41ef 100644 --- a/src/engine/enginebase.cpp +++ b/src/engine/enginebase.cpp @@ -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, - * Copyright 2010, David Sansome + * Copyright 2004 - 2005 Max Howell, + * Copyright 2010 David Sansome + * Copyright 2017 - 2018 Jonas Kvinge * * 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 #include +#include #include #include #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); -} diff --git a/src/engine/enginebase.h b/src/engine/enginebase.h index 68b71b5f..d3133bea 100644 --- a/src/engine/enginebase.h +++ b/src/engine/enginebase.h @@ -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, - * Copyright 2010, David Sansome + * Copyright 2004 - 2005 Max Howell, + * Copyright 2010 David Sansome + * Copyright 2017 - 2018 Jonas Kvinge * * 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 #include #include +#include #include #include @@ -49,52 +51,13 @@ typedef std::vector 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 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 &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 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); diff --git a/src/engine/enginetype.cpp b/src/engine/enginetype.cpp index a3981158..4bd57605 100644 --- a/src/engine/enginetype.cpp +++ b/src/engine/enginetype.cpp @@ -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"); + + } +} + } diff --git a/src/engine/enginetype.h b/src/engine/enginetype.h index 21a1edf9..62977d46 100644 --- a/src/engine/enginetype.h +++ b/src/engine/enginetype.h @@ -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); diff --git a/src/engine/gstengine.cpp b/src/engine/gstengine.cpp index 1e598c01..ddfead50 100644 --- a/src/engine/gstengine.cpp +++ b/src/engine/gstengine.cpp @@ -1,4 +1,5 @@ /*************************************************************************** + * Copyright (C) 2017-2018 Jonas Kvinge * * Copyright (C) 2003-2005 by Mark Kretschmann * * Copyright (C) 2005 by Jakub Stachowski * * Copyright (C) 2006 Paul Cifarelli * @@ -30,7 +31,6 @@ #include #include -#include #include #include @@ -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(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(map.size - (chunk_size * scope_chunk_)), scope_.size() * sizeof(sample_type)); - } - else { - bytes = qMin(static_cast(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 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 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 GstEngine::CreatePipeline() { EnsureInitialised(); shared_ptr 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 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(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(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(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); -} - diff --git a/src/engine/gstengine.h b/src/engine/gstengine.h index ce483d17..6709a1a6 100644 --- a/src/engine/gstengine.h +++ b/src/engine/gstengine.h @@ -1,7 +1,8 @@ /*************************************************************************** + * Copyright (C) 2017-2018 Jonas Kvinge * * Copyright (C) 2003-2005 by Mark Kretschmann * * Copyright (C) 2005 by Jakub Stachowski * - * Copyright (C) 2006 Paul Cifarelli * + * Copyright (C) 2006 Paul Cifarelli * * * * 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 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 initialising_; - QString sink_; - QVariant device_; - std::shared_ptr current_pipeline_; std::shared_ptr fadeout_pipeline_; std::shared_ptr fadeout_pause_pipeline_; @@ -189,22 +180,10 @@ class GstEngine : public Engine::Base, public GstBufferConsumer { GstBuffer *latest_buffer_; - bool equalizer_enabled_; int equalizer_preamp_; QList 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_; diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index 360626ef..b3be25e5 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -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; } diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h index a0a17401..f5e2e2d6 100644 --- a/src/engine/gstenginepipeline.h +++ b/src/engine/gstenginepipeline.h @@ -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 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 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_; diff --git a/src/engine/osxdevicefinder.cpp b/src/engine/osxdevicefinder.cpp index 73b52e0d..6dbb0716 100644 --- a/src/engine/osxdevicefinder.cpp +++ b/src/engine/osxdevicefinder.cpp @@ -62,7 +62,7 @@ std::unique_ptr GetProperty(const AudioDeviceID& device_id, const AudioObject OsxDeviceFinder::OsxDeviceFinder() - : DeviceFinder("osxaudio", "osxaudiosink") { + : DeviceFinder("osxaudio", { "osxaudio", "osx", "osxaudiosink"} ) { } QList OsxDeviceFinder::ListDevices() { diff --git a/src/engine/phononengine.cpp b/src/engine/phononengine.cpp index 25665b7d..566af258 100644 --- a/src/engine/phononengine.cpp +++ b/src/engine/phononengine.cpp @@ -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 . -*/ +/* + * Strawberry Music Player + * This file was part of Clementine + * Copyright 2017-2018, Jonas Kvinge + * + * 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 . + * + */ #include "config.h" #include +#include #include #include #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; } diff --git a/src/engine/phononengine.h b/src/engine/phononengine.h index 3d500dd0..af035f7c 100644 --- a/src/engine/phononengine.h +++ b/src/engine/phononengine.h @@ -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 . -*/ +/* + * Strawberry Music Player + * This file was part of Clementine + * Copyright 2017-2018, Jonas Kvinge + * + * 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 . + * + */ #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 ); diff --git a/src/engine/pulsedevicefinder.cpp b/src/engine/pulsedevicefinder.cpp index e96ad29c..3a344f18 100644 --- a/src/engine/pulsedevicefinder.cpp +++ b/src/engine/pulsedevicefinder.cpp @@ -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() { diff --git a/src/engine/vlcengine.cpp b/src/engine/vlcengine.cpp index 5a60ac13..e1b1bede 100644 --- a/src/engine/vlcengine.cpp +++ b/src/engine/vlcengine.cpp @@ -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 . -*/ +/* + * Strawberry Music Player + * This file was part of Clementine + * Copyright 2017-2018, Jonas Kvinge + * + * 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 . + * + */ #include "config.h" @@ -24,22 +28,42 @@ #include #include +#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 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 ; iscope_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 ; iscope_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 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(&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(&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(&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(&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 ; iscope_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 ; ip_next) { + qLog(Debug) << audio_device->psz_device << audio_device->psz_description; + } + libvlc_audio_output_device_list_release(audio_output_device_list); } diff --git a/src/engine/vlcengine.h b/src/engine/vlcengine.h index f0a4d76b..0ef0c86e 100644 --- a/src/engine/vlcengine.h +++ b/src/engine/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 . -*/ +/* + * Strawberry Music Player + * This file was part of Clementine + * Copyright 2017-2018, Jonas Kvinge + * + * 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 . + * + */ #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 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 scope_data_; - - Engine::State state_; }; #endif // VLCENGINE_H diff --git a/src/engine/vlcscopedref.h b/src/engine/vlcscopedref.h index 2580f315..881c70ce 100644 --- a/src/engine/vlcscopedref.h +++ b/src/engine/vlcscopedref.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 . -*/ +/* + * Strawberry Music Player + * This file was part of Clementine + * Copyright 2017-2018, Jonas Kvinge + * + * 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 . + * + */ #ifndef VLCSCOPEDREF_H #define VLCSCOPEDREF_H diff --git a/src/engine/xineengine.cpp b/src/engine/xineengine.cpp index df4a7045..3c6f76d9 100644 --- a/src/engine/xineengine.cpp +++ b/src/engine/xineengine.cpp @@ -1,9 +1,10 @@ /*************************************************************************** - * Copyright (C) 2005 Christophe Thommeret * - * (C) 2005 Ian Monroe * - * (C) 2005,6 Mark Kretschmann * - * (C) 2004,5 Max Howell * - * (C) 2003,4 J. Kofler * + * Copyright (C) 2017-2018 Jonas Kvinge * + * Copyright (C) 2005 Christophe Thommeret * + * (C) 2005 Ian Monroe * + * (C) 2005-2006 Mark Kretschmann * + * (C) 2004-2005 Max Howell * + * (C) 2003-2004 J. Kofler * * * * 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 * @@ -12,10 +13,16 @@ * * ***************************************************************************/ -#define DEBUG_PREFIX "xine-engine" - #include "config.h" +#include +#include +#include +#include +#include +#include + +#include #include #include #include @@ -27,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -41,67 +49,56 @@ #include #include "core/logging.h" +#include #include "engine_fwd.h" #include "enginebase.h" #include "enginetype.h" #include "xineengine.h" #include "xinescope.h" - -extern "C" -{ -#include -#include -} +#include "xinefader.h" #include "settings/backendsettingspage.h" +using std::shared_ptr; + #ifndef LLONG_MAX #define LLONG_MAX 9223372036854775807LL #endif //define this to use xine in a more standard way -#ifdef Q_OS_WIN32 -#define XINE_SAFE_MODE -#endif +//#ifdef Q_OS_WIN32 +//#define XINE_SAFE_MODE +//#endif -///some logging static globals -namespace Log -{ - static uint bufferCount = 0; - static uint scopeCallCount = 1; //prevent divideByZero - static uint noSuitableBuffer = 0; -} - -static Fader *s_fader = 0; -static OutFader *s_outfader = 0; - +const char *XineEngine::kAutoOutput = "auto"; +int XineEngine::last_error_ = XINE_MSG_NO_ERROR; +time_t XineEngine::last_error_time_ = 0; // Hysteresis on xine errors XineEngine::XineEngine(TaskManager *task_manager) - : EngineBase() - , xine_( 0 ) - , stream_( 0 ) - , audioPort_( 0 ) - , eventQueue_( 0 ) - , post_( 0 ) - , preamp_( 1.0 ) - , stopFader_( false ) - , fadeOutRunning_ ( false ) - , equalizerEnabled_( false ) - , prune_(NULL) -{ + : EngineBase(), + xine_(nullptr), + stream_(nullptr), + audioport_(nullptr), + eventqueue_(nullptr), + post_(nullptr), + preamp_(1.0), + stop_fader_(false), + fadeout_running_ (false), + prune_(nullptr) { ReloadSettings(); + } XineEngine::~XineEngine() { // Wait until the fader thread is done - if( s_fader ) { - stopFader_ = true; - s_fader->resume(); // safety call if the engine is in the pause state - s_fader->wait(); + if (s_fader_) { + stop_fader_ = true; + s_fader_->resume(); // safety call if the engine is in the pause state + s_fader_->wait(); } // Wait until the prune scope thread is done @@ -110,29 +107,270 @@ XineEngine::~XineEngine() { prune_->wait(); } - delete s_fader; - delete s_outfader; - delete prune_; + s_fader_.reset(); + s_outfader_.reset(); + prune_.reset(); - if( fadeoutOnExit_ ) { + if (fadeout_enabled_) { bool terminateFader = false; - fadeOut( fadeoutDuration_, &terminateFader, true ); // true == exiting + FadeOut(fadeout_duration_, &terminateFader, true); // true == exiting } - //if( xine_ ) xine_config_save( xine_, configPath() ); + if (stream_) xine_close(stream_); + if (eventqueue_) xine_event_dispose_queue(eventqueue_); + if (stream_) xine_dispose(stream_); + if (audioport_) xine_close_audio_driver(xine_, audioport_); + if (post_) xine_post_dispose(xine_, post_); + if (xine_) xine_exit(xine_); - if( stream_ ) xine_close( stream_ ); - if( eventQueue_ ) xine_event_dispose_queue( eventQueue_ ); - if( stream_ ) xine_dispose( stream_ ); - if( audioPort_ ) xine_close_audio_driver( xine_, audioPort_ ); - if( post_ ) xine_post_dispose( xine_, post_ ); - if( xine_ ) xine_exit( xine_ ); + //qLog(Debug) << "xine closed"; + //qLog(Debug) << "Scope statistics:"; + //qLog(Debug) << "Average list size: " << log_buffer_count_ / log_scope_call_count_; + //qLog(Debug) << "Buffer failure: " << double(log_no_suitable_buffer_*100) / log_scope_call_count_ << "%"; - qDebug() << "xine closed"; +} - qDebug() << "Scope statistics:"; - qDebug() << "Average list size: " << Log::bufferCount / Log::scopeCallCount; - qDebug() << "Buffer failure: " << double(Log::noSuitableBuffer*100) / Log::scopeCallCount << "%"; +bool XineEngine::Init() { + + type_ = Engine::Xine; + + SetEnvironment(); + + QMutexLocker l(&init_mutex_); + + xine_ = xine_new(); + if (!xine_) { + emit Error("Could not initialize xine."); + return false; + } + +#ifdef XINE_SAFE_MODE + xine_engine_set_param(xine_, XINE_ENGINE_PARAM_VERBOSITY, 99); +#endif + + xine_init(xine_); + + MakeNewStream(); + +#ifndef XINE_SAFE_MODE + prune_.reset(new PruneScopeThread(this)); + prune_->start(); +#endif + + return true; +} + +Engine::State XineEngine::state() const { + + if (!stream_ || fadeout_running_) return Engine::Empty; + + switch(xine_get_status(stream_)) { + case XINE_STATUS_PLAY: + return xine_get_param(stream_, XINE_PARAM_SPEED) != XINE_SPEED_PAUSE ? Engine::Playing : Engine::Paused; + case XINE_STATUS_IDLE: + return Engine::Empty; + case XINE_STATUS_STOP: + default: + return url_.isEmpty() ? Engine::Empty : Engine::Idle; + } +} + +bool XineEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { + + if (!EnsureStream()) return false; + + Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec, end_nanosec); + + if (s_outfader_) { + s_outfader_->finish(); + if (s_outfader_) s_outfader_.reset(); + } + + if (fade_length_ > 0 && xine_get_status(stream_) == XINE_STATUS_PLAY && url.scheme().toLower() == "file" && xine_get_param(stream_, XINE_PARAM_SPEED) != XINE_SPEED_PAUSE && (fade_next_track_ || crossfade_enabled_)) { + + fade_next_track_ = false; // Set by engine controller when switching tracks automatically + + // Stop a probably running fader + if (s_fader_) { + stop_fader_ = true; + s_fader_->finish(); // Makes the fader stop abruptly + } + s_fader_.reset(new XineFader(this, xine_, stream_, audioport_, post_, fade_length_)); + SetEqualizerParameters(int_preamp_, equalizer_gains_); + } + + xine_close(stream_); + + int result = xine_open(stream_, url.path().toUtf8()); + if (result) { + +#ifndef XINE_SAFE_MODE + xine_post_out_t *source = xine_get_audio_source(stream_); + xine_post_in_t *target = (xine_post_in_t*)xine_post_input(post_, const_cast("audio in")); + xine_post_wire(source, target); +#endif + + return true; + } + else { + qLog(Error) << "Failed to play"; + } + + // FAILURE to load! + // s_fader_ will delete itself + DetermineAndShowErrorMessage(); + + return false; +} + +bool XineEngine::Play(quint64 offset_nanosec) { + + if (!EnsureStream()) return false; + + int offset = (offset_nanosec / kNsecPerMsec); + const bool has_audio = xine_get_stream_info(stream_, XINE_STREAM_INFO_HAS_AUDIO); + const bool audio_handled = xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_HANDLED); + + if (has_audio && audio_handled && xine_play(stream_, 0, offset)) { + if (s_fader_) s_fader_->start(QThread::LowestPriority); + + emit StateChanged(Engine::Playing); + + return true; + } + + // We need to stop the track that is prepped for crossfade + if (s_fader_) s_fader_.reset(); + + emit StateChanged(Engine::Empty); + + DetermineAndShowErrorMessage(); + + xine_close(stream_); + + return false; +} + +void XineEngine::Stop(bool stop_after) { + + if (s_fader_ && s_fader_->isRunning()) + s_fader_->resume(); // Safety call if the engine is in the pause state + + if (!stream_) return; + + if ((fadeout_enabled_ && !fadeout_running_) || state() == Engine::Paused) { + s_outfader_.reset(new XineOutFader(this, fadeout_duration_)); + s_outfader_->start(); + ::usleep(100); // To be sure engine state won't be changed before it is checked in FadeOut() + url_ = QUrl(); // To ensure we return Empty from state() + + std::fill(scope_.begin(), scope_.end(), 0); + } + else if (!fadeout_running_) { + xine_stop(stream_); + xine_close(stream_); + xine_set_param(stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); + } + + emit StateChanged(Engine::Empty); +} + +void XineEngine::Pause() { + + if (!stream_) return; + + if (xine_get_param(stream_, XINE_PARAM_SPEED) != XINE_SPEED_PAUSE) { + + if (s_fader_ && s_fader_->isRunning()) s_fader_->pause(); + + xine_set_param(stream_, XINE_PARAM_SPEED, XINE_SPEED_PAUSE); + xine_set_param(stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); + emit StateChanged(Engine::Paused); + + } + +} + +void XineEngine::Unpause() { + + if (!stream_) return; + + if (xine_get_param(stream_, XINE_PARAM_SPEED) == XINE_SPEED_PAUSE) { + + if (s_fader_ && s_fader_->isRunning()) s_fader_->resume(); + + xine_set_param(stream_, XINE_PARAM_SPEED, XINE_SPEED_NORMAL); + emit StateChanged(Engine::Playing); + + } + +} + +void XineEngine::Seek(quint64 offset_nanosec) { + + if (!EnsureStream()) return; + + int offset = (offset_nanosec / kNsecPerMsec); + + if (xine_get_param(stream_, XINE_PARAM_SPEED) == XINE_SPEED_PAUSE) { + xine_play(stream_, 0, offset); + xine_set_param(stream_, XINE_PARAM_SPEED, XINE_SPEED_PAUSE); + } + else xine_play(stream_, 0, offset); + +} + +void XineEngine::SetVolumeSW(uint vol) { + + if (!stream_) return; + if (!s_fader_) + xine_set_param(stream_, XINE_PARAM_AUDIO_AMP_LEVEL, static_cast(vol * preamp_)); + +} + +qint64 XineEngine::position_nanosec() const { + if (state() == Engine::Empty) return 0; + const qint64 result = (position() * kNsecPerMsec); + return qint64(qMax(0ll, result)); +} + +qint64 XineEngine::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); + } +} + +EngineBase::OutputDetailsList XineEngine::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 == "pulseaudio") output.iconname = "pulseaudio"; + else if (plugin.name == "bluetooth") output.iconname = "bluetooth"; + else if (plugin.name == "file") output.iconname = "document-new"; + else output.iconname = "soundcard"; + ret.append(output); + } + + return ret; +} + +bool XineEngine::CustomDeviceSupport(const QString &name) { + return (name == DefaultOutput() ? false : true); } void XineEngine::ReloadSettings() { @@ -141,23 +379,11 @@ void XineEngine::ReloadSettings() { Engine::Base::ReloadSettings(); - s.beginGroup(BackendSettingsPage::kSettingsGroup); - currentAudioPlugin_ = s.value("output", "auto").toString(); - if (currentAudioPlugin_ == "") currentAudioPlugin_ = "auto"; - currentAudioDevice_ = s.value("device", "").toString(); - fadeoutEnabled_ = s.value("FadeoutEnabled", false).toBool(); - fadeoutOnExit_ = s.value("FadeoutOnExit", false).toBool(); - fadeoutDuration_ = s.value("FadeoutDuration", 2000).toInt(); - crossfadeEnabled_ = s.value("CrossfadeEnabled", false).toBool(); - s.endGroup(); - - qLog(Debug) << "OUTPUT: " << currentAudioPlugin_; + if (output_ == "") output_ = DefaultOutput(); } -bool XineEngine::Init() { - - type_ = Engine::Xine; +void XineEngine::SetEnvironment() { #ifdef Q_OS_WIN32 putenv(QString("XINE_PLUGIN_PATH=" + QCoreApplication::applicationDirPath() + "/xine/plugins").toLatin1().constData()); @@ -166,759 +392,338 @@ bool XineEngine::Init() { #ifdef Q_OS_DARWIN setenv("XINE_PLUGIN_PATH", QString(QCoreApplication::applicationDirPath() + "/../PlugIns/xine").toLatin1().constData(), 1); #endif // Q_OS_DARWIN - - QMutexLocker l(&initMutex_); - - xine_ = xine_new(); - - if (!xine_) { - emit Error("Could not initialize xine."); - return false; - } - -#ifdef XINE_SAFE_MODE - xine_engine_set_param( xine_, XINE_ENGINE_PARAM_VERBOSITY, 99 ); -#endif - - //xine_config_load( xine_, configPath() ); - //debug() << "w00t" << configPath() << endl; - - xine_init(xine_); - - makeNewStream(); - -#ifndef XINE_SAFE_MODE - prune_ = new PruneScopeThread(this); - prune_->start(); -#endif - - return true; -} - -bool XineEngine::makeNewStream() { - - audioPort_ = xine_open_audio_driver(xine_, currentAudioPlugin_.toLocal8Bit().constData(), NULL); - if( !audioPort_ ) { - //TODO make engine method that is the same but parents the dialog for us - emit Error("xine was unable to initialize any audio drivers."); - return false; - } - - stream_ = xine_stream_new( xine_, audioPort_, NULL ); - if( !stream_ ) { - xine_close_audio_driver( xine_, audioPort_ ); - audioPort_ = NULL; - emit Error("Could not create a new xine stream"); - return false; - } - - if( eventQueue_ ) - xine_event_dispose_queue( eventQueue_ ); - - xine_event_create_listener_thread(eventQueue_ = xine_event_new_queue( stream_ ), &XineEngine::XineEventListener, (void*)this ); - -#ifndef XINE_SAFE_MODE - //implemented in xine-scope.h - post_ = scope_plugin_new( xine_, audioPort_ ); - - xine_set_param( stream_, XINE_PARAM_METRONOM_PREBUFFER, 6000 ); - xine_set_param( stream_, XINE_PARAM_IGNORE_VIDEO, 1 ); -#endif -#ifdef XINE_PARAM_EARLY_FINISHED_EVENT - if ( xine_check_version(1,1,1) && !(xfadeLength_ > 0) ) { - // enable gapless playback - qDebug() << "gapless playback enabled."; - //xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 1 ); - } -#endif - return true; -} - -// Makes sure an audio port and a stream exist. -bool XineEngine::ensureStream() { - - if(!stream_) return makeNewStream(); - - return true; - -} - -bool XineEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { - - if( !ensureStream() ) - return false; - - Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec, end_nanosec); - - if( s_outfader ) { - s_outfader->finish(); - delete s_outfader; - } - - if( xfadeLength_ > 0 && xine_get_status( stream_ ) == XINE_STATUS_PLAY && - url.scheme().toLower() == "file" && - xine_get_param( stream_, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE && - ( xfadeNextTrack_ || //set by engine controller when switching tracks automatically - crossfadeEnabled_)) - { - xfadeNextTrack_ = false; - // Stop a probably running fader - if( s_fader ) { - stopFader_ = true; - s_fader->finish(); // makes the fader stop abruptly - delete s_fader; - } - s_fader = new Fader( this, xfadeLength_ ); - setEqualizerParameters( intPreamp_, equalizerGains_ ); - } - - // for users who stubbonly refuse to use DMIX or buy a good soundcard - // why doesn't xine do this? I cannot say. - xine_close( stream_ ); - - qDebug() << "Before xine_open() *****"; - - if( xine_open( stream_, url.toEncoded() ) ) { - - qDebug() << "After xine_open() *****"; - -#ifndef XINE_SAFE_MODE - xine_post_out_t *source = xine_get_audio_source( stream_ ); - xine_post_in_t *target = (xine_post_in_t*)xine_post_input( post_, const_cast("audio in") ); - xine_post_wire( source, target ); -#endif - - playlistChanged(); - - return true; - } - else - { - } - - // FAILURE to load! - //s_fader will delete itself - determineAndShowErrorMessage(); - - return false; -} - -bool XineEngine::Play(quint64 offset_nanosec) { - - if( !ensureStream() ) - return false; - - const bool has_audio = xine_get_stream_info( stream_, XINE_STREAM_INFO_HAS_AUDIO ); - const bool audio_handled = xine_get_stream_info( stream_, XINE_STREAM_INFO_AUDIO_HANDLED ); - - if (has_audio && audio_handled && xine_play( stream_, 0, offset_nanosec)) { - if( s_fader ) s_fader->start( QThread::LowestPriority ); - - emit StateChanged( Engine::Playing ); - - return true; - } - - //we need to stop the track that is prepped for crossfade - delete s_fader; - - emit StateChanged( Engine::Empty ); - - determineAndShowErrorMessage(); - - xine_close( stream_ ); - - return false; -} - -void XineEngine::determineAndShowErrorMessage() { - - QString body; - switch (xine_get_error( stream_ )) { - case XINE_ERROR_NO_INPUT_PLUGIN: - body = "No suitable input plugin. This often means that the url's protocol is not supported. Network failures are other possible causes."; - break; - - case XINE_ERROR_NO_DEMUX_PLUGIN: - body = "No suitable demux plugin. This often means that the file format is not supported."; - break; - - case XINE_ERROR_DEMUX_FAILED: - body = "Demuxing failed."; - break; - - case XINE_ERROR_INPUT_FAILED: - body = "Could not open file."; - break; - - case XINE_ERROR_MALFORMED_MRL: - body = "The location is malformed."; - break; - - case XINE_ERROR_NONE: - // xine is thick. xine doesn't think there is an error - // but there may be! We check for other errors below. - - default: - if (!xine_get_stream_info( stream_, XINE_STREAM_INFO_AUDIO_HANDLED )) - { - // xine can read the plugin but it didn't find any codec - // THUS xine=daft for telling us it could handle the format in canDecode! - body = "There is no available decoder."; - QString const ext = QFileInfo(url_.path()).completeSuffix(); - // TODO - //if (ext == "mp3" && EngineController::installDistroCodec( "xine-engine" )) - // return; - } - else if (!xine_get_stream_info( stream_, XINE_STREAM_INFO_HAS_AUDIO )) - body = "There is no audio channel!"; - break; - } - - // TODO - qWarning() << body; -} - -void XineEngine::Stop(bool stop_after) { - - if( s_fader && s_fader->isRunning()) - s_fader->resume(); // safety call if the engine is in the pause state - - if ( !stream_ ) - return; - - if( (fadeoutEnabled_ && !fadeOutRunning_) || state() == Engine::Paused ) - { - s_outfader = new OutFader( this, fadeoutDuration_ ); - s_outfader->start(); - ::usleep( 100 ); //to be sure engine state won't be changed before it is checked in fadeOut() - url_ = QUrl(); //to ensure we return Empty from state() - - std::fill( scope_.begin(), scope_.end(), 0 ); - } - else if( !fadeOutRunning_ ) - { - xine_stop( stream_ ); - xine_close( stream_ ); - xine_set_param( stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); - } - - emit StateChanged( Engine::Empty ); -} - -void XineEngine::Pause() { - - if ( !stream_ ) - return; - - if (xine_get_param( stream_, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE ) { - if( s_fader && s_fader->isRunning() ) - s_fader->pause(); - - xine_set_param( stream_, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ); - xine_set_param( stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); - emit StateChanged( Engine::Paused ); - - } - -} - -void XineEngine::Unpause() { - - if ( !stream_ ) - return; - - if( xine_get_param( stream_, XINE_PARAM_SPEED ) == XINE_SPEED_PAUSE ) { - if( s_fader && s_fader->isRunning() ) - s_fader->resume(); - - xine_set_param( stream_, XINE_PARAM_SPEED, XINE_SPEED_NORMAL ); - emit StateChanged( Engine::Playing ); - } - -} - -Engine::State XineEngine::state() const { - - if ( !stream_ || fadeOutRunning_ ) - return Engine::Empty; - - switch( xine_get_status( stream_ ) ) - { - case XINE_STATUS_PLAY: return xine_get_param( stream_, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE ? Engine::Playing : Engine::Paused; - case XINE_STATUS_IDLE: return Engine::Empty; - case XINE_STATUS_STOP: - default: return url_.isEmpty() ? Engine::Empty : Engine::Idle; - } -} - -uint XineEngine::position() const { - - if ( state() == Engine::Empty ) - return 0; - - int pos; - int time = 0; - int length; - - // Workaround for problems when you seek too quickly, see BUG 99808 - int tmp = 0, i = 0; - while( ++i < 4 ) - { - xine_get_pos_length( stream_, &pos, &time, &length ); - if( time > tmp ) break; - usleep( 100000 ); - } - - // Here we check for new metadata periodically, because xine does not emit an event - // in all cases (e.g. with ogg streams). See BUG 122505 - if ( state() != Engine::Idle && state() != Engine::Empty ) - { - const Engine::SimpleMetaBundle bundle = fetchMetaData(); - if( bundle.title != currentBundle_.title || bundle.artist != currentBundle_.artist ) { - qDebug() << "Metadata received."; - currentBundle_ = bundle; - - XineEngine* p = const_cast( this ); - p->emit MetaData( bundle ); - } - } - - return time; } uint XineEngine::length() const { - if ( !stream_ ) - return 0; + if (!stream_) return 0; - // xine often delivers nonsense values for VBR files and such, so we only - // use the length for remote files - - if( url_.scheme().toLower() == "file" ) - return 0; + // Xine often delivers nonsense values for VBR files and such, so we only use the length for remote files + if (url_.scheme().toLower() == "file") return 0; else { - int pos; - int time; - int length = 0; + int pos = 0, time = 0, length = 0; - xine_get_pos_length( stream_, &pos, &time, &length ); - if( length < 0 ) - length=0; + xine_get_pos_length(stream_, &pos, &time, &length); + if (length < 0) length=0; return length; } + } -void XineEngine::Seek(quint64 offset_nanosec) { +uint XineEngine::position() const { - if( !ensureStream() ) - return; + if (state() == Engine::Empty) return 0; - if( xine_get_param( stream_, XINE_PARAM_SPEED ) == XINE_SPEED_PAUSE ) { - // FIXME this is a xine API issue really, they need to add a seek function - xine_play( stream_, 0, (int) offset_nanosec); - xine_set_param( stream_, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ); + int pos = 0, time = 0, length = 0; + + // Workaround for problems when you seek too quickly, see BUG 99808 + int tmp = 0, i = 0; + while (++i < 4) { + xine_get_pos_length(stream_, &pos, &time, &length); + if (time > tmp) break; + usleep(100000); } - else - xine_play( stream_, 0, (int)offset_nanosec ); -} -void XineEngine::SetVolumeSW( uint vol ) { + // Here we check for new metadata periodically, because xine does not emit an event in all cases (e.g. with ogg streams). See BUG 122505 + if (state() != Engine::Idle && state() != Engine::Empty) { + const Engine::SimpleMetaBundle bundle = fetchMetaData(); + if (bundle.title != current_bundle_.title || bundle.artist != current_bundle_.artist) { + qLog(Debug) << "Metadata received."; + current_bundle_ = bundle; - if ( !stream_ ) - return; - if( !s_fader ) - xine_set_param( stream_, XINE_PARAM_AUDIO_AMP_LEVEL, static_cast( vol * preamp_ ) ); -} - -void XineEngine::fadeOut( uint fadeLength, bool* terminate, bool exiting ) { - - if( fadeOutRunning_ ) //Let us not start another fadeout... - return; - - fadeOutRunning_ = !fadeOutRunning_; - const bool isPlaying = stream_ && ( xine_get_status( stream_ ) == XINE_STATUS_PLAY ); - const float originalVol = Engine::Base::MakeVolumeLogarithmic( volume_ ) * preamp_; - - // On shutdown, limit fadeout to 3 secs max, so that we don't risk getting killed - const int length = exiting ? qMin( fadeLength, 3000u ) : fadeLength; - - if( length > 0 && isPlaying ) { - // fader-class doesn't work in this spot as is, so some parts need to be copied here... (ugly) - uint stepsCount = length < 1000 ? length / 10 : 100; - uint stepSizeUs = (int)( 1000.0 * (float)length / (float)stepsCount ); - - ::usleep( stepSizeUs ); - QTime t; - t.start(); - float mix = 0.0; - while ( mix < 1.0 ) - { - if( *terminate ) break; - - ::usleep( stepSizeUs ); - float vol = Engine::Base::MakeVolumeLogarithmic( volume_ ) * preamp_; - float mix = (float)t.elapsed() / (float)length; - if ( mix > 1.0 ) - { - break; - } - if ( stream_ ) - { - float v = 4.0 * (1.0 - mix) / 3.0; - xine_set_param( stream_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) ); - } + XineEngine *p = const_cast(this); + p->emit MetaData(bundle); } } - if( fadeOutRunning_ && stream_ ) - xine_set_param( stream_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint) originalVol ); - fadeOutRunning_ = !fadeOutRunning_; + + return time; + } -void XineEngine::setEqualizerEnabled( bool enable ) { - - if ( !stream_ ) - return; - - equalizerEnabled_ = enable; - - if( !enable ) { - QList gains; - for( uint x = 0; x < 10; x++ ) - gains << -101; // sets eq gains to zero. - - setEqualizerParameters( 0, gains ); - } -} - -/* - sets the eq params for xine engine - have to rescale eq params to fitting range (adapted from kaffeine and xfmedia) - - preamp -pre: (-100..100) -post: (0.1..1.9) - this is not really a preamp but we use the xine preamp parameter for our normal volume. so we make a postamp. - -gains -pre: (-100..100) -post: (1..200) - (1 = down, 100 = middle, 200 = up, 0 = off) - */ -void XineEngine::setEqualizerParameters( int preamp, const QList &gains ) { - - if ( !stream_ ) - return; - - equalizerGains_ = gains; - intPreamp_ = preamp; - QList::ConstIterator it = gains.begin(); - - xine_set_param( stream_, XINE_PARAM_EQ_30HZ, int( (*it )*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_60HZ, int( (*++it)*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_125HZ, int( (*++it)*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_250HZ, int( (*++it)*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_500HZ, int( (*++it)*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_1000HZ, int( (*++it)*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_2000HZ, int( (*++it)*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_4000HZ, int( (*++it)*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_8000HZ, int( (*++it)*0.995 + 100 ) ); - xine_set_param( stream_, XINE_PARAM_EQ_16000HZ, int( (*++it)*0.995 + 100 ) ); - - preamp_ = ( preamp - 0.1 * preamp + 100 ) / 100.0; - SetVolume( volume_ ); -} - -bool XineEngine::CanDecode( const QUrl &url ) { +bool XineEngine::CanDecode(const QUrl &url) { static QStringList list; + if (list.isEmpty()) { - - QMutexLocker l(&const_cast(this)->initMutex_); + + QMutexLocker l(&const_cast(this)->init_mutex_); if (list.isEmpty()) { - char* exts = xine_get_file_extensions( xine_ ); + char* exts = xine_get_file_extensions(xine_); list = QString(exts).split(' '); - free( exts ); exts = NULL; - //images + free(exts); + exts = nullptr; + // Images list.removeAll("png"); list.removeAll("jpg"); list.removeAll("jpeg"); list.removeAll("gif"); list.removeAll("ilbm"); list.removeAll("iff"); - //subtitles + // Subtitles list.removeAll("asc"); list.removeAll("txt"); list.removeAll("sub"); list.removeAll("srt"); list.removeAll("smi"); list.removeAll("ssa"); - //HACK we also check for m4a because xine plays them but - //for some reason doesn't return the extension - if(!list.contains("m4a")) + // HACK: we also check for m4a because xine plays them but for some reason doesn't return the extension + if (!list.contains("m4a")) list << "m4a"; } } - if (url.scheme() == "cdda") - // play audio CDs pls - return true; + if (url.scheme() == "cdda") return true; QString path = url.path(); - // partial downloads from Konqi and other browsers tend to have a .part extension - if (path.endsWith( ".part" )) - path = path.left( path.length() - 5 ); + // Partial downloads from Konqi and other browsers tend to have a .part extension + if (path.endsWith(".part")) path = path.left(path.length() - 5); - const QString ext = path.mid( path.lastIndexOf( '.' ) + 1 ).toLower(); + const QString ext = path.mid(path.lastIndexOf('.') + 1).toLower(); + + return list.contains(ext); - return list.contains( ext ); } -const Engine::Scope & XineEngine::scope(int chunk_length) { +bool XineEngine::MetaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b) { - if( !post_ || !stream_ || xine_get_status( stream_ ) != XINE_STATUS_PLAY ) - return scope_; + bool result = false; + xine_stream_t *tmpstream = xine_stream_new(xine_, nullptr, nullptr); + if (xine_open(tmpstream, QFile::encodeName(url.toString()))) { + QString audioCodec = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_SYSTEMLAYER)); - MyNode* const myList = scope_plugin_list( post_ ); - metronom_t* const myMetronom = scope_plugin_metronom( post_ ); - const int myChannels = scope_plugin_channels( post_ ); - int scopeidx = 0; - - if (myChannels > 2) - return scope_; - - for( int n, frame = 0; frame < 512; ) { - MyNode *best_node = 0; - - for( MyNode *node = myList->next; node != myList; node = node->next, Log::bufferCount++ ) - if( node->vpts <= currentVpts_ && (!best_node || node->vpts > best_node->vpts) ) - best_node = node; - - if( !best_node || best_node->vpts_end < currentVpts_ ) { - Log::noSuitableBuffer++; break; } - - int64_t - diff = currentVpts_; - diff -= best_node->vpts; - diff *= 1<<16; - diff /= myMetronom->pts_per_smpls; - - const int16_t* - data16 = best_node->mem; - data16 += diff; - - diff += diff % myChannels; //important correction to ensure we don't overflow the buffer - diff /= myChannels; //use units of frames, not samples - - //calculate the number of available samples in this buffer - n = best_node->num_frames; - n -= diff; - n += frame; //clipping for # of frames we need - - if( n > 512 ) - n = 512; //we don't want more than 512 frames - - //int a, c - for( int c; frame < n; ++frame, data16 += myChannels ) { - //a = c = 0 - for( c=0; c < myChannels; ++c ) - { - // we now give interleaved pcm to the scope - scope_[scopeidx++] = data16[c]; - if (myChannels == 1) // duplicate mono samples - scope_[scopeidx++] = data16[c]; + if (audioCodec == "CDDA") { + QString title = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_TITLE)); + if ((!title.isNull()) && (!title.isEmpty())) { //no meta info + b.title = title; + b.artist = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_ARTIST)); + b.album = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_ALBUM)); + b.genre = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_GENRE)); + b.year = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_YEAR)); + b.tracknr = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_TRACK_NUMBER)); + if (b.tracknr.isEmpty()) + b.tracknr = QFileInfo(url.path()).fileName(); + } + else { + b.title = QString("Track %1").arg(QFileInfo(url.path()).fileName()); + b.album = "AudioCD"; } } - currentVpts_ = best_node->vpts_end; - currentVpts_++; //FIXME needs to be done for some reason, or you get situations where it uses same buffer again and again + if (audioCodec == "CDDA" || audioCodec == "WAV") { + result = true; + int samplerate = xine_get_stream_info(tmpstream, XINE_STREAM_INFO_AUDIO_SAMPLERATE); + + // Xine would provide a XINE_STREAM_INFO_AUDIO_BITRATE, but unfortunately not for CDDA or WAV so we calculate the bitrate by our own + int bitsPerSample = xine_get_stream_info(tmpstream, XINE_STREAM_INFO_AUDIO_BITS); + int nbrChannels = xine_get_stream_info(tmpstream, XINE_STREAM_INFO_AUDIO_CHANNELS); + int bitrate = (samplerate * bitsPerSample * nbrChannels) / 1000; + + b.bitrate = QString::number(bitrate); + b.samplerate = QString::number(samplerate); + int pos, time, length = 0; + xine_get_pos_length(tmpstream, &pos, &time, &length); + b.length = QString::number(length / 1000); + } + xine_close(tmpstream); } - - Log::scopeCallCount++; - - return scope_; + xine_dispose(tmpstream); + return result; } -void XineEngine::PruneScope() { +bool XineEngine::GetAudioCDContents(const QString &device, QList &urls) { - if (!stream_) return; + const char * const *xine_urls = nullptr; + int num; + int i = 0; - //here we prune the buffer list regularly - - MyNode *myList = scope_plugin_list( post_ ); - - if ( ! myList ) return; - - //we operate on a subset of the list for thread-safety - MyNode * const first_node = myList->next; - MyNode const * const list_end = myList; - - currentVpts_ = (xine_get_status( stream_ ) == XINE_STATUS_PLAY) - ? xine_get_current_vpts( stream_ ) - : LLONG_MAX; //if state is not playing OR paused, empty the list - //: std::numeric_limits::max(); //TODO don't support crappy gcc 2.95 - - for( MyNode *prev = first_node, *node = first_node->next; node != list_end; node = node->next ) - { - //we never delete first_node - //this maintains thread-safety - if( node->vpts_end < currentVpts_ ) { - prev->next = node->next; - - free( node->mem ); - free( node ); - - node = prev; + if (!device.isNull()) { + qLog(Debug) << "xine-engine setting CD Device to: " << device; + xine_cfg_entry_t config; + if (!xine_config_lookup_entry(xine_, "input.cdda_device", &config)) { + emit StatusText("Failed CD device lookup in xine engine"); + return false; } - - prev = node; + config.str_value = (char *)device.toLatin1().constData(); + xine_config_update_entry(xine_, &config); } + + emit StatusText("Getting AudioCD contents..."); + + xine_urls = xine_get_autoplay_mrls(xine_, "CD", &num); + + if (xine_urls) { + while (xine_urls[i]) { + urls << QUrl(xine_urls[i]); + ++i; + } + } + else emit StatusText("Could not read AudioCD"); + + return true; } -bool XineEngine::event( QEvent *e ) { - -#define message static_cast(static_cast(e)->data()) -#if 0 - switch(e->type()) { - - case XineEvent::PlaybackFinished: //XINE_EVENT_UI_PLAYBACK_FINISHED - emit TrackEnded(); - return true; - - case XineEvent::InfoMessage: - emit InfoMessage( (*message).arg( url_.toString() ) ); - delete message; - return true; - - case XineEvent::StatusMessage: - emit StatusText( *message ); - delete message; - return true; - - case XineEvent::MetaInfoChanged: { //meta info has changed - qDebug() << "Metadata received."; - const Engine::SimpleMetaBundle bundle = fetchMetaData(); - if( bundle.title != currentBundle_.title || bundle.artist != currentBundle_.artist ) { - currentBundle_ = bundle; - - emit MetaData( bundle ); - } - return true; - } - - case XineEvent::Redirecting: - emit StatusText( QString("Redirecting to: ").arg( *message ) ); - Load(QUrl(*message), Engine::Auto, 0, 0, 0); - Play(0); - delete message; - return true; - case XineEvent::LastFMTrackChanged: - emit LastFmTrackChange(); - return true; - default: - ; - } -#endif - -#undef message +bool XineEngine::FlushBuffer() { return false; } -//SLOT -void XineEngine::playlistChanged() { +void XineEngine::SetEqualizerEnabled(bool enabled) { - // TODO - /*#ifdef XINE_PARAM_EARLY_FINISHED_EVENT -#ifdef XINE_PARAM_GAPLESS_SWITCH -if ( xine_check_version(1,1,1) && !(xfadeLength_ > 0) -&& url_.isLocalFile() && Playlist::instance()->isTrackAfter() ) -{ -xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 1 ); -debug() << "XINE_PARAM_EARLY_FINISHED_EVENT enabled" << endl; -} -else -{ - //we don't want an early finish event if there is no track after the current one - xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 0 ); - debug() << "XINE_PARAM_EARLY_FINISHED_EVENT disabled" << endl; + if (!stream_) return; + + equalizer_enabled_ = enabled; + + if (!enabled) { + QList gains; + for (uint x = 0; x < 10; x++) + gains << -101; // sets eq gains to zero. + + SetEqualizerParameters(0, gains); } -#endif -#endif*/ + } -static time_t last_error_time = 0; // hysteresis on xine errors -static int last_error = XINE_MSG_NO_ERROR; +/* + Sets the eq params for xine engine - have to rescale eq params to fitting range (adapted from kaffeine and xfmedia) -void XineEngine::XineEventListener( void *p, const xine_event_t* xineEvent ) { + preamp: + pre: (-100..100) + post: (0.1..1.9) - this is not really a preamp but we use the xine preamp parameter for our normal volume. so we make a postamp. + + gains: + pre: (-100..100) + post: (1..200) - (1 = down, 100 = middle, 200 = up, 0 = off) +*/ +void XineEngine::SetEqualizerParameters(int preamp, const QList &gains) { + + if (!stream_) return; + + equalizer_gains_ = gains; + int_preamp_ = preamp; + QList::ConstIterator it = gains.begin(); + + xine_set_param(stream_, XINE_PARAM_EQ_30HZ, int((*it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_60HZ, int((*++it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_125HZ, int((*++it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_250HZ, int((*++it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_500HZ, int((*++it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_1000HZ, int((*++it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_2000HZ, int((*++it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_4000HZ, int((*++it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_8000HZ, int((*++it)*0.995 + 100)); + xine_set_param(stream_, XINE_PARAM_EQ_16000HZ, int((*++it)*0.995 + 100)); + + preamp_ = (preamp - 0.1 * preamp + 100) / 100.0; + SetVolume(volume_); + +} + +void XineEngine::FadeOut(uint fadeLength, bool *terminate, bool exiting) { + + if (fadeout_running_) return; // Don't start another fadeout + + fadeout_running_ = !fadeout_running_; + const bool isPlaying = stream_ && (xine_get_status(stream_) == XINE_STATUS_PLAY); + const float originalVol = Engine::Base::MakeVolumeLogarithmic(volume_) * preamp_; + + // On shutdown, limit fadeout to 3 secs max, so that we don't risk getting killed + const int length = exiting ? qMin(fadeLength, 3000u) : fadeLength; + + if (length > 0 && isPlaying) { + // fader-class doesn't work in this spot as is, so some parts need to be copied here... (ugly) + uint stepsCount = length < 1000 ? length / 10 : 100; + uint stepSizeUs = (int)(1000.0 * (float)length / (float)stepsCount); + + ::usleep(stepSizeUs); + QTime t; + t.start(); + float mix = 0.0; + while (mix < 1.0) { + if (*terminate) break; + + ::usleep(stepSizeUs); + float vol = Engine::Base::MakeVolumeLogarithmic(volume_) * preamp_; + float mix = (float)t.elapsed() / (float)length; + if (mix > 1.0) break; + if (stream_) { + float v = 4.0 * (1.0 - mix) / 3.0; + xine_set_param(stream_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(v < 1.0 ? vol * v : vol)); + } + } + } + if (fadeout_running_ && stream_) + xine_set_param(stream_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint) originalVol); + + fadeout_running_ = !fadeout_running_; + +} + +void XineEngine::XineEventListener(void *p, const xine_event_t *xineEvent) { time_t current; - if( !p ) return; + if (!p) return; #define xe static_cast(p) - switch( xineEvent->type ) - { + switch(xineEvent->type) { case XINE_EVENT_UI_SET_TITLE: - - qDebug() << "XINE_EVENT_UI_SET_TITLE"; - - QApplication::postEvent( xe, new XineEvent( XineEvent::MetaInfoChanged ) ); - + qLog(Debug) << "XINE_EVENT_UI_SET_TITLE"; + QApplication::postEvent(xe, new XineEvent(XineEvent::MetaInfoChanged)); break; case XINE_EVENT_UI_PLAYBACK_FINISHED: - qDebug() << "XINE_EVENT_UI_PLAYBACK_FINISHED"; + qLog(Debug) << "XINE_EVENT_UI_PLAYBACK_FINISHED"; //emit signal from GUI thread - QApplication::postEvent( xe, new XineEvent(XineEvent::PlaybackFinished) ); + QApplication::postEvent(xe, new XineEvent(XineEvent::PlaybackFinished)); break; case XINE_EVENT_PROGRESS: { xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data; QString msg = "%1 %2%"; - msg = msg.arg( QString::fromUtf8( pd->description ) ).arg( QString::number(pd->percent) + QLocale::system().percent() ); + msg = msg.arg(QString::fromUtf8(pd->description)).arg(QString::number(pd->percent) + QLocale::system().percent()); - XineEvent *e = new XineEvent( XineEvent::StatusMessage ); - e->setData( new QString( msg ) ); + XineEvent *e = new XineEvent(XineEvent::StatusMessage); + e->setData(new QString(msg)); - QApplication::postEvent( xe, e ); + QApplication::postEvent(xe, e); - } break; + } + break; case XINE_EVENT_MRL_REFERENCE_EXT: { - /// xine has read the stream and found it actually links to something else - /// so we need to play that instead + // xine has read the stream and found it actually links to something else so we need to play that instead QString message = QString::fromUtf8(static_cast(xineEvent->data)->mrl); - XineEvent *e = new XineEvent( XineEvent::Redirecting ); - e->setData( new QString( message ) ); + XineEvent *e = new XineEvent(XineEvent::Redirecting); + e->setData(new QString(message)); - QApplication::postEvent( xe, e ); + QApplication::postEvent(xe, e); - } break; + } + break; - case XINE_EVENT_UI_MESSAGE: - { - qDebug() << "message received from xine"; + case XINE_EVENT_UI_MESSAGE: { + qLog(Debug) << "message received from xine"; xine_ui_message_data_t *data = (xine_ui_message_data_t *)xineEvent->data; QString message; - switch( data->type ) - { - case XINE_MSG_NO_ERROR: - { + switch (data->type) { + case XINE_MSG_NO_ERROR: { //series of \0 separated strings, terminated with a \0\0 char str[2000]; char *p = str; - for( char *msg = data->messages; !(*msg == '\0' && *(msg+1) == '\0'); ++msg, ++p ) + for (char *msg = data->messages; !(*msg == '\0' && *(msg+1) == '\0'); ++msg, ++p) *p = *msg == '\0' ? '\n' : *msg; *p = '\0'; - qDebug() << str; + qLog(Debug) << str; break; } @@ -956,20 +761,17 @@ void XineEngine::XineEventListener( void *p, const xine_event_t* xineEvent ) { explain: // Don't flood the user with error messages - if( (last_error_time + 10) > time( ¤t ) && - data->type == last_error ) - { - last_error_time = current; + if ((last_error_time_ + 10) > time(¤t) && data->type == last_error_) { + last_error_time_ = current; return; } - last_error_time = current; - last_error = data->type; + last_error_time_ = current; + last_error_ = data->type; - if( data->explanation ) - { - message.prepend( "" ); + if (data->explanation) { + message.prepend(""); message += ":

"; - message += QString::fromUtf8( (char*)data + data->explanation ); + message += QString::fromUtf8((char*)data + data->explanation); } else break; //if no explanation then why bother! @@ -978,288 +780,305 @@ void XineEngine::XineEventListener( void *p, const xine_event_t* xineEvent ) { param: // Don't flood the user with error messages - if((last_error_time + 10) > time(¤t) && - data->type == last_error) - { - last_error_time = current; + if ((last_error_time_ + 10) > time(¤t) && data->type == last_error_) { + last_error_time_ = current; return; } - last_error_time = current; - last_error = data->type; + last_error_time_ = current; + last_error_ = data->type; - message.prepend( "

" ); + message.prepend("

"); message += "

"; - if(data->explanation) - { + if (data->explanation) { message += "xine parameters: "; - message += QString::fromUtf8( (char*)data + data->parameters ); + message += QString::fromUtf8((char*)data + data->parameters); message += ""; } else message += "Sorry, no additional information is available."; - QApplication::postEvent( xe, new XineEvent(XineEvent::InfoMessage, new QString(message)) ); + QApplication::postEvent(xe, new XineEvent(XineEvent::InfoMessage, new QString(message))); } } //case - case XINE_EVENT_UI_CHANNELS_CHANGED: //Flameeyes used this for last.fm track changes - QApplication::postEvent( xe, new XineEvent(XineEvent::LastFMTrackChanged) ); - break; } //switch #undef xe } -Engine::SimpleMetaBundle -XineEngine::fetchMetaData() const -{ - Engine::SimpleMetaBundle bundle; - bundle.title = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_TITLE ) ); - bundle.artist = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_ARTIST ) ); - bundle.album = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_ALBUM ) ); - bundle.comment = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_COMMENT ) ); - bundle.genre = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_GENRE ) ); - bundle.bitrate = QString::number( xine_get_stream_info( stream_, XINE_STREAM_INFO_AUDIO_BITRATE ) / 1000 ); - bundle.samplerate = QString::number( xine_get_stream_info( stream_, XINE_STREAM_INFO_AUDIO_SAMPLERATE ) ); - bundle.year = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_YEAR ) ); - bundle.tracknr = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_TRACK_NUMBER ) ); +bool XineEngine::event(QEvent *e) { - return bundle; -} +#define message static_cast(static_cast(e)->data()) -bool XineEngine::metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b) { - - bool result = false; - xine_stream_t* tmpstream = xine_stream_new(xine_, NULL, NULL); - if (xine_open(tmpstream, QFile::encodeName(url.toString()))) { - QString audioCodec = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_SYSTEMLAYER)); + switch(e->type()) { + case XineEvent::PlaybackFinished: //XINE_EVENT_UI_PLAYBACK_FINISHED + emit TrackEnded(); + return true; - if (audioCodec == "CDDA") { - QString title = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_TITLE)); - if ((!title.isNull()) && (!title.isEmpty())) { //no meta info - b.title = title; - b.artist = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_ARTIST)); - b.album = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_ALBUM)); - b.genre = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_GENRE)); - b.year = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_YEAR)); - b.tracknr = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_TRACK_NUMBER)); - if( b.tracknr.isEmpty() ) - b.tracknr = QFileInfo(url.path()).fileName(); - } - else { - b.title = QString("Track %1").arg(QFileInfo(url.path()).fileName()); - b.album = "AudioCD"; + case XineEvent::InfoMessage: + emit InfoMessage((*message).arg(url_.toString())); + delete message; + return true; + + case XineEvent::StatusMessage: + emit StatusText(*message); + delete message; + return true; + + case XineEvent::MetaInfoChanged: { //meta info has changed + qLog(Debug) << "Metadata received."; + const Engine::SimpleMetaBundle bundle = fetchMetaData(); + if (bundle.title != current_bundle_.title || bundle.artist != current_bundle_.artist) { + current_bundle_ = bundle; + emit MetaData(bundle); } + return true; } - if (audioCodec == "CDDA" || audioCodec == "WAV") { - result = true; - int samplerate = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_SAMPLERATE ); + case XineEvent::Redirecting: + emit StatusText(QString("Redirecting to: ").arg(*message)); + Load(QUrl(*message), Engine::Auto, false, 0, 0); + Play(0); + delete message; + return true; - // xine would provide a XINE_STREAM_INFO_AUDIO_BITRATE, but unfortunately not for CDDA or WAV so we calculate the bitrate by our own - int bitsPerSample = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_BITS ); - int nbrChannels = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_CHANNELS ); - int bitrate = (samplerate * bitsPerSample * nbrChannels) / 1000; - - b.bitrate = QString::number(bitrate); - b.samplerate = QString::number(samplerate); - int pos, time, length = 0; - xine_get_pos_length(tmpstream, &pos, &time, &length); - b.length = QString::number(length / 1000); - } - xine_close(tmpstream); - } - xine_dispose(tmpstream); - return result; -} - -bool XineEngine::getAudioCDContents(const QString &device, QList &urls) { - - const char * const *xine_urls = NULL; - int num; - int i = 0; - - if (!device.isNull()) { - qDebug() << "xine-engine setting CD Device to: " << device; - xine_cfg_entry_t config; - if (!xine_config_lookup_entry(xine_, "input.cdda_device", &config)) { - emit StatusText("Failed CD device lookup in xine engine"); - return false; - } - config.str_value = (char *)device.toLatin1().constData(); - xine_config_update_entry(xine_, &config); + default: + break; } - emit StatusText("Getting AudioCD contents..."); - - xine_urls = xine_get_autoplay_mrls(xine_, "CD", &num); - - if (xine_urls) { - while (xine_urls[i]) { - urls << QUrl(xine_urls[i]); - ++i; - } - } - else emit StatusText("Could not read AudioCD"); - - return true; -} - -bool XineEngine::flushBuffer() -{ +#undef message return false; } -bool XineEngine::lastFmProxyRequired() { - return !( xine_check_version(1,1,9) ); +Engine::SimpleMetaBundle XineEngine::fetchMetaData() const { + + Engine::SimpleMetaBundle bundle; + bundle.title = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_TITLE)); + bundle.artist = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_ARTIST)); + bundle.album = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_ALBUM)); + bundle.comment = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_COMMENT)); + bundle.genre = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_GENRE)); + bundle.bitrate = QString::number(xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_BITRATE) / 1000); + bundle.samplerate = QString::number(xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_SAMPLERATE)); + bundle.year = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_YEAR)); + bundle.tracknr = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_TRACK_NUMBER)); + + return bundle; + } -////////////////////////////////////////////////////////////////////////////// -/// class Fader -////////////////////////////////////////////////////////////////////////////// +bool XineEngine::MakeNewStream() { -Fader::Fader( XineEngine *engine, uint fadeMs ) - : QThread(engine) - , engine_( engine ) - , xine_( engine->xine_ ) - , decrease_( engine->stream_ ) - , increase_( 0 ) - , port_( engine->audioPort_ ) - , post_( engine->post_ ) - , fadeLength_( fadeMs ) - , paused_( false ) - , terminated_( false ) -{ - - if( engine->makeNewStream() ) { - increase_ = engine->stream_; - - xine_set_param( increase_, XINE_PARAM_AUDIO_AMP_LEVEL, 0 ); + if (device_.isValid()) { + bool valid(false); + xine_cfg_entry_t entry; + switch (device_.type()) { + case QVariant::String: + if (device_.toString().isEmpty()) break; + valid = true; + xine_config_register_string(xine_, "audio.device.alsa_front_device", device_.toString().toUtf8().data(), "", "", 10, nullptr, nullptr); + break; + case QVariant::ByteArray: + valid = true; + xine_config_register_string(xine_, "audio.device.alsa_front_device", device_.toByteArray().data(), "", "", 10, nullptr, nullptr); + break; + default: + qLog(Error) << "Unknown device type" << device_; + break; + } + if (valid) { + xine_config_lookup_entry(xine_, "audio.device.alsa_front_device", &entry); + xine_config_update_entry(xine_, &entry); + } } - else { - s_fader = 0; - deleteLater(); + + audioport_ = xine_open_audio_driver(xine_, (output_.isEmpty() || output_ == kAutoOutput ? nullptr : output_.toUtf8().constData()), nullptr); + if (!audioport_) { + emit Error("Xine was unable to initialize any audio drivers."); + return false; } + + stream_ = xine_stream_new(xine_, audioport_, nullptr); + if (!stream_) { + xine_close_audio_driver(xine_, audioport_); + audioport_ = nullptr; + emit Error("Could not create a new xine stream"); + return false; + } + + if (eventqueue_) xine_event_dispose_queue(eventqueue_); + + eventqueue_ = xine_event_new_queue(stream_); + xine_event_create_listener_thread(eventqueue_, &XineEngine::XineEventListener, (void*)this); + +#ifndef XINE_SAFE_MODE + // Implemented in xinescope.h + post_ = scope_plugin_new(xine_, audioport_); + xine_set_param(stream_, XINE_PARAM_METRONOM_PREBUFFER, 6000); + xine_set_param(stream_, XINE_PARAM_IGNORE_VIDEO, 1); +#endif + +#ifdef XINE_PARAM_EARLY_FINISHED_EVENT + if (xine_check_version(1, 1, 1) && !(fade_length_ > 0)) { + // Enable gapless playback + qLog(Debug) << "gapless playback enabled."; + // xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 1); + } +#endif + return true; } -Fader::~Fader() { +bool XineEngine::EnsureStream() { - wait(); + if (!stream_) return MakeNewStream(); + return true; - xine_close( decrease_ ); - xine_dispose( decrease_ ); - xine_close_audio_driver( xine_, port_ ); - if( post_ ) xine_post_dispose( xine_, post_ ); - - if( !engine_->stopFader_ ) - engine_->SetVolume( engine_->volume() ); - - engine_->stopFader_ = false; - s_fader = 0; } -void Fader::run() { +void XineEngine::DetermineAndShowErrorMessage() { - // do a volume change in 100 steps (or every 10ms) - uint stepsCount = fadeLength_ < 1000 ? fadeLength_ / 10 : 100; - uint stepSizeUs = (int)( 1000.0 * (float)fadeLength_ / (float)stepsCount ); + QString body; - float mix = 0.0; - float elapsedUs = 0.0; - while ( mix < 1.0 ) { - if ( terminated_ ) + switch (xine_get_error(stream_)) { + case XINE_ERROR_NO_INPUT_PLUGIN: + body = "No suitable input plugin. This often means that the url's protocol is not supported. Network failures are other possible causes."; break; - // sleep a constant amount of time - QThread::usleep( stepSizeUs ); - if ( paused_ ) - continue; + case XINE_ERROR_NO_DEMUX_PLUGIN: + body = "No suitable demux plugin. This often means that the file format is not supported."; + break; - elapsedUs += stepSizeUs; + case XINE_ERROR_DEMUX_FAILED: + body = "Demuxing failed."; + break; - // get volume (amarok main * equalizer preamp) - float vol = Engine::Base::MakeVolumeLogarithmic( engine_->volume_ ) * engine_->preamp_; + case XINE_ERROR_INPUT_FAILED: + body = "Could not open file."; + break; - // compute the mix factor as the percentage of time spent since fade begun - float mix = (elapsedUs / 1000.0) / (float)fadeLength_; - if ( mix > 1.0 ) - { - if ( increase_ ) - xine_set_param( increase_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)vol ); + case XINE_ERROR_MALFORMED_MRL: + body = "The location is malformed."; + break; + + case XINE_ERROR_NONE: + // Xine is thick. Xine doesn't think there is an error but there may be! We check for other errors below. + default: + if (!xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_HANDLED)) { + // xine can read the plugin but it didn't find any codec + // THUS xine=daft for telling us it could handle the format in canDecode! + body = "There is no available decoder."; + QString const ext = QFileInfo(url_.path()).completeSuffix(); + // TODO: + // if (ext == "mp3" && EngineController::installDistroCodec("xine-engine")) + // return; + } + else if (!xine_get_stream_info(stream_, XINE_STREAM_INFO_HAS_AUDIO)) + body = "There is no audio channel!"; + break; + } + + // TODO: + qWarning() << body; + +} + +const Engine::Scope &XineEngine::scope(int chunk_length) { + + if (!post_ || !stream_ || xine_get_status(stream_) != XINE_STATUS_PLAY) + return scope_; + + MyNode *const myList = scope_plugin_list(post_); + metronom_t *const myMetronom = scope_plugin_metronom(post_); + const int myChannels = scope_plugin_channels(post_); + int scopeidx = 0; + + if (myChannels > 2) return scope_; + + for (int n, frame = 0; frame < 512;) { + + MyNode *best_node = 0; + + for (MyNode *node = myList->next; node != myList; node = node->next, log_buffer_count_++) + if (node->vpts <= current_vpts_ && (!best_node || node->vpts > best_node->vpts)) + best_node = node; + + if (!best_node || best_node->vpts_end < current_vpts_) { + log_no_suitable_buffer_++; 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 ) ); + int64_t diff = current_vpts_; + diff -= best_node->vpts; + diff *= 1<<16; + diff /= myMetronom->pts_per_smpls; + + const int16_t *data16 = best_node->mem; + data16 += diff; + + diff += diff % myChannels; // Important correction to ensure we don't overflow the buffer + diff /= myChannels; // Use units of frames, not samples + + // Calculate the number of available samples in this buffer + n = best_node->num_frames; + n -= diff; + n += frame; //clipping for # of frames we need + + if (n > 512) + n = 512; // We don't want more than 512 frames + + for (int c; frame < n; ++frame, data16 += myChannels) { + for (c = 0; c < myChannels; ++c) { + // We now give interleaved pcm to the scope + scope_[scopeidx++] = data16[c]; + if (myChannels == 1) // Duplicate mono samples + scope_[scopeidx++] = data16[c]; + } } + + current_vpts_ = best_node->vpts_end; + current_vpts_++; // FIXME: Needs to be done for some reason, or you get situations where it uses same buffer again and again } - //stop using cpu! - xine_stop( decrease_ ); + log_scope_call_count_++; + + return scope_; - deleteLater(); } -void Fader::pause() { - paused_ = true; +void XineEngine::PruneScope() { + + if (!stream_) return; + + // Here we prune the buffer list regularly + + MyNode *myList = scope_plugin_list(post_); + + if (!myList) return; + + // We operate on a subset of the list for thread-safety + MyNode * const first_node = myList->next; + MyNode const * const list_end = myList; + + current_vpts_ = (xine_get_status(stream_) == XINE_STATUS_PLAY) ? xine_get_current_vpts(stream_) + : LLONG_MAX; //if state is not playing OR paused, empty the list + //: std::numeric_limits::max(); //TODO Don't support crappy gcc 2.95 + + for (MyNode *prev = first_node, *node = first_node->next; node != list_end; node = node->next) { + //we never delete first_node this maintains thread-safety + if (node->vpts_end < current_vpts_) { + prev->next = node->next; + + free(node->mem); + free(node); + + node = prev; + } + + prev = node; + } } -void Fader::resume() { - paused_ = false; -} - -void Fader::finish() { - terminated_ = true; -} - -////////////////////////////////////////////////////////////////////////////// -/// class OutFader -////////////////////////////////////////////////////////////////////////////// - -OutFader::OutFader( XineEngine *engine, uint fadeLength ) - : QThread(engine) - , engine_( engine ) - , terminated_( false ) - , fadeLength_( fadeLength ) -{ -} - -OutFader::~OutFader() { - wait(); - - s_outfader = 0; -} - -void OutFader::run() { - - engine_->fadeOut( fadeLength_, &terminated_ ); - - xine_stop( engine_->stream_ ); - xine_close( engine_->stream_ ); - xine_set_param( engine_->stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); - - deleteLater(); -} - -void OutFader::finish() { - - terminated_ = true; -} - -PruneScopeThread::PruneScopeThread(XineEngine *parent) - : engine_(parent) -{ -} +PruneScopeThread::PruneScopeThread(XineEngine *parent) : engine_(parent) {} void PruneScopeThread::run() { @@ -1270,19 +1089,11 @@ void PruneScopeThread::run() { exec(); } -qint64 XineEngine::position_nanosec() const { - return 0; -} - -qint64 XineEngine::length_nanosec() const { - return 0; -} - EngineBase::PluginDetailsList XineEngine::GetPluginList() const { PluginDetailsList ret; const char *const *plugins = xine_list_audio_output_plugins(xine_); - + { PluginDetails details; details.name = "auto"; @@ -1290,57 +1101,20 @@ EngineBase::PluginDetailsList XineEngine::GetPluginList() const { ret << details; } - for(int i =0 ; plugins[i] ; ++i) { + for (int i =0 ; plugins[i] ; ++i) { PluginDetails details; details.name = QString::fromUtf8(plugins[i]); - if (details.name == "alsa") details.description = "ALSA"; - else if (details.name == "oss") details.description = "OSS"; - else if (details.name == "pulseaudio") details.description = "PulseAudio"; - else if (details.name == "file") details.description = "File"; + if (details.name == "alsa") details.description = "ALSA audio output"; + else if (details.name == "oss") details.description = "OSS audio output"; + else if (details.name == "pulseaudio") details.description = "PulseAudio audio output"; + else if (details.name == "file") details.description = "File audio output"; else if (details.name == "none") details.description = "None"; else details.description = QString::fromUtf8(plugins[i]); ret << details; - qLog(Debug) << details.name << details.description; } return ret; } -EngineBase::OutputDetailsList XineEngine::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 == "pulseaudio") output.iconname = "pulseaudio"; - else if (plugin.name == "bluetooth") output.iconname = "bluetooth"; - else if (plugin.name == "file") output.iconname = "document-new"; - else output.iconname = "soundcard"; - ret.append(output); - } - - return ret; -} - -bool XineEngine::ALSADeviceSupport(const QString &name) { - - return (name == "alsa" || name == "oss"); - -} - -#if 0 -void XineEngine::SetOutput(QString output, QString device) { - - currentAudioPlugin_ = output; - currentAudioDevice_ = device; - -} - -#endif diff --git a/src/engine/xineengine.h b/src/engine/xineengine.h index fa5253c2..e9f353dc 100644 --- a/src/engine/xineengine.h +++ b/src/engine/xineengine.h @@ -1,5 +1,10 @@ /*************************************************************************** - * Copyright (C) 2004,5 Max Howell * + * Copyright (C) 2017-2018 Jonas Kvinge * + * Copyright (C) 2005 Christophe Thommeret * + * (C) 2005 Ian Monroe * + * (C) 2005-2006 Mark Kretschmann * + * (C) 2004-2005 Max Howell * + * (C) 2003-2004 J. Kofler * * * * 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 +#include +#include +#include +#include + #include #include #include @@ -22,24 +33,20 @@ #include #include -extern "C" -{ - #include - #include - #include - #include - #include -} - #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 &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& ); - 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 &urls); + bool FlushBuffer(); - Engine::SimpleMetaBundle fetchMetaData() const; + void SetEqualizerEnabled(bool enabled); + void SetEqualizerParameters(int preamp, const QList&); - 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 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 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 s_fader_; + std::unique_ptr s_outfader_; + + int int_preamp_; + QMutex init_mutex_; + int64_t current_vpts_; + QList 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_; }; diff --git a/src/engine/xinefader.cpp b/src/engine/xinefader.cpp new file mode 100644 index 00000000..f8f40d64 --- /dev/null +++ b/src/engine/xinefader.cpp @@ -0,0 +1,150 @@ +/*************************************************************************** + * Copyright (C) 2017-2018 Jonas Kvinge * + * Copyright (C) 2005 Christophe Thommeret * + * (C) 2005 Ian Monroe * + * (C) 2005-2006 Mark Kretschmann * + * (C) 2004-2005 Max Howell * + * (C) 2003-2004 J. Kofler * + * * + * 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 + +#include +#include + +#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; +} + diff --git a/src/engine/xinefader.h b/src/engine/xinefader.h new file mode 100644 index 00000000..3594923f --- /dev/null +++ b/src/engine/xinefader.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2017-2018 Jonas Kvinge * + * Copyright (C) 2005 Christophe Thommeret * + * (C) 2005 Ian Monroe * + * (C) 2005-2006 Mark Kretschmann * + * (C) 2004-2005 Max Howell * + * (C) 2003-2004 J. Kofler * + * * + * 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 +#include + +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 diff --git a/src/engine/xinescope.c b/src/engine/xinescope.c index b1738d9a..f8925b62 100644 --- a/src/engine/xinescope.c +++ b/src/engine/xinescope.c @@ -11,6 +11,8 @@ #include "config.h" +#include + #include "xinescope.h" #include #include @@ -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; } diff --git a/src/globalshortcuts/globalshortcuts.cpp b/src/globalshortcuts/globalshortcuts.cpp index 0232e294..938ddb48 100644 --- a/src/globalshortcuts/globalshortcuts.cpp +++ b/src/globalshortcuts/globalshortcuts.cpp @@ -133,7 +133,7 @@ void GlobalShortcuts::Register() { } bool GlobalShortcuts::IsMacAccessibilityEnabled() const { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS return static_cast(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(system_backend_)->ShowAccessibilityDialog(); #endif } diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index ca577be3..33a4b30b 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -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)"); diff --git a/src/playlist/playlistfilter.cpp b/src/playlist/playlistfilter.cpp index 4d76b478..c8e1463f 100644 --- a/src/playlist/playlistfilter.cpp +++ b/src/playlist/playlistfilter.cpp @@ -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() { diff --git a/src/settings/backendsettingspage.cpp b/src/settings/backendsettingspage.cpp index 093b7466..d71b3342 100644 --- a/src/settings/backendsettingspage.cpp +++ b/src/settings/backendsettingspage.cpp @@ -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(); + 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(); + 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(); - 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(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(); - 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(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(); - 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(); + 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(); + 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(); + 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(); - s_.setValue("device", ui_->combobox_device->itemData(ui_->combobox_device->currentIndex()).value()); + 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(); - 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(); - 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(); + 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(); - 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(); Load_Device(output.name, QVariant()); -} -#endif - -#ifdef HAVE_GSTREAMER -void BackendSettingsPage::Gst_OutputChanged(int index) { - - EngineBase::OutputDetails output = ui_->combobox_output->itemData(index).value(); - 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(); QVariant device = ui_->combobox_device->itemData(index).value(); - 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(); + bool found(false); + for (int i = 0; i < ui_->combobox_device->count(); ++i) { - QVariant v = ui_->combobox_device->itemData(i).value(); - 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(); + 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("" + text + ""); + + 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; + +} diff --git a/src/settings/backendsettingspage.h b/src/settings/backendsettingspage.h index ef9a8feb..643a9b01 100644 --- a/src/settings/backendsettingspage.h +++ b/src/settings/backendsettingspage.h @@ -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_; }; diff --git a/src/settings/backendsettingspage.ui b/src/settings/backendsettingspage.ui index e4815529..8bf0bfed 100644 --- a/src/settings/backendsettingspage.ui +++ b/src/settings/backendsettingspage.ui @@ -25,6 +25,12 @@ + + + 60 + 0 + + Engine @@ -38,6 +44,12 @@ true + + + 60 + 0 + + Output @@ -55,6 +67,12 @@ true + + + 60 + 0 + + Device @@ -76,7 +94,7 @@ - 80 + 160 16777215 @@ -102,14 +120,26 @@ - + + + + + + + Buffer + + + + QFormLayout::AllNonFixedFieldsGrow + + Buffer duration - + ms @@ -122,14 +152,14 @@ - + Minimum buffer fill - + @@ -159,16 +189,6 @@ - - - - Changing mono playback preference will be effective for the next playing songs - - - Mono playback - - - @@ -273,6 +293,75 @@ + + + + Changing mono playback preference will be effective for the next playing songs + + + Mono playback + + + + + + + + + + + + + false + + + + 0 + 70 + + + + + 64 + 64 + + + + + + + + + + + false + + + + 0 + 70 + + + + + 16777215 + 64 + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + diff --git a/src/settings/playbacksettingspage.cpp b/src/settings/playbacksettingspage.cpp index fcce1d2b..1bddf06f 100644 --- a/src/settings/playbacksettingspage.cpp +++ b/src/settings/playbacksettingspage.cpp @@ -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()); } diff --git a/src/settings/playbacksettingspage.ui b/src/settings/playbacksettingspage.ui index 902f558b..05749e78 100644 --- a/src/settings/playbacksettingspage.ui +++ b/src/settings/playbacksettingspage.ui @@ -15,7 +15,7 @@ - + Show a glowing animation on the current track @@ -25,13 +25,13 @@ - + Fading - + Fade out when stopping a track @@ -41,7 +41,7 @@ - + Cross-fade when changing tracks manually @@ -51,14 +51,14 @@ - + Cross-fade when changing tracks automatically - + false @@ -68,13 +68,22 @@ - + - + + 0 + + + 0 + + + 0 + + 0 - + Fading duration @@ -84,7 +93,7 @@ - + ms @@ -100,7 +109,7 @@ - + Qt::Horizontal @@ -116,16 +125,16 @@ - + Fade out on pause / fade in on resume - + - + Fading duration @@ -135,7 +144,7 @@ - + ms @@ -183,19 +192,12 @@ - - - StickySlider - QSlider -

widgets/stickyslider.h
- - - fading_auto + checkbox_fadeout_auto toggled(bool) - fading_samealbum + checkbox_fadeout_samealbum setEnabled(bool) diff --git a/src/settings/settingsdialog.cpp b/src/settings/settingsdialog.cpp index c0b97426..4f522e40 100644 --- a/src/settings/settingsdialog.cpp +++ b/src/settings/settingsdialog.cpp @@ -44,6 +44,8 @@ #include #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(app_->player()->engine())), - //engine_(app_->player()->engine()), appearance_(app_->appearance()), ui_(new Ui_SettingsDialog), - //mui_(parent), loading_settings_(false) { ui_->setupUi(this); diff --git a/src/settings/settingsdialog.h b/src/settings/settingsdialog.h index a9cebe36..e863b130 100644 --- a/src/settings/settingsdialog.h +++ b/src/settings/settingsdialog.h @@ -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_; diff --git a/src/settings/shortcutssettingspage.cpp b/src/settings/shortcutssettingspage.cpp index bb0ce309..f7d97012 100644 --- a/src/settings/shortcutssettingspage.cpp +++ b/src/settings/shortcutssettingspage.cpp @@ -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; diff --git a/src/widgets/statusview.cpp b/src/widgets/statusview.cpp index 7d6dc6fa..4d2f3c00 100644 --- a/src/widgets/statusview.cpp +++ b/src/widgets/statusview.cpp @@ -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("
"); - html += QString("Engine: %1
").arg(EngineName); + html += QString("Engine: %1
").arg(EngineName(enginetype)); } html += QString("
");