feat: Playtube Stream Dock Plugin 1.0.0 (Ajazz AKP153E) mit Icon-Pack
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.log
|
||||||
|
log/
|
||||||
|
# Das Icon-Pack wird mit "npm run iconpack" erzeugt; das fertige .sdIconPack liegt im Repo,
|
||||||
|
# die losen Ordner (gradient/, dark/) und das Zip nicht.
|
||||||
|
iconpack/Playtube-Icons/
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Playtube – Stream Dock Plugin
|
||||||
|
|
||||||
|
Plugin für **Stream Dock** (Ajazz AKP153E und andere Mirabox/HotSpot-Geräte), mit dem du
|
||||||
|
[Playtube](https://git.fojadrachi.de/admin/Playtube) steuern kannst: Wiedergabe, Lautstärke
|
||||||
|
(lauter/leiser/stumm), Playlist-Auswahl, Cover und Titel auf den Tasten.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- Stream Dock Software ab 3.10.188 (Windows), Gerät z. B. Ajazz AKP153E.
|
||||||
|
- **Playtube ab 4.5.1** (oder Playtube Edge), denn erst ab dieser Version gibt es die lokale
|
||||||
|
Fernsteuerung. Das Plugin verbindet sich über eine Named Pipe
|
||||||
|
(`\\.\pipe\Playtube.Remote.<Name>`, nur dein Windows-Benutzer) – kein Netzwerk, kein Konto.
|
||||||
|
- Ohne laufendes Playtube zeigen die Tasten „Playtube offline".
|
||||||
|
|
||||||
|
## Installieren
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File install.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach Stream Dock komplett beenden (Tray-Symbol → Beenden) und neu starten. Die Aktionen
|
||||||
|
findest du in der Kategorie **Playtube**. Das Skript installiert auch das Icon-Pack.
|
||||||
|
|
||||||
|
## Aktionen
|
||||||
|
|
||||||
|
| Aktion | Was sie tut |
|
||||||
|
|---|---|
|
||||||
|
| Wiedergabe/Pause | Umschalten, Symbol zeigt den Zustand |
|
||||||
|
| Vorheriger / Nächster Titel | Titel springen |
|
||||||
|
| Zurück-/Vorspulen | ± Sekunden (einstellbar) |
|
||||||
|
| Lauter / Leiser | Schrittweite einstellbar, gedrückt halten wiederholt |
|
||||||
|
| Lautstärke setzen | Feste Lautstärke |
|
||||||
|
| Stumm | Ton aus/ein, zeigt die Lautstärke |
|
||||||
|
| Aktueller Titel | Cover und Titel; auf dem Info-Display eine große Karte mit Interpret und Fortschritt |
|
||||||
|
| Gefällt mir, Zufall, Wiederholen | Umschalten, Symbol zeigt den Zustand |
|
||||||
|
| Playlist abspielen | Playlist aus YouTube Music wählen (Liste wird von Playtube geladen) |
|
||||||
|
| Tab wechseln / Playtube anzeigen | YouTube ↔ YouTube Music, Fenster nach vorn |
|
||||||
|
|
||||||
|
Pro Taste einstellbar: Ziel-Tab (automatisch = aktiver Tab, YouTube oder YouTube Music), je
|
||||||
|
nach Aktion Schrittweite, Sekunden, Lautstärke, Playlist, Titelanzeige. Alle Aktionen sind auch
|
||||||
|
auf den Info-Feldern rechts am Gerät nutzbar.
|
||||||
|
|
||||||
|
## Icon-Pack
|
||||||
|
|
||||||
|
`iconpack/com.fojadrachi.playtube.sdIconPack` enthält 75 Symbole im Playtube-Look (Verlauf
|
||||||
|
violett → pink): Ordner, Pfeile, Einstellungen, Player-Symbole und vereinfachte App-Symbole
|
||||||
|
(Chrome, Discord, Steam, …; eigene Zeichnungen, keine Original-Logos). Zwei Varianten:
|
||||||
|
`gradient_*` und `dark_*`. In der Stream-Dock-Software über das Bild einer Taste auswählbar.
|
||||||
|
|
||||||
|
## Entwicklung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm test # 40 Tests (Node >= 20)
|
||||||
|
npm run build # bündelt src/ nach com.fojadrachi.playtube.sdPlugin/plugin/index.js
|
||||||
|
npm run assets # erzeugt manifest.json, Übersetzungen und Tasten-Icons
|
||||||
|
npm run iconpack # erzeugt das Icon-Pack (+ Zip in dist/)
|
||||||
|
npm run package # Zip des Plugins in dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
Aufbau: `src/playtubeClient.js` (Pipe-Protokoll), `src/actions.js` (Aktionen, reine
|
||||||
|
Funktionen), `src/controller.js` (Tasten ↔ Playtube), `src/infoPanel.js` (Karte fürs
|
||||||
|
Info-Display), `src/host.js` (Stream-Dock-WebSocket), `tools/` (Icon-Generator).
|
||||||
|
Das gebündelte `plugin/index.js` liegt im Repo, damit `install.ps1` ohne Build funktioniert.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Buendelt das Plugin (inkl. "ws") zu com.fojadrachi.playtube.sdPlugin/plugin/index.js.
|
||||||
|
// Mit --zip zusaetzlich dist/Playtube-StreamDock-<version>.zip erzeugen.
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import { mkdirSync, readFileSync, rmSync } from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { build } from 'esbuild';
|
||||||
|
|
||||||
|
const ROOT = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const PLUGIN_NAME = 'com.fojadrachi.playtube.sdPlugin';
|
||||||
|
const OUTFILE = path.join(ROOT, PLUGIN_NAME, 'plugin', 'index.js');
|
||||||
|
const { version } = JSON.parse(readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
||||||
|
|
||||||
|
await build({
|
||||||
|
entryPoints: [path.join(ROOT, 'src', 'main.js')],
|
||||||
|
outfile: OUTFILE,
|
||||||
|
bundle: true,
|
||||||
|
platform: 'node',
|
||||||
|
target: 'node20',
|
||||||
|
format: 'cjs',
|
||||||
|
// Optionale native Beschleuniger von "ws" - nicht noetig, ws faellt auf reines JS zurueck.
|
||||||
|
external: ['bufferutil', 'utf-8-validate'],
|
||||||
|
legalComments: 'none',
|
||||||
|
logLevel: 'info',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.argv.includes('--zip')) {
|
||||||
|
const dist = path.join(ROOT, 'dist');
|
||||||
|
const zip = path.join(dist, `Playtube-StreamDock-${version}.zip`);
|
||||||
|
mkdirSync(dist, { recursive: true });
|
||||||
|
rmSync(zip, { force: true });
|
||||||
|
// tar.exe (bsdtar, in Windows 10+ enthalten) kann Zip-Archive schreiben.
|
||||||
|
const tar = process.platform === 'win32' ? path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe') : 'tar';
|
||||||
|
execFileSync(tar, ['-a', '-c', '-f', zip, '-C', ROOT, PLUGIN_NAME], { stdio: 'inherit' });
|
||||||
|
process.stdout.write(`Paket: ${zip}\n`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"Name": "Playtube",
|
||||||
|
"Description": "Playtube (YouTube / YouTube Musik) steuern",
|
||||||
|
"Category": "Playtube",
|
||||||
|
"com.fojadrachi.playtube.playpause": {
|
||||||
|
"Name": "Wiedergabe/Pause",
|
||||||
|
"Tooltip": "Wiedergabe starten oder pausieren"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.previous": {
|
||||||
|
"Name": "Vorheriger Titel",
|
||||||
|
"Tooltip": "Zum vorherigen Titel springen"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.next": {
|
||||||
|
"Name": "Nächster Titel",
|
||||||
|
"Tooltip": "Zum nächsten Titel springen"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.seekback": {
|
||||||
|
"Name": "Zurückspulen",
|
||||||
|
"Tooltip": "Ein paar Sekunden zurückspringen"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.seekforward": {
|
||||||
|
"Name": "Vorspulen",
|
||||||
|
"Tooltip": "Ein paar Sekunden vorspringen"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.volumeup": {
|
||||||
|
"Name": "Lauter",
|
||||||
|
"Tooltip": "Lauter (gedrückt halten für Dauer-Anpassung)"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.volumedown": {
|
||||||
|
"Name": "Leiser",
|
||||||
|
"Tooltip": "Leiser (gedrückt halten für Dauer-Anpassung)"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.volumeset": {
|
||||||
|
"Name": "Lautstärke setzen",
|
||||||
|
"Tooltip": "Auf eine feste Lautstärke springen"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.mute": {
|
||||||
|
"Name": "Stummschalten",
|
||||||
|
"Tooltip": "Ton aus- oder einschalten"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.nowplaying": {
|
||||||
|
"Name": "Aktueller Titel",
|
||||||
|
"Tooltip": "Cover und Titel, Drücken = Wiedergabe/Pause"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.like": {
|
||||||
|
"Name": "Gefällt mir",
|
||||||
|
"Tooltip": "Aktuellen Titel mit „Gefällt mir“ markieren"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.shuffle": {
|
||||||
|
"Name": "Zufallswiedergabe",
|
||||||
|
"Tooltip": "Zufallswiedergabe umschalten"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.repeat": {
|
||||||
|
"Name": "Wiederholen",
|
||||||
|
"Tooltip": "Wiederholungsmodus wechseln"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.playlist": {
|
||||||
|
"Name": "Playlist abspielen",
|
||||||
|
"Tooltip": "Eine Playlist aus YouTube Music starten"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.switchtab": {
|
||||||
|
"Name": "Tab wechseln",
|
||||||
|
"Tooltip": "Zwischen YouTube und YouTube Musik wechseln"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.show": {
|
||||||
|
"Name": "Playtube anzeigen",
|
||||||
|
"Tooltip": "Das Playtube-Fenster in den Vordergrund holen"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"Name": "Playtube",
|
||||||
|
"Description": "Control Playtube (YouTube / YouTube Music)",
|
||||||
|
"Category": "Playtube",
|
||||||
|
"com.fojadrachi.playtube.playpause": {
|
||||||
|
"Name": "Play/Pause",
|
||||||
|
"Tooltip": "Toggle playback"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.previous": {
|
||||||
|
"Name": "Previous track",
|
||||||
|
"Tooltip": "Go to the previous track"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.next": {
|
||||||
|
"Name": "Next track",
|
||||||
|
"Tooltip": "Skip to the next track"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.seekback": {
|
||||||
|
"Name": "Rewind",
|
||||||
|
"Tooltip": "Jump back a few seconds"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.seekforward": {
|
||||||
|
"Name": "Fast forward",
|
||||||
|
"Tooltip": "Jump forward a few seconds"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.volumeup": {
|
||||||
|
"Name": "Volume up",
|
||||||
|
"Tooltip": "Louder (hold to keep going)"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.volumedown": {
|
||||||
|
"Name": "Volume down",
|
||||||
|
"Tooltip": "Quieter (hold to keep going)"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.volumeset": {
|
||||||
|
"Name": "Set volume",
|
||||||
|
"Tooltip": "Jump to a fixed volume"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.mute": {
|
||||||
|
"Name": "Mute",
|
||||||
|
"Tooltip": "Mute or unmute"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.nowplaying": {
|
||||||
|
"Name": "Now playing",
|
||||||
|
"Tooltip": "Cover and title, press to play/pause"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.like": {
|
||||||
|
"Name": "Like",
|
||||||
|
"Tooltip": "Like the current track"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.shuffle": {
|
||||||
|
"Name": "Shuffle",
|
||||||
|
"Tooltip": "Toggle shuffle"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.repeat": {
|
||||||
|
"Name": "Repeat",
|
||||||
|
"Tooltip": "Cycle repeat mode"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.playlist": {
|
||||||
|
"Name": "Play playlist",
|
||||||
|
"Tooltip": "Start a playlist from YouTube Music"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.switchtab": {
|
||||||
|
"Name": "Switch tab",
|
||||||
|
"Tooltip": "Switch between YouTube and YouTube Music"
|
||||||
|
},
|
||||||
|
"com.fojadrachi.playtube.show": {
|
||||||
|
"Name": "Show Playtube",
|
||||||
|
"Tooltip": "Bring the Playtube window to the front"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,444 @@
|
|||||||
|
{
|
||||||
|
"Actions": [
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Play/Pause",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/playpause_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Image": "static/btn/playpause_1.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Toggle playback",
|
||||||
|
"UUID": "com.fojadrachi.playtube.playpause",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Previous track",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/previous_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Go to the previous track",
|
||||||
|
"UUID": "com.fojadrachi.playtube.previous",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Next track",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/next_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Skip to the next track",
|
||||||
|
"UUID": "com.fojadrachi.playtube.next",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Rewind",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/seekback_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Jump back a few seconds",
|
||||||
|
"UUID": "com.fojadrachi.playtube.seekback",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Fast forward",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/seekforward_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Jump forward a few seconds",
|
||||||
|
"UUID": "com.fojadrachi.playtube.seekforward",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Volume up",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/volumeup_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Louder (hold to keep going)",
|
||||||
|
"UUID": "com.fojadrachi.playtube.volumeup",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Volume down",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/volumedown_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Quieter (hold to keep going)",
|
||||||
|
"UUID": "com.fojadrachi.playtube.volumedown",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Set volume",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/volumeset_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Jump to a fixed volume",
|
||||||
|
"UUID": "com.fojadrachi.playtube.volumeset",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Mute",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/mute_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Image": "static/btn/mute_1.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Mute or unmute",
|
||||||
|
"UUID": "com.fojadrachi.playtube.mute",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Now playing",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/nowplaying_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Cover and title, press to play/pause",
|
||||||
|
"UUID": "com.fojadrachi.playtube.nowplaying",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Like",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/like_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Image": "static/btn/like_1.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Like the current track",
|
||||||
|
"UUID": "com.fojadrachi.playtube.like",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Shuffle",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/shuffle_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Toggle shuffle",
|
||||||
|
"UUID": "com.fojadrachi.playtube.shuffle",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Repeat",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/repeat_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Image": "static/btn/repeat_1.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Image": "static/btn/repeat_2.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Cycle repeat mode",
|
||||||
|
"UUID": "com.fojadrachi.playtube.repeat",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Play playlist",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/playlist_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Start a playlist from YouTube Music",
|
||||||
|
"UUID": "com.fojadrachi.playtube.playlist",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Switch tab",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/switchtab_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Switch between YouTube and YouTube Music",
|
||||||
|
"UUID": "com.fojadrachi.playtube.switchtab",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"Name": "Show Playtube",
|
||||||
|
"DisableAutomaticStates": true,
|
||||||
|
"States": [
|
||||||
|
{
|
||||||
|
"Image": "static/btn/show_0.png",
|
||||||
|
"TitleAlignment": "bottom",
|
||||||
|
"FontSize": "11",
|
||||||
|
"FontStyle": "Bold",
|
||||||
|
"TitleColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Settings": {},
|
||||||
|
"Controllers": [
|
||||||
|
"Keypad",
|
||||||
|
"Information"
|
||||||
|
],
|
||||||
|
"UserTitleEnabled": false,
|
||||||
|
"SupportedInMultiActions": true,
|
||||||
|
"Tooltip": "Bring the Playtube window to the front",
|
||||||
|
"UUID": "com.fojadrachi.playtube.show",
|
||||||
|
"PropertyInspectorPath": "propertyInspector/index.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SDKVersion": 1,
|
||||||
|
"Author": "Playtube",
|
||||||
|
"Name": "Playtube",
|
||||||
|
"Icon": "static/icon/[email protected]",
|
||||||
|
"CodePathWin": "plugin/index.js",
|
||||||
|
"Description": "Control Playtube (YouTube / YouTube Music) from your Stream Dock",
|
||||||
|
"Category": "Playtube",
|
||||||
|
"CategoryIcon": "static/icon/[email protected]",
|
||||||
|
"Version": "1.0.0",
|
||||||
|
"OS": [
|
||||||
|
{
|
||||||
|
"Platform": "windows",
|
||||||
|
"MinimumVersion": "10"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Software": {
|
||||||
|
"MinimumVersion": "3.10.188.226"
|
||||||
|
},
|
||||||
|
"Nodejs": {
|
||||||
|
"Version": "20"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Playtube</title>
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; background: #2a2a2a; color: #e6e6e6;
|
||||||
|
font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; }
|
||||||
|
body { padding: 12px 14px; }
|
||||||
|
.row { margin-bottom: 12px; }
|
||||||
|
.row[hidden] { display: none; }
|
||||||
|
label { display: block; margin-bottom: 4px; color: #b5b5b5; }
|
||||||
|
select, input[type=number], input[type=text] { width: 100%; box-sizing: border-box; padding: 6px 8px;
|
||||||
|
background: #3a3a3a; color: #fff; border: 1px solid #4a4a4a; border-radius: 4px; }
|
||||||
|
input[type=checkbox] { vertical-align: middle; margin-right: 6px; }
|
||||||
|
.check label { display: inline; color: #e6e6e6; }
|
||||||
|
button { padding: 6px 10px; background: #3a3a3a; color: #fff; border: 1px solid #4a4a4a; border-radius: 4px; cursor: pointer; }
|
||||||
|
button:hover { background: #4a4a4a; }
|
||||||
|
.hint { margin-top: 4px; color: #9a9a9a; font-size: 12px; }
|
||||||
|
.hint.error { color: #ff8a8a; }
|
||||||
|
.inline { display: flex; gap: 8px; }
|
||||||
|
.inline select { flex: 1; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="row" data-for="target">
|
||||||
|
<label id="l-target" for="target"></label>
|
||||||
|
<select id="target">
|
||||||
|
<option value="auto" id="o-auto"></option>
|
||||||
|
<option value="youtube">YouTube</option>
|
||||||
|
<option value="music">YouTube Music</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" data-for="step" hidden>
|
||||||
|
<label id="l-step" for="step"></label>
|
||||||
|
<input type="number" id="step" min="1" max="25" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" data-for="volume" hidden>
|
||||||
|
<label id="l-volume" for="volume"></label>
|
||||||
|
<input type="number" id="volume" min="0" max="100" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" data-for="seconds" hidden>
|
||||||
|
<label id="l-seconds" for="seconds"></label>
|
||||||
|
<input type="number" id="seconds" min="1" max="300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" data-for="tab" hidden>
|
||||||
|
<label id="l-tab" for="tab"></label>
|
||||||
|
<select id="tab">
|
||||||
|
<option value="toggle" id="o-toggle"></option>
|
||||||
|
<option value="youtube">YouTube</option>
|
||||||
|
<option value="music">YouTube Music</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" data-for="playlist" hidden>
|
||||||
|
<label id="l-playlist" for="playlistSelect"></label>
|
||||||
|
<div class="inline">
|
||||||
|
<select id="playlistSelect"></select>
|
||||||
|
<button id="reload"></button>
|
||||||
|
</div>
|
||||||
|
<div class="hint" id="playlistHint"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" data-for="playlist" hidden>
|
||||||
|
<label id="l-playlistId" for="playlistId"></label>
|
||||||
|
<input type="text" id="playlistId" spellcheck="false" placeholder="PL..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row check" data-for="showTitle" hidden>
|
||||||
|
<input type="checkbox" id="showTitle" />
|
||||||
|
<label id="l-showTitle" for="showTitle"></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var PREFIX = 'com.fojadrachi.playtube.';
|
||||||
|
var TEXT = {
|
||||||
|
en: {
|
||||||
|
target: 'Target tab', auto: 'Automatic (active tab)', step: 'Step (%)', volume: 'Volume (%)',
|
||||||
|
seconds: 'Seconds', tab: 'Switch to', toggle: 'Toggle YouTube / Music', playlist: 'Playlist',
|
||||||
|
playlistId: 'Playlist ID (manual)', showTitle: 'Show title on the key', reload: 'Reload',
|
||||||
|
choose: '- choose a playlist -', loading: 'Loading playlists ...',
|
||||||
|
offline: 'Playtube is not running or too old (needs the newer version with Stream Dock support).',
|
||||||
|
failed: 'Playlists could not be read. Is YouTube Music loaded in Playtube?', count: 'playlists found'
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
target: 'Ziel-Tab', auto: 'Automatisch (aktiver Tab)', step: 'Schrittweite (%)', volume: 'Lautstärke (%)',
|
||||||
|
seconds: 'Sekunden', tab: 'Wechseln zu', toggle: 'YouTube / Musik umschalten', playlist: 'Playlist',
|
||||||
|
playlistId: 'Playlist-ID (manuell)', showTitle: 'Titel auf der Taste anzeigen', reload: 'Neu laden',
|
||||||
|
choose: '- Playlist wählen -', loading: 'Playlists werden geladen ...',
|
||||||
|
offline: 'Playtube läuft nicht oder ist zu alt (benötigt die neuere Version mit Stream-Dock-Unterstützung).',
|
||||||
|
failed: 'Playlists konnten nicht gelesen werden. Ist YouTube Musik in Playtube geladen?', count: 'Playlists gefunden'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Welche Felder eine Aktion zeigt; alle anderen Aktionen zeigen nur den Ziel-Tab.
|
||||||
|
var FIELDS = {
|
||||||
|
volumeup: ['target', 'step'], volumedown: ['target', 'step'], volumeset: ['target', 'volume'],
|
||||||
|
seekback: ['target', 'seconds'], seekforward: ['target', 'seconds'],
|
||||||
|
playpause: ['target', 'showTitle'], nowplaying: ['target', 'showTitle'],
|
||||||
|
playlist: ['playlist'], switchtab: ['tab'], show: []
|
||||||
|
};
|
||||||
|
var DEFAULT_FIELDS = ['target'];
|
||||||
|
|
||||||
|
var websocket = null, uuid = null, actionUuid = null, settings = {}, t = TEXT.en;
|
||||||
|
var saveTimer = null;
|
||||||
|
|
||||||
|
function $(id) { return document.getElementById(id); }
|
||||||
|
function send(message) { if (websocket && websocket.readyState === 1) websocket.send(JSON.stringify(message)); }
|
||||||
|
function actionId() { return actionUuid.indexOf(PREFIX) === 0 ? actionUuid.slice(PREFIX.length) : ''; }
|
||||||
|
|
||||||
|
function connectElgatoStreamDeckSocket(port, inUuid, registerEvent, info, actionInfo) {
|
||||||
|
uuid = inUuid;
|
||||||
|
var lang = 'en';
|
||||||
|
try { lang = (JSON.parse(info).application.language || 'en').slice(0, 2); } catch (e) { lang = 'en'; }
|
||||||
|
t = TEXT[lang] || TEXT.en;
|
||||||
|
var action = {};
|
||||||
|
try { action = JSON.parse(actionInfo); } catch (e) { action = {}; }
|
||||||
|
actionUuid = action.action || '';
|
||||||
|
settings = (action.payload && action.payload.settings) || {};
|
||||||
|
|
||||||
|
applyTexts();
|
||||||
|
showFields();
|
||||||
|
loadValues();
|
||||||
|
|
||||||
|
websocket = new WebSocket('ws://127.0.0.1:' + port);
|
||||||
|
websocket.onopen = function () {
|
||||||
|
send({ event: registerEvent, uuid: inUuid });
|
||||||
|
if (actionId() === 'playlist') requestPlaylists();
|
||||||
|
};
|
||||||
|
websocket.onmessage = function (event) {
|
||||||
|
var message = JSON.parse(event.data);
|
||||||
|
if (message.event === 'sendToPropertyInspector' && message.payload && message.payload.command === 'playlists') {
|
||||||
|
showPlaylists(message.payload);
|
||||||
|
} else if (message.event === 'didReceiveSettings') {
|
||||||
|
settings = (message.payload && message.payload.settings) || settings;
|
||||||
|
loadValues();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
window.connectElgatoStreamDeckSocket = connectElgatoStreamDeckSocket;
|
||||||
|
|
||||||
|
function applyTexts() {
|
||||||
|
['target', 'step', 'volume', 'seconds', 'tab', 'playlist', 'playlistId', 'showTitle'].forEach(function (key) {
|
||||||
|
$('l-' + key).textContent = t[key];
|
||||||
|
});
|
||||||
|
$('o-auto').textContent = t.auto;
|
||||||
|
$('o-toggle').textContent = t.toggle;
|
||||||
|
$('reload').textContent = t.reload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFields() {
|
||||||
|
var visible = FIELDS[actionId()] || DEFAULT_FIELDS;
|
||||||
|
Array.prototype.forEach.call(document.querySelectorAll('[data-for]'), function (row) {
|
||||||
|
row.hidden = visible.indexOf(row.getAttribute('data-for')) < 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadValues() {
|
||||||
|
$('target').value = settings.target || 'auto';
|
||||||
|
$('step').value = settings.step || 5;
|
||||||
|
$('volume').value = settings.volume === undefined ? 30 : settings.volume;
|
||||||
|
$('seconds').value = settings.seconds || 10;
|
||||||
|
$('tab').value = settings.tab || 'toggle';
|
||||||
|
$('playlistId').value = settings.playlistId || '';
|
||||||
|
var defaultShow = actionId() === 'nowplaying';
|
||||||
|
$('showTitle').checked = settings.showTitle === undefined
|
||||||
|
? defaultShow : settings.showTitle === true || settings.showTitle === 'true';
|
||||||
|
selectPlaylist(settings.playlistId || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(extra) {
|
||||||
|
settings = Object.assign({}, settings, extra || {}, {
|
||||||
|
target: $('target').value,
|
||||||
|
step: Number($('step').value) || 5,
|
||||||
|
volume: Number($('volume').value),
|
||||||
|
seconds: Number($('seconds').value) || 10,
|
||||||
|
tab: $('tab').value,
|
||||||
|
playlistId: $('playlistId').value.trim(),
|
||||||
|
showTitle: $('showTitle').checked
|
||||||
|
});
|
||||||
|
clearTimeout(saveTimer);
|
||||||
|
saveTimer = setTimeout(function () { send({ event: 'setSettings', context: uuid, payload: settings }); }, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Playlists
|
||||||
|
|
||||||
|
function requestPlaylists() {
|
||||||
|
$('playlistHint').className = 'hint';
|
||||||
|
$('playlistHint').textContent = t.loading;
|
||||||
|
send({ event: 'sendToPlugin', action: actionUuid, context: uuid, payload: { command: 'requestPlaylists' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPlaylists(payload) {
|
||||||
|
var select = $('playlistSelect');
|
||||||
|
var hint = $('playlistHint');
|
||||||
|
while (select.firstChild) select.removeChild(select.firstChild);
|
||||||
|
var placeholder = document.createElement('option');
|
||||||
|
placeholder.value = '';
|
||||||
|
placeholder.textContent = t.choose;
|
||||||
|
select.appendChild(placeholder);
|
||||||
|
if (!payload.ok) {
|
||||||
|
hint.className = 'hint error';
|
||||||
|
hint.textContent = payload.error === 'offline' || payload.error === 'remote' ? t.offline : t.failed;
|
||||||
|
selectPlaylist(settings.playlistId || '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
payload.playlists.forEach(function (playlist) {
|
||||||
|
var option = document.createElement('option');
|
||||||
|
option.value = playlist.id;
|
||||||
|
option.textContent = playlist.title;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
hint.className = 'hint';
|
||||||
|
hint.textContent = payload.playlists.length + ' ' + t.count;
|
||||||
|
selectPlaylist(settings.playlistId || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPlaylist(id) {
|
||||||
|
var select = $('playlistSelect');
|
||||||
|
var known = Array.prototype.some.call(select.options, function (option) { return option.value === id; });
|
||||||
|
select.value = known ? id : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('playlistSelect').addEventListener('change', function () {
|
||||||
|
var select = $('playlistSelect');
|
||||||
|
if (!select.value) return;
|
||||||
|
$('playlistId').value = select.value;
|
||||||
|
save({ playlistTitle: select.options[select.selectedIndex].textContent });
|
||||||
|
});
|
||||||
|
$('playlistId').addEventListener('input', function () {
|
||||||
|
// Manuell eingegebene ID: den Titel nur behalten, wenn die ID in der Liste vorkommt.
|
||||||
|
selectPlaylist($('playlistId').value.trim());
|
||||||
|
var select = $('playlistSelect');
|
||||||
|
save({ playlistTitle: select.value ? select.options[select.selectedIndex].textContent : '' });
|
||||||
|
});
|
||||||
|
$('reload').addEventListener('click', requestPlaylists);
|
||||||
|
['target', 'step', 'volume', 'seconds', 'tab', 'showTitle'].forEach(function (id) {
|
||||||
|
$(id).addEventListener('input', function () { save(); });
|
||||||
|
$(id).addEventListener('change', function () { save(); });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 912 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 3.3 KiB |