refactor media player, separate into backend logic
This commit is contained in:
parent
d1ae926b5d
commit
2b2aaee70a
16 changed files with 642 additions and 376 deletions
|
@ -24,6 +24,7 @@
|
||||||
# NO_UPDATE_CHECK - Disable automatic checking for new application updates.
|
# NO_UPDATE_CHECK - Disable automatic checking for new application updates.
|
||||||
# IS_FLATPAK_BUILD - Set to "ON" when building RSS Guard with Flatpak.
|
# IS_FLATPAK_BUILD - Set to "ON" when building RSS Guard with Flatpak.
|
||||||
# FORCE_BUNDLE_ICONS - Forcibly bundles icons into executables.
|
# FORCE_BUNDLE_ICONS - Forcibly bundles icons into executables.
|
||||||
|
# ENABLE_MEDIAPLAYER_QTMULTIMEDIA - Enable media player (QtMultimedia/ffmpeg implementation).
|
||||||
# ENABLE_COMPRESSED_SITEMAP - Set to "ON" if you want to enable support for "sitemap.xml.gz" format.
|
# ENABLE_COMPRESSED_SITEMAP - Set to "ON" if you want to enable support for "sitemap.xml.gz" format.
|
||||||
# This requires "zlib" library and if you want to use specific
|
# This requires "zlib" library and if you want to use specific
|
||||||
# zlib location, then use "ZLIB_ROOT" variable, for example
|
# zlib location, then use "ZLIB_ROOT" variable, for example
|
||||||
|
@ -122,6 +123,7 @@ option(NO_UPDATE_CHECK "Disable automatic checking for new application updates"
|
||||||
option(IS_FLATPAK_BUILD "Set to 'ON' when building RSS Guard with Flatpak." OFF)
|
option(IS_FLATPAK_BUILD "Set to 'ON' when building RSS Guard with Flatpak." OFF)
|
||||||
option(FORCE_BUNDLE_ICONS "Forcibly bundle icon themes into RSS Guard." OFF)
|
option(FORCE_BUNDLE_ICONS "Forcibly bundle icon themes into RSS Guard." OFF)
|
||||||
option(ENABLE_COMPRESSED_SITEMAP "Enable support for gzip-compressed sitemap feeds. Requires zlib." OFF)
|
option(ENABLE_COMPRESSED_SITEMAP "Enable support for gzip-compressed sitemap feeds. Requires zlib." OFF)
|
||||||
|
option(ENABLE_MEDIAPLAYER_QTMULTIMEDIA "Enable built-in media player. Requires QtMultimedia FFMPEG plugin." ON)
|
||||||
|
|
||||||
# Import Qt libraries.
|
# Import Qt libraries.
|
||||||
set(QT6_MIN_VERSION 6.3.0)
|
set(QT6_MIN_VERSION 6.3.0)
|
||||||
|
@ -139,14 +141,20 @@ set(QT_COMPONENTS
|
||||||
Concurrent
|
Concurrent
|
||||||
)
|
)
|
||||||
|
|
||||||
if(NOT OS2)
|
|
||||||
list(APPEND QT_COMPONENTS Multimedia MultimediaWidgets)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32 AND NOT BUILD_WITH_QT6)
|
if(WIN32 AND NOT BUILD_WITH_QT6)
|
||||||
list(APPEND QT_COMPONENTS WinExtras)
|
list(APPEND QT_COMPONENTS WinExtras)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_MEDIAPLAYER_QTMULTIMEDIA)
|
||||||
|
list(APPEND QT_COMPONENTS Multimedia MultimediaWidgets)
|
||||||
|
add_compile_definitions(ENABLE_MEDIAPLAYER_QTMULTIMEDIA)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_MEDIAPLAYER_QTMULTIMEDIA OR ENABLE_MEDIAPLAYER_LIBMVP)
|
||||||
|
set(ENABLE_MEDIAPLAYER TRUE)
|
||||||
|
add_compile_definitions(ENABLE_MEDIAPLAYER)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(USE_WEBENGINE)
|
if(USE_WEBENGINE)
|
||||||
list(APPEND QT_COMPONENTS WebEngineWidgets)
|
list(APPEND QT_COMPONENTS WebEngineWidgets)
|
||||||
add_compile_definitions(USE_WEBENGINE)
|
add_compile_definitions(USE_WEBENGINE)
|
||||||
|
|
|
@ -133,8 +133,6 @@ set(SOURCES
|
||||||
gui/reusable/locationlineedit.h
|
gui/reusable/locationlineedit.h
|
||||||
gui/reusable/messagecountspinbox.cpp
|
gui/reusable/messagecountspinbox.cpp
|
||||||
gui/reusable/messagecountspinbox.h
|
gui/reusable/messagecountspinbox.h
|
||||||
gui/reusable/mediaplayer.cpp
|
|
||||||
gui/reusable/mediaplayer.h
|
|
||||||
gui/reusable/networkproxydetails.cpp
|
gui/reusable/networkproxydetails.cpp
|
||||||
gui/reusable/networkproxydetails.h
|
gui/reusable/networkproxydetails.h
|
||||||
gui/reusable/nonclosablemenu.cpp
|
gui/reusable/nonclosablemenu.cpp
|
||||||
|
@ -467,7 +465,6 @@ set(UI_FILES
|
||||||
gui/notifications/notificationseditor.ui
|
gui/notifications/notificationseditor.ui
|
||||||
gui/notifications/singlenotificationeditor.ui
|
gui/notifications/singlenotificationeditor.ui
|
||||||
gui/notifications/toastnotification.ui
|
gui/notifications/toastnotification.ui
|
||||||
gui/reusable/mediaplayer.ui
|
|
||||||
gui/reusable/networkproxydetails.ui
|
gui/reusable/networkproxydetails.ui
|
||||||
gui/reusable/searchtextwidget.ui
|
gui/reusable/searchtextwidget.ui
|
||||||
gui/richtexteditor/mrichtextedit.ui
|
gui/richtexteditor/mrichtextedit.ui
|
||||||
|
@ -509,6 +506,27 @@ set(UI_FILES
|
||||||
services/tt-rss/gui/ttrssfeeddetails.ui
|
services/tt-rss/gui/ttrssfeeddetails.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(ENABLE_MEDIAPLAYER)
|
||||||
|
list(APPEND SOURCES
|
||||||
|
gui/mediaplayer/playerbackend.cpp
|
||||||
|
gui/mediaplayer/playerbackend.h
|
||||||
|
gui/mediaplayer/mediaplayer.cpp
|
||||||
|
gui/mediaplayer/mediaplayer.h
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND UI_FILES
|
||||||
|
gui/mediaplayer/mediaplayer.ui
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_MEDIAPLAYER_QTMULTIMEDIA)
|
||||||
|
list(APPEND SOURCES
|
||||||
|
gui/mediaplayer/qtmultimedia/qtmultimediabackend.cpp
|
||||||
|
gui/mediaplayer/qtmultimedia/qtmultimediabackend.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if(USE_WEBENGINE)
|
if(USE_WEBENGINE)
|
||||||
list(APPEND SOURCES
|
list(APPEND SOURCES
|
||||||
# WebEngine-based web (and message) browser.
|
# WebEngine-based web (and message) browser.
|
||||||
|
@ -770,7 +788,7 @@ if(WIN32 AND NOT BUILD_WITH_QT6)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT OS2)
|
if(ENABLE_MEDIAPLAYER_QTMULTIMEDIA)
|
||||||
target_link_libraries(rssguard PUBLIC
|
target_link_libraries(rssguard PUBLIC
|
||||||
Qt${QT_VERSION_MAJOR}::Multimedia
|
Qt${QT_VERSION_MAJOR}::Multimedia
|
||||||
Qt${QT_VERSION_MAJOR}::MultimediaWidgets
|
Qt${QT_VERSION_MAJOR}::MultimediaWidgets
|
||||||
|
|
|
@ -1678,7 +1678,10 @@ UpdatedArticles DatabaseQueries::updateMessages(const QSqlDatabase& db,
|
||||||
|
|
||||||
QMutexLocker lck(db_mutex);
|
QMutexLocker lck(db_mutex);
|
||||||
|
|
||||||
auto bulk_query = db.exec(final_bulk);
|
auto bulk_query = QSqlQuery(final_bulk, db);
|
||||||
|
|
||||||
|
bulk_query.exec();
|
||||||
|
|
||||||
auto bulk_error = bulk_query.lastError();
|
auto bulk_error = bulk_query.lastError();
|
||||||
|
|
||||||
if (bulk_error.isValid()) {
|
if (bulk_error.isValid()) {
|
||||||
|
|
172
src/librssguard/gui/mediaplayer/mediaplayer.cpp
Normal file
172
src/librssguard/gui/mediaplayer/mediaplayer.cpp
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||||
|
|
||||||
|
#include "gui/mediaplayer/mediaplayer.h"
|
||||||
|
|
||||||
|
#include "miscellaneous/iconfactory.h"
|
||||||
|
|
||||||
|
#include "gui/mediaplayer/qtmultimedia/qtmultimediabackend.h"
|
||||||
|
|
||||||
|
MediaPlayer::MediaPlayer(QWidget* parent)
|
||||||
|
: TabContent(parent), m_backend(new QtMultimediaBackend(this)), m_muted(false) {
|
||||||
|
m_ui.setupUi(this);
|
||||||
|
|
||||||
|
m_ui.m_layoutMain->insertWidget(0, m_backend, 1);
|
||||||
|
|
||||||
|
setupIcons();
|
||||||
|
|
||||||
|
createBackendConnections();
|
||||||
|
createConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPlayer::~MediaPlayer() {}
|
||||||
|
|
||||||
|
WebBrowser* MediaPlayer::webBrowser() const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::playUrl(const QString& url) {
|
||||||
|
if (m_muted) {
|
||||||
|
muteUnmute();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setVolume(m_ui.m_slidVolume->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_backend->playUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::playPause() {
|
||||||
|
m_backend->playPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::stop() {
|
||||||
|
m_backend->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::download() {
|
||||||
|
emit urlDownloadRequested(m_backend->url());
|
||||||
|
}
|
||||||
|
|
||||||
|
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::setSpeed(int speed) {
|
||||||
|
m_backend->setPlaybackSpeed(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::setVolume(int volume) {
|
||||||
|
m_backend->setVolume(volume);
|
||||||
|
|
||||||
|
m_ui.m_btnVolume->setIcon(volume <= 0 ? m_iconMute : m_iconUnmute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::seek(int position) {
|
||||||
|
m_backend->setPosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::onPositionChanged(int position) {
|
||||||
|
m_ui.m_slidProgress->blockSignals(true);
|
||||||
|
m_ui.m_slidProgress->setValue(position);
|
||||||
|
m_ui.m_slidProgress->blockSignals(false);
|
||||||
|
|
||||||
|
updateTimeAndProgress(position, m_backend->duration());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::onSpeedChanged(int speed) {
|
||||||
|
m_ui.m_spinSpeed->blockSignals(true);
|
||||||
|
m_ui.m_spinSpeed->setValue(speed);
|
||||||
|
m_ui.m_spinSpeed->blockSignals(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::onDurationChanged(int duration) {
|
||||||
|
m_ui.m_slidProgress->blockSignals(true);
|
||||||
|
m_ui.m_slidProgress->setMaximum(duration);
|
||||||
|
m_ui.m_slidProgress->blockSignals(false);
|
||||||
|
|
||||||
|
updateTimeAndProgress(m_backend->position(), duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::updateTimeAndProgress(int progress, int total) {
|
||||||
|
m_ui.m_lblTime->setText(QSL("%1/%2").arg(QDateTime::fromSecsSinceEpoch(progress).toUTC().toString("hh:mm:ss"),
|
||||||
|
QDateTime::fromSecsSinceEpoch(total).toUTC().toString("hh:mm:ss")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::onErrorOccurred(const QString& error_string) {
|
||||||
|
m_ui.m_lblStatus->setStatus(WidgetWithStatus::StatusType::Error, error_string, 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::onStatusChanged(const QString& status) {
|
||||||
|
m_ui.m_lblStatus->setStatus(WidgetWithStatus::StatusType::Information, status, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::onPlaybackStateChanged(PlayerBackend::PlaybackState state) {
|
||||||
|
switch (state) {
|
||||||
|
case PlayerBackend::PlaybackState::StoppedState:
|
||||||
|
m_ui.m_btnPlayPause->setIcon(m_iconPlay);
|
||||||
|
m_ui.m_btnStop->setEnabled(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlayerBackend::PlaybackState::PlayingState:
|
||||||
|
m_ui.m_btnPlayPause->setIcon(m_iconPause);
|
||||||
|
m_ui.m_btnStop->setEnabled(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlayerBackend::PlaybackState::PausedState:
|
||||||
|
m_ui.m_btnPlayPause->setIcon(m_iconPlay);
|
||||||
|
m_ui.m_btnStop->setEnabled(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::onSeekableChanged(bool seekable) {
|
||||||
|
m_ui.m_slidProgress->setEnabled(seekable);
|
||||||
|
|
||||||
|
if (!seekable) {
|
||||||
|
onPositionChanged(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::createBackendConnections() {
|
||||||
|
connect(m_backend, &PlayerBackend::speedChanged, this, &MediaPlayer::onSpeedChanged);
|
||||||
|
connect(m_backend, &PlayerBackend::durationChanged, this, &MediaPlayer::onDurationChanged);
|
||||||
|
connect(m_backend, &PlayerBackend::positionChanged, this, &MediaPlayer::onPositionChanged);
|
||||||
|
connect(m_backend, &PlayerBackend::errorOccurred, this, &MediaPlayer::onErrorOccurred);
|
||||||
|
connect(m_backend, &PlayerBackend::playbackStateChanged, this, &MediaPlayer::onPlaybackStateChanged);
|
||||||
|
connect(m_backend, &PlayerBackend::statusChanged, this, &MediaPlayer::onStatusChanged);
|
||||||
|
connect(m_backend, &PlayerBackend::audioAvailable, this, &MediaPlayer::onAudioAvailable);
|
||||||
|
connect(m_backend, &PlayerBackend::videoAvailable, this, &MediaPlayer::onVideoAvailable);
|
||||||
|
connect(m_backend, &PlayerBackend::seekableChanged, this, &MediaPlayer::onSeekableChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaPlayer::createConnections() {
|
||||||
|
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);
|
||||||
|
connect(m_ui.m_spinSpeed, QOverload<int>::of(&QSpinBox::valueChanged), this, &MediaPlayer::setSpeed);
|
||||||
|
}
|
71
src/librssguard/gui/mediaplayer/mediaplayer.h
Normal file
71
src/librssguard/gui/mediaplayer/mediaplayer.h
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||||
|
|
||||||
|
#ifndef MEDIAPLAYER_H
|
||||||
|
#define MEDIAPLAYER_H
|
||||||
|
|
||||||
|
#include "gui/tabcontent.h"
|
||||||
|
|
||||||
|
#include "gui/mediaplayer/playerbackend.h"
|
||||||
|
|
||||||
|
#include "ui_mediaplayer.h"
|
||||||
|
|
||||||
|
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: 100 means standard speed, above that value means faster, below means slower.
|
||||||
|
void setSpeed(int speed);
|
||||||
|
|
||||||
|
// NOTE: Volume is from 0 to 100 taken directly from slider or
|
||||||
|
// elsewhere.
|
||||||
|
void setVolume(int volume);
|
||||||
|
|
||||||
|
// NOTE: We seek by second.
|
||||||
|
void seek(int position);
|
||||||
|
|
||||||
|
void onSpeedChanged(int speed);
|
||||||
|
void onDurationChanged(int duration);
|
||||||
|
void onPositionChanged(int position);
|
||||||
|
void onErrorOccurred(const QString& error_string);
|
||||||
|
void onStatusChanged(const QString& status);
|
||||||
|
void onPlaybackStateChanged(PlayerBackend::PlaybackState state);
|
||||||
|
void onAudioAvailable(bool available);
|
||||||
|
void onVideoAvailable(bool available);
|
||||||
|
void onSeekableChanged(bool seekable);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void urlDownloadRequested(const QUrl& url);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateTimeAndProgress(int progress, int total);
|
||||||
|
void setupIcons();
|
||||||
|
|
||||||
|
void createBackendConnections();
|
||||||
|
void createConnections();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MediaPlayer m_ui;
|
||||||
|
|
||||||
|
PlayerBackend* m_backend;
|
||||||
|
QIcon m_iconPlay;
|
||||||
|
QIcon m_iconPause;
|
||||||
|
QIcon m_iconMute;
|
||||||
|
QIcon m_iconUnmute;
|
||||||
|
bool m_muted;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MEDIAPLAYER_H
|
|
@ -13,17 +13,7 @@
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="m_layoutMain">
|
||||||
<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>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
|
@ -132,12 +122,6 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
|
||||||
<class>QVideoWidget</class>
|
|
||||||
<extends>QWidget</extends>
|
|
||||||
<header>qvideowidget.h</header>
|
|
||||||
<container>1</container>
|
|
||||||
</customwidget>
|
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>PlainToolButton</class>
|
<class>PlainToolButton</class>
|
||||||
<extends>QToolButton</extends>
|
<extends>QToolButton</extends>
|
10
src/librssguard/gui/mediaplayer/playerbackend.cpp
Normal file
10
src/librssguard/gui/mediaplayer/playerbackend.cpp
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||||
|
|
||||||
|
#include "gui/mediaplayer/playerbackend.h"
|
||||||
|
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
PlayerBackend::PlayerBackend(QWidget* parent) : QWidget(parent), m_mainLayout(new QVBoxLayout(this)) {
|
||||||
|
m_mainLayout->setSpacing(0);
|
||||||
|
m_mainLayout->setContentsMargins({0, 0, 0, 0});
|
||||||
|
}
|
53
src/librssguard/gui/mediaplayer/playerbackend.h
Normal file
53
src/librssguard/gui/mediaplayer/playerbackend.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||||
|
|
||||||
|
#ifndef PLAYERBACKEND_H
|
||||||
|
#define PLAYERBACKEND_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
|
class PlayerBackend : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class PlaybackState {
|
||||||
|
StoppedState,
|
||||||
|
PlayingState,
|
||||||
|
PausedState
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit PlayerBackend(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
virtual QUrl url() const = 0;
|
||||||
|
virtual int position() const = 0;
|
||||||
|
virtual int duration() const = 0;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void speedChanged(int speed);
|
||||||
|
void durationChanged(int duration);
|
||||||
|
void positionChanged(int position);
|
||||||
|
void errorOccurred(const QString& error_string);
|
||||||
|
void statusChanged(const QString& status);
|
||||||
|
void playbackStateChanged(PlaybackState state);
|
||||||
|
void audioAvailable(bool available);
|
||||||
|
void videoAvailable(bool available);
|
||||||
|
void seekableChanged(bool seekable);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
virtual void playUrl(const QUrl& url) = 0;
|
||||||
|
virtual void playPause() = 0;
|
||||||
|
virtual void pause() = 0;
|
||||||
|
virtual void stop() = 0;
|
||||||
|
|
||||||
|
virtual void setPlaybackSpeed(int speed) = 0;
|
||||||
|
virtual void setVolume(int volume) = 0;
|
||||||
|
virtual void setPosition(int position) = 0;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVBoxLayout* m_mainLayout;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PLAYERBACKEND_H
|
|
@ -0,0 +1,248 @@
|
||||||
|
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||||
|
|
||||||
|
#include "gui/mediaplayer/qtmultimedia/qtmultimediabackend.h"
|
||||||
|
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
#include <QAudioOutput>
|
||||||
|
#include <QWindow>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QVideoWidget>
|
||||||
|
|
||||||
|
QtMultimediaBackend::QtMultimediaBackend(QWidget* parent)
|
||||||
|
: PlayerBackend(parent),
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
m_audio(new QAudioOutput(this)),
|
||||||
|
#endif
|
||||||
|
m_player(new QMediaPlayer(this)),
|
||||||
|
|
||||||
|
m_video(new QVideoWidget(this)) {
|
||||||
|
layout()->addWidget(m_video);
|
||||||
|
|
||||||
|
m_player->setVideoOutput(m_video);
|
||||||
|
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
m_player->setAudioOutput(m_audio);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
connect(m_player, &QMediaPlayer::durationChanged, this, &QtMultimediaBackend::onDurationChanged);
|
||||||
|
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
connect(m_player, &QMediaPlayer::errorOccurred, this, &QtMultimediaBackend::onErrorOccurred);
|
||||||
|
#else
|
||||||
|
connect(m_player, QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error), this, [this](QMediaPlayer::Error error) {
|
||||||
|
onErrorOccurred(error);
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
connect(m_player, &QMediaPlayer::hasAudioChanged, this, &QtMultimediaBackend::onAudioAvailable);
|
||||||
|
connect(m_player, &QMediaPlayer::hasVideoChanged, this, &QtMultimediaBackend::onVideoAvailable);
|
||||||
|
connect(m_player, &QMediaPlayer::playbackStateChanged, this, &QtMultimediaBackend::onPlaybackStateChanged);
|
||||||
|
#else
|
||||||
|
connect(m_player, &QMediaPlayer::audioAvailableChanged, this, &QtMultimediaBackend::onAudioAvailable);
|
||||||
|
connect(m_player, &QMediaPlayer::videoAvailableChanged, this, &QtMultimediaBackend::onVideoAvailable);
|
||||||
|
connect(m_player, &QMediaPlayer::stateChanged, this, &QtMultimediaBackend::onPlaybackStateChanged);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &QtMultimediaBackend::onMediaStatusChanged);
|
||||||
|
connect(m_player, &QMediaPlayer::positionChanged, this, &QtMultimediaBackend::onPositionChanged);
|
||||||
|
connect(m_player, &QMediaPlayer::seekableChanged, this, &QtMultimediaBackend::onSeekableChanged);
|
||||||
|
connect(m_player, &QMediaPlayer::playbackRateChanged, this, &QtMultimediaBackend::onPlaybackRateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
int QtMultimediaBackend::convertToSliderProgress(qint64 player_progress) const {
|
||||||
|
return player_progress / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QtMultimediaBackend::convertDuration(qint64 duration) const {
|
||||||
|
return duration / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal QtMultimediaBackend::convertSpeed(int speed) const {
|
||||||
|
return speed / 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QtMultimediaBackend::convertSpinSpeed(qreal speed) const {
|
||||||
|
return speed * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
float QtMultimediaBackend::convertSliderVolume(int slider_volume) const {
|
||||||
|
return slider_volume / 100.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 QtMultimediaBackend::convertSliderProgress(int slider_progress) const {
|
||||||
|
return qint64(slider_progress) * qint64(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QtMultimediaBackend::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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QtMultimediaBackend::errorToString(QMediaPlayer::Error error) const {
|
||||||
|
switch (error) {
|
||||||
|
case QMediaPlayer::ResourceError:
|
||||||
|
return tr("Cannot load media (missing codecs)");
|
||||||
|
|
||||||
|
case QMediaPlayer::FormatError:
|
||||||
|
return tr("Unrecognized format");
|
||||||
|
|
||||||
|
case QMediaPlayer::NetworkError:
|
||||||
|
return tr("Network problem");
|
||||||
|
|
||||||
|
case QMediaPlayer::AccessDeniedError:
|
||||||
|
return tr("Access denied");
|
||||||
|
|
||||||
|
#if QT_VERSION_MAJOR == 5
|
||||||
|
case QMediaPlayer::ServiceMissingError:
|
||||||
|
return tr("Service is missing");
|
||||||
|
|
||||||
|
case QMediaPlayer::MediaIsPlaylist:
|
||||||
|
return tr("This is playlist");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case QMediaPlayer::NoError:
|
||||||
|
return tr("No errors");
|
||||||
|
|
||||||
|
default:
|
||||||
|
return tr("Unknown error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::playUrl(const QUrl& url) {
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
m_player->setSource(url);
|
||||||
|
#else
|
||||||
|
m_player->setMedia(QUrl(url));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_player->play();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::playPause() {
|
||||||
|
if (m_player->PLAYBACK_STATE_METHOD() != QMediaPlayer::PLAYBACK_STATE::PlayingState) {
|
||||||
|
m_player->play();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_player->pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::pause() {
|
||||||
|
m_player->pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::stop() {
|
||||||
|
m_player->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::setPlaybackSpeed(int speed) {
|
||||||
|
m_player->setPlaybackRate(convertSpeed(speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::setVolume(int volume) {
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
m_player->audioOutput()->setVolume(convertSliderVolume(volume));
|
||||||
|
#else
|
||||||
|
m_player->setVolume(volume);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::setPosition(int position) {
|
||||||
|
m_player->setPosition(convertSliderProgress(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl QtMultimediaBackend::url() const {
|
||||||
|
return
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
m_player->source();
|
||||||
|
#else
|
||||||
|
m_player->media().request().url();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int QtMultimediaBackend::position() const {
|
||||||
|
return convertToSliderProgress(m_player->position());
|
||||||
|
}
|
||||||
|
|
||||||
|
int QtMultimediaBackend::duration() const {
|
||||||
|
return convertDuration(m_player->duration());
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onPositionChanged(qint64 position) {
|
||||||
|
emit positionChanged(convertToSliderProgress(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onPlaybackRateChanged(qreal speed) {
|
||||||
|
emit speedChanged(convertSpinSpeed(speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onDurationChanged(qint64 duration) {
|
||||||
|
emit durationChanged(convertDuration(duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onErrorOccurred(QMediaPlayer::Error error, const QString& error_string) {
|
||||||
|
QString err = error_string.isEmpty() ? errorToString(error) : error_string;
|
||||||
|
emit errorOccurred(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onAudioAvailable(bool available) {
|
||||||
|
emit audioAvailable(available);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onVideoAvailable(bool available) {
|
||||||
|
emit videoAvailable(available);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onMediaStatusChanged(QMediaPlayer::MediaStatus status) {
|
||||||
|
QString st = mediaStatusToString(status);
|
||||||
|
emit statusChanged(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onPlaybackStateChanged(QMediaPlayer::PLAYBACK_STATE state) {
|
||||||
|
switch (state) {
|
||||||
|
case QMediaPlayer::PLAYBACK_STATE::StoppedState:
|
||||||
|
emit playbackStateChanged(PlayerBackend::PlaybackState::StoppedState);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QMediaPlayer::PLAYBACK_STATE::PlayingState:
|
||||||
|
emit playbackStateChanged(PlayerBackend::PlaybackState::PlayingState);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QMediaPlayer::PLAYBACK_STATE::PausedState:
|
||||||
|
emit playbackStateChanged(PlayerBackend::PlaybackState::PausedState);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtMultimediaBackend::onSeekableChanged(bool seekable) {
|
||||||
|
emit seekableChanged(seekable);
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||||
|
|
||||||
#ifndef MEDIAPLAYER_H
|
#ifndef QTMULTIMEDIABACKEND_H
|
||||||
#define MEDIAPLAYER_H
|
#define QTMULTIMEDIABACKEND_H
|
||||||
|
|
||||||
#include "gui/tabcontent.h"
|
#include "gui/mediaplayer/playerbackend.h"
|
||||||
|
|
||||||
#include "ui_mediaplayer.h"
|
#include <QObject>
|
||||||
|
|
||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
|
|
||||||
|
@ -17,35 +17,32 @@
|
||||||
#define PLAYBACK_STATE_METHOD state
|
#define PLAYBACK_STATE_METHOD state
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
class QAudioOutput;
|
class QAudioOutput;
|
||||||
|
#endif
|
||||||
|
|
||||||
class MediaPlayer : public TabContent {
|
class QVideoWidget;
|
||||||
|
|
||||||
|
class QtMultimediaBackend : public PlayerBackend {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MediaPlayer(QWidget* parent = nullptr);
|
explicit QtMultimediaBackend(QWidget* parent = nullptr);
|
||||||
virtual ~MediaPlayer();
|
|
||||||
|
|
||||||
virtual WebBrowser* webBrowser() const;
|
virtual QUrl url() const;
|
||||||
|
virtual int position() const;
|
||||||
|
virtual int duration() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void playUrl(const QString& url);
|
virtual void playUrl(const QUrl& url);
|
||||||
|
virtual void playPause();
|
||||||
|
virtual void pause();
|
||||||
|
virtual void stop();
|
||||||
|
virtual void setPlaybackSpeed(int speed);
|
||||||
|
virtual void setVolume(int volume);
|
||||||
|
virtual void setPosition(int position);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void playPause();
|
|
||||||
void stop();
|
|
||||||
void download();
|
|
||||||
void muteUnmute();
|
|
||||||
void setSpeed(int speed);
|
|
||||||
|
|
||||||
// NOTE: Volume is from 0 to 100 taken directly from slider or
|
|
||||||
// elsewhere.
|
|
||||||
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 onPlaybackRateChanged(qreal speed);
|
void onPlaybackRateChanged(qreal speed);
|
||||||
void onDurationChanged(qint64 duration);
|
void onDurationChanged(qint64 duration);
|
||||||
void onErrorOccurred(QMediaPlayer::Error error, const QString& error_string = {});
|
void onErrorOccurred(QMediaPlayer::Error error, const QString& error_string = {});
|
||||||
|
@ -56,9 +53,6 @@ class MediaPlayer : public TabContent {
|
||||||
void onPositionChanged(qint64 position);
|
void onPositionChanged(qint64 position);
|
||||||
void onSeekableChanged(bool seekable);
|
void onSeekableChanged(bool seekable);
|
||||||
|
|
||||||
signals:
|
|
||||||
void urlDownloadRequested(const QUrl& url);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float convertSliderVolume(int slider_volume) const;
|
float convertSliderVolume(int slider_volume) const;
|
||||||
qint64 convertSliderProgress(int slider_progress) const;
|
qint64 convertSliderProgress(int slider_progress) const;
|
||||||
|
@ -70,23 +64,13 @@ class MediaPlayer : public TabContent {
|
||||||
QString errorToString(QMediaPlayer::Error error) const;
|
QString errorToString(QMediaPlayer::Error error) const;
|
||||||
QString mediaStatusToString(QMediaPlayer::MediaStatus status) const;
|
QString mediaStatusToString(QMediaPlayer::MediaStatus status) const;
|
||||||
|
|
||||||
void updateTimeAndProgress(int progress, int total);
|
|
||||||
void setupIcons();
|
|
||||||
void createConnections();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MediaPlayer m_ui;
|
|
||||||
|
|
||||||
#if QT_VERSION_MAJOR == 6
|
#if QT_VERSION_MAJOR == 6
|
||||||
QAudioOutput* m_audio;
|
QAudioOutput* m_audio;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QMediaPlayer* m_player;
|
QMediaPlayer* m_player;
|
||||||
QIcon m_iconPlay;
|
QVideoWidget* m_video;
|
||||||
QIcon m_iconPause;
|
|
||||||
QIcon m_iconMute;
|
|
||||||
QIcon m_iconUnmute;
|
|
||||||
bool m_muted;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MEDIAPLAYER_H
|
#endif // QTMULTIMEDIABACKEND_H
|
|
@ -6,7 +6,6 @@
|
||||||
#include "miscellaneous/settings.h"
|
#include "miscellaneous/settings.h"
|
||||||
|
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
#include <QTimer>
|
|
||||||
#include <QTimerEvent>
|
#include <QTimerEvent>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -28,6 +27,11 @@ BaseToastNotification::BaseToastNotification(QWidget* parent) : QDialog(parent),
|
||||||
|
|
||||||
setStyleSheet(QSL("BaseToastNotification { border: 1px solid %1; }").arg(palette().windowText().color().name()));
|
setStyleSheet(QSL("BaseToastNotification { border: 1px solid %1; }").arg(palette().windowText().color().name()));
|
||||||
installEventFilter(this);
|
installEventFilter(this);
|
||||||
|
|
||||||
|
m_timerClosingClick.setInterval(200);
|
||||||
|
m_timerClosingClick.setSingleShot(true);
|
||||||
|
|
||||||
|
connect(&m_timerClosingClick, &QTimer::timeout, this, &BaseToastNotification::close);
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseToastNotification::~BaseToastNotification() {}
|
BaseToastNotification::~BaseToastNotification() {}
|
||||||
|
@ -82,7 +86,7 @@ bool BaseToastNotification::eventFilter(QObject* watched, QEvent* event) {
|
||||||
if (dynamic_cast<QMouseEvent*>(event)->button() == Qt::MouseButton::RightButton) {
|
if (dynamic_cast<QMouseEvent*>(event)->button() == Qt::MouseButton::RightButton) {
|
||||||
event->accept();
|
event->accept();
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
QTimer::singleShot(200, this, &BaseToastNotification::close);
|
m_timerClosingClick.start();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
class QAbstractButton;
|
class QAbstractButton;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@ class BaseToastNotification : public QDialog {
|
||||||
void closeRequested(BaseToastNotification* notif);
|
void closeRequested(BaseToastNotification* notif);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QTimer m_timerClosingClick;
|
||||||
int m_timerId;
|
int m_timerId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,309 +0,0 @@
|
||||||
// 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>
|
|
||||||
#include <QWindow>
|
|
||||||
#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);
|
|
||||||
|
|
||||||
#if QT_VERSION_MAJOR == 6
|
|
||||||
m_player->setAudioOutput(m_audio);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
setupIcons();
|
|
||||||
createConnections();
|
|
||||||
|
|
||||||
onPlaybackStateChanged(QMediaPlayer::PLAYBACK_STATE::StoppedState);
|
|
||||||
onMediaStatusChanged(QMediaPlayer::MediaStatus::NoMedia);
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaPlayer::~MediaPlayer() {}
|
|
||||||
|
|
||||||
WebBrowser* MediaPlayer::webBrowser() const {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::playUrl(const QString& url) {
|
|
||||||
if (m_muted) {
|
|
||||||
muteUnmute();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setVolume(m_ui.m_slidVolume->value());
|
|
||||||
}
|
|
||||||
|
|
||||||
#if QT_VERSION_MAJOR == 6
|
|
||||||
m_player->setSource(url);
|
|
||||||
#else
|
|
||||||
m_player->setMedia(QUrl(url));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_player->play();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::playPause() {
|
|
||||||
if (m_player->PLAYBACK_STATE_METHOD() != QMediaPlayer::PLAYBACK_STATE::PlayingState) {
|
|
||||||
m_player->play();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_player->pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::stop() {
|
|
||||||
m_player->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::download() {
|
|
||||||
emit urlDownloadRequested(
|
|
||||||
#if QT_VERSION_MAJOR == 6
|
|
||||||
m_player->source()
|
|
||||||
#else
|
|
||||||
m_player->media().request().url()
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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::setSpeed(int speed) {
|
|
||||||
m_player->setPlaybackRate(convertSpeed(speed));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::setVolume(int volume) {
|
|
||||||
#if QT_VERSION_MAJOR == 6
|
|
||||||
m_player->audioOutput()->setVolume(convertSliderVolume(volume));
|
|
||||||
#else
|
|
||||||
m_player->setVolume(volume);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_ui.m_btnVolume->setIcon(volume <= 0 ? m_iconMute : m_iconUnmute);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::seek(int position) {
|
|
||||||
m_player->setPosition(convertSliderProgress(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::onPlaybackRateChanged(qreal speed) {
|
|
||||||
m_ui.m_spinSpeed->blockSignals(true);
|
|
||||||
m_ui.m_spinSpeed->setValue(convertSpinSpeed(speed));
|
|
||||||
m_ui.m_spinSpeed->blockSignals(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::onDurationChanged(qint64 duration) {
|
|
||||||
m_ui.m_slidProgress->blockSignals(true);
|
|
||||||
m_ui.m_slidProgress->setMaximum(convertDuration(duration));
|
|
||||||
m_ui.m_slidProgress->blockSignals(false);
|
|
||||||
|
|
||||||
updateTimeAndProgress(convertToSliderProgress(m_player->position()), convertDuration(duration));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::onPositionChanged(qint64 position) {
|
|
||||||
m_ui.m_slidProgress->blockSignals(true);
|
|
||||||
m_ui.m_slidProgress->setValue(convertToSliderProgress(position));
|
|
||||||
m_ui.m_slidProgress->blockSignals(false);
|
|
||||||
|
|
||||||
updateTimeAndProgress(convertToSliderProgress(position), convertDuration(m_player->duration()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::updateTimeAndProgress(int progress, int total) {
|
|
||||||
m_ui.m_lblTime->setText(QSL("%1/%2").arg(QDateTime::fromSecsSinceEpoch(progress).toUTC().toString("hh:mm:ss"),
|
|
||||||
QDateTime::fromSecsSinceEpoch(total).toUTC().toString("hh:mm:ss")));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::onErrorOccurred(QMediaPlayer::Error error, const QString& error_string) {
|
|
||||||
QString err = error_string.isEmpty() ? errorToString(error) : error_string;
|
|
||||||
m_ui.m_lblStatus->setStatus(WidgetWithStatus::StatusType::Error, err, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
QString st = mediaStatusToString(status);
|
|
||||||
m_ui.m_lblStatus->setStatus(status == QMediaPlayer::MediaStatus::InvalidMedia
|
|
||||||
? WidgetWithStatus::StatusType::Error
|
|
||||||
: WidgetWithStatus::StatusType::Information,
|
|
||||||
st,
|
|
||||||
st);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::onPlaybackStateChanged(QMediaPlayer::PLAYBACK_STATE state) {
|
|
||||||
switch (state) {
|
|
||||||
case QMediaPlayer::PLAYBACK_STATE::StoppedState:
|
|
||||||
m_ui.m_btnPlayPause->setIcon(m_iconPlay);
|
|
||||||
m_ui.m_btnStop->setEnabled(false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QMediaPlayer::PLAYBACK_STATE::PlayingState:
|
|
||||||
m_ui.m_btnPlayPause->setIcon(m_iconPause);
|
|
||||||
m_ui.m_btnStop->setEnabled(true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QMediaPlayer::PLAYBACK_STATE::PausedState:
|
|
||||||
m_ui.m_btnPlayPause->setIcon(m_iconPlay);
|
|
||||||
m_ui.m_btnStop->setEnabled(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int MediaPlayer::convertToSliderProgress(qint64 player_progress) const {
|
|
||||||
return player_progress / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MediaPlayer::convertDuration(qint64 duration) const {
|
|
||||||
return duration / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal MediaPlayer::convertSpeed(int speed) const {
|
|
||||||
return speed / 100.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MediaPlayer::convertSpinSpeed(qreal speed) const {
|
|
||||||
return speed * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaPlayer::onSeekableChanged(bool seekable) {
|
|
||||||
m_ui.m_slidProgress->setEnabled(seekable);
|
|
||||||
|
|
||||||
if (!seekable) {
|
|
||||||
onPositionChanged(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MediaPlayer::errorToString(QMediaPlayer::Error error) const {
|
|
||||||
switch (error) {
|
|
||||||
case QMediaPlayer::ResourceError:
|
|
||||||
return tr("Cannot load media (missing codecs)");
|
|
||||||
|
|
||||||
case QMediaPlayer::FormatError:
|
|
||||||
return tr("Unrecognized format");
|
|
||||||
|
|
||||||
case QMediaPlayer::NetworkError:
|
|
||||||
return tr("Network problem");
|
|
||||||
|
|
||||||
case QMediaPlayer::AccessDeniedError:
|
|
||||||
return tr("Access denied");
|
|
||||||
|
|
||||||
#if QT_VERSION_MAJOR == 5
|
|
||||||
case QMediaPlayer::ServiceMissingError:
|
|
||||||
return tr("Service is missing");
|
|
||||||
|
|
||||||
case QMediaPlayer::MediaIsPlaylist:
|
|
||||||
return tr("This is playlist");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case QMediaPlayer::NoError:
|
|
||||||
return tr("No errors");
|
|
||||||
|
|
||||||
default:
|
|
||||||
return tr("Unknown error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float MediaPlayer::convertSliderVolume(int slider_volume) const {
|
|
||||||
return slider_volume / 100.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 MediaPlayer::convertSliderProgress(int slider_progress) const {
|
|
||||||
return qint64(slider_progress) * qint64(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
#if QT_VERSION_MAJOR == 6
|
|
||||||
connect(m_player, &QMediaPlayer::errorOccurred, this, &MediaPlayer::onErrorOccurred);
|
|
||||||
#else
|
|
||||||
connect(m_player, QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error), this, [this](QMediaPlayer::Error error) {
|
|
||||||
onErrorOccurred(error);
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if QT_VERSION_MAJOR == 6
|
|
||||||
connect(m_player, &QMediaPlayer::hasAudioChanged, this, &MediaPlayer::onAudioAvailable);
|
|
||||||
connect(m_player, &QMediaPlayer::hasVideoChanged, this, &MediaPlayer::onVideoAvailable);
|
|
||||||
connect(m_player, &QMediaPlayer::playbackStateChanged, this, &MediaPlayer::onPlaybackStateChanged);
|
|
||||||
#else
|
|
||||||
connect(m_player, &QMediaPlayer::audioAvailableChanged, this, &MediaPlayer::onAudioAvailable);
|
|
||||||
connect(m_player, &QMediaPlayer::videoAvailableChanged, this, &MediaPlayer::onVideoAvailable);
|
|
||||||
connect(m_player, &QMediaPlayer::stateChanged, this, &MediaPlayer::onPlaybackStateChanged);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &MediaPlayer::onMediaStatusChanged);
|
|
||||||
connect(m_player, &QMediaPlayer::positionChanged, this, &MediaPlayer::onPositionChanged);
|
|
||||||
connect(m_player, &QMediaPlayer::seekableChanged, this, &MediaPlayer::onSeekableChanged);
|
|
||||||
connect(m_player, &QMediaPlayer::playbackRateChanged, this, &MediaPlayer::onPlaybackRateChanged);
|
|
||||||
|
|
||||||
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);
|
|
||||||
connect(m_ui.m_spinSpeed, QOverload<int>::of(&QSpinBox::valueChanged), this, &MediaPlayer::setSpeed);
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include "gui/feedsview.h"
|
#include "gui/feedsview.h"
|
||||||
#include "gui/messagepreviewer.h"
|
#include "gui/messagepreviewer.h"
|
||||||
#include "gui/messagesview.h"
|
#include "gui/messagesview.h"
|
||||||
#include "gui/reusable/mediaplayer.h"
|
|
||||||
#include "gui/reusable/plaintoolbutton.h"
|
#include "gui/reusable/plaintoolbutton.h"
|
||||||
#include "gui/tabbar.h"
|
#include "gui/tabbar.h"
|
||||||
#include "gui/webbrowser.h"
|
#include "gui/webbrowser.h"
|
||||||
|
@ -17,6 +16,10 @@
|
||||||
#include "miscellaneous/settings.h"
|
#include "miscellaneous/settings.h"
|
||||||
#include "miscellaneous/textfactory.h"
|
#include "miscellaneous/textfactory.h"
|
||||||
|
|
||||||
|
#if defined(ENABLE_MEDIAPLAYER)
|
||||||
|
#include "gui/mediaplayer/mediaplayer.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
|
@ -224,6 +227,7 @@ int TabWidget::addEmptyBrowser() {
|
||||||
return addBrowser(false, true);
|
return addBrowser(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ENABLE_MEDIAPLAYER)
|
||||||
int TabWidget::addMediaPlayer(const QString& url, bool make_active) {
|
int TabWidget::addMediaPlayer(const QString& url, bool make_active) {
|
||||||
auto* player = new MediaPlayer(this);
|
auto* player = new MediaPlayer(this);
|
||||||
|
|
||||||
|
@ -248,6 +252,7 @@ int TabWidget::addMediaPlayer(const QString& url, bool make_active) {
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int TabWidget::addLinkedBrowser(const QUrl& initial_url) {
|
int TabWidget::addLinkedBrowser(const QUrl& initial_url) {
|
||||||
return addBrowser(false, false, initial_url);
|
return addBrowser(false, false, initial_url);
|
||||||
|
|
|
@ -78,7 +78,9 @@ class TabWidget : public QTabWidget {
|
||||||
// Adds new WebBrowser tab to global TabWidget.
|
// Adds new WebBrowser tab to global TabWidget.
|
||||||
int addEmptyBrowser();
|
int addEmptyBrowser();
|
||||||
|
|
||||||
|
#if defined(ENABLE_MEDIAPLAYER)
|
||||||
int addMediaPlayer(const QString& url, bool make_active);
|
int addMediaPlayer(const QString& url, bool make_active);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Adds new WebBrowser with link. This is used when user
|
// Adds new WebBrowser with link. This is used when user
|
||||||
// selects to "Open link in new tab.".
|
// selects to "Open link in new tab.".
|
||||||
|
|
|
@ -27,7 +27,10 @@ void WebViewer::processContextMenu(QMenu* specific_menu, QContextMenuEvent* even
|
||||||
specific_menu->addAction(m_actionPlayLink.data());
|
specific_menu->addAction(m_actionPlayLink.data());
|
||||||
|
|
||||||
m_actionOpenExternalBrowser.data()->setEnabled(m_contextMenuData.m_linkUrl.isValid());
|
m_actionOpenExternalBrowser.data()->setEnabled(m_contextMenuData.m_linkUrl.isValid());
|
||||||
|
|
||||||
|
#if defined(ENABLE_MEDIAPLAYER)
|
||||||
m_actionPlayLink.data()->setEnabled(m_contextMenuData.m_linkUrl.isValid());
|
m_actionPlayLink.data()->setEnabled(m_contextMenuData.m_linkUrl.isValid());
|
||||||
|
#endif
|
||||||
|
|
||||||
if (m_contextMenuData.m_linkUrl.isValid()) {
|
if (m_contextMenuData.m_linkUrl.isValid()) {
|
||||||
QFileIconProvider icon_provider;
|
QFileIconProvider icon_provider;
|
||||||
|
@ -61,11 +64,13 @@ void WebViewer::processContextMenu(QMenu* specific_menu, QContextMenuEvent* even
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebViewer::playClickedLinkAsMedia() {
|
void WebViewer::playClickedLinkAsMedia() {
|
||||||
|
#if defined(ENABLE_MEDIAPLAYER)
|
||||||
auto context_url = m_contextMenuData.m_linkUrl;
|
auto context_url = m_contextMenuData.m_linkUrl;
|
||||||
|
|
||||||
if (context_url.isValid()) {
|
if (context_url.isValid()) {
|
||||||
qApp->mainForm()->tabWidget()->addMediaPlayer(context_url.toString(), true);
|
qApp->mainForm()->tabWidget()->addMediaPlayer(context_url.toString(), true);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebViewer::openClickedLinkInExternalBrowser() {
|
void WebViewer::openClickedLinkInExternalBrowser() {
|
||||||
|
@ -97,6 +102,11 @@ void WebViewer::initializeCommonMenuItems() {
|
||||||
m_actionPlayLink.reset(new QAction(qApp->icons()->fromTheme(QSL("player_play"), QSL("media-playback-start")),
|
m_actionPlayLink.reset(new QAction(qApp->icons()->fromTheme(QSL("player_play"), QSL("media-playback-start")),
|
||||||
QObject::tr("Play link as audio/video")));
|
QObject::tr("Play link as audio/video")));
|
||||||
|
|
||||||
|
#if !defined(ENABLE_MEDIAPLAYER)
|
||||||
|
m_actionPlayLink->setText(m_actionPlayLink->text() + QSL(" ") + QObject::tr("(not supported)"));
|
||||||
|
m_actionPlayLink->setEnabled(false);
|
||||||
|
#endif
|
||||||
|
|
||||||
QObject::connect(m_actionOpenExternalBrowser.data(),
|
QObject::connect(m_actionOpenExternalBrowser.data(),
|
||||||
&QAction::triggered,
|
&QAction::triggered,
|
||||||
m_actionOpenExternalBrowser.data(),
|
m_actionOpenExternalBrowser.data(),
|
||||||
|
|
Loading…
Add table
Reference in a new issue