mirror of
https://github.com/jellyfin/jellyfin-desktop.git
synced 2026-10-26 16:45:24 +00:00
Add MPRIS support
This commit is contained in:
1 parent
811ca84b24
commit
00dfb70e1b
21 files changed
+2340
-36
No files matched your search
@@ -140,6 +140,8 @@ document.addEventListener('keydown', (e) => {
|
||||
(async () => {
|
||||
console.log('Auto-connect: starting');
|
||||
|
||||
await window.apiPromise;
|
||||
|
||||
const savedServer = window.jmpInfo.settings.main.userWebClient;
|
||||
console.log('Auto-connect: savedServer =', savedServer);
|
||||
|
||||
|
||||
+323
-15
@@ -19,38 +19,346 @@ class jmpInputPlugin {
|
||||
this.type = 'input';
|
||||
this.id = 'jmpInputPlugin';
|
||||
|
||||
this.durationCheckInterval = null;
|
||||
this.positionUpdateInterval = null;
|
||||
this.attachedPlayer = null;
|
||||
|
||||
(async () => {
|
||||
const api = await window.apiPromise;
|
||||
|
||||
api.input.hostInput.connect((actions) => {
|
||||
actions.forEach(action => {
|
||||
if (remap.hasOwnProperty(action)) {
|
||||
action = remap[action];
|
||||
if (action === 'shuffle') {
|
||||
playbackManager.setQueueShuffleMode('Shuffle');
|
||||
} else if (action === 'sorted') {
|
||||
playbackManager.setQueueShuffleMode('Sorted');
|
||||
} else if (action === 'previous') {
|
||||
const currentPlayer = playbackManager._currentPlayer;
|
||||
if (currentPlayer && playbackManager.isPlayingAudio(currentPlayer)) {
|
||||
const currentTime = playbackManager.currentTime(currentPlayer);
|
||||
const currentIndex = playbackManager.getCurrentPlaylistIndex(currentPlayer);
|
||||
|
||||
if (currentTime >= 5 * 1000 || currentIndex <= 0) {
|
||||
playbackManager.seekPercent(0, currentPlayer);
|
||||
} else {
|
||||
playbackManager.previousTrack(currentPlayer);
|
||||
}
|
||||
} else if (currentPlayer) {
|
||||
playbackManager.previousTrack(currentPlayer);
|
||||
}
|
||||
} else {
|
||||
if (remap.hasOwnProperty(action)) {
|
||||
action = remap[action];
|
||||
}
|
||||
inputManager.handleCommand(action, {});
|
||||
}
|
||||
inputManager.handleCommand(action, {});
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for fullscreen setting changes and trigger fullscreenchange event
|
||||
let lastFullscreenState = window.jmpInfo.settings.main.fullscreen;
|
||||
window.jmpInfo.settingsUpdate.push(function(section) {
|
||||
if (section === 'main') {
|
||||
const currentFullscreenState = window.jmpInfo.settings.main.fullscreen;
|
||||
if (currentFullscreenState !== lastFullscreenState) {
|
||||
lastFullscreenState = currentFullscreenState;
|
||||
console.log('[jmpInputPlugin] Fullscreen setting changed, triggering fullscreenchange event');
|
||||
const currentPlayer = playbackManager._currentPlayer;
|
||||
if (currentPlayer) {
|
||||
window.Events.trigger(currentPlayer, 'fullscreenchange');
|
||||
const updateQueueState = function() {
|
||||
try {
|
||||
if (!api || !api.player) {
|
||||
return;
|
||||
}
|
||||
|
||||
const playlist = playbackManager._playQueueManager?.getPlaylist();
|
||||
if (!playlist || !Array.isArray(playlist)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = playbackManager._playQueueManager?.getCurrentPlaylistIndex();
|
||||
if (currentIndex === undefined || currentIndex === null || currentIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const canNext = currentIndex < playlist.length - 1;
|
||||
|
||||
const state = playbackManager.getPlayerState();
|
||||
const isMusic = state?.NowPlayingItem?.MediaType === 'Audio';
|
||||
const canPrevious = isMusic ? true : currentIndex > 0;
|
||||
|
||||
api.player.notifyQueueChange(canNext, canPrevious);
|
||||
} catch (e) {
|
||||
console.error('PlayerMedia: Error in updateQueueState:', e);
|
||||
}
|
||||
};
|
||||
|
||||
let globalListenersAttached = false;
|
||||
let lastReportedPosition = 0;
|
||||
|
||||
if (!globalListenersAttached) {
|
||||
let lastFullscreenState = window.jmpInfo.settings.main.fullscreen;
|
||||
window.jmpInfo.settingsUpdate.push(function(section) {
|
||||
if (section === 'main') {
|
||||
const currentFullscreenState = window.jmpInfo.settings.main.fullscreen;
|
||||
if (currentFullscreenState !== lastFullscreenState) {
|
||||
lastFullscreenState = currentFullscreenState;
|
||||
const currentPlayer = playbackManager._currentPlayer;
|
||||
if (currentPlayer) {
|
||||
window.Events.trigger(currentPlayer, 'fullscreenchange');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (window.api && window.api.player) {
|
||||
window.api.player.playbackRateChanged.connect(function(rate) {
|
||||
const currentPlayer = playbackManager._currentPlayer;
|
||||
|
||||
if (currentPlayer && currentPlayer._playRate !== undefined) {
|
||||
currentPlayer._playRate = rate;
|
||||
}
|
||||
|
||||
if (window.Events && currentPlayer) {
|
||||
window.Events.trigger(currentPlayer, 'ratechange');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('SystemMedia: Cannot attach playbackRateChanged listener - api:', !!window.api, 'player:', !!window.api?.player);
|
||||
}
|
||||
|
||||
if (window.api && window.api.input) {
|
||||
window.api.input.volumeChanged.connect(function(volume) {
|
||||
const currentPlayer = playbackManager._currentPlayer;
|
||||
if (currentPlayer && typeof currentPlayer.setVolume === 'function') {
|
||||
currentPlayer.setVolume(volume);
|
||||
}
|
||||
});
|
||||
|
||||
window.api.input.rateChanged.connect(function(rate) {
|
||||
const currentPlayer = playbackManager._currentPlayer;
|
||||
if (currentPlayer && typeof currentPlayer.setPlaybackRate === 'function') {
|
||||
currentPlayer.setPlaybackRate(rate);
|
||||
}
|
||||
});
|
||||
|
||||
// WORKAROUND: MPRIS spec has no "Buffering" playback state
|
||||
// During seek buffering, we set Rate to 0.0 to stop MPRIS clients from
|
||||
// auto-incrementing position. Once buffering completes, we restore Rate to 1.0.
|
||||
|
||||
window.api.input.positionSeek.connect(function(positionMs) {
|
||||
const currentPlayer = playbackManager._currentPlayer;
|
||||
if (currentPlayer) {
|
||||
const duration = playbackManager.duration();
|
||||
if (duration) {
|
||||
const percent = (positionMs * 10000) / duration * 100;
|
||||
playbackManager.seekPercent(percent, currentPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
api.player.notifyPosition(Math.floor(positionMs));
|
||||
lastReportedPosition = positionMs;
|
||||
|
||||
api.player.notifyRateChange(0.0);
|
||||
});
|
||||
}
|
||||
|
||||
window.Events.on(playbackManager, 'playlistitemremove', updateQueueState);
|
||||
window.Events.on(playbackManager, 'playlistitemadd', updateQueueState);
|
||||
window.Events.on(playbackManager, 'playlistitemchange', updateQueueState);
|
||||
|
||||
window.Events.on(playbackManager, 'playbackstop', function(e, playbackStopInfo) {
|
||||
updateQueueState();
|
||||
|
||||
const isNavigating = !!(playbackStopInfo && playbackStopInfo.nextMediaType);
|
||||
api.player.notifyPlaybackStop(isNavigating);
|
||||
});
|
||||
|
||||
globalListenersAttached = true;
|
||||
}
|
||||
|
||||
window.Events.on(playbackManager, 'playbackstart', (e, player) => {
|
||||
if (!player) return;
|
||||
|
||||
const state = playbackManager.getPlayerState();
|
||||
if (state && state.NowPlayingItem) {
|
||||
let serverAddress = '';
|
||||
if (window.ApiClient && typeof window.ApiClient.serverAddress === 'function') {
|
||||
serverAddress = window.ApiClient.serverAddress();
|
||||
}
|
||||
|
||||
api.player.notifyMetadata(state.NowPlayingItem, serverAddress || '');
|
||||
|
||||
const initialPos = playbackManager.currentTime();
|
||||
if (initialPos !== undefined && initialPos !== null) {
|
||||
api.player.notifyPosition(Math.floor(initialPos));
|
||||
lastReportedPosition = initialPos;
|
||||
}
|
||||
|
||||
if (player && typeof player.getVolume === 'function') {
|
||||
const volume = player.getVolume();
|
||||
api.player.notifyVolumeChange(volume / 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
let lastDuration = 0;
|
||||
const checkDuration = function() {
|
||||
const duration = playbackManager.duration();
|
||||
if (duration && duration !== lastDuration) {
|
||||
lastDuration = duration;
|
||||
const durationMs = Math.floor(duration / 10000);
|
||||
api.player.notifyDurationChange(durationMs);
|
||||
}
|
||||
};
|
||||
|
||||
if (player !== this.attachedPlayer) {
|
||||
if (this.attachedPlayer) {
|
||||
window.Events.off(this.attachedPlayer, 'shufflequeuemodechange');
|
||||
window.Events.off(this.attachedPlayer, 'repeatmodechange');
|
||||
window.Events.off(this.attachedPlayer, 'playing');
|
||||
window.Events.off(this.attachedPlayer, 'pause');
|
||||
window.Events.off(this.attachedPlayer, 'playbackstop');
|
||||
window.Events.off(this.attachedPlayer, 'volumechange');
|
||||
window.Events.off(this.attachedPlayer, 'ratechange');
|
||||
window.Events.off(this.attachedPlayer, 'timeupdate');
|
||||
}
|
||||
|
||||
this.attachedPlayer = player;
|
||||
|
||||
window.Events.on(player, 'shufflequeuemodechange', () => {
|
||||
const mode = playbackManager.getQueueShuffleMode();
|
||||
const enabled = (mode === 'Shuffle');
|
||||
api.player.notifyShuffleChange(enabled);
|
||||
});
|
||||
|
||||
window.Events.on(player, 'repeatmodechange', () => {
|
||||
const mode = playbackManager.getRepeatMode();
|
||||
api.player.notifyRepeatChange(mode);
|
||||
});
|
||||
|
||||
window.Events.on(player, 'playing', () => {
|
||||
api.player.notifyPlaybackState('Playing');
|
||||
|
||||
updateQueueState();
|
||||
|
||||
api.player.notifyRateChange(1.0);
|
||||
|
||||
if (this.durationCheckInterval) {
|
||||
clearInterval(this.durationCheckInterval);
|
||||
}
|
||||
checkDuration();
|
||||
this.durationCheckInterval = setInterval(checkDuration, 1000);
|
||||
|
||||
const initialPos = playbackManager.currentTime();
|
||||
if (initialPos !== undefined && initialPos !== null) {
|
||||
api.player.notifyPosition(Math.floor(initialPos));
|
||||
}
|
||||
|
||||
if (this.positionUpdateInterval) {
|
||||
clearInterval(this.positionUpdateInterval);
|
||||
}
|
||||
this.positionUpdateInterval = setInterval(() => {
|
||||
const positionMs = playbackManager.currentTime();
|
||||
if (positionMs !== undefined && positionMs !== null) {
|
||||
const positionDiff = Math.abs(positionMs - lastReportedPosition);
|
||||
if (lastReportedPosition > 0 && positionDiff > 2000) {
|
||||
api.player.notifyRateChange(0.0);
|
||||
api.player.notifySeek(Math.floor(positionMs));
|
||||
} else {
|
||||
api.player.notifyPosition(Math.floor(positionMs));
|
||||
}
|
||||
lastReportedPosition = positionMs;
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
window.Events.on(player, 'pause', () => {
|
||||
api.player.notifyPlaybackState('Paused');
|
||||
api.player.notifyRateChange(0.0);
|
||||
|
||||
if (this.durationCheckInterval) {
|
||||
clearInterval(this.durationCheckInterval);
|
||||
this.durationCheckInterval = null;
|
||||
}
|
||||
|
||||
const currentPos = playbackManager.currentTime();
|
||||
if (currentPos !== undefined && currentPos !== null) {
|
||||
lastReportedPosition = currentPos;
|
||||
}
|
||||
});
|
||||
|
||||
window.Events.on(player, 'playbackstop', () => {
|
||||
if (this.durationCheckInterval) {
|
||||
clearInterval(this.durationCheckInterval);
|
||||
this.durationCheckInterval = null;
|
||||
}
|
||||
|
||||
if (this.positionUpdateInterval) {
|
||||
clearInterval(this.positionUpdateInterval);
|
||||
this.positionUpdateInterval = null;
|
||||
}
|
||||
|
||||
lastDuration = 0;
|
||||
lastReportedPosition = 0;
|
||||
});
|
||||
|
||||
window.Events.on(player, 'volumechange', () => {
|
||||
if (player && typeof player.getVolume === 'function') {
|
||||
const volume = player.getVolume();
|
||||
api.player.notifyVolumeChange(volume / 100.0);
|
||||
}
|
||||
});
|
||||
|
||||
window.Events.on(player, 'ratechange', () => {
|
||||
if (player && typeof player.getPlaybackRate === 'function') {
|
||||
const rate = player.getPlaybackRate();
|
||||
api.player.notifyRateChange(rate);
|
||||
}
|
||||
});
|
||||
|
||||
window.Events.on(player, 'timeupdate', () => {
|
||||
const positionMs = playbackManager.currentTime();
|
||||
if (positionMs !== undefined && positionMs !== null) {
|
||||
const positionDiff = Math.abs(positionMs - lastReportedPosition);
|
||||
if (lastReportedPosition > 0 && positionDiff > 2000) {
|
||||
// Player-initiated seek - set rate to 0 during buffering
|
||||
api.player.notifyRateChange(0.0);
|
||||
api.player.notifySeek(Math.floor(positionMs));
|
||||
} else if (positionDiff > 100) {
|
||||
api.player.notifyPosition(Math.floor(positionMs));
|
||||
}
|
||||
lastReportedPosition = positionMs;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
api.system.hello("jmpInputPlugin");
|
||||
})();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.durationCheckInterval) {
|
||||
clearInterval(this.durationCheckInterval);
|
||||
this.durationCheckInterval = null;
|
||||
}
|
||||
|
||||
if (this.positionUpdateInterval) {
|
||||
clearInterval(this.positionUpdateInterval);
|
||||
this.positionUpdateInterval = null;
|
||||
}
|
||||
|
||||
if (this.attachedPlayer) {
|
||||
window.Events.off(this.attachedPlayer, 'shufflequeuemodechange');
|
||||
window.Events.off(this.attachedPlayer, 'repeatmodechange');
|
||||
window.Events.off(this.attachedPlayer, 'playing');
|
||||
window.Events.off(this.attachedPlayer, 'pause');
|
||||
window.Events.off(this.attachedPlayer, 'playbackstop');
|
||||
window.Events.off(this.attachedPlayer, 'volumechange');
|
||||
window.Events.off(this.attachedPlayer, 'timeupdate');
|
||||
this.attachedPlayer = null;
|
||||
}
|
||||
|
||||
if (window.playbackManager) {
|
||||
window.Events.off(window.playbackManager, 'playlistitemremove');
|
||||
window.Events.off(window.playbackManager, 'playlistitemadd');
|
||||
window.Events.off(window.playbackManager, 'playlistitemchange');
|
||||
window.Events.off(window.playbackManager, 'playbackstop');
|
||||
}
|
||||
|
||||
console.log('SystemMedia: jmpInputPlugin destroyed and cleaned up');
|
||||
}
|
||||
}
|
||||
|
||||
window._jmpInputPlugin = jmpInputPlugin;
|
||||
})();
|
||||
})();
|
||||
@@ -262,6 +262,10 @@ class mpvAudioPlayer {
|
||||
setPlaybackRate(value) {
|
||||
this._playRate = value;
|
||||
window.api.player.setPlaybackRate(value * 1000);
|
||||
|
||||
if (window.api && window.api.player) {
|
||||
window.api.player.notifyRateChange(value);
|
||||
}
|
||||
}
|
||||
|
||||
getPlaybackRate() {
|
||||
@@ -348,4 +352,4 @@ function getSupportedFeatures() {
|
||||
}
|
||||
|
||||
window._mpvAudioPlayer = mpvAudioPlayer;
|
||||
})();
|
||||
})();
|
||||
+19
-16
@@ -612,6 +612,20 @@
|
||||
return this._supportedFeatures.includes(feature);
|
||||
}
|
||||
|
||||
isFullscreen() {
|
||||
// Check native window fullscreen state
|
||||
if (window.jmpInfo && window.jmpInfo.settings && window.jmpInfo.settings.main) {
|
||||
return window.jmpInfo.settings.main.fullscreen === true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
toggleFullscreen() {
|
||||
if (window.api && window.api.input) {
|
||||
window.api.input.executeActions(['host:fullscreen']);
|
||||
}
|
||||
}
|
||||
|
||||
// Save this for when playback stops, because querying the time at that point might return 0
|
||||
currentTime(val) {
|
||||
if (val != null) {
|
||||
@@ -690,6 +704,10 @@
|
||||
let playSpeed = +value; //this comes as a string from player force int for now
|
||||
this._playRate = playSpeed;
|
||||
window.api.player.setPlaybackRate(playSpeed * 1000);
|
||||
|
||||
if (window.api && window.api.player) {
|
||||
window.api.player.notifyRateChange(playSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
getPlaybackRate() {
|
||||
@@ -879,22 +897,7 @@
|
||||
setAspectRatio(value) {
|
||||
window.jmpInfo.settings.video.aspect = value;
|
||||
}
|
||||
|
||||
isFullscreen() {
|
||||
// Check native window fullscreen state from settings (universal source of truth)
|
||||
if (window.jmpInfo && window.jmpInfo.settings && window.jmpInfo.settings.main) {
|
||||
return window.jmpInfo.settings.main.fullscreen === true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
toggleFullscreen() {
|
||||
// Toggle native window fullscreen via host command
|
||||
if (window.api && window.api.input) {
|
||||
window.api.input.executeActions(['host:fullscreen']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* eslint-enable indent */
|
||||
|
||||
window._mpvVideoPlayer = mpvVideoPlayer;
|
||||
|
||||
@@ -554,3 +554,23 @@ async function showSettingsModal() {
|
||||
});
|
||||
closeContainer.appendChild(close);
|
||||
}
|
||||
|
||||
let lastFullscreenState = window.jmpInfo.settings.main.fullscreen;
|
||||
|
||||
window.jmpInfo.settingsUpdate.push(function(section) {
|
||||
if (section === 'main') {
|
||||
const currentFullscreenState = window.jmpInfo.settings.main.fullscreen;
|
||||
if (currentFullscreenState !== lastFullscreenState) {
|
||||
lastFullscreenState = currentFullscreenState;
|
||||
|
||||
if (window.api && window.api.player) {
|
||||
window.api.player.notifyFullscreenChange(currentFullscreenState);
|
||||
console.log('Player fullscreen notified');
|
||||
}
|
||||
|
||||
if (window.Events && window.playbackManager && window.playbackManager._currentPlayer) {
|
||||
window.Events.trigger(window.playbackManager._currentPlayer, 'fullscreenchange');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -22,6 +22,7 @@ add_subdirectory(system)
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(power)
|
||||
add_subdirectory(taskbar)
|
||||
add_subdirectory(mpris)
|
||||
|
||||
get_property(ALL_SRCS GLOBAL PROPERTY SRCS_LIST)
|
||||
set(MAIN_SRCS main.cpp)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "settings/SettingsComponent.h"
|
||||
#include "taskbar/TaskbarComponent.h"
|
||||
#include "ui/WindowManager.h"
|
||||
#include "mpris/MprisComponent.h"
|
||||
|
||||
#if KONVERGO_OPENELEC
|
||||
#include "system/openelec/OESystemComponent.h"
|
||||
@@ -60,6 +61,7 @@ void ComponentManager::initialize()
|
||||
registerComponent(&PowerComponent::Get());
|
||||
registerComponent(&TaskbarComponent::Get());
|
||||
registerComponent(&WindowManager::Get());
|
||||
registerComponent(&MprisComponent::Get());
|
||||
|
||||
#if KONVERGO_OPENELEC
|
||||
registerComponent(&OESystemComponent::Get());
|
||||
|
||||
@@ -258,6 +258,27 @@ void InputComponent::sendAction(const QString action)
|
||||
emit hostInput(actionsToSend);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
void InputComponent::setVolume(int volume)
|
||||
{
|
||||
qDebug() << "InputComponent: setVolume" << volume;
|
||||
emit volumeChanged(volume);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
void InputComponent::seekTo(qint64 position)
|
||||
{
|
||||
qDebug() << "InputComponent: seekTo" << position << "ms";
|
||||
emit positionSeek(position);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
void InputComponent::setRate(double rate)
|
||||
{
|
||||
qDebug() << "InputComponent: setRate" << rate;
|
||||
emit rateChanged(rate);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
void InputComponent::registerHostCommand(const QString& command, QObject* receiver, const char* slot)
|
||||
{
|
||||
|
||||
@@ -108,6 +108,11 @@ public:
|
||||
void cancelAutoRepeat();
|
||||
void sendAction(const QString action);
|
||||
|
||||
// Volume, seek, and rate control
|
||||
Q_INVOKABLE void setVolume(int volume);
|
||||
Q_INVOKABLE void seekTo(qint64 position);
|
||||
Q_INVOKABLE void setRate(double rate);
|
||||
|
||||
signals:
|
||||
// Always emitted when any input arrives
|
||||
void receivedInput();
|
||||
@@ -116,6 +121,15 @@ signals:
|
||||
// in the keymap, such as "host:fullscreen".
|
||||
void hostInput(const QStringList& actions);
|
||||
|
||||
// Emitted when volume should be changed (0-100)
|
||||
void volumeChanged(int volume);
|
||||
|
||||
// Emitted when position should be changed (milliseconds)
|
||||
void positionSeek(qint64 position);
|
||||
|
||||
// Emitted when playback rate should be changed (1.0 = normal speed)
|
||||
void rateChanged(double rate);
|
||||
|
||||
private Q_SLOTS:
|
||||
void remapInput(const QString& source, const QString& keycode, InputBase::InputkeyState keyState);
|
||||
|
||||
|
||||
+7
-1
@@ -92,7 +92,8 @@ void ShowLicenseInfo()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
QStringList g_qtFlags = {
|
||||
"--enable-gpu-rasterization"
|
||||
"--enable-gpu-rasterization",
|
||||
"--disable-features=MediaSessionService"
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -238,6 +239,11 @@ int main(int argc, char *argv[])
|
||||
webEngineDataDir = d.absolutePath() + "/QtWebEngine";
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// Disable QtWebEngine's automatic MPRIS registration - we handle it ourselves
|
||||
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--disable-features=MediaSessionService,HardwareMediaKeyHandling");
|
||||
#endif
|
||||
|
||||
QtWebEngineQuick::initialize();
|
||||
QApplication app(newArgc, newArgv);
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
add_sources(MprisComponent.cpp MprisComponent.h)
|
||||
add_sources(MprisRootAdaptor.cpp MprisRootAdaptor.h)
|
||||
add_sources(MprisPlayerAdaptor.cpp MprisPlayerAdaptor.h)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,164 @@
|
||||
#ifndef MPRISCOMPONENT_H
|
||||
#define MPRISCOMPONENT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QVariantMap>
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
#include "ComponentManager.h"
|
||||
#include "player/PlayerComponent.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
#include "MprisRootAdaptor.h"
|
||||
#include "MprisPlayerAdaptor.h"
|
||||
|
||||
class MprisComponent : public ComponentBase
|
||||
{
|
||||
Q_OBJECT
|
||||
DEFINE_SINGLETON(MprisComponent);
|
||||
|
||||
public:
|
||||
explicit MprisComponent(QObject* parent = nullptr);
|
||||
~MprisComponent() override;
|
||||
|
||||
const char* componentName() override { return "mpris"; }
|
||||
bool componentExport() override { return false; }
|
||||
bool componentInitialize() override;
|
||||
void componentPostInitialize() override;
|
||||
|
||||
bool canQuit() const { return false; }
|
||||
bool canRaise() const { return true; }
|
||||
bool canSetFullscreen() const { return true; }
|
||||
bool fullscreen() const;
|
||||
void setFullscreen(bool value);
|
||||
bool hasTrackList() const { return false; }
|
||||
QString identity() const { return "Jellyfin Media Player"; }
|
||||
QString desktopEntry() const { return "jellyfinmediaplayer"; }
|
||||
QStringList supportedUriSchemes() const { return QStringList(); }
|
||||
QStringList supportedMimeTypes() const { return QStringList(); }
|
||||
|
||||
QString playbackStatus() const;
|
||||
QString loopStatus() const;
|
||||
double rate() const;
|
||||
bool shuffle() const;
|
||||
QVariantMap metadata() const { return m_metadata; }
|
||||
double volume() const;
|
||||
qint64 position() const;
|
||||
double minimumRate() const { return 0.25; }
|
||||
double maximumRate() const { return 2.0; }
|
||||
bool canGoNext() const;
|
||||
bool canGoPrevious() const;
|
||||
bool canPlay() const;
|
||||
bool canPause() const;
|
||||
bool canSeek() const;
|
||||
bool canControl() const;
|
||||
|
||||
// Notification slots - connected to PlayerComponent signals
|
||||
void notifyShuffleChange(bool enabled);
|
||||
void notifyRepeatChange(const QString& mode);
|
||||
void notifyFullscreenChange(bool isFullscreen);
|
||||
void notifyRateChange(double rate);
|
||||
void notifyQueueChange(bool canNext, bool canPrevious);
|
||||
void notifyPlaybackStop(bool isNavigating);
|
||||
void notifyDurationChange(qint64 durationMs);
|
||||
void notifyPlaybackState(const QString& state);
|
||||
void notifyPosition(qint64 positionMs);
|
||||
void notifySeek(qint64 positionMs);
|
||||
void notifyMetadata(const QVariantMap& metadata, const QString& baseUrl);
|
||||
void notifyVolumeChange(double volume);
|
||||
|
||||
public Q_SLOTS:
|
||||
// MPRIS D-Bus method handlers - called from D-Bus clients
|
||||
void Raise();
|
||||
void Quit();
|
||||
|
||||
void Next();
|
||||
void Previous();
|
||||
void Pause();
|
||||
void PlayPause();
|
||||
void Stop();
|
||||
void Play();
|
||||
void Seek(qint64 offset);
|
||||
void SetPosition(const QDBusObjectPath& trackId, qint64 position);
|
||||
void OpenUri(const QString& uri);
|
||||
|
||||
void setVolume(double volume);
|
||||
void setLoopStatus(const QString& value);
|
||||
void setRate(double value);
|
||||
void setShuffle(bool value);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onPlayerPlaying();
|
||||
void onPlayerPaused();
|
||||
void onPlayerStopped();
|
||||
void onPlayerFinished();
|
||||
void onPlayerStateChanged(PlayerComponent::State newState, PlayerComponent::State oldState);
|
||||
void onPlayerPositionUpdate(quint64 position);
|
||||
void onPlayerDurationChanged(qint64 duration);
|
||||
void onPlayerMetaData(const QVariantMap& metadata, const QUrl& baseUrl);
|
||||
void onPlayerVolumeChanged();
|
||||
void onShuffleModeChanged(bool shuffleEnabled);
|
||||
void onRepeatModeChanged(const QString& repeatMode);
|
||||
|
||||
void onAlbumArtDownloaded();
|
||||
|
||||
Q_SIGNALS:
|
||||
void propertiesChanged(const QString& interface,
|
||||
const QVariantMap& changedProperties,
|
||||
const QStringList& invalidatedProperties);
|
||||
|
||||
private:
|
||||
void updatePlaybackStatus(const QString& status);
|
||||
void updateMetadata(const QVariantMap& jellyfinMeta, const QUrl& baseUrl = QUrl());
|
||||
void emitPropertyChange(const QString& interface, const QString& property, const QVariant& value);
|
||||
void emitMultiplePropertyChanges(const QString& interface, const QVariantMap& properties);
|
||||
void connectPlayerSignals();
|
||||
void disconnectPlayerSignals();
|
||||
QString generateTrackId() const;
|
||||
QString handleAlbumArt(const QString& artUrl);
|
||||
void cleanupAlbumArt();
|
||||
QString extractArtworkUrl(const QVariantMap& metadata, const QUrl& baseUrl);
|
||||
void updateNavigationCapabilities();
|
||||
|
||||
bool m_enabled;
|
||||
PlayerComponent* m_player;
|
||||
std::unique_ptr<MprisRootAdaptor> m_rootAdaptor;
|
||||
std::unique_ptr<MprisPlayerAdaptor> m_playerAdaptor;
|
||||
|
||||
QString m_playbackStatus;
|
||||
QVariantMap m_metadata;
|
||||
qint64 m_position;
|
||||
qint64 m_duration;
|
||||
QString m_currentTrackId;
|
||||
QString m_currentMediaType;
|
||||
|
||||
QTimer* m_positionTimer;
|
||||
bool m_canGoNext;
|
||||
bool m_canGoPrevious;
|
||||
|
||||
bool m_shuffle;
|
||||
QString m_loopStatus;
|
||||
double m_rate;
|
||||
double m_volume;
|
||||
|
||||
bool m_seekPending;
|
||||
qint64 m_expectedPosition;
|
||||
|
||||
bool m_isNavigating;
|
||||
|
||||
QString m_currentArtPath;
|
||||
QString m_albumArtDir;
|
||||
QString m_pendingArtPath;
|
||||
QString m_pendingArtUrl;
|
||||
PlayerComponent::State m_playerState;
|
||||
|
||||
QNetworkAccessManager* m_albumArtManager;
|
||||
QNetworkReply* m_pendingArtReply;
|
||||
};
|
||||
|
||||
#endif // MPRISCOMPONENT_H
|
||||
@@ -0,0 +1,150 @@
|
||||
#include "MprisPlayerAdaptor.h"
|
||||
#include "MprisComponent.h"
|
||||
|
||||
MprisPlayerAdaptor::MprisPlayerAdaptor(MprisComponent* parent)
|
||||
: QDBusAbstractAdaptor(parent)
|
||||
, m_component(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString MprisPlayerAdaptor::playbackStatus() const
|
||||
{
|
||||
return m_component->playbackStatus();
|
||||
}
|
||||
|
||||
QString MprisPlayerAdaptor::loopStatus() const
|
||||
{
|
||||
return m_component->loopStatus();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::setLoopStatus(const QString& value)
|
||||
{
|
||||
m_component->setLoopStatus(value);
|
||||
}
|
||||
|
||||
double MprisPlayerAdaptor::rate() const
|
||||
{
|
||||
return m_component->rate();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::setRate(double value)
|
||||
{
|
||||
m_component->setRate(value);
|
||||
}
|
||||
|
||||
bool MprisPlayerAdaptor::shuffle() const
|
||||
{
|
||||
return m_component->shuffle();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::setShuffle(bool value)
|
||||
{
|
||||
m_component->setShuffle(value);
|
||||
}
|
||||
|
||||
QVariantMap MprisPlayerAdaptor::metadata() const
|
||||
{
|
||||
return m_component->metadata();
|
||||
}
|
||||
|
||||
double MprisPlayerAdaptor::volume() const
|
||||
{
|
||||
return m_component->volume();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::setVolume(double value)
|
||||
{
|
||||
m_component->setVolume(value);
|
||||
}
|
||||
|
||||
qint64 MprisPlayerAdaptor::position() const
|
||||
{
|
||||
return m_component->position();
|
||||
}
|
||||
|
||||
double MprisPlayerAdaptor::minimumRate() const
|
||||
{
|
||||
return m_component->minimumRate();
|
||||
}
|
||||
|
||||
double MprisPlayerAdaptor::maximumRate() const
|
||||
{
|
||||
return m_component->maximumRate();
|
||||
}
|
||||
|
||||
bool MprisPlayerAdaptor::canGoNext() const
|
||||
{
|
||||
return m_component->canGoNext();
|
||||
}
|
||||
|
||||
bool MprisPlayerAdaptor::canGoPrevious() const
|
||||
{
|
||||
return m_component->canGoPrevious();
|
||||
}
|
||||
|
||||
bool MprisPlayerAdaptor::canPlay() const
|
||||
{
|
||||
return m_component->canPlay();
|
||||
}
|
||||
|
||||
bool MprisPlayerAdaptor::canPause() const
|
||||
{
|
||||
return m_component->canPause();
|
||||
}
|
||||
|
||||
bool MprisPlayerAdaptor::canSeek() const
|
||||
{
|
||||
return m_component->canSeek();
|
||||
}
|
||||
|
||||
bool MprisPlayerAdaptor::canControl() const
|
||||
{
|
||||
return m_component->canControl();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::Next()
|
||||
{
|
||||
m_component->Next();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::Previous()
|
||||
{
|
||||
m_component->Previous();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::Pause()
|
||||
{
|
||||
m_component->Pause();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::PlayPause()
|
||||
{
|
||||
m_component->PlayPause();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::Stop()
|
||||
{
|
||||
m_component->Stop();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::Play()
|
||||
{
|
||||
m_component->Play();
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::Seek(qint64 offset)
|
||||
{
|
||||
m_component->Seek(offset);
|
||||
Q_EMIT Seeked(m_component->position());
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::SetPosition(const QDBusObjectPath& trackId, qint64 position)
|
||||
{
|
||||
m_component->SetPosition(trackId, position);
|
||||
Q_EMIT Seeked(m_component->position());
|
||||
}
|
||||
|
||||
void MprisPlayerAdaptor::OpenUri(const QString& uri)
|
||||
{
|
||||
m_component->OpenUri(uri);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef MPRISPLAYERADAPTOR_H
|
||||
#define MPRISPLAYERADAPTOR_H
|
||||
|
||||
#include <QDBusAbstractAdaptor>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QVariantMap>
|
||||
|
||||
class MprisComponent;
|
||||
|
||||
class MprisPlayerAdaptor : public QDBusAbstractAdaptor
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player")
|
||||
|
||||
// Properties
|
||||
Q_PROPERTY(QString PlaybackStatus READ playbackStatus)
|
||||
Q_PROPERTY(QString LoopStatus READ loopStatus WRITE setLoopStatus)
|
||||
Q_PROPERTY(double Rate READ rate WRITE setRate)
|
||||
Q_PROPERTY(bool Shuffle READ shuffle WRITE setShuffle)
|
||||
Q_PROPERTY(QVariantMap Metadata READ metadata)
|
||||
Q_PROPERTY(double Volume READ volume WRITE setVolume)
|
||||
Q_PROPERTY(qint64 Position READ position)
|
||||
Q_PROPERTY(double MinimumRate READ minimumRate)
|
||||
Q_PROPERTY(double MaximumRate READ maximumRate)
|
||||
Q_PROPERTY(bool CanGoNext READ canGoNext)
|
||||
Q_PROPERTY(bool CanGoPrevious READ canGoPrevious)
|
||||
Q_PROPERTY(bool CanPlay READ canPlay)
|
||||
Q_PROPERTY(bool CanPause READ canPause)
|
||||
Q_PROPERTY(bool CanSeek READ canSeek)
|
||||
Q_PROPERTY(bool CanControl READ canControl)
|
||||
|
||||
public:
|
||||
explicit MprisPlayerAdaptor(MprisComponent* parent);
|
||||
~MprisPlayerAdaptor() override = default;
|
||||
|
||||
// Properties
|
||||
QString playbackStatus() const;
|
||||
QString loopStatus() const;
|
||||
void setLoopStatus(const QString& value);
|
||||
double rate() const;
|
||||
void setRate(double value);
|
||||
bool shuffle() const;
|
||||
void setShuffle(bool value);
|
||||
QVariantMap metadata() const;
|
||||
double volume() const;
|
||||
void setVolume(double value);
|
||||
qint64 position() const;
|
||||
double minimumRate() const;
|
||||
double maximumRate() const;
|
||||
bool canGoNext() const;
|
||||
bool canGoPrevious() const;
|
||||
bool canPlay() const;
|
||||
bool canPause() const;
|
||||
bool canSeek() const;
|
||||
bool canControl() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
// Methods
|
||||
void Next();
|
||||
void Previous();
|
||||
void Pause();
|
||||
void PlayPause();
|
||||
void Stop();
|
||||
void Play();
|
||||
void Seek(qint64 offset);
|
||||
void SetPosition(const QDBusObjectPath& trackId, qint64 position);
|
||||
void OpenUri(const QString& uri);
|
||||
|
||||
Q_SIGNALS:
|
||||
// Signals
|
||||
void Seeked(qint64 position);
|
||||
|
||||
private:
|
||||
MprisComponent* m_component;
|
||||
};
|
||||
|
||||
#endif // MPRISPLAYERADAPTOR_H
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "MprisRootAdaptor.h"
|
||||
#include "MprisComponent.h"
|
||||
|
||||
MprisRootAdaptor::MprisRootAdaptor(MprisComponent* parent)
|
||||
: QDBusAbstractAdaptor(parent)
|
||||
, m_component(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool MprisRootAdaptor::canQuit() const
|
||||
{
|
||||
return m_component->canQuit();
|
||||
}
|
||||
|
||||
bool MprisRootAdaptor::canRaise() const
|
||||
{
|
||||
return m_component->canRaise();
|
||||
}
|
||||
|
||||
bool MprisRootAdaptor::canSetFullscreen() const
|
||||
{
|
||||
return m_component->canSetFullscreen();
|
||||
}
|
||||
|
||||
bool MprisRootAdaptor::fullscreen() const
|
||||
{
|
||||
return m_component->fullscreen();
|
||||
}
|
||||
|
||||
void MprisRootAdaptor::setFullscreen(bool value)
|
||||
{
|
||||
m_component->setFullscreen(value);
|
||||
}
|
||||
|
||||
bool MprisRootAdaptor::hasTrackList() const
|
||||
{
|
||||
return m_component->hasTrackList();
|
||||
}
|
||||
|
||||
QString MprisRootAdaptor::identity() const
|
||||
{
|
||||
return m_component->identity();
|
||||
}
|
||||
|
||||
QString MprisRootAdaptor::desktopEntry() const
|
||||
{
|
||||
return m_component->desktopEntry();
|
||||
}
|
||||
|
||||
QStringList MprisRootAdaptor::supportedUriSchemes() const
|
||||
{
|
||||
return m_component->supportedUriSchemes();
|
||||
}
|
||||
|
||||
QStringList MprisRootAdaptor::supportedMimeTypes() const
|
||||
{
|
||||
return m_component->supportedMimeTypes();
|
||||
}
|
||||
|
||||
void MprisRootAdaptor::Raise()
|
||||
{
|
||||
m_component->Raise();
|
||||
}
|
||||
|
||||
void MprisRootAdaptor::Quit()
|
||||
{
|
||||
m_component->Quit();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef MPRISROOTADAPTOR_H
|
||||
#define MPRISROOTADAPTOR_H
|
||||
|
||||
#include <QDBusAbstractAdaptor>
|
||||
#include <QStringList>
|
||||
|
||||
class MprisComponent;
|
||||
|
||||
class MprisRootAdaptor : public QDBusAbstractAdaptor
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2")
|
||||
|
||||
// Properties
|
||||
Q_PROPERTY(bool CanQuit READ canQuit)
|
||||
Q_PROPERTY(bool CanRaise READ canRaise)
|
||||
Q_PROPERTY(bool CanSetFullscreen READ canSetFullscreen)
|
||||
Q_PROPERTY(bool Fullscreen READ fullscreen WRITE setFullscreen)
|
||||
Q_PROPERTY(bool HasTrackList READ hasTrackList)
|
||||
Q_PROPERTY(QString Identity READ identity)
|
||||
Q_PROPERTY(QString DesktopEntry READ desktopEntry)
|
||||
Q_PROPERTY(QStringList SupportedUriSchemes READ supportedUriSchemes)
|
||||
Q_PROPERTY(QStringList SupportedMimeTypes READ supportedMimeTypes)
|
||||
|
||||
public:
|
||||
explicit MprisRootAdaptor(MprisComponent* parent);
|
||||
~MprisRootAdaptor() override = default;
|
||||
|
||||
// Properties
|
||||
bool canQuit() const;
|
||||
bool canRaise() const;
|
||||
bool canSetFullscreen() const;
|
||||
bool fullscreen() const;
|
||||
void setFullscreen(bool value);
|
||||
bool hasTrackList() const;
|
||||
QString identity() const;
|
||||
QString desktopEntry() const;
|
||||
QStringList supportedUriSchemes() const;
|
||||
QStringList supportedMimeTypes() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
// Methods
|
||||
void Raise();
|
||||
void Quit();
|
||||
|
||||
private:
|
||||
MprisComponent* m_component;
|
||||
};
|
||||
|
||||
#endif // MPRISROOTADAPTOR_H
|
||||
@@ -738,6 +738,78 @@ void PlayerComponent::play()
|
||||
m_mpv->command( args);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyShuffleChange(bool enabled)
|
||||
{
|
||||
emit shuffleChanged(enabled);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyRepeatChange(const QString& mode)
|
||||
{
|
||||
emit repeatChanged(mode);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyFullscreenChange(bool isFullscreen)
|
||||
{
|
||||
emit fullscreenChanged(isFullscreen);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyRateChange(double rate)
|
||||
{
|
||||
emit rateChanged(rate);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyQueueChange(bool canNext, bool canPrevious)
|
||||
{
|
||||
emit queueChanged(canNext, canPrevious);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyPlaybackStop(bool isNavigating)
|
||||
{
|
||||
emit playbackStopped(isNavigating);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyDurationChange(qint64 durationMs)
|
||||
{
|
||||
emit durationChanged(durationMs);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyPlaybackState(const QString& state)
|
||||
{
|
||||
emit playbackStateChanged(state);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyPosition(qint64 positionMs)
|
||||
{
|
||||
emit positionChanged(positionMs);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifySeek(qint64 positionMs)
|
||||
{
|
||||
emit seekPerformed(positionMs);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyMetadata(const QVariantMap& metadata, const QString& baseUrl)
|
||||
{
|
||||
emit metadataChanged(metadata, baseUrl);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::notifyVolumeChange(double volume)
|
||||
{
|
||||
emit volumeChanged(volume);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::stop()
|
||||
{
|
||||
@@ -1031,7 +1103,9 @@ void PlayerComponent::setPlaybackRate(int rate)
|
||||
qWarning() << "PlayerComponent::setPlaybackRate: mpv not initialized yet";
|
||||
return;
|
||||
}
|
||||
m_mpv->setProperty( "speed", rate / 1000.0);
|
||||
double speed = rate / 1000.0;
|
||||
m_mpv->setProperty( "speed", speed);
|
||||
emit playbackRateChanged(speed);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1764,3 +1838,22 @@ QString PlayerComponent::videoInformation() const
|
||||
return infoStr;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PlayerComponent::setWebPlaylist(const QVariantList& playlist, const QString& currentItemId)
|
||||
{
|
||||
m_webPlaylist = playlist;
|
||||
m_currentWebPlaylistItemId = currentItemId;
|
||||
emit webPlaylistChanged(playlist, currentItemId);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
QVariantList PlayerComponent::getWebPlaylist() const
|
||||
{
|
||||
return m_webPlaylist;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
QString PlayerComponent::getCurrentWebPlaylistItemId() const
|
||||
{
|
||||
return m_currentWebPlaylistItemId;
|
||||
}
|
||||
@@ -61,7 +61,21 @@ public:
|
||||
|
||||
Q_INVOKABLE virtual void pause();
|
||||
Q_INVOKABLE virtual void play();
|
||||
|
||||
|
||||
// OS media integration notifications (called from JavaScript)
|
||||
Q_INVOKABLE void notifyShuffleChange(bool enabled);
|
||||
Q_INVOKABLE void notifyRepeatChange(const QString& mode);
|
||||
Q_INVOKABLE void notifyFullscreenChange(bool isFullscreen);
|
||||
Q_INVOKABLE void notifyRateChange(double rate);
|
||||
Q_INVOKABLE void notifyQueueChange(bool canNext, bool canPrevious);
|
||||
Q_INVOKABLE void notifyPlaybackStop(bool isNavigating);
|
||||
Q_INVOKABLE void notifyDurationChange(qint64 durationMs);
|
||||
Q_INVOKABLE void notifyPlaybackState(const QString& state);
|
||||
Q_INVOKABLE void notifyPosition(qint64 positionMs);
|
||||
Q_INVOKABLE void notifySeek(qint64 positionMs);
|
||||
Q_INVOKABLE void notifyMetadata(const QVariantMap& metadata, const QString& baseUrl);
|
||||
Q_INVOKABLE void notifyVolumeChange(double volume);
|
||||
|
||||
// 0-100 volume 0=mute and 100=normal
|
||||
// Ignored if no audio output active (e.g. when no file is playing).
|
||||
Q_INVOKABLE virtual void setVolume(int volume);
|
||||
@@ -123,6 +137,10 @@ public:
|
||||
Q_INVOKABLE qint64 getPosition();
|
||||
Q_INVOKABLE qint64 getDuration();
|
||||
|
||||
Q_INVOKABLE QVariantList getWebPlaylist() const;
|
||||
Q_INVOKABLE QString getCurrentWebPlaylistItemId() const;
|
||||
Q_INVOKABLE void setWebPlaylist(const QVariantList& playlist, const QString& currentItemId);
|
||||
|
||||
QRect videoRectangle() { return m_videoRectangle; }
|
||||
|
||||
void setMpvController(MpvController* controller) {
|
||||
@@ -188,6 +206,7 @@ Q_SIGNALS:
|
||||
void windowVisible(bool visible);
|
||||
// emitted as soon as the duration of the current file is known
|
||||
void updateDuration(qint64 milliseconds);
|
||||
void playbackRateChanged(double rate);
|
||||
|
||||
// current position in ms should be triggered 2 times a second
|
||||
// when position updates
|
||||
@@ -198,7 +217,22 @@ Q_SIGNALS:
|
||||
void onMpvEvents();
|
||||
|
||||
void onMetaData(const QVariantMap &meta, QUrl baseUrl);
|
||||
|
||||
|
||||
void webPlaylistChanged(const QVariantList& playlist, const QString& currentItemId);
|
||||
|
||||
// OS media integration signals (for MPRIS, SMTC, etc.)
|
||||
void shuffleChanged(bool enabled);
|
||||
void repeatChanged(const QString& mode);
|
||||
void fullscreenChanged(bool isFullscreen);
|
||||
void rateChanged(double rate);
|
||||
void queueChanged(bool canNext, bool canPrevious);
|
||||
void playbackStopped(bool isNavigating);
|
||||
void durationChanged(qint64 durationMs);
|
||||
void playbackStateChanged(const QString& state);
|
||||
void positionChanged(qint64 positionMs);
|
||||
void seekPerformed(qint64 positionMs);
|
||||
void metadataChanged(const QVariantMap& metadata, const QString& baseUrl);
|
||||
void volumeChanged(double volume);
|
||||
private:
|
||||
// this is the function actually implemented in the backends. the variantmap contains
|
||||
// a few known keys:
|
||||
@@ -255,6 +289,11 @@ private:
|
||||
QVariant m_currentSubtitleStream;
|
||||
QVariant m_currentAudioStream;
|
||||
QRect m_videoRectangle;
|
||||
|
||||
QVariantList m_webPlaylist;
|
||||
QString m_currentWebPlaylistItemId;
|
||||
QTimer* m_playlistTimer;
|
||||
QVariantList m_queuedItems;
|
||||
};
|
||||
|
||||
#endif // PLAYERCOMPONENT_H
|
||||
@@ -254,6 +254,8 @@ void WindowManager::onVisibilityChanged(QWindow::Visibility visibility)
|
||||
{
|
||||
section->setValueNoSave("fullscreen", m_fullscreen);
|
||||
}
|
||||
|
||||
emit fullScreenSwitched();
|
||||
}
|
||||
|
||||
// Save geometry when exiting fullscreen
|
||||
|
||||
@@ -49,6 +49,9 @@ public:
|
||||
public slots:
|
||||
void toggleFullscreen();
|
||||
|
||||
signals:
|
||||
void fullScreenSwitched();
|
||||
|
||||
private slots:
|
||||
void onVisibilityChanged(QWindow::Visibility visibility);
|
||||
void onScreenAdded(QScreen* screen);
|
||||
|
||||
Reference in new issue
Block a user