mirror of https://github.com/KDE/kasts.git
parent
ac58ad0a1a
commit
5f94b4a357
|
@ -222,6 +222,7 @@ if(ANDROID)
|
|||
emblem-music-symbolic
|
||||
gpodder
|
||||
kaccounts-nextcloud
|
||||
clock
|
||||
)
|
||||
else()
|
||||
target_link_libraries(kasts PRIVATE Qt::Widgets Qt::DBus)
|
||||
|
|
|
@ -44,6 +44,10 @@ private:
|
|||
// still pending
|
||||
qint64 m_pendingSeek = -1;
|
||||
|
||||
QTimer *m_sleepTimer = nullptr;
|
||||
qint64 m_sleepTime = -1;
|
||||
qint64 m_remainingSleepTime = -1;
|
||||
|
||||
friend class AudioManager;
|
||||
};
|
||||
|
||||
|
@ -600,3 +604,73 @@ QString AudioManager::formattedPosition() const
|
|||
{
|
||||
return m_kformat.formatDuration(position());
|
||||
}
|
||||
|
||||
qint64 AudioManager::sleepTime() const
|
||||
{
|
||||
if (d->m_sleepTimer) {
|
||||
return d->m_sleepTime;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
qint64 AudioManager::remainingSleepTime() const
|
||||
{
|
||||
if (d->m_sleepTimer) {
|
||||
return d->m_remainingSleepTime;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::setSleepTimer(qint64 duration)
|
||||
{
|
||||
if (duration > 0) {
|
||||
if (d->m_sleepTimer) {
|
||||
stopSleepTimer();
|
||||
}
|
||||
|
||||
d->m_sleepTime = duration;
|
||||
d->m_remainingSleepTime = duration;
|
||||
|
||||
d->m_sleepTimer = new QTimer(this);
|
||||
connect(d->m_sleepTimer, &QTimer::timeout, this, [this]() {
|
||||
(d->m_remainingSleepTime)--;
|
||||
if (d->m_remainingSleepTime > 0) {
|
||||
Q_EMIT remainingSleepTimeChanged(remainingSleepTime());
|
||||
} else {
|
||||
pause();
|
||||
stopSleepTimer();
|
||||
}
|
||||
});
|
||||
d->m_sleepTimer->start(1000);
|
||||
|
||||
Q_EMIT sleepTimerChanged(duration);
|
||||
Q_EMIT remainingSleepTimeChanged(remainingSleepTime());
|
||||
} else {
|
||||
stopSleepTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::stopSleepTimer()
|
||||
{
|
||||
if (d->m_sleepTimer) {
|
||||
d->m_sleepTime = -1;
|
||||
d->m_remainingSleepTime = -1;
|
||||
|
||||
delete d->m_sleepTimer;
|
||||
d->m_sleepTimer = nullptr;
|
||||
|
||||
Q_EMIT sleepTimerChanged(-1);
|
||||
Q_EMIT remainingSleepTimeChanged(-1);
|
||||
}
|
||||
}
|
||||
|
||||
QString AudioManager::formattedRemainingSleepTime() const
|
||||
{
|
||||
qint64 timeLeft = remainingSleepTime() * 1000;
|
||||
if (timeLeft < 0) {
|
||||
timeLeft = 0;
|
||||
}
|
||||
return m_kformat.formatDuration(timeLeft);
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ class AudioManager : public QObject
|
|||
Q_PROPERTY(QString formattedLeftDuration READ formattedLeftDuration NOTIFY positionChanged)
|
||||
Q_PROPERTY(QString formattedDuration READ formattedDuration NOTIFY durationChanged)
|
||||
Q_PROPERTY(QString formattedPosition READ formattedPosition NOTIFY positionChanged)
|
||||
Q_PROPERTY(qint64 sleepTime READ sleepTime WRITE setSleepTimer RESET stopSleepTimer NOTIFY sleepTimerChanged)
|
||||
Q_PROPERTY(qint64 remainingSleepTime READ remainingSleepTime NOTIFY remainingSleepTimeChanged)
|
||||
Q_PROPERTY(QString formattedRemainingSleepTime READ formattedRemainingSleepTime NOTIFY remainingSleepTimeChanged)
|
||||
|
||||
public:
|
||||
const double MAX_RATE = 1.0;
|
||||
|
@ -58,133 +61,88 @@ public:
|
|||
~AudioManager() override;
|
||||
|
||||
[[nodiscard]] Entry *entry() const;
|
||||
|
||||
[[nodiscard]] bool muted() const;
|
||||
|
||||
[[nodiscard]] qreal volume() const;
|
||||
|
||||
[[nodiscard]] QUrl source() const;
|
||||
|
||||
[[nodiscard]] QMediaPlayer::MediaStatus status() const;
|
||||
|
||||
[[nodiscard]] QMediaPlayer::State playbackState() const;
|
||||
|
||||
[[nodiscard]] qreal playbackRate() const;
|
||||
|
||||
[[nodiscard]] qreal minimumPlaybackRate() const;
|
||||
|
||||
[[nodiscard]] qreal maximumPlaybackRate() const;
|
||||
|
||||
[[nodiscard]] QMediaPlayer::Error error() const;
|
||||
|
||||
[[nodiscard]] qint64 duration() const;
|
||||
|
||||
[[nodiscard]] qint64 position() const;
|
||||
|
||||
[[nodiscard]] bool seekable() const;
|
||||
|
||||
[[nodiscard]] bool canPlay() const;
|
||||
|
||||
[[nodiscard]] bool canPause() const;
|
||||
|
||||
[[nodiscard]] bool canSkipForward() const;
|
||||
|
||||
[[nodiscard]] bool canSkipBackward() const;
|
||||
|
||||
[[nodiscard]] bool canGoNext() const;
|
||||
|
||||
QString formattedDuration() const;
|
||||
QString formattedLeftDuration() const;
|
||||
QString formattedPosition() const;
|
||||
|
||||
qint64 sleepTime() const; // returns originally set sleep time
|
||||
qint64 remainingSleepTime() const; // returns remaining sleep time
|
||||
QString formattedRemainingSleepTime() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
void entryChanged(Entry *entry);
|
||||
|
||||
void mutedChanged(bool muted);
|
||||
|
||||
void volumeChanged();
|
||||
|
||||
void sourceChanged();
|
||||
|
||||
void statusChanged(QMediaPlayer::MediaStatus status);
|
||||
|
||||
void playbackStateChanged(QMediaPlayer::State state);
|
||||
|
||||
void playbackRateChanged(qreal rate);
|
||||
|
||||
void errorChanged(QMediaPlayer::Error error);
|
||||
|
||||
void durationChanged(qint64 duration);
|
||||
|
||||
void positionChanged(qint64 position);
|
||||
|
||||
void seekableChanged(bool seekable);
|
||||
|
||||
void playing();
|
||||
|
||||
void paused();
|
||||
|
||||
void stopped();
|
||||
|
||||
void canPlayChanged();
|
||||
|
||||
void canPauseChanged();
|
||||
|
||||
void canSkipForwardChanged();
|
||||
|
||||
void canSkipBackwardChanged();
|
||||
|
||||
void canGoNextChanged();
|
||||
|
||||
void sleepTimerChanged(qint64 duration);
|
||||
void remainingSleepTimeChanged(qint64 duration);
|
||||
|
||||
void logError(Error::Type type, const QString &url, const QString &id, const int errorId, const QString &errorString, const QString &title);
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
void setEntry(Entry *entry);
|
||||
|
||||
void setMuted(bool muted);
|
||||
|
||||
void setVolume(qreal volume);
|
||||
|
||||
// void setSource(const QUrl &source); //source should only be set by audiomanager itself
|
||||
|
||||
void setPosition(qint64 position);
|
||||
|
||||
void setPlaybackRate(qreal rate);
|
||||
|
||||
void play();
|
||||
|
||||
void pause();
|
||||
|
||||
void playPause();
|
||||
|
||||
void stop();
|
||||
|
||||
void seek(qint64 position);
|
||||
|
||||
void skipBackward();
|
||||
|
||||
void skipForward();
|
||||
|
||||
void next();
|
||||
|
||||
void setSleepTimer(qint64 duration);
|
||||
void stopSleepTimer();
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
void mediaStatusChanged();
|
||||
|
||||
void playerStateChanged();
|
||||
|
||||
void playerDurationChanged(qint64 duration);
|
||||
|
||||
void playerMutedChanged();
|
||||
|
||||
void playerVolumeChanged();
|
||||
|
||||
void savePlayPosition();
|
||||
|
||||
void prepareAudio();
|
||||
|
||||
void checkForPendingSeek();
|
||||
|
||||
private:
|
||||
|
|
|
@ -110,7 +110,7 @@ Rectangle {
|
|||
|
||||
Controls.ToolButton {
|
||||
id: chapterButton
|
||||
visible: AudioManager.entry && chapterList.count !== 0 && (titlesAndButtons.width > essentialButtons.width + infoButton.implicitWidth + implicitWidth + parent.optionalButtonCollapseWidth)
|
||||
visible: AudioManager.entry && chapterList.count !== 0 && (titlesAndButtons.width > essentialButtons.width + infoButton.implicitWidth + implicitWidth + 2 * infoButton.implicitWidth + parent.optionalButtonCollapseWidth)
|
||||
text: i18n("Chapters")
|
||||
icon.name: "view-media-playlist"
|
||||
icon.height: essentialButtons.iconSize
|
||||
|
@ -121,7 +121,7 @@ Rectangle {
|
|||
}
|
||||
Controls.ToolButton {
|
||||
id: infoButton
|
||||
visible: AudioManager.entry && (titlesAndButtons.width > essentialButtons.width + implicitWidth + parent.optionalButtonCollapseWidth)
|
||||
visible: AudioManager.entry && (titlesAndButtons.width > essentialButtons.width + 2 * implicitWidth + parent.optionalButtonCollapseWidth)
|
||||
icon.name: "help-about-symbolic"
|
||||
icon.height: essentialButtons.iconSize
|
||||
icon.width: essentialButtons.iconSize
|
||||
|
@ -129,6 +129,21 @@ Rectangle {
|
|||
Layout.preferredWidth: essentialButtons.buttonSize
|
||||
onClicked: entryDetailsOverlay.open();
|
||||
}
|
||||
Controls.ToolButton {
|
||||
id: sleepButton
|
||||
checkable: true
|
||||
checked: AudioManager.remainingSleepTime > 0
|
||||
visible: titlesAndButtons.width > essentialButtons.width + implicitWidth + parent.optionalButtonCollapseWidth
|
||||
icon.name: "clock"
|
||||
icon.height: essentialButtons.iconSize
|
||||
icon.width: essentialButtons.iconSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: essentialButtons.buttonSize
|
||||
onClicked: {
|
||||
toggle(); // only set the on/off state based on sleep timer state
|
||||
sleepTimerDialog.open();
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: essentialButtons
|
||||
property int iconSize: Kirigami.Units.gridUnit
|
||||
|
|
|
@ -294,6 +294,24 @@ Kirigami.Page {
|
|||
swipeView.currentIndex = 2;
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Controls.ToolButton {
|
||||
checkable: true
|
||||
checked: AudioManager.remainingSleepTime > 0
|
||||
Layout.maximumHeight: parent.height
|
||||
Layout.preferredHeight: contextButtons.buttonSize
|
||||
Layout.maximumWidth: height
|
||||
Layout.preferredWidth: height
|
||||
icon.name: "clock"
|
||||
icon.width: contextButtons.iconSize
|
||||
icon.height: contextButtons.iconSize
|
||||
onClicked: {
|
||||
toggle(); // only set the on/off state based on sleep timer state
|
||||
sleepTimerDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2022 Bart De Vries <bart@mogwai.be>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.kasts 1.0
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: sleepTimerDialog
|
||||
title: i18n("Sleep Timer")
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
closePolicy: Kirigami.Dialog.CloseOnEscape | Kirigami.Dialog.CloseOnPressOutside
|
||||
standardButtons: Kirigami.Dialog.NoButton
|
||||
|
||||
property bool timerActive: AudioManager.remainingSleepTime > 0
|
||||
|
||||
customFooterActions: [
|
||||
Kirigami.Action {
|
||||
enabled: !timerActive
|
||||
text: i18n("Start")
|
||||
iconName: "dialog-ok"
|
||||
onTriggered: {
|
||||
sleepTimerDialog.close();
|
||||
var sleepTimeSeconds = sleepTimerValueBox.value * sleepTimerUnitsBox.model[sleepTimerUnitsBox.currentIndex]["secs"];
|
||||
if (sleepTimeSeconds > 0) {
|
||||
SettingsManager.sleepTimerValue = sleepTimerValueBox.value;
|
||||
SettingsManager.sleepTimerUnits = sleepTimerUnitsBox.currentValue;
|
||||
SettingsManager.save();
|
||||
AudioManager.sleepTime = sleepTimeSeconds;
|
||||
}
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
enabled: timerActive
|
||||
text: i18n("Stop")
|
||||
iconName: "dialog-cancel"
|
||||
onTriggered: {
|
||||
sleepTimerDialog.close();
|
||||
AudioManager.sleepTime = undefined; // make use of RESET
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
Text {
|
||||
text: (timerActive) ? i18n("Status: Active") : i18n("Status: Inactive")
|
||||
}
|
||||
|
||||
Text {
|
||||
opacity: (timerActive) ? 1 : 0.5
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||
text: i18n("Remaining Time: %1", AudioManager.formattedRemainingSleepTime)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Controls.SpinBox {
|
||||
id: sleepTimerValueBox
|
||||
enabled: !timerActive
|
||||
value: SettingsManager.sleepTimerValue
|
||||
from: 1
|
||||
to: 24 * 60 * 60
|
||||
}
|
||||
|
||||
Controls.ComboBox {
|
||||
id: sleepTimerUnitsBox
|
||||
enabled: !timerActive
|
||||
textRole: "text"
|
||||
valueRole: "value"
|
||||
model: [{"text": i18n("Seconds"), "value": 0, "secs": 1, "max": 24 * 60 * 60},
|
||||
{"text": i18n("Minutes"), "value": 1, "secs": 60, "max": 24 * 60},
|
||||
{"text": i18n("Hours"), "value": 2, "secs": 60 * 60, "max": 24}]
|
||||
Component.onCompleted: {
|
||||
currentIndex = indexOfValue(SettingsManager.sleepTimerUnits);
|
||||
sleepTimerValueBox.to = sleepTimerUnitsBox.model[currentIndex]["max"];
|
||||
if (sleepTimerValueBox.value > sleepTimerUnitsBox.model[currentIndex]["max"]) {
|
||||
sleepTimerValueBox.value = sleepTimerUnitsBox.model[currentIndex]["max"];
|
||||
}
|
||||
}
|
||||
onActivated: {
|
||||
SettingsManager.sleepTimerUnits = currentValue;
|
||||
if (sleepTimerValueBox.value > sleepTimerUnitsBox.model[currentIndex]["max"]) {
|
||||
sleepTimerValueBox.value = sleepTimerUnitsBox.model[currentIndex]["max"];
|
||||
}
|
||||
sleepTimerValueBox.to = sleepTimerUnitsBox.model[currentIndex]["max"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -355,6 +355,10 @@ Kirigami.ApplicationWindow {
|
|||
id: playbackRateDialog
|
||||
}
|
||||
|
||||
SleepTimerDialog {
|
||||
id: sleepTimerDialog
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Sync
|
||||
function onPasswordInputRequired() {
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<file alias="StorageSettingsPage.qml">qml/Settings/StorageSettingsPage.qml</file>
|
||||
<file alias="SynchronizationSettingsPage.qml">qml/Settings/SynchronizationSettingsPage.qml</file>
|
||||
<file alias="BottomToolbar.qml">qml/BottomToolbar.qml</file>
|
||||
<file alias="SleepTimerDialog.qml">qml/SleepTimerDialog.qml</file>
|
||||
<file>qtquickcontrols2.conf</file>
|
||||
<file alias="logo.svg">../kasts.svg</file>
|
||||
</qresource>
|
||||
|
|
|
@ -88,6 +88,28 @@
|
|||
<label>The top-level page that was open at shutdown</label>
|
||||
<default>FeedListPage</default>
|
||||
</entry>
|
||||
<entry name="sleepTimerValue" type="Int">
|
||||
<label>The number of seconds/minutes/hours to set the sleep timer to</label>
|
||||
<default>30</default>
|
||||
</entry>
|
||||
<entry name="sleepTimerUnits" type="Enum">
|
||||
<label>The units for the sleepTimerValue</label>
|
||||
<choices>
|
||||
<choice name="Seconds">
|
||||
<label>Seconds</label>
|
||||
<value>0</value>
|
||||
</choice>
|
||||
<choice name="Minutes">
|
||||
<label>Minutes</label>
|
||||
<value>1</value>
|
||||
</choice>
|
||||
<choice name="Hours">
|
||||
<label>Hours</label>
|
||||
<value>2</value>
|
||||
</choice>
|
||||
</choices>
|
||||
<default>Minutes</default>
|
||||
</entry>
|
||||
</group>
|
||||
<group name="Synchronization">
|
||||
<entry name="syncEnabled" type="Bool">
|
||||
|
|
Loading…
Reference in New Issue