feat: Playtube Stream Dock Plugin 1.0.0 (Ajazz AKP153E) mit Icon-Pack

This commit is contained in:
2026-09-26 21:00:36 +02:00
commit 179a785adb
221 changed files with 10254 additions and 0 deletions
+129
View File
@@ -0,0 +1,129 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const net = require('node:net');
const { PlaytubeClient, PlaytubeError, pipePath } = require('../src/playtubeClient');
const uniqueName = () => `Test${process.pid}${Date.now()}${Math.floor(Math.random() * 1e6)}`;
const until = async (condition, timeoutMs = 3000) => {
const start = Date.now();
while (!condition()) {
if (Date.now() - start > timeoutMs) throw new Error('Zeitueberschreitung im Test');
await new Promise((resolve) => setTimeout(resolve, 10));
}
};
/** Fake-Playtube: beantwortet zeilenweise JSON-Befehle wie playtube/remote_control.py. */
function startServer(name, handler) {
const sockets = new Set();
const server = net.createServer((socket) => {
sockets.add(socket);
socket.setEncoding('utf8');
let buffer = '';
socket.on('data', (chunk) => {
buffer += chunk;
let index;
while ((index = buffer.indexOf('\n')) >= 0) {
const line = buffer.slice(0, index);
buffer = buffer.slice(index + 1);
const request = JSON.parse(line);
const reply = handler(request);
if (reply) socket.write(`${JSON.stringify({ id: request.id, ...reply })}\n`);
}
});
socket.on('close', () => sockets.delete(socket));
socket.on('error', () => {});
});
return new Promise((resolve) => {
server.listen(pipePath(name), () => resolve({
dropClients: () => sockets.forEach((socket) => socket.destroy()),
close: () => new Promise((done) => { sockets.forEach((s) => s.destroy()); server.close(done); }),
}));
});
}
test('pipePath bildet den Windows-Pipe-Namen', () => {
assert.equal(pipePath('Playtube', 'win32'), '\\\\.\\pipe\\Playtube.Remote.Playtube');
});
test('verbindet sich, sendet Befehle und liefert Antworten', async () => {
const name = uniqueName();
const fake = await startServer(name, (request) => {
if (request.cmd === 'status') return { ok: true, state: { activeTab: 'music' } };
if (request.cmd === 'list_playlists') return { ok: true, playlists: [{ id: 'LM', title: 'Liked' }] };
if (request.cmd === 'boom') return { ok: false, error: 'kaputt' };
return { ok: true, echo: request };
});
const client = new PlaytubeClient({ pipeNames: [name] });
client.start();
try {
await until(() => client.connected);
assert.deepEqual(await client.status(), { activeTab: 'music' });
assert.deepEqual(await client.listPlaylists(), [{ id: 'LM', title: 'Liked' }]);
const echoed = await client.request('volume_change', { target: 'auto', value: 5, skip: undefined });
assert.deepEqual(echoed.echo, { id: echoed.id, cmd: 'volume_change', target: 'auto', value: 5 });
await assert.rejects(client.request('boom'),
(error) => error instanceof PlaytubeError && error.code === 'remote' && /kaputt/.test(error.message));
} finally {
client.stop();
await fake.close();
}
});
test('ohne Verbindung wird sofort abgelehnt', async () => {
const client = new PlaytubeClient({ pipeNames: [uniqueName()] });
await assert.rejects(client.request('status'), (error) => error.code === 'not_connected');
});
test('Timeout, wenn Playtube nicht antwortet', async () => {
const name = uniqueName();
const fake = await startServer(name, () => null);
const client = new PlaytubeClient({ pipeNames: [name], requestTimeoutMs: 80 });
client.start();
try {
await until(() => client.connected);
await assert.rejects(client.request('status'), (error) => error.code === 'timeout');
} finally {
client.stop();
await fake.close();
}
});
test('Verbindungsabbruch: offene Anfragen scheitern, danach automatische Wiederverbindung', async () => {
const name = uniqueName();
const fake = await startServer(name, () => null);
const client = new PlaytubeClient({ pipeNames: [name], backoffMs: [20] });
const events = [];
client.on('connected', () => events.push('connected'));
client.on('disconnected', () => events.push('disconnected'));
client.start();
try {
await until(() => client.connected);
const pending = client.request('status');
fake.dropClients();
await assert.rejects(pending, (error) => error.code === 'disconnected');
await until(() => events.filter((e) => e === 'connected').length === 2);
assert.deepEqual(events, ['connected', 'disconnected', 'connected']);
} finally {
client.stop();
await fake.close();
}
});
test('probiert mehrere Pipe-Namen der Reihe nach und verbindet Playtube spaeter', async () => {
const missing = uniqueName();
const present = uniqueName();
const client = new PlaytubeClient({ pipeNames: [missing, present], backoffMs: [20] });
client.start();
await new Promise((resolve) => setTimeout(resolve, 60));
assert.equal(client.connected, false);
const fake = await startServer(present, () => ({ ok: true }));
try {
await until(() => client.connected);
assert.equal(client.pipeName, present);
} finally {
client.stop();
await fake.close();
}
});