initial slim implementation of media player for qt6, will fix for qt5 and polish and integrate better later

This commit is contained in:
Martin Rotter 2023-11-21 14:33:48 +01:00
parent 53e6714642
commit 56c9e973df
31 changed files with 545 additions and 68 deletions

View file

@ -140,7 +140,7 @@ set(QT_COMPONENTS
)
if(NOT OS2)
list(APPEND QT_COMPONENTS Multimedia)
list(APPEND QT_COMPONENTS Multimedia MultimediaWidgets)
endif()
if(WIN32 AND NOT BUILD_WITH_QT6)

View file

@ -381,7 +381,7 @@ version by clicking this popup notification.</source>
<context>
<name>BaseToastNotification</name>
<message>
<location filename="../src/librssguard/gui/notifications/basetoastnotification.cpp" line="41"/>
<location filename="../src/librssguard/gui/notifications/basetoastnotification.cpp" line="40"/>
<source>Close this notification</source>
<translation type="unfinished"></translation>
</message>
@ -468,12 +468,12 @@ version by clicking this popup notification.</source>
<context>
<name>DiscoveredFeedsModel</name>
<message>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.cpp" line="297"/>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.cpp" line="294"/>
<source>Title</source>
<translation type="unfinished">Title</translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.cpp" line="297"/>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.cpp" line="294"/>
<source>Type</source>
<translation type="unfinished"></translation>
</message>
@ -1838,7 +1838,7 @@ QtWebEngine cache folder -&gt; &quot;%7&quot;</source>
</message>
<message>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.ui" line="51"/>
<source>Discover Sitemaps too (can take some time for bigger websites)</source>
<source>Recursive discovery (can take some time for bigger websites)</source>
<translation type="unfinished"></translation>
</message>
<message>
@ -1897,12 +1897,12 @@ QtWebEngine cache folder -&gt; &quot;%7&quot;</source>
<translation type="unfinished">Error: %1</translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.cpp" line="195"/>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.cpp" line="192"/>
<source>URL is valid.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.cpp" line="198"/>
<location filename="../src/librssguard/services/standard/gui/formdiscoverfeeds.cpp" line="195"/>
<source>URL is NOT valid.</source>
<translation type="unfinished"></translation>
</message>
@ -4496,6 +4496,59 @@ Login tokens expiration: %2</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MediaPlayer</name>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.ui" line="14"/>
<source>Form</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="134"/>
<source>No media</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="137"/>
<source>Loading...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="140"/>
<source>Media loaded</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="143"/>
<source>Media stalled</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="146"/>
<source>Buffering...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="149"/>
<source>Loaded</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="152"/>
<source>Ended</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="155"/>
<source>Media is invalid</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/reusable/mediaplayer.cpp" line="158"/>
<source>Unknown</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MessageBrowser</name>
<message>
@ -5719,50 +5772,50 @@ List of supported readers:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/atomparser.cpp" line="240"/>
<location filename="../src/librssguard/services/standard/parsers/rdfparser.cpp" line="190"/>
<location filename="../src/librssguard/services/standard/parsers/rssparser.cpp" line="190"/>
<location filename="../src/librssguard/services/standard/parsers/sitemapparser.cpp" line="176"/>
<location filename="../src/librssguard/services/standard/parsers/atomparser.cpp" line="242"/>
<location filename="../src/librssguard/services/standard/parsers/rdfparser.cpp" line="192"/>
<location filename="../src/librssguard/services/standard/parsers/rssparser.cpp" line="192"/>
<location filename="../src/librssguard/services/standard/parsers/sitemapparser.cpp" line="180"/>
<source>XML is not well-formed, %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/atomparser.cpp" line="246"/>
<location filename="../src/librssguard/services/standard/parsers/atomparser.cpp" line="248"/>
<source>not an ATOM feed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/jsonparser.cpp" line="117"/>
<location filename="../src/librssguard/services/standard/parsers/jsonparser.cpp" line="119"/>
<source>JSON error &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/jsonparser.cpp" line="148"/>
<location filename="../src/librssguard/services/standard/parsers/jsonparser.cpp" line="150"/>
<source>not a JSON feed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/rdfparser.cpp" line="196"/>
<location filename="../src/librssguard/services/standard/parsers/rdfparser.cpp" line="198"/>
<source>not an RDF feed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/rssparser.cpp" line="196"/>
<location filename="../src/librssguard/services/standard/parsers/rssparser.cpp" line="198"/>
<source>not a RSS feed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/sitemapparser.cpp" line="141"/>
<location filename="../src/librssguard/services/standard/parsers/sitemapparser.cpp" line="145"/>
<source>support for gzipped sitemaps is not enabled</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/sitemapparser.cpp" line="189"/>
<location filename="../src/librssguard/services/standard/parsers/sitemapparser.cpp" line="193"/>
<source>sitemap indices are not supported</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/services/standard/parsers/sitemapparser.cpp" line="193"/>
<location filename="../src/librssguard/services/standard/parsers/sitemapparser.cpp" line="197"/>
<source>not a Sitemap</source>
<translation type="unfinished"></translation>
</message>
@ -7895,22 +7948,27 @@ Unread news: %2</translation>
<translation>Feeds</translation>
</message>
<message>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="38"/>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="39"/>
<source>Displays main menu.</source>
<translation>Displays main menu.</translation>
</message>
<message>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="47"/>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="48"/>
<source>Main menu</source>
<translation>Main menu</translation>
</message>
<message>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="78"/>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="79"/>
<source>Downloads</source>
<translation>Downloads</translation>
</message>
<message>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="237"/>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="232"/>
<source>Media player</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/tabwidget.cpp" line="257"/>
<source>Web browser</source>
<translation>Web browser</translation>
</message>
@ -8439,12 +8497,17 @@ Last login on: %4</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/webviewers/webengine/webengineviewer.cpp" line="109"/>
<location filename="../src/librssguard/gui/webviewers/webengine/webengineviewer.cpp" line="104"/>
<source>Open link as audio/video</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/webviewers/webengine/webengineviewer.cpp" line="113"/>
<source>Open with external tool</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/librssguard/gui/webviewers/webengine/webengineviewer.cpp" line="128"/>
<location filename="../src/librssguard/gui/webviewers/webengine/webengineviewer.cpp" line="132"/>
<source>No external tools activated</source>
<translation type="unfinished"></translation>
</message>

View file

@ -12,6 +12,7 @@
<file>./graphics/Breeze/actions/32/arrow-right.svg</file>
<file>./graphics/Breeze/actions/32/arrow-up.svg</file>
<file>./graphics/Breeze/actions/32/arrow-up-double.svg</file>
<file>./graphics/Breeze/status/22/audio-volume-muted.svg</file>
<file>./graphics/Breeze/actions/32/call-start.svg</file>
<file>./graphics/Breeze/actions/32/dialog-cancel.svg</file>
<file>./graphics/Breeze/actions/22/dialog-close.svg</file>
@ -77,7 +78,11 @@
<file>./graphics/Breeze/actions/32/mail-reply-sender.svg</file>
<file>./graphics/Breeze/actions/32/mail-send.svg</file>
<file>./graphics/Breeze/actions/32/mail-sent.svg</file>
<file>./graphics/Breeze/actions/32/media-playback-pause.svg</file>
<file>./graphics/Breeze/actions/32/media-playback-start.svg</file>
<file>./graphics/Breeze/actions/32/media-playback-stop.svg</file>
<file>./graphics/Breeze/actions/22/player-volume.svg</file>
<file>./graphics/Breeze/actions/22/player-volume-muted.svg</file>
<file>./graphics/Breeze/actions/22/process-stop.svg</file>
<file>./graphics/Breeze/actions/22/system-search.svg</file>
<file>./graphics/Breeze/actions/22/system-upgrade.svg</file>
@ -106,6 +111,7 @@
<file>./graphics/Breeze Dark/actions/32/arrow-right.svg</file>
<file>./graphics/Breeze Dark/actions/32/arrow-up.svg</file>
<file>./graphics/Breeze Dark/actions/32/arrow-up-double.svg</file>
<file>./graphics/Breeze Dark/status/22/audio-volume-muted.svg</file>
<file>./graphics/Breeze Dark/actions/32/call-start.svg</file>
<file>./graphics/Breeze Dark/actions/32/dialog-cancel.svg</file>
<file>./graphics/Breeze Dark/actions/22/dialog-close.svg</file>
@ -171,7 +177,11 @@
<file>./graphics/Breeze Dark/actions/32/mail-reply-sender.svg</file>
<file>./graphics/Breeze Dark/actions/32/mail-send.svg</file>
<file>./graphics/Breeze Dark/actions/32/mail-sent.svg</file>
<file>./graphics/Breeze Dark/actions/32/media-playback-pause.svg</file>
<file>./graphics/Breeze Dark/actions/32/media-playback-start.svg</file>
<file>./graphics/Breeze Dark/actions/32/media-playback-stop.svg</file>
<file>./graphics/Breeze Dark/actions/22/player-volume.svg</file>
<file>./graphics/Breeze Dark/actions/22/player-volume-muted.svg</file>
<file>./graphics/Breeze Dark/actions/22/process-stop.svg</file>
<file>./graphics/Breeze Dark/actions/22/system-search.svg</file>
<file>./graphics/Breeze Dark/actions/22/system-upgrade.svg</file>
@ -194,6 +204,7 @@
<file>./graphics/Faenza/categories/64/applications-office.png</file>
<file>./graphics/Faenza/categories/64/applications-science.png</file>
<file>./graphics/Faenza/categories/64/applications-system.png</file>
<file>./graphics/Faenza/status/64/audio-volume-muted.png</file>
<file>./graphics/Faenza/actions/64/browser-download.png</file>
<file>./graphics/Faenza/actions/64/call-start.png</file>
<file>./graphics/Faenza/status/64/dialog-error.png</file>
@ -256,7 +267,9 @@
<file>./graphics/Faenza/actions/64/mail-reply-sender.png</file>
<file>./graphics/Faenza/actions/64/mail-send.png</file>
<file>./graphics/Faenza/actions/64/mail-sent.png</file>
<file>./graphics/Faenza/actions/64/media-playback-pause.png</file>
<file>./graphics/Faenza/actions/64/media-playback-start.png</file>
<file>./graphics/Faenza/actions/64/media-playback-stop.png</file>
<file>./graphics/Faenza/actions/64/process-stop.png</file>
<file>./graphics/Faenza/actions/64/reload.png</file>
<file>./graphics/Faenza/actions/64/system-search.png</file>
@ -284,6 +297,7 @@
<file>./graphics/Numix/22/actions/arrow-right.svg</file>
<file>./graphics/Numix/22/actions/arrow-up.svg</file>
<file>./graphics/Numix/22/actions/arrow-up-double.svg</file>
<file>./graphics/Numix/22/status/audio-volume-muted.svg</file>
<file>./graphics/Numix/22/actions/browser-download.svg</file>
<file>./graphics/Numix/22/actions/call-start.svg</file>
<file>./graphics/Numix/22/actions/dialog-cancel.svg</file>
@ -349,7 +363,9 @@
<file>./graphics/Numix/22/actions/mail-reply-sender.svg</file>
<file>./graphics/Numix/22/actions/mail-send.svg</file>
<file>./graphics/Numix/22/places/mail-sent.svg</file>
<file>./graphics/Numix/22/actions/media-playback-pause.svg</file>
<file>./graphics/Numix/22/actions/media-playback-start.svg</file>
<file>./graphics/Numix/22/actions/media-playback-stop.svg</file>
<file>./graphics/Numix/22/actions/process-stop.svg</file>
<file>./graphics/Numix/22/actions/reload.svg</file>
<file>./graphics/Numix/22/actions/system-search.svg</file>

View file

@ -133,6 +133,8 @@ set(SOURCES
gui/reusable/locationlineedit.h
gui/reusable/messagecountspinbox.cpp
gui/reusable/messagecountspinbox.h
gui/reusable/mediaplayer.cpp
gui/reusable/mediaplayer.h
gui/reusable/networkproxydetails.cpp
gui/reusable/networkproxydetails.h
gui/reusable/nonclosablemenu.cpp
@ -464,6 +466,7 @@ set(UI_FILES
gui/notifications/notificationseditor.ui
gui/notifications/singlenotificationeditor.ui
gui/notifications/toastnotification.ui
gui/reusable/mediaplayer.ui
gui/reusable/networkproxydetails.ui
gui/reusable/searchtextwidget.ui
gui/richtexteditor/mrichtextedit.ui
@ -769,6 +772,7 @@ endif()
if(NOT OS2)
target_link_libraries(rssguard PUBLIC
Qt${QT_VERSION_MAJOR}::Multimedia
Qt${QT_VERSION_MAJOR}::MultimediaWidgets
)
endif()

View file

@ -673,7 +673,7 @@ void FormMain::loadSize() {
setWindowState(windowState() | Qt::WindowState::WindowMaximized);
// We process events so that window is really maximized fast.
qApp->processEvents();
QCoreApplication::processEvents();
}
m_ui->m_actionMessagePreviewEnabled

View file

@ -244,7 +244,7 @@ void FeedMessageViewer::loadMessageToFeedAndArticleList(Feed* feed, const Messag
// TODO: expand properly
m_feedsView->setExpanded(idx_map, true);
m_feedsView->setCurrentIndex(idx_map);
qApp->processEvents();
QCoreApplication::processEvents();
auto idx_map_msg = m_messagesView->model()->indexFromMessage(message);
auto msg_is_visible = !m_messagesView->isRowHidden(idx_map_msg.row(), idx_map_msg);

View file

@ -15,7 +15,6 @@ using namespace std::chrono_literals;
BaseToastNotification::BaseToastNotification(QWidget* parent) : QDialog(parent), m_timerId(-1) {
setAttribute(Qt::WidgetAttribute::WA_ShowWithoutActivating);
// setFixedWidth(qApp->settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsWidth)).toInt());
setFocusPolicy(Qt::FocusPolicy::NoFocus);
setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose, false);
@ -82,7 +81,8 @@ bool BaseToastNotification::eventFilter(QObject* watched, QEvent* event) {
if (event->type() == QEvent::Type::MouseButtonPress || event->type() == QEvent::Type::MouseButtonRelease) {
if (dynamic_cast<QMouseEvent*>(event)->button() == Qt::MouseButton::RightButton) {
event->accept();
QTimer::singleShot(100, this, &BaseToastNotification::close);
QCoreApplication::processEvents();
QTimer::singleShot(200, this, &BaseToastNotification::close);
return true;
}
}

View file

@ -100,7 +100,7 @@ void ToastNotificationsManager::processNotification(BaseToastNotification* notif
// Make sure notification is finally resized.
notif->adjustSize();
qApp->processEvents();
QCoreApplication::processEvents();
// Move notification, at this point we already need to know its precise size.
moveNotificationToCorner(notif, notif_new_pos);

View file

@ -0,0 +1,188 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "mediaplayer.h"
#include "miscellaneous/iconfactory.h"
#if QT_VERSION_MAJOR == 6
#include <QAudioOutput>
#endif
MediaPlayer::MediaPlayer(QWidget* parent)
: TabContent(parent),
#if QT_VERSION_MAJOR == 6
m_audio(new QAudioOutput(this)),
#endif
m_player(new QMediaPlayer(this)), m_muted(false) {
m_ui.setupUi(this);
m_player->setVideoOutput(m_ui.m_video);
m_player->setAudioOutput(m_audio);
setupIcons();
createConnections();
onPlaybackStateChanged(QMediaPlayer::PlaybackState::StoppedState);
onMediaStatusChanged(QMediaPlayer::MediaStatus::NoMedia);
}
MediaPlayer::~MediaPlayer() {}
WebBrowser* MediaPlayer::webBrowser() const {
return nullptr;
}
void MediaPlayer::playUrl(const QString& url) {
setVolume(m_ui.m_slidVolume->value());
m_player->setSource(url);
m_player->play();
}
void MediaPlayer::playPause() {
if (m_player->playbackState() != QMediaPlayer::PlaybackState::PlayingState) {
m_player->play();
}
else {
m_player->pause();
}
}
void MediaPlayer::stop() {
m_player->stop();
}
void MediaPlayer::download() {
emit urlDownloadRequested(m_player->source());
}
void MediaPlayer::muteUnmute() {
m_ui.m_slidVolume->setEnabled(m_muted);
setVolume(m_muted ? m_ui.m_slidVolume->value() : 0);
m_muted = !m_muted;
}
void MediaPlayer::setVolume(int volume) {
m_player->audioOutput()->setVolume(volume / 100.0f);
m_ui.m_btnVolume->setIcon(volume <= 0 ? m_iconMute : m_iconUnmute);
}
void MediaPlayer::seek(int position) {
m_player->setPosition(position * 1000);
}
void MediaPlayer::onDurationChanged(qint64 duration) {
m_ui.m_slidProgress->blockSignals(true);
m_ui.m_slidProgress->setMaximum(duration / 1000);
m_ui.m_slidProgress->blockSignals(false);
}
void MediaPlayer::onErrorOccurred(QMediaPlayer::Error error, const QString& error_string) {
m_ui.m_lblStatus->setText(error_string);
}
void MediaPlayer::onAudioAvailable(bool available) {
m_ui.m_slidVolume->setEnabled(available);
m_ui.m_btnVolume->setEnabled(available);
}
void MediaPlayer::onVideoAvailable(bool available) {
Q_UNUSED(available)
}
void MediaPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus status) {
m_ui.m_lblStatus->setText(mediaStatusToString(status));
}
void MediaPlayer::onPlaybackStateChanged(QMediaPlayer::PlaybackState state) {
switch (state) {
case QMediaPlayer::StoppedState:
m_ui.m_btnPlayPause->setIcon(m_iconPlay);
m_ui.m_btnStop->setEnabled(false);
break;
case QMediaPlayer::PlayingState:
m_ui.m_btnPlayPause->setIcon(m_iconPause);
m_ui.m_btnStop->setEnabled(true);
break;
case QMediaPlayer::PausedState:
m_ui.m_btnPlayPause->setIcon(m_iconPlay);
m_ui.m_btnStop->setEnabled(true);
break;
}
}
void MediaPlayer::onPositionChanged(qint64 position) {
m_ui.m_slidProgress->blockSignals(true);
m_ui.m_slidProgress->setValue(position / 1000);
m_ui.m_slidProgress->blockSignals(false);
}
void MediaPlayer::onSeekableChanged(bool seekable) {
m_ui.m_slidProgress->setEnabled(seekable);
if (!seekable) {
onPositionChanged(0);
}
}
QString MediaPlayer::mediaStatusToString(QMediaPlayer::MediaStatus status) const {
switch (status) {
case QMediaPlayer::NoMedia:
return tr("No media");
case QMediaPlayer::LoadingMedia:
return tr("Loading...");
case QMediaPlayer::LoadedMedia:
return tr("Media loaded");
case QMediaPlayer::StalledMedia:
return tr("Media stalled");
case QMediaPlayer::BufferingMedia:
return tr("Buffering...");
case QMediaPlayer::BufferedMedia:
return tr("Loaded");
case QMediaPlayer::EndOfMedia:
return tr("Ended");
case QMediaPlayer::InvalidMedia:
return tr("Media is invalid");
default:
return tr("Unknown");
}
}
void MediaPlayer::setupIcons() {
m_iconPlay = qApp->icons()->fromTheme(QSL("media-playback-start"), QSL("player_play"));
m_iconPause = qApp->icons()->fromTheme(QSL("media-playback-pause"), QSL("player_pause"));
m_iconMute = qApp->icons()->fromTheme(QSL("player-volume-muted"), QSL("audio-volume-muted"));
m_iconUnmute = qApp->icons()->fromTheme(QSL("player-volume"), QSL("stock_volume"));
m_ui.m_btnDownload->setIcon(qApp->icons()->fromTheme(QSL("download"), QSL("browser-download")));
m_ui.m_btnStop->setIcon(qApp->icons()->fromTheme(QSL("media-playback-stop"), QSL("player_stop")));
}
void MediaPlayer::createConnections() {
connect(m_player, &QMediaPlayer::durationChanged, this, &MediaPlayer::onDurationChanged);
connect(m_player, &QMediaPlayer::errorOccurred, this, &MediaPlayer::onErrorOccurred);
connect(m_player, &QMediaPlayer::hasAudioChanged, this, &MediaPlayer::onAudioAvailable);
connect(m_player, &QMediaPlayer::hasVideoChanged, this, &MediaPlayer::onVideoAvailable);
connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &MediaPlayer::onMediaStatusChanged);
connect(m_player, &QMediaPlayer::playbackStateChanged, this, &MediaPlayer::onPlaybackStateChanged);
connect(m_player, &QMediaPlayer::positionChanged, this, &MediaPlayer::onPositionChanged);
connect(m_player, &QMediaPlayer::seekableChanged, this, &MediaPlayer::onSeekableChanged);
connect(m_ui.m_btnPlayPause, &PlainToolButton::clicked, this, &MediaPlayer::playPause);
connect(m_ui.m_btnStop, &PlainToolButton::clicked, this, &MediaPlayer::stop);
connect(m_ui.m_btnDownload, &PlainToolButton::clicked, this, &MediaPlayer::download);
connect(m_ui.m_btnVolume, &PlainToolButton::clicked, this, &MediaPlayer::muteUnmute);
connect(m_ui.m_slidVolume, &QSlider::valueChanged, this, &MediaPlayer::setVolume);
connect(m_ui.m_slidProgress, &QSlider::valueChanged, this, &MediaPlayer::seek);
}

View file

@ -0,0 +1,72 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef MEDIAPLAYER_H
#define MEDIAPLAYER_H
#include "gui/tabcontent.h"
#include "ui_mediaplayer.h"
#include <QMediaPlayer>
class QAudioOutput;
class MediaPlayer : public TabContent {
Q_OBJECT
public:
explicit MediaPlayer(QWidget* parent = nullptr);
virtual ~MediaPlayer();
virtual WebBrowser* webBrowser() const;
public slots:
void playUrl(const QString& url);
private slots:
void playPause();
void stop();
void download();
void muteUnmute();
// NOTE: Volume is from 0 to 100.
void setVolume(int volume);
// NOTE: Media is seekable in miliseconds, but that is too muc
// for "int" data type, therefore we seek by second.
void seek(int position);
void onDurationChanged(qint64 duration);
void onErrorOccurred(QMediaPlayer::Error error, const QString& error_string);
void onAudioAvailable(bool available);
void onVideoAvailable(bool available);
void onMediaStatusChanged(QMediaPlayer::MediaStatus status);
void onPlaybackStateChanged(QMediaPlayer::PlaybackState state);
void onPositionChanged(qint64 position);
void onSeekableChanged(bool seekable);
signals:
void urlDownloadRequested(const QUrl& url);
private:
QString mediaStatusToString(QMediaPlayer::MediaStatus status) const;
void setupIcons();
void createConnections();
private:
Ui::MediaPlayer m_ui;
#if QT_VERSION_MAJOR == 6
QAudioOutput* m_audio;
#endif
QMediaPlayer* m_player;
QIcon m_iconPlay;
QIcon m_iconPause;
QIcon m_iconMute;
QIcon m_iconUnmute;
bool m_muted;
};
#endif // MEDIAPLAYER_H

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MediaPlayer</class>
<widget class="QWidget" name="MediaPlayer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>588</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QVideoWidget" name="m_video" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="m_lblStatus"/>
</item>
<item>
<widget class="PlainToolButton" name="m_btnPlayPause"/>
</item>
<item>
<widget class="PlainToolButton" name="m_btnStop"/>
</item>
<item>
<widget class="QSlider" name="m_slidProgress">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_lblTime"/>
</item>
<item>
<widget class="PlainToolButton" name="m_btnVolume"/>
</item>
<item>
<widget class="QSlider" name="m_slidVolume">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickInterval">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="PlainToolButton" name="m_btnDownload"/>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QVideoWidget</class>
<extends>QWidget</extends>
<header>qvideowidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PlainToolButton</class>
<extends>QToolButton</extends>
<header>plaintoolbutton.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -22,11 +22,11 @@ void PlainToolButton::paintEvent(QPaintEvent* e) {
if (isEnabled()) {
if (underMouse() || isChecked()) {
p.setOpacity(0.7);
p.setOpacity(0.8);
}
}
else {
p.setOpacity(0.3);
p.setOpacity(0.2);
}
icon().paint(&p, rect);

View file

@ -8,6 +8,7 @@
#include "gui/feedsview.h"
#include "gui/messagepreviewer.h"
#include "gui/messagesview.h"
#include "gui/reusable/mediaplayer.h"
#include "gui/reusable/plaintoolbutton.h"
#include "gui/tabbar.h"
#include "gui/webbrowser.h"
@ -86,14 +87,13 @@ void TabWidget::checkTabBarVisibility() {
if (should_be_visible) {
setCornerWidget(m_btnMainMenu, Qt::Corner::TopLeftCorner);
m_btnMainMenu->setVisible(true);
}
else {
setCornerWidget(nullptr, Qt::Corner::TopLeftCorner);
setCornerWidget(nullptr, Qt::Corner::TopRightCorner);
m_btnMainMenu->setVisible(false);
}
m_btnMainMenu->setVisible(should_be_visible);
tabBar()->setVisible(should_be_visible);
}
@ -224,6 +224,26 @@ int TabWidget::addEmptyBrowser() {
return addBrowser(false, true);
}
int TabWidget::addMediaPlayer(const QString& url, bool make_active) {
auto* player = new MediaPlayer(this);
int index = addTab(player,
qApp->icons()->fromTheme(QSL("player_play"), QSL("media-playback-start")),
tr("Media player"),
TabBar::TabType::Closable);
if (make_active) {
setCurrentIndex(index);
player->setFocus(Qt::FocusReason::OtherFocusReason);
}
QTimer::singleShot(500, player, [player, url]() {
player->playUrl(url);
});
return index;
}
int TabWidget::addLinkedBrowser(const QUrl& initial_url) {
return addBrowser(false, false, initial_url);
}

View file

@ -78,6 +78,8 @@ class TabWidget : public QTabWidget {
// Adds new WebBrowser tab to global TabWidget.
int addEmptyBrowser();
int addMediaPlayer(const QString& url, bool make_active);
// Adds new WebBrowser with link. This is used when user
// selects to "Open link in new tab.".
int addLinkedBrowser(const QUrl& initial_url = QUrl());

View file

@ -100,6 +100,10 @@ void WebEngineViewer::contextMenuEvent(QContextMenuEvent* event) {
});
}
});
menu->addAction(qApp->icons()->fromTheme(QSL("document-open")), tr("Open link as audio/video"), [link_url]() {
qApp->mainForm()->tabWidget()->addMediaPlayer(link_url, true);
});
}
if (menu_data.mediaUrl().isValid() || menu_data.linkUrl().isValid()) {

View file

@ -141,8 +141,10 @@ FormDiscoverFeeds::~FormDiscoverFeeds() {
m_discoveredModel->setRootItem(nullptr);
}
QList<StandardFeed*> FormDiscoverFeeds::discoverFeedsWithParser(const FeedParser* parser, const QString& url) {
auto feeds = parser->discoverFeeds(m_serviceRoot, url);
QList<StandardFeed*> FormDiscoverFeeds::discoverFeedsWithParser(const FeedParser* parser,
const QString& url,
bool greedy) {
auto feeds = parser->discoverFeeds(m_serviceRoot, url, greedy);
QPixmap icon;
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
@ -158,15 +160,10 @@ QList<StandardFeed*> FormDiscoverFeeds::discoverFeedsWithParser(const FeedParser
void FormDiscoverFeeds::discoverFeeds() {
QString url = m_ui.m_txtUrl->lineEdit()->text();
bool sitemap_discover = m_ui.m_cbDiscoverSitemaps->isChecked();
bool greedy_discover = m_ui.m_cbDiscoverRecursive->isChecked();
std::function<QList<StandardFeed*>(const FeedParser*)> func = [=](const FeedParser* parser) -> QList<StandardFeed*> {
if (!sitemap_discover && dynamic_cast<const SitemapParser*>(parser) != nullptr) {
return {};
}
else {
return discoverFeedsWithParser(parser, url);
}
return discoverFeedsWithParser(parser, url, greedy_discover);
};
std::function<QList<StandardFeed*>(QList<StandardFeed*>&, const QList<StandardFeed*>&)> reducer =

View file

@ -57,7 +57,7 @@ class FormDiscoverFeeds : public QDialog {
StandardFeed* selectedFeed() const;
RootItem* targetParent() const;
QList<StandardFeed*> discoverFeedsWithParser(const FeedParser* parser, const QString& url);
QList<StandardFeed*> discoverFeedsWithParser(const FeedParser* parser, const QString& url, bool greedy);
void userWantsAdvanced();
void loadDiscoveredFeeds(const QList<StandardFeed*>& feeds);

View file

@ -46,9 +46,9 @@
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="m_cbDiscoverSitemaps">
<widget class="QCheckBox" name="m_cbDiscoverRecursive">
<property name="text">
<string>Discover Sitemaps too (can take some time for bigger websites)</string>
<string>Recursive discovery (can take some time for bigger websites)</string>
</property>
</widget>
</item>

View file

@ -25,7 +25,9 @@ AtomParser::AtomParser(const QString& data) : FeedParser(data) {
AtomParser::~AtomParser() {}
QList<StandardFeed*> AtomParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const {
QList<StandardFeed*> AtomParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const {
Q_UNUSED(greedy)
QString my_url = url.toString();
QList<StandardFeed*> feeds;

View file

@ -15,7 +15,7 @@ class AtomParser : public FeedParser {
explicit AtomParser(const QString& data);
virtual ~AtomParser();
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url) const;
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const;
virtual QPair<StandardFeed*, QList<IconLocation>> guessFeed(const QByteArray& content,
const QString& content_type) const;

View file

@ -41,7 +41,11 @@ FeedParser::FeedParser(QString data, bool is_xml)
FeedParser::~FeedParser() {}
QList<StandardFeed*> FeedParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const {
QList<StandardFeed*> FeedParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const {
Q_UNUSED(root)
Q_UNUSED(url)
Q_UNUSED(greedy)
return {};
}

View file

@ -20,7 +20,7 @@ class FeedParser {
virtual ~FeedParser();
// Returns list of absolute URLs of discovered feeds from provided base URL.
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url) const;
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const;
// Guesses feed.
virtual QPair<StandardFeed*, QList<IconLocation>> guessFeed(const QByteArray& content,

View file

@ -19,7 +19,9 @@ JsonParser::JsonParser(const QString& data) : FeedParser(data, false) {}
JsonParser::~JsonParser() {}
QList<StandardFeed*> JsonParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const {
QList<StandardFeed*> JsonParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const {
Q_UNUSED(greedy)
QString my_url = url.toString();
QList<StandardFeed*> feeds;

View file

@ -12,7 +12,7 @@ class JsonParser : public FeedParser {
explicit JsonParser(const QString& data);
virtual ~JsonParser();
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url) const;
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const;
virtual QPair<StandardFeed*, QList<IconLocation>> guessFeed(const QByteArray& content,
const QString& content_type) const;

View file

@ -18,7 +18,9 @@ RdfParser::RdfParser(const QString& data)
RdfParser::~RdfParser() {}
QList<StandardFeed*> RdfParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const {
QList<StandardFeed*> RdfParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const {
Q_UNUSED(greedy)
QString my_url = url.toString();
QList<StandardFeed*> feeds;

View file

@ -14,7 +14,7 @@ class RdfParser : public FeedParser {
explicit RdfParser(const QString& data);
virtual ~RdfParser();
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url) const;
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const;
virtual QPair<StandardFeed*, QList<IconLocation>> guessFeed(const QByteArray& content,
const QString& content_type) const;

View file

@ -18,7 +18,9 @@ RssParser::RssParser(const QString& data) : FeedParser(data) {}
RssParser::~RssParser() {}
QList<StandardFeed*> RssParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const {
QList<StandardFeed*> RssParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const {
Q_UNUSED(greedy)
QString my_url = url.toString();
QList<StandardFeed*> feeds;

View file

@ -14,7 +14,7 @@ class RssParser : public FeedParser {
explicit RssParser(const QString& data);
virtual ~RssParser();
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url) const;
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const;
virtual QPair<StandardFeed*, QList<IconLocation>> guessFeed(const QByteArray& content,
const QString& content_type) const;

View file

@ -21,20 +21,24 @@ SitemapParser::SitemapParser(const QString& data) : FeedParser(data) {}
SitemapParser::~SitemapParser() {}
QList<StandardFeed*> SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const {
QList<StandardFeed*> SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const {
QHash<QString, StandardFeed*> feeds;
QStringList to_process_sitemaps;
int sitemap_index_limit = 2;
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
// 1. Process "URL/robots.txt" file.
// 2. Process "URLHOST/robots.txt" file.
// 3. Direct URL test. If sitemap index, process its children.
// 1. Direct URL test. If sitemap index, process its children. If found, stop if non-recursive
// discovery is chosen.
// 2. Process "URL/robots.txt" file.
// 3. Process "URLHOST/robots.txt" file.
// 4. Test "URL/sitemap.xml" endpoint.
// 5. Test "URL/sitemap.xml.gz" endpoint.
// 1.
to_process_sitemaps.append(url.toString());
// 2.
// 3.
QStringList to_process_robots = {
url.toString(QUrl::UrlFormattingOption::StripTrailingSlash).replace(QRegularExpression(QSL("\\/$")), QString()) +
QSL("/robots.txt"),
@ -70,9 +74,6 @@ QList<StandardFeed*> SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl&
}
}
// 3.
to_process_sitemaps.append(url.toString());
// 4.
to_process_sitemaps.append(url.toString(QUrl::UrlFormattingOption::StripTrailingSlash)
.replace(QRegularExpression(QSL("\\/$")), QString()) +
@ -107,13 +108,16 @@ QList<StandardFeed*> SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl&
if (res.m_networkError == QNetworkReply::NetworkError::NoError) {
try {
// 1.
auto guessed_feed = guessFeed(data, res.m_contentType);
guessed_feed.first->setSource(my_url);
guessed_feed.first->setTitle(my_url);
feeds.insert(my_url, guessed_feed.first);
if (!greedy) {
break;
}
}
catch (const FeedRecognizedButFailedException& ex) {
// This is index.

View file

@ -12,7 +12,7 @@ class SitemapParser : public FeedParser {
explicit SitemapParser(const QString& data);
virtual ~SitemapParser();
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url) const;
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const;
virtual QPair<StandardFeed*, QList<IconLocation>> guessFeed(const QByteArray& content,
const QString& content_type) const;

View file

@ -49,7 +49,7 @@ FeedsImportExportModel::~FeedsImportExportModel() {
if (m_watcherLookup.isRunning()) {
m_watcherLookup.cancel();
m_watcherLookup.waitForFinished();
qApp->processEvents();
QCoreApplication::processEvents();
}
if (sourceModel() != nullptr && sourceModel()->rootItem() != nullptr && m_mode == Mode::Import) {
@ -433,7 +433,7 @@ void FeedsImportExportModel::importAsOPML20(const QByteArray& data,
if (!fetch_metadata_online) {
m_watcherLookup.waitForFinished();
qApp->processEvents();
QCoreApplication::processEvents();
}
}
@ -502,7 +502,7 @@ void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray& data,
if (!fetch_metadata_online) {
m_watcherLookup.waitForFinished();
qApp->processEvents();
QCoreApplication::processEvents();
}
}