Add dialog to display streams' audio details (#5547)

* Add Stream Details window

* Fix capitalization in StreamDiscoverer::Discover()

* StreamDiscoverer::Discover(): get URL by const reference

* Refactor StreamDiscoverer::Discover

* Rename StreamDiscoverer callbacks

* StreamDiscoverer::OnDiscovered: fix nullptr comparison

* StreamDiscoverer: rename DiscoverFinished signal

* StreamDiscoverer::DataReady: receive const reference

* StreamDiscoverer: Remove unsigned types

* StreamDetailsDialog: rename Close slot

* StreamDetailsDialog: rename ui pointer to ui_

* MainWindow::ShowStreamDetails: receive a const reference

* StreamDetailsDialog: use unique_ptr, remove unsigned types
This commit is contained in:
Santiago Gil 2016-12-21 13:57:04 -03:00 committed by John Maguire
parent 589d641955
commit d3898d2f47
11 changed files with 459 additions and 0 deletions

View File

@ -68,6 +68,7 @@ pkg_check_modules(GSTREAMER_APP REQUIRED gstreamer-app-1.0)
pkg_check_modules(GSTREAMER_AUDIO REQUIRED gstreamer-audio-1.0)
pkg_check_modules(GSTREAMER_BASE REQUIRED gstreamer-base-1.0)
pkg_check_modules(GSTREAMER_TAG REQUIRED gstreamer-tag-1.0)
pkg_check_modules(GSTREAMER_PBUTILS REQUIRED gstreamer-pbutils-1.0)
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
pkg_check_modules(LIBMTP libmtp>=1.0)
pkg_check_modules(LIBMYGPO_QT libmygpo-qt>=1.0.9)
@ -155,6 +156,7 @@ include_directories(${GSTREAMER_APP_INCLUDE_DIRS})
include_directories(${GSTREAMER_AUDIO_INCLUDE_DIRS})
include_directories(${GSTREAMER_BASE_INCLUDE_DIRS})
include_directories(${GSTREAMER_TAG_INCLUDE_DIRS})
include_directories(${GSTREAMER_PBUTILS_INCLUDE_DIRS})
include_directories(${GLIB_INCLUDE_DIRS})
include_directories(${GLIBCONFIG_INCLUDE_DIRS})
include_directories(${LIBXML_INCLUDE_DIRS})

View File

@ -311,6 +311,7 @@ set(SOURCES
songinfo/songkickconcertwidget.cpp
songinfo/songplaystats.cpp
songinfo/spotifyimages.cpp
songinfo/streamdiscoverer.cpp
songinfo/taglyricsinfoprovider.cpp
songinfo/ultimatelyricslyric.cpp
songinfo/ultimatelyricsprovider.cpp
@ -358,6 +359,7 @@ set(SOURCES
ui/settingsdialog.cpp
ui/settingspage.cpp
ui/standarditemiconloader.cpp
ui/streamdetailsdialog.cpp
ui/systemtrayicon.cpp
ui/trackselectiondialog.cpp
ui/windows7thumbbar.cpp
@ -603,6 +605,7 @@ set(HEADERS
songinfo/songkickconcertwidget.h
songinfo/songplaystats.h
songinfo/spotifyimages.h
songinfo/streamdiscoverer.h
songinfo/taglyricsinfoprovider.h
songinfo/ultimatelyricslyric.h
songinfo/ultimatelyricsprovider.h
@ -641,6 +644,7 @@ set(HEADERS
ui/settingsdialog.h
ui/settingspage.h
ui/standarditemiconloader.h
ui/streamdetailsdialog.h
ui/systemtrayicon.h
ui/trackselectiondialog.h
ui/windows7thumbbar.h
@ -764,6 +768,7 @@ set(UI
ui/organiseerrordialog.ui
ui/playbacksettingspage.ui
ui/settingsdialog.ui
ui/streamdetailsdialog.ui
ui/trackselectiondialog.ui
widgets/equalizerslider.ui
@ -1265,6 +1270,7 @@ target_link_libraries(clementine_lib
${GSTREAMER_LIBRARIES}
${GSTREAMER_APP_LIBRARIES}
${GSTREAMER_TAG_LIBRARIES}
${GSTREAMER_PBUTILS_LIBRARIES}
${QTSINGLEAPPLICATION_LIBRARIES}
${QTSINGLECOREAPPLICATION_LIBRARIES}
${QTIOCOMPRESSOR_LIBRARIES}

View File

@ -39,6 +39,7 @@
#include <QtConcurrentRun>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include "config.h"
#include "devicefinder.h"
@ -146,6 +147,8 @@ bool GstEngine::Init() {
void GstEngine::InitialiseGstreamer() {
gst_init(nullptr, nullptr);
gst_pb_utils_init();
#ifdef HAVE_MOODBAR
gstfastspectrum_register_static();
#endif

View File

@ -0,0 +1,125 @@
#include "streamdiscoverer.h"
#include <gst/pbutils/pbutils.h>
#include "core/logging.h"
#include "core/signalchecker.h"
#include "core/waitforsignal.h"
#include <QEventLoop>
const int StreamDiscoverer::kDiscoveryTimeoutS = 10;
StreamDiscoverer::StreamDiscoverer() : QObject(nullptr) {
// Setting up a discoverer:
discoverer_ = gst_discoverer_new(kDiscoveryTimeoutS * GST_SECOND, NULL);
if (discoverer_ == NULL) {
qLog(Error) << "Error creating discoverer" << endl;
return;
}
// Connecting its signals:
CHECKED_GCONNECT(discoverer_, "discovered", &OnDiscovered, this);
CHECKED_GCONNECT(discoverer_, "finished", &OnFinished, this);
// Starting the discoverer process:
gst_discoverer_start(discoverer_);
}
StreamDiscoverer::~StreamDiscoverer() {
gst_discoverer_stop(discoverer_);
g_object_unref(discoverer_);
}
void StreamDiscoverer::Discover(const QString& url) {
// Adding the request to discover the url given as a parameter:
qLog(Debug) << "Discover" << url;
if (!gst_discoverer_discover_uri_async(discoverer_,
url.toStdString().c_str())) {
qLog(Error) << "Failed to start discovering" << url << endl;
return;
}
WaitForSignal(this, SIGNAL(DiscoverFinished()));
}
void StreamDiscoverer::OnDiscovered(GstDiscoverer* discoverer,
GstDiscovererInfo* info, GError* err,
gpointer self) {
StreamDiscoverer* instance = reinterpret_cast<StreamDiscoverer*>(self);
QString discovered_url(gst_discoverer_info_get_uri(info));
GstDiscovererResult result = gst_discoverer_info_get_result(info);
if (result != GST_DISCOVERER_OK) {
QString error_message = GSTdiscovererErrorMessage(result);
qLog(Error) << "Discovery failed:" << error_message << endl;
emit instance->Error(
tr("Error discovering %1: %2").arg(discovered_url).arg(error_message));
return;
}
// Get audio streams (we will only care about the first one, which should be
// the only one).
GList* audio_streams = gst_discoverer_info_get_audio_streams(info);
if (audio_streams != nullptr) {
qLog(Debug) << "Discovery successful" << endl;
// We found a valid audio stream, extracting and saving its info:
GstDiscovererStreamInfo* stream_audio_info =
(GstDiscovererStreamInfo*)g_list_first(audio_streams)->data;
StreamDetails stream_details;
stream_details.url = discovered_url;
stream_details.bitrate = gst_discoverer_audio_info_get_bitrate(
GST_DISCOVERER_AUDIO_INFO(stream_audio_info));
stream_details.channels = gst_discoverer_audio_info_get_channels(
GST_DISCOVERER_AUDIO_INFO(stream_audio_info));
stream_details.depth = gst_discoverer_audio_info_get_depth(
GST_DISCOVERER_AUDIO_INFO(stream_audio_info));
stream_details.sample_rate = gst_discoverer_audio_info_get_sample_rate(
GST_DISCOVERER_AUDIO_INFO(stream_audio_info));
// Human-readable codec name:
GstCaps* stream_caps =
gst_discoverer_stream_info_get_caps(stream_audio_info);
gchar* decoder_description =
gst_pb_utils_get_codec_description(stream_caps);
stream_details.format = (decoder_description == NULL)
? QString(tr("Unknown"))
: QString(decoder_description);
gst_caps_unref(stream_caps);
g_free(decoder_description);
emit instance->DataReady(stream_details);
} else {
emit instance->Error(
tr("Could not detect an audio stream in %1").arg(discovered_url));
}
gst_discoverer_stream_info_list_free(audio_streams);
}
void StreamDiscoverer::OnFinished(GstDiscoverer* discoverer, gpointer self) {
// The discoverer doesn't have any more urls in its queue. Let the loop know
// it can exit.
StreamDiscoverer* instance = reinterpret_cast<StreamDiscoverer*>(self);
emit instance->DiscoverFinished();
}
QString StreamDiscoverer::GSTdiscovererErrorMessage(
GstDiscovererResult result) {
switch (result) {
case (GST_DISCOVERER_URI_INVALID):
return tr("Invalid URL");
case (GST_DISCOVERER_TIMEOUT):
return tr("Connection timed out");
case (GST_DISCOVERER_BUSY):
return tr("The discoverer is busy");
case (GST_DISCOVERER_MISSING_PLUGINS):
return tr("Missing plugins");
case (GST_DISCOVERER_ERROR):
default:
return tr("Could not get details");
}
}

View File

@ -0,0 +1,48 @@
#ifndef STREAMDISCOVERER_H
#define STREAMDISCOVERER_H
#include <gst/pbutils/pbutils.h>
#include <QMetaType>
#include <QObject>
#include <QString>
struct StreamDetails {
QString url;
QString format;
int bitrate;
int depth;
int channels;
int sample_rate;
};
Q_DECLARE_METATYPE(StreamDetails)
class StreamDiscoverer : public QObject {
Q_OBJECT
public:
StreamDiscoverer();
~StreamDiscoverer();
void Discover(const QString& url);
signals:
void DiscoverFinished();
void DataReady(const StreamDetails& data);
void Error(const QString& message);
private:
GstDiscoverer* discoverer_;
static const int kDiscoveryTimeoutS;
// GstDiscoverer callbacks:
static void OnDiscovered(GstDiscoverer* discoverer, GstDiscovererInfo* info,
GError* err, gpointer instance);
static void OnFinished(GstDiscoverer* discoverer, gpointer instance);
// Helper to return descriptive error messages:
static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
};
#endif // STREAMDISCOVERER_H

View File

@ -101,6 +101,7 @@
#include "smartplaylists/generatormimedata.h"
#include "songinfo/artistinfoview.h"
#include "songinfo/songinfoview.h"
#include "songinfo/streamdiscoverer.h"
#include "transcoder/transcodedialog.h"
#include "ui/about.h"
#include "ui/addstreamdialog.h"
@ -113,6 +114,7 @@
#include "ui/organiseerrordialog.h"
#include "ui/qtsystemtrayicon.h"
#include "ui/settingsdialog.h"
#include "ui/streamdetailsdialog.h"
#include "ui/systemtrayicon.h"
#include "ui/trackselectiondialog.h"
#include "ui/windows7thumbbar.h"
@ -173,6 +175,7 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
tray_icon_(tray_icon),
osd_(osd),
edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)),
stream_discoverer_(std::bind(&MainWindow::CreateStreamDiscoverer, this)),
global_shortcuts_(new GlobalShortcuts(this)),
global_search_view_(new GlobalSearchView(app_, this)),
library_view_(new LibraryViewContainer(this)),
@ -477,6 +480,8 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
SLOT(ShowQueueManager()));
connect(ui_->action_add_files_to_transcoder, SIGNAL(triggered()),
SLOT(AddFilesToTranscoder()));
connect(ui_->action_view_stream_details, SIGNAL(triggered()),
SLOT(DiscoverStreamDetails()));
background_streams_->AddAction("Rain", ui_->action_rain);
background_streams_->AddAction("Hypnotoad", ui_->action_hypnotoad);
@ -684,6 +689,7 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
playlist_menu_->addAction(ui_->action_remove_from_playlist);
playlist_undoredo_ = playlist_menu_->addSeparator();
playlist_menu_->addAction(ui_->action_edit_track);
playlist_menu_->addAction(ui_->action_view_stream_details);
playlist_menu_->addAction(ui_->action_edit_value);
playlist_menu_->addAction(ui_->action_renumber_tracks);
playlist_menu_->addAction(ui_->action_selection_set_value);
@ -1712,6 +1718,10 @@ void MainWindow::PlaylistRightClick(const QPoint& global_pos,
// no 'show in browser' action if only streams are selected
playlist_open_in_browser_->setVisible(streams != all);
// If exactly one stream is selected, enable the 'show details' action.
ui_->action_view_stream_details->setEnabled(all == 1 && streams == 1);
ui_->action_view_stream_details->setVisible(all == 1 && streams == 1);
bool track_column = (index.column() == Playlist::Column_Track);
ui_->action_renumber_tracks->setVisible(editable >= 2 && track_column);
ui_->action_selection_set_value->setVisible(editable >= 2 && !track_column);
@ -1882,6 +1892,27 @@ void MainWindow::EditTagDialogAccepted() {
app_->playlist_manager()->current()->Save();
}
void MainWindow::DiscoverStreamDetails() {
int row = playlist_menu_index_.row();
Song song = app_->playlist_manager()->current()->item_at(row)->Metadata();
QString url = song.url().toString();
stream_discoverer_->Discover(url);
}
void MainWindow::ShowStreamDetails(const StreamDetails& details) {
StreamDetailsDialog stream_details_dialog(this);
stream_details_dialog.setUrl(details.url);
stream_details_dialog.setFormat(details.format);
stream_details_dialog.setBitrate(details.bitrate);
stream_details_dialog.setChannels(details.channels);
stream_details_dialog.setDepth(details.depth);
stream_details_dialog.setSampleRate(details.sample_rate);
stream_details_dialog.exec();
}
void MainWindow::RenumberTracks() {
QModelIndexList indexes =
ui_->playlist->view()->selectionModel()->selection().indexes();
@ -2490,6 +2521,14 @@ EditTagDialog* MainWindow::CreateEditTagDialog() {
return edit_tag_dialog;
}
StreamDiscoverer* MainWindow::CreateStreamDiscoverer() {
StreamDiscoverer* discoverer = new StreamDiscoverer();
connect(discoverer, SIGNAL(DataReady(StreamDetails)),
SLOT(ShowStreamDetails(StreamDetails)));
connect(discoverer, SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString)));
return discoverer;
}
void MainWindow::ShowAboutDialog() { about_dialog_->show(); }
void MainWindow::ShowTranscodeDialog() { transcode_dialog_->show(); }

View File

@ -31,8 +31,10 @@
#include "engines/engine_fwd.h"
#include "library/librarymodel.h"
#include "playlist/playlistitem.h"
#include "songinfo/streamdiscoverer.h"
#include "ui/organisedialog.h"
#include "ui/settingsdialog.h"
#include "ui/streamdetailsdialog.h"
class About;
class AddStreamDialog;
@ -72,6 +74,7 @@ class RipCDDialog;
class Song;
class SongInfoBase;
class SongInfoView;
class StreamDetailsDialog;
class SystemTrayIcon;
class TagFetcher;
class TaskManager;
@ -165,6 +168,8 @@ signals:
void PlaylistEditFinished(const QModelIndex& index);
void EditTracks();
void EditTagDialogAccepted();
void DiscoverStreamDetails();
void ShowStreamDetails(const StreamDetails& details);
void RenumberTracks();
void SelectionSetValue();
void EditValue();
@ -252,6 +257,7 @@ signals:
void ShowVisualisations();
SettingsDialog* CreateSettingsDialog();
EditTagDialog* CreateEditTagDialog();
StreamDiscoverer* CreateStreamDiscoverer();
void OpenSettingsDialog();
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
void ShowSongInfoConfig();
@ -299,6 +305,7 @@ signals:
OSD* osd_;
Lazy<EditTagDialog> edit_tag_dialog_;
Lazy<About> about_dialog_;
Lazy<StreamDiscoverer> stream_discoverer_;
GlobalShortcuts* global_shortcuts_;

View File

@ -928,6 +928,11 @@
<string>Remove unavailable tracks from playlist</string>
</property>
</action>
<action name="action_view_stream_details">
<property name="text">
<string>View Stream Details</string>
</property>
</action>
<action name="action_toggle_show_sidebar">
<property name="checkable">
<bool>true</bool>

View File

@ -0,0 +1,42 @@
#include "streamdetailsdialog.h"
#include "ui_streamdetailsdialog.h"
#include <QDialogButtonBox>
StreamDetailsDialog::StreamDetailsDialog(QWidget* parent)
: QDialog(parent), ui_(new Ui::StreamDetailsDialog) {
ui_->setupUi(this);
}
StreamDetailsDialog::~StreamDetailsDialog() {}
void StreamDetailsDialog::setUrl(const QString& url) {
ui_->url->setText(url);
ui_->url->setCursorPosition(0);
}
void StreamDetailsDialog::setFormat(const QString& format) {
ui_->format->setText(format);
}
void StreamDetailsDialog::setBitrate(int bitrate) {
ui_->bitrate->setText(QString("%1 kbps").arg(bitrate / 1000));
// Some bitrates aren't properly reported by GStreamer.
// In that case do not display bitrate information.
ui_->bitrate->setVisible(bitrate != 0);
ui_->bitrate_label->setVisible(bitrate != 0);
}
void StreamDetailsDialog::setChannels(int channels) {
ui_->channels->setText(QString::number(channels));
}
void StreamDetailsDialog::setDepth(int depth) {
// Right now GStreamer seems to be reporting incorrect numbers for MP3 and AAC
// streams, so we leave that value hidden in the UI.
// ui_->depth->setText(QString("%1 bits").arg(depth));
ui_->depth->setVisible(false);
ui_->depth_label->setVisible(false);
}
void StreamDetailsDialog::setSampleRate(int sample_rate) {
ui_->sample_rate->setText(QString("%1 Hz").arg(sample_rate));
}
void StreamDetailsDialog::Close() { this->close(); }

View File

@ -0,0 +1,34 @@
#ifndef STREAMDETAILSDIALOG_H
#define STREAMDETAILSDIALOG_H
#include <memory>
#include <QDialog>
namespace Ui {
class StreamDetailsDialog;
}
class StreamDetailsDialog : public QDialog {
Q_OBJECT
public:
explicit StreamDetailsDialog(QWidget* parent = 0);
~StreamDetailsDialog();
void setUrl(const QString& url);
void setFormat(const QString& codec); // This is localized, so only for human
// consumption.
void setBitrate(int);
void setDepth(int);
void setChannels(int);
void setSampleRate(int);
private slots:
void Close();
private:
std::unique_ptr<Ui::StreamDetailsDialog> ui_;
};
#endif // STREAMDETAILSDIALOG_H

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StreamDetailsDialog</class>
<widget class="QDialog" name="StreamDetailsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>210</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>210</height>
</size>
</property>
<property name="windowTitle">
<string>Stream Details</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="url_label">
<property name="text">
<string>URL</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="url">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="format_label">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="format">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="channels_label">
<property name="text">
<string>Channels</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="channels">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="bitrate_label">
<property name="text">
<string>Bit rate</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="bitrate">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="sample_rate_label">
<property name="text">
<string>Sample rate</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="sample_rate">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="depth_label">
<property name="text">
<string>Depth</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="depth">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
<item row="6" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>StreamDetailsDialog</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>299</x>
<y>186</y>
</hint>
<hint type="destinationlabel">
<x>249</x>
<y>104</y>
</hint>
</hints>
</connection>
</connections>
</ui>