1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2025-09-06 02:32:44 +00:00

Discover remote sessions

Adds a way of discovering remote sessions and in Jellyfin the UI.
This commit is contained in:
Chris Josten 2022-12-28 21:20:04 +01:00 committed by Chris Josten
parent b1bd15f2c1
commit b257fe60aa
20 changed files with 1051 additions and 80 deletions

View file

@ -44,6 +44,7 @@ public:
// Authentication-related variables
QString token;
QString baseUrl;
QString appName;
QString deviceName;
QString deviceId;
QString userId;
@ -103,6 +104,21 @@ void ApiClient::setBaseUrl(const QString &url) {
emit this->baseUrlChanged(d->baseUrl);
}
const QString &ApiClient::appName() const {
const Q_D(ApiClient);
return d->appName;
}
void ApiClient::setAppName(const QString &name) {
Q_D(ApiClient);
d->appName = name;
emit appNameChanged(name);
if (!d->componentBeingParsed) {
generateDeviceProfile();
}
}
const QString &ApiClient::userId() const {
Q_D(const ApiClient);
return d->userId;
@ -220,7 +236,7 @@ void ApiClient::addBaseRequestHeaders(QNetworkRequest &request, const QString &p
void ApiClient::addTokenHeader(QNetworkRequest &request) const {
Q_D(const ApiClient);
QString authentication = "MediaBrowser ";
authentication += "Client=\"Sailfin\"";
authentication += "Client=\"" +d->appName +"\"";
authentication += ", Device=\"" + d->deviceName + "\"";
authentication += ", DeviceId=\"" + d->deviceId + "\"";
authentication += ", Version=\"" + version() + "\"";
@ -425,7 +441,6 @@ QString ApiClient::downloadUrl(const QString &itemId) const {
void ApiClient::generateDeviceProfile() {
Q_D(ApiClient);
QSharedPointer<DTO::DeviceProfile> deviceProfile = QSharedPointer<DTO::DeviceProfile>::create(Model::DeviceProfile::generateProfile());
deviceProfile->setName(d->deviceName);
deviceProfile->setJellyfinId(d->deviceId);
deviceProfile->setFriendlyName(QSysInfo::prettyProductName());
deviceProfile->setMaxStreamingBitrate(d->settings->maxStreamingBitRate());

View file

@ -30,6 +30,7 @@
#include "JellyfinQt/eventbus.h"
#include "JellyfinQt/serverdiscoverymodel.h"
#include "JellyfinQt/websocket.h"
#include "JellyfinQt/model/controllablesession.h"
#include "JellyfinQt/model/player.h"
#include "JellyfinQt/viewmodel/item.h"
#include "JellyfinQt/viewmodel/itemmodel.h"
@ -39,6 +40,7 @@
#include "JellyfinQt/viewmodel/platformmediacontrol.h"
#include "JellyfinQt/viewmodel/playbackmanager.h"
#include "JellyfinQt/viewmodel/playlist.h"
#include "JellyfinQt/viewmodel/remotedevice.h"
#include "JellyfinQt/viewmodel/settings.h"
#include "JellyfinQt/viewmodel/userdata.h"
#include "JellyfinQt/viewmodel/usermodel.h"
@ -66,6 +68,7 @@ void JellyfinPlugin::registerTypes(const char *uri) {
qmlRegisterType<ViewModel::ItemModel>(uri, 1, 0, "ItemModel");
qmlRegisterType<ViewModel::UserModel>(uri, 1, 0, "UserModel");
qmlRegisterUncreatableType<ViewModel::Playlist>(uri, 1, 0, "Playlist", "Available via PlaybackManager");
qmlRegisterType<ViewModel::RemoteDeviceList>(uri, 1, 0, "RemoteDeviceList");
// Loaders
qmlRegisterUncreatableType<ViewModel::LoaderBase>(uri, 1, 0, "LoaderBase", "Use one of its subclasses");
@ -90,6 +93,7 @@ void JellyfinPlugin::registerTypes(const char *uri) {
qmlRegisterUncreatableType<Jellyfin::ViewModel::NowPlayingSection>(uri, 1, 0, "NowPlayingSection", "Is an enum");
qmlRegisterUncreatableType<Jellyfin::Model::PlayerStateClass>(uri, 1, 0, "PlayerState", "Is an enum");
qmlRegisterUncreatableType<Jellyfin::Model::MediaStatusClass>(uri, 1, 0, "MediaStatus", "Is an enum");
qmlRegisterUncreatableType<Jellyfin::Model::DeviceTypeClass>(uri, 1, 0, "DeviceType", "Is an enum");
qRegisterMetaType<Jellyfin::DTO::PlayMethodClass::Value>();
}

View file

@ -0,0 +1,132 @@
#include "JellyfinQt/model/controllablesession.h"
#include <optional>
#include "JellyfinQt/loader/http/session.h"
#include "JellyfinQt/loader/requesttypes.h"
#include <JellyfinQt/model/playbackmanager.h>
namespace Jellyfin {
namespace Model {
ControllableSession::ControllableSession(QObject *parent)
: QObject(parent) {}
// LocalSession
LocalSession::LocalSession(ApiClient &apiClient, QObject *parent)
: ControllableSession(parent), m_apiClient(apiClient) {}
QString LocalSession::id() const {
return m_apiClient.deviceId();
}
QString LocalSession::appName() const {
return m_apiClient.appName();
}
QString LocalSession::name() const {
//: Shown in a list of devices to indicate that media should be played on this device
return tr("This device");
}
DeviceType LocalSession::deviceType() const {
return DeviceType::Unknown;
}
QString LocalSession::userName() const {
return m_apiClient.userId();
}
PlaybackManager *LocalSession::createPlaybackManager() const {
return new LocalPlaybackManager();
}
// ControllableJellyfinSession
ControllableJellyfinSession::ControllableJellyfinSession(const QSharedPointer<DTO::SessionInfo> info, QObject *parent)
: ControllableSession(parent),
m_data(info) {}
QString ControllableJellyfinSession::id() const {
return m_data->jellyfinId();
}
QString ControllableJellyfinSession::appName() const {
return m_data->client();
}
QString ControllableJellyfinSession::name() const {
return m_data->deviceName();
}
DeviceType ControllableJellyfinSession::deviceType() const {
return DeviceType::Unknown;
}
QString ControllableJellyfinSession::userName() const {
return m_data->userName();
}
PlaybackManager * ControllableJellyfinSession::createPlaybackManager() const {
// TODO: implement
return nullptr;
}
RemoteSessionScanner::RemoteSessionScanner(QObject *parent)
: QObject(parent) {}
using GetSessionsLoader = Loader::HTTP::GetSessionsLoader;
class RemoteJellyfinSessionScannerPrivate {
public:
RemoteJellyfinSessionScannerPrivate(ApiClient *apiClient)
: apiClient(apiClient) {
};
ApiClient *apiClient;
GetSessionsLoader *loader = nullptr;
};
RemoteJellyfinSessionScanner::RemoteJellyfinSessionScanner(ApiClient *apiClient, QObject *parent)
: RemoteSessionScanner(parent),
d_ptr(new RemoteJellyfinSessionScannerPrivate(apiClient)) {
}
RemoteJellyfinSessionScanner::~RemoteJellyfinSessionScanner() {}
void RemoteJellyfinSessionScanner::startScanning() {
Q_D(RemoteJellyfinSessionScanner);
if (d->loader != nullptr) return;
emit resetSessions();
emit sessionFound(new LocalSession(*d->apiClient));
Loader::GetSessionsParams params;
params.setControllableByUserId(d->apiClient->userId());
d->loader = new GetSessionsLoader(d->apiClient);
d->loader->setParameters(params);
connect(d->loader, &Loader::HTTP::GetSessionsLoader::ready, this, [this, d]() {
if (d->loader == nullptr) return;
QList<DTO::SessionInfo> sessions = d->loader->result();
for(auto it = sessions.begin(); it != sessions.end(); it++) {
// Skip this device
if (it->jellyfinId() == d->apiClient->deviceId()) continue;
emit sessionFound(new ControllableJellyfinSession(QSharedPointer<DTO::SessionInfo>::create(*it)));
}
});
d->loader->load();
}
void RemoteJellyfinSessionScanner::stopScanning() {
Q_D(RemoteJellyfinSessionScanner);
if (d->loader != nullptr) {
d->loader->deleteLater();
d->loader = nullptr;
}
}
} // NS Model
} // NS Jellyfin

View file

@ -89,7 +89,7 @@ QtMultimediaPlayerPrivate::QtMultimediaPlayerPrivate(QtMultimediaPlayer *q)
q->connect(m_mediaPlayer, &QMediaPlayer::seekableChanged, q, &QtMultimediaPlayer::seekableChanged);
q->connect(m_mediaPlayer, &QMediaPlayer::audioAvailableChanged, q, &QtMultimediaPlayer::hasAudioChanged);
q->connect(m_mediaPlayer, &QMediaPlayer::videoAvailableChanged, q, &QtMultimediaPlayer::hasVideoChanged);
q->connect(m_mediaPlayer, SIGNAL(error(QMediaPlayer::Error)), q, SLOT(errorStringChanged));
//q->connect(m_mediaPlayer, SIGNAL(error(QMediaPlayer::Error)), q, SLOT(errorStringChanged(QString)));
if (m_mediaStreamsControl != nullptr) {
q->connect(m_mediaStreamsControl, &QMediaStreamsControl::streamsChanged, q, [this](){
qCDebug(player) << m_mediaStreamsControl->streamCount() << " streams in the medi source";

View file

@ -0,0 +1,132 @@
/*
* Sailfin: a Jellyfin client written using Qt
* Copyright (C) 2023 Chris Josten and the Sailfin Contributors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <JellyfinQt/model/remotejellyfinplayback.h>
#include <JellyfinQt/apiclient.h>
#include <JellyfinQt/loader/http/session.h>
#include <JellyfinQt/loader/requesttypes.h>
namespace Jellyfin {
namespace Model {
RemoteJellyfinPlayback::RemoteJellyfinPlayback(ApiClient &apiClient, QObject *parent)
: PlaybackManager(parent), m_apiClient(apiClient) {
}
void RemoteJellyfinPlayback::swap(PlaybackManager &other) {
}
PlayerState RemoteJellyfinPlayback::playbackState() const {
}
MediaStatus RemoteJellyfinPlayback::mediaStatus() const {
}
bool RemoteJellyfinPlayback::hasNext() const {
}
bool RemoteJellyfinPlayback::hasPrevious() const {
}
PlaybackManagerError RemoteJellyfinPlayback::error() const {
}
const QString &RemoteJellyfinPlayback::errorString() const {
}
qint64 RemoteJellyfinPlayback::position() const {
}
qint64 RemoteJellyfinPlayback::duration() const {
}
bool RemoteJellyfinPlayback::seekable() const {
}
bool RemoteJellyfinPlayback::hasAudio() const {
}
bool RemoteJellyfinPlayback::hasVideo() const {
}
void RemoteJellyfinPlayback::playItem(QSharedPointer<Item> item) {
}
void RemoteJellyfinPlayback::playItemInList(const QList<QSharedPointer<Item> > &items, int index) {
}
void RemoteJellyfinPlayback::pause() {
}
void RemoteJellyfinPlayback::play() {
}
void RemoteJellyfinPlayback::playItemId(const QString &id) {
}
void RemoteJellyfinPlayback::previous() {
}
void RemoteJellyfinPlayback::next() {
}
void RemoteJellyfinPlayback::goTo(int index) {
}
void RemoteJellyfinPlayback::stop() {
}
void RemoteJellyfinPlayback::seek(qint64 pos) {
}
void RemoteJellyfinPlayback::sendGeneralCommand(DTO::GeneralCommandType command, QJsonObject arguments) {
Loader::SendFullGeneralCommandParams params;
QSharedPointer<DTO::GeneralCommand> fullCommand = QSharedPointer<DTO::GeneralCommand>::create(command, m_apiClient.userId());
fullCommand->setArguments(arguments);
// FIXME: send command
}
} // NS Model
} // NS Jellyfin

View file

@ -23,10 +23,13 @@
#include <JellyfinQt/dto/playstatecommand.h>
#include <JellyfinQt/dto/playstaterequest.h>
// #include "JellyfinQt/DTO/dto.h"
#include <JellyfinQt/dto/useritemdatadto.h>
#include <JellyfinQt/model/controllablesession.h>
#include <JellyfinQt/model/playbackmanager.h>
#include <JellyfinQt/viewmodel/settings.h>
#include <QSharedPointer>
#include <utility>
namespace Jellyfin {
@ -47,7 +50,8 @@ public:
PlaybackManager *q_ptr = nullptr;
ApiClient *m_apiClient = nullptr;
Model::PlaybackManager *m_impl = nullptr;
QSharedPointer<Model::ControllableSession> m_session;
QScopedPointer<Model::PlaybackManager> m_impl;
/// The currently played item that will be shown in the GUI
ViewModel::Item *m_displayItem = nullptr;
@ -59,6 +63,7 @@ public:
PlaybackManagerPrivate::PlaybackManagerPrivate(PlaybackManager *q)
: q_ptr(q),
m_session(nullptr),
m_impl(new Model::LocalPlaybackManager(q)),
m_displayItem(new ViewModel::Item(q)),
m_displayQueue(new ViewModel::Playlist(m_impl->queue())) {
@ -73,21 +78,22 @@ PlaybackManager::PlaybackManager(QObject *parent)
Q_D(PlaybackManager);
// Set up connections.
connect(d->m_impl, &Model::PlaybackManager::positionChanged, this, &PlaybackManager::positionChanged);
connect(d->m_impl, &Model::PlaybackManager::durationChanged, this, &PlaybackManager::durationChanged);
connect(d->m_impl, &Model::PlaybackManager::hasNextChanged, this, &PlaybackManager::hasNextChanged);
connect(d->m_impl, &Model::PlaybackManager::hasPreviousChanged, this, &PlaybackManager::hasPreviousChanged);
connect(d->m_impl, &Model::PlaybackManager::seekableChanged, this, &PlaybackManager::seekableChanged);
connect(d->m_impl, &Model::PlaybackManager::queueIndexChanged, this, &PlaybackManager::queueIndexChanged);
connect(d->m_impl, &Model::PlaybackManager::itemChanged, this, &PlaybackManager::mediaPlayerItemChanged);
connect(d->m_impl, &Model::PlaybackManager::playbackStateChanged, this, &PlaybackManager::playbackStateChanged);
if (auto localImp = qobject_cast<Model::LocalPlaybackManager*>(d->m_impl)) {
connect(d->m_impl.data(), &Model::PlaybackManager::positionChanged, this, &PlaybackManager::positionChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::durationChanged, this, &PlaybackManager::durationChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::hasNextChanged, this, &PlaybackManager::hasNextChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::hasPreviousChanged, this, &PlaybackManager::hasPreviousChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::seekableChanged, this, &PlaybackManager::seekableChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::queueIndexChanged, this, &PlaybackManager::queueIndexChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::itemChanged, this, &PlaybackManager::mediaPlayerItemChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::playbackStateChanged, this, &PlaybackManager::playbackStateChanged);
if (auto localImp = qobject_cast<Model::LocalPlaybackManager*>(d->m_impl.data())) {
connect(localImp, &Model::LocalPlaybackManager::streamUrlChanged, this, [this](const QUrl& newUrl){
this->streamUrlChanged(newUrl.toString());
emit this->streamUrlChanged(newUrl.toString());
});
connect(localImp, &Model::LocalPlaybackManager::playMethodChanged, this, &PlaybackManager::playMethodChanged);
}
connect(d->m_impl, &Model::PlaybackManager::mediaStatusChanged, this, &PlaybackManager::mediaStatusChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::mediaStatusChanged, this, &PlaybackManager::mediaStatusChanged);
}
PlaybackManager::~PlaybackManager() {
@ -107,6 +113,10 @@ void PlaybackManager::setApiClient(ApiClient *apiClient) {
d->m_impl->setApiClient(apiClient);
if (d->m_apiClient != nullptr) {
// Set the session to a new LocalSession in case it hasn't been set yet.
if (d->m_session.isNull()) {
setControllingSession(QSharedPointer<Model::LocalSession>::create(*apiClient, this));
}
connect(d->m_apiClient->eventbus(), &EventBus::playstateCommandReceived, this, &PlaybackManager::handlePlaystateRequest);
}
}
@ -155,9 +165,42 @@ ApiClient * PlaybackManager::apiClient() const {
return d->m_apiClient;
}
QSharedPointer<Model::ControllableSession> PlaybackManager::controllingSession() const {
const Q_D(PlaybackManager);
return d->m_session;
}
void PlaybackManager::setControllingSession(QSharedPointer<Model::ControllableSession> session) {
Q_D(PlaybackManager);
qCDebug(playbackManager()) << "Now controlling session " << session->name();
session->setParent(this);
d->m_session.swap(session);
// TODO: swap out playback manager
emit controllingSessionChanged();
emit controllingSessionIdChanged();
emit controllingSessionNameChanged();
emit controllingSessionLocalChanged();
}
QString PlaybackManager::controllingSessionId() const {
const Q_D(PlaybackManager);
return d->m_session->id();
}
QString PlaybackManager::controllingSessionName() const {
const Q_D(PlaybackManager);
return d->m_session->name();
}
bool PlaybackManager::controllingSessionLocal() const {
const Q_D(PlaybackManager);
return qobject_cast<Model::LocalPlaybackManager *>(d->m_impl.data()) != nullptr;
}
QString PlaybackManager::streamUrl() const {
const Q_D(PlaybackManager);
if (Model::LocalPlaybackManager *lpm = qobject_cast<Model::LocalPlaybackManager *>(d->m_impl)) {
if (Model::LocalPlaybackManager *lpm = qobject_cast<Model::LocalPlaybackManager *>(d->m_impl.data())) {
return lpm->streamUrl().toString();
} else {
return QStringLiteral("<not playing back locally>");
@ -166,7 +209,7 @@ QString PlaybackManager::streamUrl() const {
PlayMethod PlaybackManager::playMethod() const {
const Q_D(PlaybackManager);
if (Model::LocalPlaybackManager *lpm = qobject_cast<Model::LocalPlaybackManager *>(d->m_impl)) {
if (Model::LocalPlaybackManager *lpm = qobject_cast<Model::LocalPlaybackManager *>(d->m_impl.data())) {
return lpm->playMethod();
} else {
return PlayMethod::EnumNotSet;
@ -210,7 +253,7 @@ bool PlaybackManager::hasPrevious() const {
QObject* PlaybackManager::mediaObject() const {
const Q_D(PlaybackManager);
if (auto localPb = qobject_cast<Model::LocalPlaybackManager*>(d->m_impl)) {
if (auto localPb = qobject_cast<Model::LocalPlaybackManager*>(d->m_impl.data())) {
return localPb->player()->videoOutputSource();
} else {
return nullptr;

View file

@ -0,0 +1,152 @@
/*
* Sailfin: a Jellyfin client written using Qt
* Copyright (C) 2022 Chris Josten and the Sailfin Contributors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <JellyfinQt/viewmodel/remotedevice.h>
#include <JellyfinQt/model/controllablesession.h>
#include <JellyfinQt/viewmodel/playbackmanager.h>
namespace Jellyfin {
namespace ViewModel {
RemoteDeviceList::RemoteDeviceList(QObject *parent)
: QAbstractListModel(parent) {}
void RemoteDeviceList::classBegin() {}
void RemoteDeviceList::componentComplete() {
m_componentComplete = true;
if (m_apiClient != nullptr) {
setApiClient(m_apiClient);
}
}
void RemoteDeviceList::setApiClient(ApiClient *apiClient) {
if (m_apiClient != nullptr) {
for (auto it = m_scanners.begin(); it != m_scanners.end(); it++) {
disconnect(*it, &Model::RemoteSessionScanner::sessionFound, this, &RemoteDeviceList::onSessionFound);
disconnect(*it, &Model::RemoteSessionScanner::sessionLost, this, &RemoteDeviceList::onSessionLost);
disconnect(*it, &Model::RemoteSessionScanner::resetSessions, this, &RemoteDeviceList::onSessionsReset);
}
for (auto it = m_sessions.begin(); it != m_sessions.end(); it++) {
it->first->stopScanning();
it->first->deleteLater();
it->second->deleteLater();
}
m_scanners.clear();
m_sessions.clear();
}
m_apiClient = apiClient;
emit apiClientChanged();
if (!m_componentComplete) return;
m_scanners.append(new Model::RemoteJellyfinSessionScanner(m_apiClient, this));
for (auto it = m_scanners.begin(); it != m_scanners.end(); it++) {
connect(*it, &Model::RemoteSessionScanner::sessionFound, this, &RemoteDeviceList::onSessionFound);
connect(*it, &Model::RemoteSessionScanner::sessionLost, this, &RemoteDeviceList::onSessionLost);
connect(*it, &Model::RemoteSessionScanner::resetSessions, this, &RemoteDeviceList::onSessionsReset);
}
}
int RemoteDeviceList::rowCount(const QModelIndex &parent) const {
return m_sessions.size();
}
QVariant RemoteDeviceList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row > rowCount()) return QVariant();
const QSharedPointer<Model::ControllableSession> session = m_sessions.at(row).second;
if (session.isNull()) return QVariant();
switch (role) {
case RoleNames::jellyfinId:
return session->id();
case RoleNames::name:
return session->name();
case RoleNames::deviceName:
return session->appName();
case RoleNames::deviceType:
return QVariant::fromValue<Model::DeviceType>(session->deviceType());
case RoleNames::userName:
return session->userName();
default:
return QVariant();
}
}
void RemoteDeviceList::activateSession(PlaybackManager *manager, int index) {
manager->setControllingSession(m_sessions.at(index).second);
}
void RemoteDeviceList::setScanning(bool scanning) {
if (scanning == m_scanning) return;
m_scanning = scanning;
emit scanningChanged();
if (scanning) {
for (auto it = m_scanners.begin(); it != m_scanners.end(); it++) {
(*it)->startScanning();
}
} else {
for (auto it = m_scanners.begin(); it != m_scanners.end(); it++) {
(*it)->stopScanning();
}
}
}
void RemoteDeviceList::onSessionFound(Model::ControllableSession *session) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_sessions.append(std::make_pair(qobject_cast<Model::RemoteSessionScanner *>(sender()), QSharedPointer<Model::ControllableSession>(session)));
endInsertRows();
}
void RemoteDeviceList::onSessionLost(QString sessionId) {
Model::RemoteSessionScanner *scanner = qobject_cast<Model::RemoteSessionScanner *>(sender());
for (int i = 0; i < m_sessions.size(); i++) {
auto row = m_sessions.at(i);
if (row.first == scanner && row.second->name() == sessionId) {
beginRemoveRows(QModelIndex(), i, i);
m_sessions.removeAt(i);
if (row.second->parent() == this) {
row.second->deleteLater();
}
endRemoveRows();
i--;
}
}
}
void RemoteDeviceList::onSessionsReset() {
Model::RemoteSessionScanner *scanner = qobject_cast<Model::RemoteSessionScanner *>(sender());
for (int i = 0; i < m_sessions.size(); i++) {
auto row = m_sessions.at(i);
if (row.first == scanner) {
beginRemoveRows(QModelIndex(), i, i);
m_sessions.removeAt(i);
if (row.second->parent() == this) {
row.second->deleteLater();
}
endRemoveRows();
i--;
}
}
}
} // NS Model
} // NS Jellyfin