fix(jellyfin): stop stale playback reports after playback ends

- Wait for in-flight progress reports before sending the stop report
- Ignore messages from superseded Jellyfin sockets
This commit is contained in:
2026-09-21 09:01:24 -07:00
parent 0efdc5db15
commit f7e444ee2c
6 changed files with 152 additions and 9 deletions
+35
View File
@@ -478,3 +478,38 @@ test('warns once per failing timeline endpoint until it recovers', async () => {
assert.equal(await service.reportStopped(state), false);
assert.equal(warnings.length, 2);
});
test('ignores messages from a superseded socket', () => {
const sockets: FakeWebSocket[] = [];
const playPayloads: unknown[] = [];
const service = new JellyfinRemoteSessionService({
serverUrl: 'http://jellyfin.local',
accessToken: 'token-stale',
deviceId: 'device-stale',
webSocketFactory: () => {
const socket = new FakeWebSocket();
sockets.push(socket);
return socket as unknown as any;
},
fetchImpl: (async () => new Response(null, { status: 200 })) as typeof fetch,
onPlay: (payload) => {
playPayloads.push(payload);
},
setTimer: (() => 1 as unknown as ReturnType<typeof setTimeout>) as unknown as typeof setTimeout,
clearTimer: (() => undefined) as typeof clearTimeout,
});
service.start();
service.stop();
service.start();
sockets[1]!.emit('open');
assert.equal(sockets.length, 2);
sockets[0]!.emit('message', JSON.stringify({ MessageType: 'ForceKeepAlive', Data: 10 }));
sockets[0]!.emit('message', JSON.stringify({ MessageType: 'Play', Data: { ItemIds: ['x'] } }));
assert.deepEqual(sockets[0]!.sent, []);
assert.deepEqual(playPayloads, []);
assert.deepEqual(sockets[1]!.sent, ['{"MessageType":"KeepAlive"}']);
});
+1
View File
@@ -334,6 +334,7 @@ export class JellyfinRemoteSessionService {
});
socket.on('message', (rawData) => {
if (this.socket !== socket || !this.running) return;
this.lastInboundAtMs = this.now();
this.handleInboundMessage(socket, rawData);
});