Implement streaming support
This implements support for streaming episodes rather than downloading them first. This introduces a new setting: prioritizeStreaming. If it's set to false (default) then a streaming play button is only added to the EntryPage. If it is set to true, then the streaming play button will also appear on the Entry delegates instead of the download button. There is a separate setting to decide if streaming is also allowed on metered connections. FEATURE: 438864
This commit is contained in:
parent
5965dc53cd
commit
562c76c799
@ -18,6 +18,10 @@ Files: kasts.svg kasts-android-square.svg logo.png android/ic_launcher-playstore
|
||||
Copyright: 2021 Mathis Brüchert <mbblp@protonmail.ch>
|
||||
License: CC-BY-SA-4.0
|
||||
|
||||
Files: icons/media-playback-start-cloud.svg
|
||||
Copyright: 2022 Bart De Vries <bart@mogwai.be>
|
||||
License: CC-BY-SA-4.0
|
||||
|
||||
Files: android/res/mipmap-anydpi-v26/ic_launcher.xml
|
||||
Copyright: None
|
||||
License: CC0-1.0
|
||||
|
10
icons/media-playback-start-cloud.svg
Normal file
10
icons/media-playback-start-cloud.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style id="current-color-scheme" type="text/css">.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" d="m7.5 2c-2.4853 0-4.5 2.0147-4.5 4.5-0.013331 0.19965-0.013331 0.39996 0 0.59961-1.2239 0.43231-2.031 1.6027-2 2.9004 0 1.6569 1.3432 3.0142 3 3 0 0 0.191 0.01093 0.33961-0.18698 0.11373-0.20472 0.096778-0.38083-0.018045-0.59167-0.16956-0.21372-0.32156-0.22136-0.32156-0.22136-1.1045-0.00919-2-0.89543-2-2 0-1.1046 0.89543-2 2-2 0.13313-0.013393 0.26726-0.013393 0.40039 0-0.24407-0.46372-0.3809-0.97633-0.40039-1.5 0-1.933 1.567-3.5 3.5-3.5s3.5 1.567 3.5 3.5c0.01192 0.16645 0.01192 0.33355 0 0.5 0.16625-0.016709 0.33375-0.016709 0.5 0 1.3807 0 2.5 1.1193 2.5 2.5 0 1.3807-1.1195 2.4777-2.5 2.5 0 0-0.20893 0.0092-0.36684 0.20938-0.14441 0.21588-0.1159 0.50231 0.01983 0.65185 0.14494 0.13233 0.34701 0.13881 0.34701 0.13881 1.933 0.005098 3.5-1.567 3.5-3.5 0.017972-1.7535-1.2644-3.2496-3-3.5-0.25579-2.2882-2.1976-4.0143-4.5-4z" fill="currentColor"/>
|
||||
<path class="ColorScheme-Text" d="m5.1552 6.208v7.7927l7.7927-3.8963z" color="#232629" fill="currentColor" stroke-width="1.2191"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
|
||||
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
||||
* SPDX-FileCopyrightText: 2021-2022 Bart De Vries <bart@mogwai.be>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*/
|
||||
@ -22,11 +22,15 @@
|
||||
#include "powermanagementinterface.h"
|
||||
#include "settingsmanager.h"
|
||||
|
||||
#include <solidextras/networkstatus.h>
|
||||
|
||||
class AudioManagerPrivate
|
||||
{
|
||||
private:
|
||||
PowerManagementInterface mPowerInterface;
|
||||
|
||||
SolidExtras::NetworkStatus m_networkStatus;
|
||||
|
||||
QMediaPlayer m_player;
|
||||
|
||||
Entry *m_entry = nullptr;
|
||||
@ -48,6 +52,8 @@ private:
|
||||
qint64 m_sleepTime = -1;
|
||||
qint64 m_remainingSleepTime = -1;
|
||||
|
||||
bool m_isStreaming = false;
|
||||
|
||||
friend class AudioManager;
|
||||
};
|
||||
|
||||
@ -189,6 +195,11 @@ qreal AudioManager::maximumPlaybackRate() const
|
||||
return MAX_RATE;
|
||||
}
|
||||
|
||||
bool AudioManager::isStreaming() const
|
||||
{
|
||||
return d->m_isStreaming;
|
||||
}
|
||||
|
||||
QMediaPlayer::MediaStatus AudioManager::status() const
|
||||
{
|
||||
return d->m_player.mediaStatus();
|
||||
@ -221,20 +232,37 @@ void AudioManager::setEntry(Entry *entry)
|
||||
}
|
||||
|
||||
// do some checks on the new entry to see whether it's valid and not corrupted
|
||||
if (entry != nullptr && entry->hasEnclosure() && entry->enclosure() && entry->enclosure()->status() == Enclosure::Downloaded) {
|
||||
if (entry != nullptr && entry->hasEnclosure() && entry->enclosure()
|
||||
&& (entry->enclosure()->status() == Enclosure::Downloaded
|
||||
|| (d->m_networkStatus.connectivity() != SolidExtras::NetworkStatus::No
|
||||
&& (d->m_networkStatus.metered() != SolidExtras::NetworkStatus::Yes || SettingsManager::self()->allowMeteredStreaming())))) {
|
||||
qCDebug(kastsAudio) << "Going to change source";
|
||||
d->m_entry = entry;
|
||||
Q_EMIT entryChanged(entry);
|
||||
QUrl loadUrl;
|
||||
if (entry->enclosure()->status() == Enclosure::Downloaded) {
|
||||
loadUrl = QUrl::fromLocalFile(d->m_entry->enclosure()->path());
|
||||
if (d->m_isStreaming) {
|
||||
d->m_isStreaming = false;
|
||||
Q_EMIT isStreamingChanged();
|
||||
}
|
||||
} else {
|
||||
loadUrl = QUrl(d->m_entry->enclosure()->url());
|
||||
if (!d->m_isStreaming) {
|
||||
d->m_isStreaming = true;
|
||||
Q_EMIT isStreamingChanged();
|
||||
}
|
||||
}
|
||||
// the gst-pipeline is required to make sure that the pitch is not
|
||||
// changed when speeding up the audio stream
|
||||
// TODO: find a solution for Android (GStreamer not available on android by default)
|
||||
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
|
||||
qCDebug(kastsAudio) << "use custom pipeline";
|
||||
d->m_player.setMedia(QUrl(QStringLiteral("gst-pipeline: playbin uri=file://") + d->m_entry->enclosure()->path()
|
||||
d->m_player.setMedia(QUrl(QStringLiteral("gst-pipeline: playbin uri=") + loadUrl.toString()
|
||||
+ QStringLiteral(" audio_sink=\"scaletempo ! audioconvert ! audioresample ! autoaudiosink\" video_sink=\"fakevideosink\"")));
|
||||
#else
|
||||
qCDebug(kastsAudio) << "regular audio backend";
|
||||
d->m_player.setMedia(QUrl::fromLocalFile(d->m_entry->enclosure()->path()));
|
||||
d->m_player.setMedia(loadUrl);
|
||||
#endif
|
||||
// save the current playing track in the settingsfile for restoring on startup
|
||||
DataManager::instance().setLastPlayingEntry(d->m_entry->id());
|
||||
@ -303,6 +331,22 @@ void AudioManager::play()
|
||||
{
|
||||
qCDebug(kastsAudio) << "AudioManager::play";
|
||||
|
||||
// if we're streaming, check that we're still connected and check for metered
|
||||
// connection
|
||||
if (isStreaming()) {
|
||||
if (d->m_networkStatus.connectivity() != SolidExtras::NetworkStatus::Yes
|
||||
|| (d->m_networkStatus.metered() != SolidExtras::NetworkStatus::No && !SettingsManager::self()->allowMeteredStreaming())) {
|
||||
qCDebug(kastsAudio) << "Refusing to play: no Connection or streaming on metered connection not allowed";
|
||||
Q_EMIT logError(Error::Type::MeteredStreamingNotAllowed,
|
||||
d->m_entry->feed()->url(),
|
||||
d->m_entry->id(),
|
||||
0,
|
||||
i18n("No connection or streaming on metered connection not allowed"),
|
||||
QString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// setting m_continuePlayback will make sure that, if the audio stream is
|
||||
// still being prepared, that the playback will start once it's ready
|
||||
d->m_continuePlayback = true;
|
||||
@ -374,7 +418,6 @@ void AudioManager::skipBackward()
|
||||
|
||||
bool AudioManager::canGoNext() const
|
||||
{
|
||||
// TODO: extend with streaming capability
|
||||
if (d->m_entry) {
|
||||
int index = DataManager::instance().queue().indexOf(d->m_entry->id());
|
||||
if (index >= 0) {
|
||||
@ -385,6 +428,12 @@ bool AudioManager::canGoNext() const
|
||||
qCDebug(kastsAudio) << "Enclosure status" << next_entry->enclosure()->path() << next_entry->enclosure()->status();
|
||||
if (next_entry->enclosure()->status() == Enclosure::Downloaded) {
|
||||
return true;
|
||||
} else {
|
||||
SolidExtras::NetworkStatus networkStatus;
|
||||
if (networkStatus.connectivity() == SolidExtras::NetworkStatus::Yes
|
||||
&& (networkStatus.metered() == SolidExtras::NetworkStatus::No || SettingsManager::self()->allowMeteredStreaming())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
|
||||
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
||||
* SPDX-FileCopyrightText: 2021-2022 Bart De Vries <bart@mogwai.be>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*/
|
||||
@ -45,6 +45,7 @@ class AudioManager : public QObject
|
||||
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)
|
||||
Q_PROPERTY(bool isStreaming READ isStreaming NOTIFY isStreamingChanged)
|
||||
|
||||
public:
|
||||
const double MAX_RATE = 1.0;
|
||||
@ -87,6 +88,8 @@ public:
|
||||
qint64 remainingSleepTime() const; // returns remaining sleep time
|
||||
QString formattedRemainingSleepTime() const;
|
||||
|
||||
bool isStreaming() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
void entryChanged(Entry *entry);
|
||||
@ -112,6 +115,8 @@ Q_SIGNALS:
|
||||
void sleepTimerChanged(qint64 duration);
|
||||
void remainingSleepTimeChanged(qint64 duration);
|
||||
|
||||
void isStreamingChanged();
|
||||
|
||||
void logError(Error::Type type, const QString &url, const QString &id, const int errorId, const QString &errorString, const QString &title);
|
||||
|
||||
public Q_SLOTS:
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
||||
* SPDX-FileCopyrightText: 2021-2022 Bart De Vries <bart@mogwai.be>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
@ -58,6 +58,8 @@ QString Error::description() const
|
||||
return i18n("Error moving storage path");
|
||||
case Error::Type::SyncError:
|
||||
return i18n("Error Syncing Feed and/or Episode Status");
|
||||
case Error::Type::MeteredStreamingNotAllowed:
|
||||
return i18n("No Connection or Streaming Not Allowed on Metered Connection");
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
@ -80,6 +82,8 @@ int Error::typeToDb(Error::Type type)
|
||||
return 5;
|
||||
case Error::Type::SyncError:
|
||||
return 6;
|
||||
case Error::Type::MeteredStreamingNotAllowed:
|
||||
return 7;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
@ -102,6 +106,8 @@ Error::Type Error::dbToType(int value)
|
||||
return Error::Type::StorageMoveError;
|
||||
case 6:
|
||||
return Error::Type::SyncError;
|
||||
case 7:
|
||||
return Error::Type::MeteredStreamingNotAllowed;
|
||||
default:
|
||||
return Error::Type::Unknown;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
||||
* SPDX-FileCopyrightText: 2021-2022 Bart De Vries <bart@mogwai.be>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
@ -24,6 +24,7 @@ public:
|
||||
DiscoverError,
|
||||
StorageMoveError,
|
||||
SyncError,
|
||||
MeteredStreamingNotAllowed,
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
|
@ -110,36 +110,62 @@ Kirigami.ScrollablePage {
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: !entry.enclosure ? i18n("Open in Browser") :
|
||||
(entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) ? i18n("Download") :
|
||||
entry.enclosure.status === Enclosure.Downloading ? i18n("Cancel Download") :
|
||||
!entry.queueStatus ? i18n("Delete Download") :
|
||||
(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) ? i18n("Pause") :
|
||||
i18n("Play")
|
||||
icon.name: !entry.enclosure ? "globe" :
|
||||
(entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) ? "download" :
|
||||
entry.enclosure.status === Enclosure.Downloading ? "edit-delete-remove" :
|
||||
!entry.queueStatus ? "delete" :
|
||||
(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) ? "media-playback-pause" :
|
||||
"media-playback-start"
|
||||
text: i18n("Open in Browser")
|
||||
visible: !entry.enclosure
|
||||
icon.name: "globe"
|
||||
onTriggered: {
|
||||
if (!entry.enclosure) {
|
||||
Qt.openUrlExternally(entry.link)
|
||||
} else if (entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) {
|
||||
downloadOverlay.entry = entry;
|
||||
downloadOverlay.run();
|
||||
} else if (entry.enclosure.status === Enclosure.Downloading) {
|
||||
entry.enclosure.cancelDownload()
|
||||
} else if (!entry.queueStatus) {
|
||||
entry.enclosure.deleteFile()
|
||||
} else {
|
||||
if(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) {
|
||||
AudioManager.pause()
|
||||
} else {
|
||||
AudioManager.entry = entry
|
||||
AudioManager.play()
|
||||
}
|
||||
}
|
||||
Qt.openUrlExternally(entry.link);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Download")
|
||||
visible: entry.enclosure && (entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded)
|
||||
icon.name: "download"
|
||||
onTriggered: {
|
||||
downloadOverlay.entry = entry;
|
||||
downloadOverlay.run();
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Cancel Download")
|
||||
visible: entry.enclosure && entry.enclosure.status === Enclosure.Downloading
|
||||
icon.name: "edit-delete-remove"
|
||||
onTriggered: {
|
||||
entry.enclosure.cancelDownload();
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Delete Download")
|
||||
visible: entry.enclosure && entry.enclosure.status === Enclosure.Downloaded && !entry.queueStatus
|
||||
icon.name: "delete"
|
||||
onTriggered: {
|
||||
entry.enclosure.deleteFile();
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Pause")
|
||||
visible: entry.enclosure && entry.queueStatus && (AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState)
|
||||
icon.name: "media-playback-pause"
|
||||
onTriggered: {
|
||||
AudioManager.pause();
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Play")
|
||||
visible: entry.enclosure && entry.enclosure.status === Enclosure.Downloaded && entry.queueStatus && (AudioManager.entry !== entry || AudioManager.playbackState !== Audio.PlayingState)
|
||||
icon.name: "media-playback-start"
|
||||
onTriggered: {
|
||||
AudioManager.entry = entry;
|
||||
AudioManager.play();
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("Action to start playback by streaming the episode rather than downloading it first", "Stream")
|
||||
visible: entry.enclosure && entry.queueStatus && entry.enclosure.status !== Enclosure.Downloaded && (AudioManager.entry !== entry || AudioManager.playbackState !== Audio.PlayingState)
|
||||
icon.name: ":/media-playback-start-cloud"
|
||||
onTriggered: {
|
||||
AudioManager.entry = entry;
|
||||
AudioManager.play()
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
|
@ -12,6 +12,7 @@ import QtGraphicalEffects 1.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
import org.kde.kasts.solidextras 1.0
|
||||
|
||||
import org.kde.kasts 1.0
|
||||
|
||||
@ -25,6 +26,18 @@ Kirigami.SwipeListItem {
|
||||
property bool selected: false
|
||||
property int row: model ? model.row : -1
|
||||
|
||||
property bool streamingAllowed: (NetworkStatus.connectivity !== NetworkStatus.No && SettingsManager.prioritizeStreaming && (SettingsManager.allowMeteredStreaming || NetworkStatus.metered !== NetworkStatus.Yes))
|
||||
|
||||
property bool showRemoveFromQueueButton: !entry.enclosure && entry.queueStatus
|
||||
property bool showDownloadButton: (!isDownloads || entry.enclosure.status === Enclosure.PartiallyDownloaded) && entry.enclosure && (entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) && !streamingAllowed && !(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState)
|
||||
property bool showCancelDownloadButton: entry.enclosure && entry.enclosure.status === Enclosure.Downloading
|
||||
property bool showDeleteDownloadButton: isDownloads && entry.enclosure && entry.enclosure.status === Enclosure.Downloaded
|
||||
property bool showAddToQueueButton: !isDownloads && !entry.queueStatus && entry.enclosure && entry.enclosure.status === Enclosure.Downloaded
|
||||
property bool showPlayButton: !isDownloads && entry.queueStatus && entry.enclosure && (entry.enclosure.status === Enclosure.Downloaded) && (AudioManager.entry !== entry || AudioManager.playbackState !== Audio.PlayingState)
|
||||
property bool showStreamingPlayButton: !isDownloads && entry.queueStatus && entry.enclosure && (entry.enclosure.status !== Enclosure.Downloaded && streamingAllowed) && (AudioManager.entry !== entry || AudioManager.playbackState !== Audio.PlayingState)
|
||||
property bool showPauseButton: !isDownloads && entry.queueStatus && entry.enclosure && (AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState)
|
||||
|
||||
|
||||
highlighted: selected
|
||||
activeBackgroundColor: Qt.lighter(Kirigami.Theme.highlightColor, 1.3)
|
||||
|
||||
@ -273,7 +286,7 @@ Kirigami.SwipeListItem {
|
||||
onTriggered: {
|
||||
entry.queueStatus = false;
|
||||
}
|
||||
visible: !entry.enclosure && entry.queueStatus
|
||||
visible: showRemoveFromQueueButton
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Download")
|
||||
@ -282,30 +295,39 @@ Kirigami.SwipeListItem {
|
||||
downloadOverlay.entry = entry;
|
||||
downloadOverlay.run();
|
||||
}
|
||||
visible: (!isDownloads || entry.enclosure.status === Enclosure.PartiallyDownloaded) && entry.enclosure && (entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded)
|
||||
visible: showDownloadButton
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Cancel Download")
|
||||
icon.name: "edit-delete-remove"
|
||||
onTriggered: entry.enclosure.cancelDownload()
|
||||
visible: entry.enclosure && entry.enclosure.status === Enclosure.Downloading
|
||||
visible: showCancelDownloadButton
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Delete Download")
|
||||
icon.name: "delete"
|
||||
onTriggered: entry.enclosure.deleteFile()
|
||||
visible: isDownloads && entry.enclosure && entry.enclosure.status === Enclosure.Downloaded
|
||||
visible: showDeleteDownloadButton
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Add to Queue")
|
||||
icon.name: "media-playlist-append"
|
||||
visible: !isDownloads && !entry.queueStatus && entry.enclosure && entry.enclosure.status === Enclosure.Downloaded
|
||||
visible: showAddToQueueButton
|
||||
onTriggered: entry.queueStatus = true
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Play")
|
||||
icon.name: "media-playback-start"
|
||||
visible: !isDownloads && entry.queueStatus && entry.enclosure && entry.enclosure.status === Enclosure.Downloaded && (AudioManager.entry !== entry || AudioManager.playbackState !== Audio.PlayingState)
|
||||
visible: showPlayButton
|
||||
onTriggered: {
|
||||
AudioManager.entry = entry
|
||||
AudioManager.play()
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("Action to start playback by streaming the episode rather than downloading it first", "Stream")
|
||||
icon.name: ":/media-playback-start-cloud"
|
||||
visible: showStreamingPlayButton
|
||||
onTriggered: {
|
||||
AudioManager.entry = entry
|
||||
AudioManager.play()
|
||||
@ -314,7 +336,7 @@ Kirigami.SwipeListItem {
|
||||
Kirigami.Action {
|
||||
text: i18n("Pause")
|
||||
icon.name: "media-playback-pause"
|
||||
visible: !isDownloads && entry.queueStatus && entry.enclosure && entry.enclosure.status === Enclosure.Downloaded && AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState
|
||||
visible: showPauseButton
|
||||
onTriggered: AudioManager.pause()
|
||||
}
|
||||
]
|
||||
|
@ -36,25 +36,41 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
Controls.CheckBox {
|
||||
id: continuePlayingNextEntry
|
||||
checked: SettingsManager.continuePlayingNextEntry
|
||||
text: i18n("Continue playing next episode after current one finishes")
|
||||
onToggled: SettingsManager.continuePlayingNextEntry = checked
|
||||
id: showTimeLeft
|
||||
Kirigami.FormData.label: i18nc("Label for settings related to the play time, e.g. whether the total track time is shown or a countdown of the remaining play time", "Play Time:")
|
||||
checked: SettingsManager.toggleRemainingTime
|
||||
text: i18n("Show time left instead of total track time")
|
||||
onToggled: SettingsManager.toggleRemainingTime = checked
|
||||
}
|
||||
Controls.CheckBox {
|
||||
id: adjustTimeLeft
|
||||
checked: SettingsManager.adjustTimeLeft
|
||||
enabled: SettingsManager.toggleRemainingTime
|
||||
text: i18n("Adjust time left based on current playback speed")
|
||||
onToggled: SettingsManager.adjustTimeLeft = checked
|
||||
}
|
||||
Controls.CheckBox {
|
||||
id: prioritizeStreaming
|
||||
Kirigami.FormData.label: i18nc("Label for settings related to streaming of episodes (as opposed to playing back locally downloaded files)", "Streaming:")
|
||||
checked: SettingsManager.prioritizeStreaming
|
||||
text: i18n("Prioritize streaming over downloading")
|
||||
onToggled: SettingsManager.prioritizeStreaming = checked
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
Kirigami.FormData.isSection: true
|
||||
text: i18n("Queue Settings")
|
||||
}
|
||||
|
||||
Controls.CheckBox {
|
||||
id: continuePlayingNextEntry
|
||||
checked: SettingsManager.continuePlayingNextEntry
|
||||
text: i18n("Continue playing next episode after current one finishes")
|
||||
onToggled: SettingsManager.continuePlayingNextEntry = checked
|
||||
}
|
||||
Controls.CheckBox {
|
||||
id: refreshOnStartup
|
||||
Kirigami.FormData.label: i18nc("Label for settings related to podcast updates", "Update Settings:")
|
||||
checked: SettingsManager.refreshOnStartup
|
||||
text: i18n("Automatically fetch podcast updates on startup")
|
||||
onToggled: SettingsManager.refreshOnStartup = checked
|
||||
@ -65,6 +81,7 @@ Kirigami.ScrollablePage {
|
||||
text: i18n("Update existing episode data on refresh (slower)")
|
||||
onToggled: SettingsManager.doFullUpdate = checked
|
||||
}
|
||||
|
||||
Controls.CheckBox {
|
||||
id: autoQueue
|
||||
Kirigami.FormData.label: i18n("New Episodes:")
|
||||
|
@ -38,5 +38,12 @@ Kirigami.ScrollablePage {
|
||||
text: i18n("Allow image downloads")
|
||||
onToggled: SettingsManager.allowMeteredImageDownloads = checked
|
||||
}
|
||||
|
||||
Controls.CheckBox {
|
||||
id: allowMeteredStreaming
|
||||
checked: SettingsManager.allowMeteredStreaming
|
||||
text: i18n("Allow streaming")
|
||||
onToggled: SettingsManager.allowMeteredStreaming = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,5 +37,6 @@
|
||||
<file alias="SleepTimerDialog.qml">qml/SleepTimerDialog.qml</file>
|
||||
<file>qtquickcontrols2.conf</file>
|
||||
<file alias="logo.svg">../kasts.svg</file>
|
||||
<file alias="media-playback-start-cloud">../icons/media-playback-start-cloud.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -9,6 +9,10 @@
|
||||
<label>Always show the title of podcast feeds in subscription view</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="prioritizeStreaming" type="Bool">
|
||||
<label>Show streaming button instead of download button</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="continuePlayingNextEntry" type="Bool">
|
||||
<label>Continue playing next episode after current one finishes</label>
|
||||
<default>true</default>
|
||||
@ -104,6 +108,10 @@
|
||||
<label>Allow image downloads on metered connections</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="allowMeteredStreaming" type="Bool">
|
||||
<label>Allow streaming on metered connections</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
</group>
|
||||
<group name="Persistency">
|
||||
<entry name="lastOpenedPage" type="String">
|
||||
|
@ -282,10 +282,10 @@ bool UpdateFeedJob::processEntry(Syndication::ItemPtr entry)
|
||||
entryDetails.read = m_isNewFeed ? m_markUnreadOnNewFeed : false; // if new feed, then check settings
|
||||
entryDetails.isNew = !m_isNewFeed; // if new feed, then mark none as new
|
||||
|
||||
if (!entry->content().isEmpty())
|
||||
entryDetails.content = entry->content();
|
||||
else
|
||||
if (!entry->description().isEmpty())
|
||||
entryDetails.content = entry->description();
|
||||
else
|
||||
entryDetails.content = entry->content();
|
||||
|
||||
// Look for image in itunes tags
|
||||
if (otherItems.value(QStringLiteral("http://www.itunes.com/dtds/podcast-1.0.dtdimage")).hasAttribute(QStringLiteral("href"))) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user