PLAYER: Fixed connected device logic.
Build and Publish / Build and Publish Docker Image (push) Successful in 2m50s

This commit is contained in:
2026-05-28 15:17:59 +03:00
parent 34e25fac2c
commit 072c00a48e
7 changed files with 132 additions and 33 deletions
+26 -15
View File
@@ -35,6 +35,10 @@ const T = {
lastfmConnected: "{{ t.player_lastfm_connected }}",
lastfmReconnect: "{{ t.player_lastfm_reconnect }}",
lastfmNotConfigured: "{{ t.player_lastfm_not_configured }}",
lastfmStatusConnect: "{{ t.player_lastfm_status_connect }}",
lastfmStatusConnected: "{{ t.player_lastfm_status_connected }}",
lastfmStatusReconnect: "{{ t.player_lastfm_status_reconnect }}",
lastfmStatusNotConfigured: "{{ t.player_lastfm_status_not_configured }}",
lastfmDisconnectConfirm: "{{ t.player_lastfm_disconnect_confirm }}",
lastfmConnectFailed: "{{ t.player_lastfm_connect_failed }}",
lastfmDisconnectFailed: "{{ t.player_lastfm_disconnect_failed }}",
@@ -257,6 +261,16 @@ document.addEventListener('alpine:init', () => {
return T.lastfmConnect;
},
lastfmStatusLabel() {
if (!this.lastfm?.configured) return T.lastfmStatusNotConfigured;
if (this.lastfm?.connected && this.lastfm?.reauth_required) return T.lastfmStatusReconnect;
if (this.lastfm?.connected) {
const user = this.lastfm.username || T.unknown;
return `${T.lastfmStatusConnected}: ${user}`;
}
return T.lastfmStatusConnect;
},
lastfmClass() {
if (!this.lastfm?.configured) return 'not-configured';
if (this.lastfm?.connected && this.lastfm?.reauth_required) return 'needs-auth';
@@ -749,13 +763,11 @@ document.addEventListener('alpine:init', () => {
const queue = Alpine.store('queue');
const track = this.currentTrack || queue?.tracks?.[queue.currentIndex] || null;
if (!track && (!queue || queue.tracks.length === 0)) return null;
const payload = this._remotePlaybackPayload(track, {
return this._remotePlaybackPayload(track, {
position_seconds: audio.currentTime || this.currentTime || 0,
duration_seconds: this._trackDuration(),
paused: !this.isPlaying,
});
payload.tracks = [];
return payload;
},
_mirrorRemoteTrack(track, playing, positionSeconds = null) {
@@ -816,7 +828,7 @@ document.addEventListener('alpine:init', () => {
if (payload.repeat_mode) this.repeatMode = payload.repeat_mode;
if (typeof payload.volume === 'number') this._setVolumeLocal(payload.volume);
if (command.command === 'play_track' || command.command === 'play_from_index') {
if (command.command === 'play_track' || command.command === 'play_from_index' || command.command === 'transfer_state') {
if (Array.isArray(payload.tracks) && payload.tracks.length > 0) {
queue.tracks = payload.tracks;
queue.currentIndex = Math.max(0, Math.min(Number(payload.index || 0), queue.tracks.length - 1));
@@ -1051,7 +1063,7 @@ document.addEventListener('alpine:init', () => {
init() {
this.id = this._ensureId();
this.heartbeat();
this._pollTimer = setInterval(() => this.poll(), 750);
this._pollTimer = setInterval(() => this.poll(), 500);
document.addEventListener('visibilitychange', () => {
if (!document.hidden) this.poll();
});
@@ -1142,12 +1154,6 @@ document.addEventListener('alpine:init', () => {
async select(deviceId) {
if (!deviceId) return;
const player = Alpine.store('player');
const transferPayload = player?.currentTrack
? player._remotePlaybackPayload(player.currentTrack, {
position_seconds: player.currentTime,
paused: !player.isPlaying,
})
: null;
try {
const res = await fetch('/api/player/devices/active', {
@@ -1159,12 +1165,17 @@ document.addEventListener('alpine:init', () => {
}),
});
if (!res.ok) return;
this._apply(await res.json());
const data = await res.json();
this._apply(data);
this.open = false;
if (deviceId !== this.id && transferPayload) {
const sent = await this.sendCommand('play_from_index', transferPayload, deviceId);
if (sent && player?.isPlaying) player._pauseLocal();
if (deviceId === this.id && data.playback_state && player) {
player._executeRemoteCommand({
command: 'transfer_state',
payload: data.playback_state,
});
} else if (deviceId !== this.id && player && this.id !== this.activeDeviceId) {
player._applyRemotePlaybackState(data.playback_state);
}
} catch {}
},
+18 -6
View File
@@ -41,9 +41,15 @@
<button class="lastfm-profile-action"
:class="$store.user.lastfmClass()"
:disabled="$store.user.lastfmBusy || !$store.user.lastfm?.configured"
@click="$store.user.handleLastfm()">
@click="$store.user.handleLastfm()"
:title="$store.user.lastfmLabel()"
:aria-label="$store.user.lastfmLabel()">
<span class="lastfm-dot"></span>
<span class="lastfm-profile-text" x-text="$store.user.lastfmLabel()"></span>
<span class="lastfm-profile-text">
<span class="lastfm-profile-brand">{{ t.player_lastfm_profile }}</span>
<span class="lastfm-profile-separator">·</span>
<span class="lastfm-profile-status" x-text="$store.user.lastfmStatusLabel()"></span>
</span>
</button>
</div>
<div class="sidebar-header">
@@ -348,9 +354,15 @@
<button class="lastfm-profile-action"
:class="$store.user.lastfmClass()"
:disabled="$store.user.lastfmBusy || !$store.user.lastfm?.configured"
@click="$store.user.handleLastfm()">
@click="$store.user.handleLastfm()"
:title="$store.user.lastfmLabel()"
:aria-label="$store.user.lastfmLabel()">
<span class="lastfm-dot"></span>
<span class="lastfm-profile-text" x-text="$store.user.lastfmLabel()"></span>
<span class="lastfm-profile-text">
<span class="lastfm-profile-brand">{{ t.player_lastfm_profile }}</span>
<span class="lastfm-profile-separator">·</span>
<span class="lastfm-profile-status" x-text="$store.user.lastfmStatusLabel()"></span>
</span>
</button>
<button class="modal-btn modal-btn-primary mobile-account-logout"
@click="$store.user.logout()">
@@ -1048,7 +1060,7 @@
</button>
<div class="device-picker" @click.outside="$store.devices.open = false">
<button class="queue-toggle-btn device-toggle-btn"
:class="{ active: !$store.devices.isActive() || $store.devices.open }"
:class="{ active: $store.devices.isActive() }"
@click="$store.devices.toggle()"
:title="$store.devices.activeLabel()"
aria-label="Devices">
@@ -1080,7 +1092,7 @@
</span>
<span class="device-row-main">
<span class="device-row-name" x-text="device.name"></span>
<span class="device-row-current" x-show="device.is_current"></span>
<span class="device-row-current" x-show="device.is_current">This device</span>
</span>
<span class="device-row-check" x-show="device.is_active">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4">
+28 -8
View File
@@ -219,12 +219,32 @@ button.user-stat:hover {
}
.lastfm-profile-text {
min-width: 0;
display: flex;
align-items: center;
gap: 4px;
overflow: hidden;
font-size: 11px;
font-weight: 650;
white-space: nowrap;
}
.lastfm-profile-brand {
flex: 0 0 auto;
color: var(--text-primary);
font-weight: 800;
}
.lastfm-profile-separator {
flex: 0 0 auto;
color: var(--text-subdued);
}
.lastfm-profile-status {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 11px;
font-weight: 650;
}
.sidebar-header {
@@ -1432,12 +1452,12 @@ button.user-stat:hover {
.device-row-current {
display: block;
width: 18px;
height: 3px;
margin-top: 5px;
border-radius: 999px;
background: currentColor;
opacity: 0.55;
margin-top: 2px;
color: var(--text-subdued);
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Loading */