iobroker.androidtvremote 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +14 -13
- package/admin/i18n/de.json +12 -0
- package/admin/i18n/en.json +12 -0
- package/admin/i18n/es.json +12 -0
- package/admin/i18n/fr.json +12 -0
- package/admin/i18n/it.json +12 -0
- package/admin/i18n/nl.json +12 -0
- package/admin/i18n/pl.json +12 -0
- package/admin/i18n/pt.json +12 -0
- package/admin/i18n/ru.json +12 -0
- package/admin/i18n/uk.json +12 -0
- package/admin/i18n/zh-cn.json +12 -0
- package/admin/jsonConfig.json +48 -8
- package/io-package.json +24 -16
- package/main.js +367 -81
- package/package.json +25 -2
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 Henrik Schönhofen
|
|
3
|
+
Copyright (c) 2026 Henrik Schönhofen <henrik.schoenhofen@icloud.com>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -137,23 +137,24 @@ If the initial connection fails or an established connection is lost, the adapte
|
|
|
137
137
|
|
|
138
138
|
## Changelog
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
|
|
140
|
+
<!--
|
|
141
|
+
Placeholder for the next version (at the beginning of the line):
|
|
142
|
+
### **WORK IN PROGRESS**
|
|
143
|
+
-->
|
|
144
|
+
|
|
145
|
+
### 0.2.7 (2026-10-09)
|
|
146
|
+
|
|
147
|
+
- Added automated GitHub Actions release workflow
|
|
148
|
+
- Prepared npm publishing through Trusted Publishing
|
|
149
|
+
- Updated release documentation and changelog
|
|
150
|
+
- No changes to adapter runtime behavior
|
|
151
|
+
|
|
152
|
+
[Older changelog entries](CHANGELOG_OLD.md)
|
|
152
153
|
|
|
153
154
|
## License
|
|
154
155
|
|
|
155
156
|
MIT License
|
|
156
157
|
|
|
157
|
-
Copyright (c) 2026 Henrik Schönhofen
|
|
158
|
+
Copyright (c) 2026 Henrik Schönhofen <henrik.schoenhofen@icloud.com>
|
|
158
159
|
|
|
159
160
|
See [LICENSE](LICENSE) for the full license text.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "IP-Adresse / Hostname",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "IP-Adresse oder Hostname des Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Anzeigename beim Pairing",
|
|
5
|
+
"Pairing-Port": "Pairing-Port",
|
|
6
|
+
"Remote-Port": "Remote-Port",
|
|
7
|
+
"Reconnect nach Sekunden": "Reconnect nach Sekunden",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Protokoll-Debug aktivieren",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Cast-Medieninfos aktivieren",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Cast-Abfrage alle Sekunden"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "IP address / Hostname",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "IP address or hostname of the Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Display name during pairing",
|
|
5
|
+
"Pairing-Port": "Pairing port",
|
|
6
|
+
"Remote-Port": "Remote port",
|
|
7
|
+
"Reconnect nach Sekunden": "Reconnect after seconds",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Enable protocol debugging",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "On first startup, the TV displays a pairing code. Enter it under Objects → androidtvremote.X → control → pairingCode. The certificate is then stored locally.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Enable Cast media information",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Cast polling interval in seconds"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "Dirección IP / Nombre de host",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "Dirección IP o nombre de host del Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Nombre mostrado durante la vinculación",
|
|
5
|
+
"Pairing-Port": "Puerto de vinculación",
|
|
6
|
+
"Remote-Port": "Puerto de control remoto",
|
|
7
|
+
"Reconnect nach Sekunden": "Reconectar después de unos segundos",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Activar depuración del protocolo",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "En el primer inicio, el televisor muestra un código de vinculación. Introdúcelo en Objetos → androidtvremote.X → control → pairingCode. El certificado se guarda localmente.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Activar información multimedia de Cast",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Intervalo de consulta de Cast en segundos"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "Adresse IP / Nom d'hôte",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "Adresse IP ou nom d'hôte de l'Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Nom affiché lors de l'association",
|
|
5
|
+
"Pairing-Port": "Port d'association",
|
|
6
|
+
"Remote-Port": "Port de télécommande",
|
|
7
|
+
"Reconnect nach Sekunden": "Reconnexion après quelques secondes",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Activer le débogage du protocole",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "Au premier démarrage, le téléviseur affiche un code d'association. Saisissez-le dans Objets → androidtvremote.X → control → pairingCode. Le certificat est ensuite enregistré localement.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Activer les informations multimédias Cast",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Intervalle d'interrogation Cast en secondes"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "Indirizzo IP / Nome host",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "Indirizzo IP o nome host di Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Nome visualizzato durante l'associazione",
|
|
5
|
+
"Pairing-Port": "Porta di associazione",
|
|
6
|
+
"Remote-Port": "Porta del telecomando",
|
|
7
|
+
"Reconnect nach Sekunden": "Riconnetti dopo alcuni secondi",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Abilita debug del protocollo",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "Al primo avvio, la TV mostra un codice di associazione. Inseriscilo in Oggetti → androidtvremote.X → control → pairingCode. Il certificato viene quindi salvato localmente.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Abilita informazioni multimediali Cast",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Intervallo di interrogazione Cast in secondi"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "IP-adres / Hostnaam",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "IP-adres of hostnaam van de Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Weergavenaam tijdens koppelen",
|
|
5
|
+
"Pairing-Port": "Koppelingspoort",
|
|
6
|
+
"Remote-Port": "Afstandsbedieningspoort",
|
|
7
|
+
"Reconnect nach Sekunden": "Opnieuw verbinden na aantal seconden",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Protocoldebugging inschakelen",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "Bij de eerste start toont de tv een koppelingscode. Voer deze in onder Objecten → androidtvremote.X → control → pairingCode. Het certificaat wordt daarna lokaal opgeslagen.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Cast-media-informatie inschakelen",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Cast-controle-interval in seconden"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "Adres IP / Nazwa hosta",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "Adres IP lub nazwa hosta Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Nazwa wyświetlana podczas parowania",
|
|
5
|
+
"Pairing-Port": "Port parowania",
|
|
6
|
+
"Remote-Port": "Port pilota",
|
|
7
|
+
"Reconnect nach Sekunden": "Połącz ponownie po kilku sekundach",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Włącz debugowanie protokołu",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "Przy pierwszym uruchomieniu telewizor wyświetla kod parowania. Wprowadź go w Obiekty → androidtvremote.X → control → pairingCode. Certyfikat zostanie następnie zapisany lokalnie.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Włącz informacje multimedialne Cast",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Interwał odpytywania Cast w sekundach"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "Endereço IP / Nome do anfitrião",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "Endereço IP ou nome do anfitrião do Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Nome apresentado durante o emparelhamento",
|
|
5
|
+
"Pairing-Port": "Porta de emparelhamento",
|
|
6
|
+
"Remote-Port": "Porta de controlo remoto",
|
|
7
|
+
"Reconnect nach Sekunden": "Reconectar após alguns segundos",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Ativar depuração do protocolo",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "Na primeira inicialização, a TV apresenta um código de emparelhamento. Introduza-o em Objetos → androidtvremote.X → control → pairingCode. O certificado será guardado localmente.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Ativar informações multimédia do Cast",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Intervalo de consulta do Cast em segundos"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "IP-адрес / Имя хоста",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "IP-адрес или имя хоста Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Отображаемое имя при сопряжении",
|
|
5
|
+
"Pairing-Port": "Порт сопряжения",
|
|
6
|
+
"Remote-Port": "Порт удалённого управления",
|
|
7
|
+
"Reconnect nach Sekunden": "Повторное подключение через несколько секунд",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Включить отладку протокола",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "При первом запуске телевизор показывает код сопряжения. Введите его в Объекты → androidtvremote.X → control → pairingCode. После этого сертификат сохраняется локально.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Включить информацию о мультимедиа Cast",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Интервал опроса Cast в секундах"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "IP-адреса / Ім'я хоста",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "IP-адреса або ім'я хоста Android TV / Google TV",
|
|
4
|
+
"Anzeigename beim Pairing": "Ім'я під час сполучення",
|
|
5
|
+
"Pairing-Port": "Порт сполучення",
|
|
6
|
+
"Remote-Port": "Порт дистанційного керування",
|
|
7
|
+
"Reconnect nach Sekunden": "Повторне підключення через кілька секунд",
|
|
8
|
+
"Protokoll-Debug aktivieren": "Увімкнути налагодження протоколу",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "Під час першого запуску телевізор показує код сполучення. Введіть його в Об'єкти → androidtvremote.X → control → pairingCode. Сертифікат буде збережено локально.",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "Увімкнути інформацію про медіа Cast",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Інтервал опитування Cast у секундах"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"IP-Adresse / Hostname": "IP 地址 / 主机名",
|
|
3
|
+
"IP-Adresse oder Hostname des Android TV / Google TV": "Android TV / Google TV 的 IP 地址或主机名",
|
|
4
|
+
"Anzeigename beim Pairing": "配对时显示的名称",
|
|
5
|
+
"Pairing-Port": "配对端口",
|
|
6
|
+
"Remote-Port": "遥控端口",
|
|
7
|
+
"Reconnect nach Sekunden": "重新连接等待秒数",
|
|
8
|
+
"Protokoll-Debug aktivieren": "启用协议调试",
|
|
9
|
+
"Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.": "首次启动时,电视会显示配对码。请在 对象 → androidtvremote.X → control → pairingCode 中输入。证书随后保存在本地。",
|
|
10
|
+
"Cast-Medieninfos aktivieren": "启用 Cast 媒体信息",
|
|
11
|
+
"Cast-Abfrage alle Sekunden": "Cast 轮询间隔(秒)"
|
|
12
|
+
}
|
package/admin/jsonConfig.json
CHANGED
|
@@ -5,13 +5,21 @@
|
|
|
5
5
|
"type": "text",
|
|
6
6
|
"label": "IP-Adresse / Hostname",
|
|
7
7
|
"help": "IP-Adresse oder Hostname des Android TV / Google TV",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"newLine": true,
|
|
9
|
+
"xs": 12,
|
|
10
|
+
"md": 6,
|
|
11
|
+
"lg": 6,
|
|
12
|
+
"xl": 6,
|
|
13
|
+
"sm": 6
|
|
10
14
|
},
|
|
11
15
|
"deviceName": {
|
|
12
16
|
"type": "text",
|
|
13
17
|
"label": "Anzeigename beim Pairing",
|
|
14
18
|
"default": "ioBroker",
|
|
19
|
+
"xs": 12,
|
|
20
|
+
"md": 6,
|
|
21
|
+
"lg": 6,
|
|
22
|
+
"xl": 6,
|
|
15
23
|
"sm": 6
|
|
16
24
|
},
|
|
17
25
|
"pairingPort": {
|
|
@@ -20,8 +28,12 @@
|
|
|
20
28
|
"default": 6467,
|
|
21
29
|
"min": 1,
|
|
22
30
|
"max": 65535,
|
|
23
|
-
"
|
|
24
|
-
"
|
|
31
|
+
"newLine": true,
|
|
32
|
+
"xs": 12,
|
|
33
|
+
"md": 4,
|
|
34
|
+
"lg": 4,
|
|
35
|
+
"xl": 4,
|
|
36
|
+
"sm": 4
|
|
25
37
|
},
|
|
26
38
|
"remotePort": {
|
|
27
39
|
"type": "number",
|
|
@@ -29,6 +41,10 @@
|
|
|
29
41
|
"default": 6466,
|
|
30
42
|
"min": 1,
|
|
31
43
|
"max": 65535,
|
|
44
|
+
"xs": 12,
|
|
45
|
+
"md": 4,
|
|
46
|
+
"lg": 4,
|
|
47
|
+
"xl": 4,
|
|
32
48
|
"sm": 4
|
|
33
49
|
},
|
|
34
50
|
"reconnectSeconds": {
|
|
@@ -37,23 +53,42 @@
|
|
|
37
53
|
"default": 15,
|
|
38
54
|
"min": 5,
|
|
39
55
|
"max": 3600,
|
|
56
|
+
"xs": 12,
|
|
57
|
+
"md": 4,
|
|
58
|
+
"lg": 4,
|
|
59
|
+
"xl": 4,
|
|
40
60
|
"sm": 4
|
|
41
61
|
},
|
|
42
62
|
"debugProtocol": {
|
|
43
63
|
"type": "checkbox",
|
|
44
64
|
"label": "Protokoll-Debug aktivieren",
|
|
45
65
|
"default": false,
|
|
46
|
-
"newLine": true
|
|
66
|
+
"newLine": true,
|
|
67
|
+
"xs": 12,
|
|
68
|
+
"sm": 12,
|
|
69
|
+
"md": 12,
|
|
70
|
+
"lg": 12,
|
|
71
|
+
"xl": 12
|
|
47
72
|
},
|
|
48
73
|
"hint": {
|
|
49
74
|
"type": "staticText",
|
|
50
|
-
"text": "Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert."
|
|
75
|
+
"text": "Beim ersten Start zeigt der Fernseher einen Pairing-Code. Diesen unter Objekte → androidtvremote.X → control → pairingCode eintragen. Danach wird das Zertifikat lokal gespeichert.",
|
|
76
|
+
"xs": 12,
|
|
77
|
+
"sm": 12,
|
|
78
|
+
"md": 12,
|
|
79
|
+
"lg": 12,
|
|
80
|
+
"xl": 12
|
|
51
81
|
},
|
|
52
82
|
"enableCast": {
|
|
53
83
|
"type": "checkbox",
|
|
54
84
|
"label": "Cast-Medieninfos aktivieren",
|
|
55
85
|
"default": true,
|
|
56
|
-
"newLine": true
|
|
86
|
+
"newLine": true,
|
|
87
|
+
"xs": 12,
|
|
88
|
+
"sm": 12,
|
|
89
|
+
"md": 12,
|
|
90
|
+
"lg": 12,
|
|
91
|
+
"xl": 12
|
|
57
92
|
},
|
|
58
93
|
"castPollSeconds": {
|
|
59
94
|
"type": "number",
|
|
@@ -61,7 +96,12 @@
|
|
|
61
96
|
"default": 5,
|
|
62
97
|
"min": 3,
|
|
63
98
|
"max": 60,
|
|
99
|
+
"xs": 12,
|
|
100
|
+
"md": 4,
|
|
101
|
+
"lg": 4,
|
|
102
|
+
"xl": 4,
|
|
64
103
|
"sm": 4
|
|
65
104
|
}
|
|
66
|
-
}
|
|
105
|
+
},
|
|
106
|
+
"i18n": true
|
|
67
107
|
}
|
package/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "androidtvremote",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.8",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "Android TV Remote",
|
|
7
7
|
"de": "Android TV Remote",
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"authors": [
|
|
32
32
|
"Henrik Schönhofen <henrik.schoenhofen@icloud.com>"
|
|
33
33
|
],
|
|
34
|
-
"license": "MIT",
|
|
35
34
|
"platform": "Javascript/Node.js",
|
|
36
35
|
"mode": "daemon",
|
|
37
36
|
"enabled": true,
|
|
@@ -71,22 +70,31 @@
|
|
|
71
70
|
"tier": 3,
|
|
72
71
|
"type": "multimedia",
|
|
73
72
|
"news": {
|
|
73
|
+
"0.2.7": {
|
|
74
|
+
"en": "Added automated GitHub Actions release workflow for GitHub and npm publishing.",
|
|
75
|
+
"de": "Automatischen GitHub-Actions-Release-Workflow für Veröffentlichungen auf GitHub und npm eingerichtet.",
|
|
76
|
+
"ru": "Добавлен автоматизированный процесс публикации на GitHub и npm через GitHub Actions.",
|
|
77
|
+
"pt": "Adicionado um fluxo automatizado do GitHub Actions para publicação no GitHub e npm.",
|
|
78
|
+
"nl": "Geautomatiseerde GitHub Actions-workflow toegevoegd voor publicatie op GitHub en npm.",
|
|
79
|
+
"fr": "Ajout d'un workflow GitHub Actions automatisé pour la publication sur GitHub et npm.",
|
|
80
|
+
"it": "Aggiunto un workflow GitHub Actions automatizzato per la pubblicazione su GitHub e npm.",
|
|
81
|
+
"es": "Añadido un flujo de trabajo automatizado de GitHub Actions para publicar en GitHub y npm.",
|
|
82
|
+
"pl": "Dodano automatyczny proces GitHub Actions do publikowania w GitHub i npm.",
|
|
83
|
+
"uk": "Додано автоматизований процес GitHub Actions для публікації на GitHub і npm.",
|
|
84
|
+
"zh-cn": "新增用于发布到 GitHub 和 npm 的自动化 GitHub Actions 工作流程."
|
|
85
|
+
},
|
|
74
86
|
"0.2.6": {
|
|
75
87
|
"en": "Improved pairing credential persistence. Credentials are no longer deleted automatically on unpaired events.",
|
|
76
|
-
"de": "Verbesserte Speicherung der Pairing-Zugangsdaten. Bei Unpaired-Ereignissen werden Zertifikate nicht mehr automatisch gelöscht."
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"es": "Versión inicial con compatibilidad con Android TV Remote v2, emparejamiento seguro y control remoto.",
|
|
87
|
-
"pl": "Pierwsza wersja z obsługą Android TV Remote v2, bezpiecznym parowaniem i zdalnym sterowaniem.",
|
|
88
|
-
"uk": "Перший випуск із підтримкою Android TV Remote v2, безпечним сполученням і дистанційним керуванням.",
|
|
89
|
-
"zh-cn": "首个版本,支持 Android TV Remote v2、安全配对和远程控制。"
|
|
88
|
+
"de": "Verbesserte Speicherung der Pairing-Zugangsdaten. Bei Unpaired-Ereignissen werden Zertifikate nicht mehr automatisch gelöscht.",
|
|
89
|
+
"ru": "Улучшено сохранение данных сопряжения. Сертификаты больше не удаляются автоматически при потере сопряжения.",
|
|
90
|
+
"pt": "Melhorada a persistência das credenciais de emparelhamento. Os certificados deixam de ser eliminados automaticamente quando ocorre um evento de desemparelhamento.",
|
|
91
|
+
"nl": "Opslag van koppelgegevens verbeterd. Certificaten worden niet langer automatisch verwijderd bij ontkoppelingsgebeurtenissen.",
|
|
92
|
+
"fr": "Amélioration de la conservation des identifiants d'appairage. Les certificats ne sont plus supprimés automatiquement lors d'un événement de désappairage.",
|
|
93
|
+
"it": "Migliorata la conservazione delle credenziali di associazione. I certificati non vengono più eliminati automaticamente in caso di disassociazione.",
|
|
94
|
+
"es": "Mejorada la conservación de las credenciales de emparejamiento. Los certificados ya no se eliminan automáticamente al producirse una desvinculación.",
|
|
95
|
+
"pl": "Ulepszono przechowywanie danych parowania. Certyfikaty nie są już automatycznie usuwane podczas zdarzeń rozparowania.",
|
|
96
|
+
"uk": "Покращено збереження даних сполучення. Сертифікати більше не видаляються автоматично під час роз'єднання.",
|
|
97
|
+
"zh-cn": "改进配对凭据的持久化存储。发生取消配对事件时不再自动删除证书."
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
100
|
},
|
package/main.js
CHANGED
|
@@ -57,34 +57,206 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
57
57
|
|
|
58
58
|
async ensureObjects() {
|
|
59
59
|
const defs = {
|
|
60
|
-
'info.connection': {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
'info.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
'info.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
60
|
+
'info.connection': {
|
|
61
|
+
name: 'Connected',
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
role: 'indicator.connected',
|
|
64
|
+
read: true,
|
|
65
|
+
write: false,
|
|
66
|
+
def: false,
|
|
67
|
+
},
|
|
68
|
+
'info.lastRemoteReady': {
|
|
69
|
+
name: 'Last remote connection',
|
|
70
|
+
type: 'number',
|
|
71
|
+
role: 'value.time',
|
|
72
|
+
read: true,
|
|
73
|
+
write: false,
|
|
74
|
+
def: 0,
|
|
75
|
+
},
|
|
76
|
+
'info.pairingRequired': {
|
|
77
|
+
name: 'Pairing required',
|
|
78
|
+
type: 'boolean',
|
|
79
|
+
role: 'indicator',
|
|
80
|
+
read: true,
|
|
81
|
+
write: false,
|
|
82
|
+
def: false,
|
|
83
|
+
},
|
|
84
|
+
'info.powered': {
|
|
85
|
+
name: 'Powered',
|
|
86
|
+
type: 'boolean',
|
|
87
|
+
role: 'indicator.power',
|
|
88
|
+
read: true,
|
|
89
|
+
write: false,
|
|
90
|
+
def: false,
|
|
91
|
+
},
|
|
92
|
+
'info.currentAppId': {
|
|
93
|
+
name: 'Current app package',
|
|
94
|
+
type: 'string',
|
|
95
|
+
role: 'text',
|
|
96
|
+
read: true,
|
|
97
|
+
write: false,
|
|
98
|
+
def: '',
|
|
99
|
+
},
|
|
100
|
+
'info.currentApp': {
|
|
101
|
+
name: 'Current app',
|
|
102
|
+
type: 'string',
|
|
103
|
+
role: 'text',
|
|
104
|
+
read: true,
|
|
105
|
+
write: false,
|
|
106
|
+
def: '',
|
|
107
|
+
},
|
|
108
|
+
'info.currentAppUnknown': {
|
|
109
|
+
name: 'Unknown current app package',
|
|
110
|
+
type: 'string',
|
|
111
|
+
role: 'text',
|
|
112
|
+
read: true,
|
|
113
|
+
write: false,
|
|
114
|
+
def: '',
|
|
115
|
+
},
|
|
116
|
+
'info.cast.connection': {
|
|
117
|
+
name: 'Cast connected',
|
|
118
|
+
type: 'boolean',
|
|
119
|
+
role: 'indicator.connected',
|
|
120
|
+
read: true,
|
|
121
|
+
write: false,
|
|
122
|
+
def: false,
|
|
123
|
+
},
|
|
124
|
+
'info.cast.appId': {
|
|
125
|
+
name: 'Cast app ID',
|
|
126
|
+
type: 'string',
|
|
127
|
+
role: 'text',
|
|
128
|
+
read: true,
|
|
129
|
+
write: false,
|
|
130
|
+
def: '',
|
|
131
|
+
},
|
|
132
|
+
'info.cast.appName': {
|
|
133
|
+
name: 'Cast app name',
|
|
134
|
+
type: 'string',
|
|
135
|
+
role: 'text',
|
|
136
|
+
read: true,
|
|
137
|
+
write: false,
|
|
138
|
+
def: '',
|
|
139
|
+
},
|
|
140
|
+
'info.media.title': {
|
|
141
|
+
name: 'Media title',
|
|
142
|
+
type: 'string',
|
|
143
|
+
role: 'media.title',
|
|
144
|
+
read: true,
|
|
145
|
+
write: false,
|
|
146
|
+
def: '',
|
|
147
|
+
},
|
|
148
|
+
'info.media.subtitle': {
|
|
149
|
+
name: 'Media subtitle',
|
|
150
|
+
type: 'string',
|
|
151
|
+
role: 'text',
|
|
152
|
+
read: true,
|
|
153
|
+
write: false,
|
|
154
|
+
def: '',
|
|
155
|
+
},
|
|
156
|
+
'info.media.playerState': {
|
|
157
|
+
name: 'Media player state',
|
|
158
|
+
type: 'string',
|
|
159
|
+
role: 'media.state',
|
|
160
|
+
read: true,
|
|
161
|
+
write: false,
|
|
162
|
+
def: '',
|
|
163
|
+
},
|
|
164
|
+
'info.media.contentId': {
|
|
165
|
+
name: 'Media content ID',
|
|
166
|
+
type: 'string',
|
|
167
|
+
role: 'text',
|
|
168
|
+
read: true,
|
|
169
|
+
write: false,
|
|
170
|
+
def: '',
|
|
171
|
+
},
|
|
172
|
+
'info.volume.level': {
|
|
173
|
+
name: 'Volume level',
|
|
174
|
+
type: 'number',
|
|
175
|
+
role: 'level.volume',
|
|
176
|
+
read: true,
|
|
177
|
+
write: false,
|
|
178
|
+
def: 0,
|
|
179
|
+
},
|
|
180
|
+
'info.volume.maximum': {
|
|
181
|
+
name: 'Maximum volume',
|
|
182
|
+
type: 'number',
|
|
183
|
+
role: 'value',
|
|
184
|
+
read: true,
|
|
185
|
+
write: false,
|
|
186
|
+
def: 0,
|
|
187
|
+
},
|
|
188
|
+
'info.volume.muted': {
|
|
189
|
+
name: 'Muted',
|
|
190
|
+
type: 'boolean',
|
|
191
|
+
role: 'indicator.mute',
|
|
192
|
+
read: true,
|
|
193
|
+
write: false,
|
|
194
|
+
def: false,
|
|
195
|
+
},
|
|
196
|
+
'control.pairingCode': {
|
|
197
|
+
name: 'Pairing code',
|
|
198
|
+
type: 'string',
|
|
199
|
+
role: 'text',
|
|
200
|
+
read: true,
|
|
201
|
+
write: true,
|
|
202
|
+
def: '',
|
|
203
|
+
},
|
|
204
|
+
'control.key': {
|
|
205
|
+
name: 'Remote key',
|
|
206
|
+
type: 'string',
|
|
207
|
+
role: 'text',
|
|
208
|
+
read: true,
|
|
209
|
+
write: true,
|
|
210
|
+
def: '',
|
|
211
|
+
},
|
|
212
|
+
'control.text': {
|
|
213
|
+
name: 'Send text',
|
|
214
|
+
type: 'string',
|
|
215
|
+
role: 'text',
|
|
216
|
+
read: true,
|
|
217
|
+
write: true,
|
|
218
|
+
def: '',
|
|
219
|
+
},
|
|
220
|
+
'control.appLink': {
|
|
221
|
+
name: 'Open app/deep link',
|
|
222
|
+
type: 'string',
|
|
223
|
+
role: 'text.url',
|
|
224
|
+
read: true,
|
|
225
|
+
write: true,
|
|
226
|
+
def: '',
|
|
227
|
+
},
|
|
228
|
+
'control.power': {
|
|
229
|
+
name: 'Power target / toggle',
|
|
230
|
+
type: 'boolean',
|
|
231
|
+
role: 'switch.power',
|
|
232
|
+
read: true,
|
|
233
|
+
write: true,
|
|
234
|
+
def: false,
|
|
235
|
+
},
|
|
236
|
+
'control.reconnect': {
|
|
237
|
+
name: 'Reconnect',
|
|
238
|
+
type: 'boolean',
|
|
239
|
+
role: 'button',
|
|
240
|
+
read: true,
|
|
241
|
+
write: true,
|
|
242
|
+
def: false,
|
|
243
|
+
},
|
|
244
|
+
'control.forgetPairing': {
|
|
245
|
+
name: 'Forget pairing',
|
|
246
|
+
type: 'boolean',
|
|
247
|
+
role: 'button',
|
|
248
|
+
read: true,
|
|
249
|
+
write: true,
|
|
250
|
+
def: false,
|
|
251
|
+
},
|
|
84
252
|
};
|
|
85
253
|
|
|
86
254
|
for (const [id, common] of Object.entries(defs)) {
|
|
87
|
-
await this.setObjectNotExistsAsync(id, {
|
|
255
|
+
await this.setObjectNotExistsAsync(id, {
|
|
256
|
+
type: 'state',
|
|
257
|
+
common,
|
|
258
|
+
native: {},
|
|
259
|
+
});
|
|
88
260
|
}
|
|
89
261
|
}
|
|
90
262
|
|
|
@@ -104,29 +276,45 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
104
276
|
}
|
|
105
277
|
|
|
106
278
|
async saveCertificate(cert) {
|
|
107
|
-
if (!cert || !cert.key || !cert.cert)
|
|
279
|
+
if (!cert || !cert.key || !cert.cert) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
108
282
|
const tmp = `${this.credentialsFile}.tmp`;
|
|
109
|
-
await fs.promises.writeFile(tmp, JSON.stringify(cert, null, 2), {
|
|
283
|
+
await fs.promises.writeFile(tmp, JSON.stringify(cert, null, 2), {
|
|
284
|
+
mode: 0o600,
|
|
285
|
+
});
|
|
110
286
|
await fs.promises.rename(tmp, this.credentialsFile);
|
|
111
|
-
try {
|
|
287
|
+
try {
|
|
288
|
+
await fs.promises.chmod(this.credentialsFile, 0o600);
|
|
289
|
+
} catch {
|
|
290
|
+
// Ignore cleanup errors.
|
|
291
|
+
}
|
|
112
292
|
}
|
|
113
293
|
|
|
114
294
|
async deleteCertificate() {
|
|
115
295
|
try {
|
|
116
296
|
await fs.promises.unlink(this.credentialsFile);
|
|
117
297
|
} catch (error) {
|
|
118
|
-
if (error && error.code !== 'ENOENT')
|
|
298
|
+
if (error && error.code !== 'ENOENT') {
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
119
301
|
}
|
|
120
302
|
}
|
|
121
303
|
|
|
122
304
|
async connect() {
|
|
123
|
-
if (this.stopping || this.connecting || !this.remoteModule)
|
|
305
|
+
if (this.stopping || this.connecting || !this.remoteModule) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
124
308
|
this.connecting = true;
|
|
125
309
|
this.clearReconnect();
|
|
126
310
|
|
|
127
311
|
try {
|
|
128
312
|
if (this.remote) {
|
|
129
|
-
try {
|
|
313
|
+
try {
|
|
314
|
+
this.remote.stop();
|
|
315
|
+
} catch {
|
|
316
|
+
// Ignore cleanup errors.
|
|
317
|
+
}
|
|
130
318
|
this.remote = null;
|
|
131
319
|
}
|
|
132
320
|
|
|
@@ -135,9 +323,11 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
135
323
|
pairing_port: Number(this.config.pairingPort) || 6467,
|
|
136
324
|
remote_port: Number(this.config.remotePort) || 6466,
|
|
137
325
|
service_name: String(this.config.deviceName || 'ioBroker'),
|
|
138
|
-
debug: !!this.config.debugProtocol
|
|
326
|
+
debug: !!this.config.debugProtocol,
|
|
139
327
|
};
|
|
140
|
-
if (cert)
|
|
328
|
+
if (cert) {
|
|
329
|
+
options.cert = cert;
|
|
330
|
+
}
|
|
141
331
|
|
|
142
332
|
const { createAndroidRemote } = this.remoteModule;
|
|
143
333
|
this.remote = createAndroidRemote(String(this.config.host).trim(), options);
|
|
@@ -147,9 +337,15 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
147
337
|
const started = await this.remote.start();
|
|
148
338
|
if (!started) {
|
|
149
339
|
await this.setStateAsync('info.connection', false, true);
|
|
150
|
-
this.log.info(
|
|
340
|
+
this.log.info(
|
|
341
|
+
`Android TV derzeit nicht erreichbar – neuer Versuch in ${Math.max(5, Number(this.config.reconnectSeconds) || 15)} Sekunden.`,
|
|
342
|
+
);
|
|
151
343
|
if (this.remote) {
|
|
152
|
-
try {
|
|
344
|
+
try {
|
|
345
|
+
this.remote.stop();
|
|
346
|
+
} catch {
|
|
347
|
+
// Ignore cleanup errors.
|
|
348
|
+
}
|
|
153
349
|
this.remote = null;
|
|
154
350
|
}
|
|
155
351
|
this.scheduleReconnect();
|
|
@@ -192,18 +388,23 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
192
388
|
});
|
|
193
389
|
|
|
194
390
|
remote.on('volume', async volume => {
|
|
195
|
-
if (!volume)
|
|
391
|
+
if (!volume) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
196
394
|
await this.setStateAsync('info.volume.level', Number(volume.level) || 0, true);
|
|
197
395
|
await this.setStateAsync('info.volume.maximum', Number(volume.maximum) || 0, true);
|
|
198
396
|
await this.setStateAsync('info.volume.muted', !!volume.muted, true);
|
|
199
397
|
});
|
|
200
398
|
|
|
201
399
|
remote.on('current_app', async app => {
|
|
202
|
-
const appId =
|
|
203
|
-
|
|
204
|
-
|
|
400
|
+
const appId =
|
|
401
|
+
typeof app === 'string'
|
|
402
|
+
? app.trim()
|
|
403
|
+
: String(app?.packageName || app?.package || app?.app || '').trim();
|
|
205
404
|
|
|
206
|
-
if (!appId)
|
|
405
|
+
if (!appId) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
207
408
|
|
|
208
409
|
const mapped = this.appNameMap()[appId];
|
|
209
410
|
await this.setStateAsync('info.currentAppId', appId, true);
|
|
@@ -223,12 +424,14 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
223
424
|
// control.forgetPairing bewusst gelöscht.
|
|
224
425
|
this.log.warn(
|
|
225
426
|
'Android TV meldet "unpaired". Gespeichertes Pairing bleibt erhalten. ' +
|
|
226
|
-
|
|
427
|
+
'Falls ein neues Pairing wirklich erforderlich ist, control.forgetPairing verwenden.',
|
|
227
428
|
);
|
|
228
429
|
});
|
|
229
430
|
|
|
230
431
|
remote.on('error', async error => {
|
|
231
|
-
if (this.stopping)
|
|
432
|
+
if (this.stopping) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
232
435
|
await this.setStateAsync('info.connection', false, true);
|
|
233
436
|
this.log.warn(`Android-TV-Protokollfehler: ${this.errorText(error)}`);
|
|
234
437
|
this.scheduleReconnect();
|
|
@@ -236,7 +439,9 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
236
439
|
}
|
|
237
440
|
|
|
238
441
|
scheduleReconnect() {
|
|
239
|
-
if (this.stopping || this.reconnectTimer || this.pairingRequested)
|
|
442
|
+
if (this.stopping || this.reconnectTimer || this.pairingRequested) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
240
445
|
const seconds = Math.max(5, Number(this.config.reconnectSeconds) || 15);
|
|
241
446
|
this.reconnectTimer = this.setTimeout(() => {
|
|
242
447
|
this.reconnectTimer = null;
|
|
@@ -252,21 +457,29 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
252
457
|
}
|
|
253
458
|
|
|
254
459
|
async onStateChange(id, state) {
|
|
255
|
-
if (!state || state.ack)
|
|
460
|
+
if (!state || state.ack) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
256
463
|
const prefix = `${this.namespace}.`;
|
|
257
|
-
if (!id.startsWith(prefix))
|
|
464
|
+
if (!id.startsWith(prefix)) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
258
467
|
const rel = id.slice(prefix.length);
|
|
259
468
|
|
|
260
469
|
try {
|
|
261
470
|
switch (rel) {
|
|
262
471
|
case 'control.pairingCode': {
|
|
263
472
|
const code = String(state.val || '').trim();
|
|
264
|
-
if (!code)
|
|
473
|
+
if (!code) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
265
476
|
if (!this.remote || !this.pairingRequested) {
|
|
266
477
|
this.log.warn('Aktuell wartet der Adapter auf keinen Pairing-Code.');
|
|
267
478
|
} else {
|
|
268
479
|
const accepted = this.remote.sendCode(code);
|
|
269
|
-
if (!accepted)
|
|
480
|
+
if (!accepted) {
|
|
481
|
+
this.log.warn('Pairing-Code konnte nicht gesendet werden.');
|
|
482
|
+
}
|
|
270
483
|
}
|
|
271
484
|
await this.setStateAsync(rel, '', true);
|
|
272
485
|
break;
|
|
@@ -274,21 +487,27 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
274
487
|
|
|
275
488
|
case 'control.key': {
|
|
276
489
|
const key = String(state.val || '').trim();
|
|
277
|
-
if (key)
|
|
490
|
+
if (key) {
|
|
491
|
+
this.sendKey(key);
|
|
492
|
+
}
|
|
278
493
|
await this.setStateAsync(rel, '', true);
|
|
279
494
|
break;
|
|
280
495
|
}
|
|
281
496
|
|
|
282
497
|
case 'control.text': {
|
|
283
498
|
const text = String(state.val ?? '');
|
|
284
|
-
if (text && this.requireRemote())
|
|
499
|
+
if (text && this.requireRemote()) {
|
|
500
|
+
this.remote.sendText(text);
|
|
501
|
+
}
|
|
285
502
|
await this.setStateAsync(rel, '', true);
|
|
286
503
|
break;
|
|
287
504
|
}
|
|
288
505
|
|
|
289
506
|
case 'control.appLink': {
|
|
290
507
|
const link = String(state.val || '').trim();
|
|
291
|
-
if (link && this.requireRemote())
|
|
508
|
+
if (link && this.requireRemote()) {
|
|
509
|
+
this.remote.sendAppLink(link);
|
|
510
|
+
}
|
|
292
511
|
await this.setStateAsync(rel, '', true);
|
|
293
512
|
break;
|
|
294
513
|
}
|
|
@@ -317,7 +536,11 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
317
536
|
await this.deleteCertificate();
|
|
318
537
|
this.pairingRequested = false;
|
|
319
538
|
if (this.remote) {
|
|
320
|
-
try {
|
|
539
|
+
try {
|
|
540
|
+
this.remote.stop();
|
|
541
|
+
} catch {
|
|
542
|
+
// Ignore cleanup errors.
|
|
543
|
+
}
|
|
321
544
|
this.remote = null;
|
|
322
545
|
}
|
|
323
546
|
await this.setStateAsync('info.connection', false, true);
|
|
@@ -332,8 +555,6 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
332
555
|
}
|
|
333
556
|
}
|
|
334
557
|
|
|
335
|
-
|
|
336
|
-
|
|
337
558
|
appNameMap() {
|
|
338
559
|
return {
|
|
339
560
|
// Launcher / System
|
|
@@ -378,15 +599,19 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
378
599
|
'com.plexapp.android': 'Plex',
|
|
379
600
|
'org.xbmc.kodi': 'Kodi',
|
|
380
601
|
'org.videolan.vlc': 'VLC',
|
|
381
|
-
'tv.twitch.android.app': 'Twitch'
|
|
602
|
+
'tv.twitch.android.app': 'Twitch',
|
|
382
603
|
};
|
|
383
604
|
}
|
|
384
605
|
|
|
385
606
|
friendlyAppName(appId) {
|
|
386
607
|
const id = String(appId || '').trim();
|
|
387
|
-
if (!id)
|
|
608
|
+
if (!id) {
|
|
609
|
+
return '';
|
|
610
|
+
}
|
|
388
611
|
const known = this.appNameMap();
|
|
389
|
-
if (known[id])
|
|
612
|
+
if (known[id]) {
|
|
613
|
+
return known[id];
|
|
614
|
+
}
|
|
390
615
|
|
|
391
616
|
// Lesbarer Fallback, Paket-ID bleibt zusätzlich in currentAppUnknown erhalten.
|
|
392
617
|
const parts = id.split('.').filter(Boolean);
|
|
@@ -395,10 +620,14 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
395
620
|
}
|
|
396
621
|
|
|
397
622
|
startCastMonitor() {
|
|
398
|
-
if (this.config.enableCast === false)
|
|
623
|
+
if (this.config.enableCast === false) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
399
626
|
const interval = Math.max(3, Number(this.config.castPollSeconds) || 5);
|
|
400
627
|
const run = async () => {
|
|
401
|
-
if (!this.stopping)
|
|
628
|
+
if (!this.stopping) {
|
|
629
|
+
await this.pollCast();
|
|
630
|
+
}
|
|
402
631
|
};
|
|
403
632
|
void run();
|
|
404
633
|
this.castTimer = this.setInterval(run, interval * 1000);
|
|
@@ -410,14 +639,20 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
410
639
|
this.castTimer = null;
|
|
411
640
|
}
|
|
412
641
|
if (this.castClient) {
|
|
413
|
-
try {
|
|
642
|
+
try {
|
|
643
|
+
this.castClient.close();
|
|
644
|
+
} catch {
|
|
645
|
+
// Ignore cleanup errors.
|
|
646
|
+
}
|
|
414
647
|
this.castClient = null;
|
|
415
648
|
this.castPlayer = null;
|
|
416
649
|
}
|
|
417
650
|
}
|
|
418
651
|
|
|
419
652
|
async pollCast() {
|
|
420
|
-
if (this.castConnecting || !this.config.host)
|
|
653
|
+
if (this.castConnecting || !this.config.host) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
421
656
|
this.castConnecting = true;
|
|
422
657
|
try {
|
|
423
658
|
await this.ensureCastConnected();
|
|
@@ -432,7 +667,9 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
432
667
|
await this.setStateAsync('info.cast.appName', String(session.displayName || ''), true);
|
|
433
668
|
|
|
434
669
|
const status = await this.castJoinAndGetStatus(session);
|
|
435
|
-
if (!status)
|
|
670
|
+
if (!status) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
436
673
|
const media = status.media || {};
|
|
437
674
|
const metadata = media.metadata || {};
|
|
438
675
|
const title = metadata.title || metadata.seriesTitle || metadata.albumName || '';
|
|
@@ -444,9 +681,15 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
444
681
|
} catch (error) {
|
|
445
682
|
await this.setStateAsync('info.cast.connection', false, true);
|
|
446
683
|
this.castPlayer = null;
|
|
447
|
-
if (this.config.debugProtocol)
|
|
684
|
+
if (this.config.debugProtocol) {
|
|
685
|
+
this.log.debug(`Cast-Abfrage: ${this.errorText(error)}`);
|
|
686
|
+
}
|
|
448
687
|
if (this.castClient) {
|
|
449
|
-
try {
|
|
688
|
+
try {
|
|
689
|
+
this.castClient.close();
|
|
690
|
+
} catch {
|
|
691
|
+
// Ignore cleanup errors.
|
|
692
|
+
}
|
|
450
693
|
this.castClient = null;
|
|
451
694
|
}
|
|
452
695
|
} finally {
|
|
@@ -455,24 +698,36 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
455
698
|
}
|
|
456
699
|
|
|
457
700
|
ensureCastConnected() {
|
|
458
|
-
if (this.castClient)
|
|
701
|
+
if (this.castClient) {
|
|
702
|
+
return Promise.resolve();
|
|
703
|
+
}
|
|
459
704
|
return new Promise((resolve, reject) => {
|
|
460
705
|
const client = new CastClient();
|
|
461
706
|
let settled = false;
|
|
462
707
|
const fail = err => {
|
|
463
|
-
if (settled)
|
|
708
|
+
if (settled) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
464
711
|
settled = true;
|
|
465
|
-
try {
|
|
712
|
+
try {
|
|
713
|
+
client.close();
|
|
714
|
+
} catch {
|
|
715
|
+
// Ignore cleanup errors.
|
|
716
|
+
}
|
|
466
717
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
467
718
|
};
|
|
468
719
|
client.once('error', fail);
|
|
469
720
|
client.connect(String(this.config.host).trim(), () => {
|
|
470
|
-
if (settled)
|
|
721
|
+
if (settled) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
471
724
|
settled = true;
|
|
472
725
|
client.removeListener('error', fail);
|
|
473
726
|
client.on('error', () => {
|
|
474
727
|
void this.setStateAsync('info.cast.connection', false, true);
|
|
475
|
-
if (this.castClient === client)
|
|
728
|
+
if (this.castClient === client) {
|
|
729
|
+
this.castClient = null;
|
|
730
|
+
}
|
|
476
731
|
});
|
|
477
732
|
this.castClient = client;
|
|
478
733
|
void this.setStateAsync('info.cast.connection', true, true);
|
|
@@ -483,17 +738,21 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
483
738
|
|
|
484
739
|
castGetSessions() {
|
|
485
740
|
return new Promise((resolve, reject) => {
|
|
486
|
-
this.castClient.getSessions((err, sessions) => err ? reject(err) : resolve(sessions || []));
|
|
741
|
+
this.castClient.getSessions((err, sessions) => (err ? reject(err) : resolve(sessions || [])));
|
|
487
742
|
});
|
|
488
743
|
}
|
|
489
744
|
|
|
490
745
|
castJoinAndGetStatus(session) {
|
|
491
746
|
return new Promise((resolve, reject) => {
|
|
492
747
|
this.castClient.join(session, DefaultMediaReceiver, (err, player) => {
|
|
493
|
-
if (err)
|
|
748
|
+
if (err) {
|
|
749
|
+
return reject(err);
|
|
750
|
+
}
|
|
494
751
|
this.castPlayer = player;
|
|
495
752
|
player.getStatus((statusErr, status) => {
|
|
496
|
-
if (statusErr)
|
|
753
|
+
if (statusErr) {
|
|
754
|
+
return reject(statusErr);
|
|
755
|
+
}
|
|
497
756
|
resolve(status || null);
|
|
498
757
|
});
|
|
499
758
|
});
|
|
@@ -510,18 +769,41 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
510
769
|
}
|
|
511
770
|
|
|
512
771
|
sendKey(input) {
|
|
513
|
-
if (!this.requireRemote())
|
|
772
|
+
if (!this.requireRemote()) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
514
775
|
const { RemoteKeyCode } = this.remoteModule;
|
|
515
776
|
const normalized = input.trim().toUpperCase().replace(/[ -]+/g, '_');
|
|
516
777
|
const aliases = {
|
|
517
|
-
UP: 'KEYCODE_DPAD_UP',
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
778
|
+
UP: 'KEYCODE_DPAD_UP',
|
|
779
|
+
DOWN: 'KEYCODE_DPAD_DOWN',
|
|
780
|
+
LEFT: 'KEYCODE_DPAD_LEFT',
|
|
781
|
+
RIGHT: 'KEYCODE_DPAD_RIGHT',
|
|
782
|
+
OK: 'KEYCODE_DPAD_CENTER',
|
|
783
|
+
ENTER: 'KEYCODE_DPAD_CENTER',
|
|
784
|
+
CENTER: 'KEYCODE_DPAD_CENTER',
|
|
785
|
+
SELECT: 'KEYCODE_DPAD_CENTER',
|
|
786
|
+
HOME: 'KEYCODE_HOME',
|
|
787
|
+
BACK: 'KEYCODE_BACK',
|
|
788
|
+
MENU: 'KEYCODE_MENU',
|
|
789
|
+
POWER: 'KEYCODE_POWER',
|
|
790
|
+
PLAY_PAUSE: 'KEYCODE_MEDIA_PLAY_PAUSE',
|
|
791
|
+
PLAY: 'KEYCODE_MEDIA_PLAY',
|
|
792
|
+
PAUSE: 'KEYCODE_MEDIA_PAUSE',
|
|
793
|
+
STOP: 'KEYCODE_MEDIA_STOP',
|
|
794
|
+
NEXT: 'KEYCODE_MEDIA_NEXT',
|
|
795
|
+
PREVIOUS: 'KEYCODE_MEDIA_PREVIOUS',
|
|
796
|
+
REWIND: 'KEYCODE_MEDIA_REWIND',
|
|
797
|
+
FAST_FORWARD: 'KEYCODE_MEDIA_FAST_FORWARD',
|
|
798
|
+
VOLUME_UP: 'KEYCODE_VOLUME_UP',
|
|
799
|
+
VOLUP: 'KEYCODE_VOLUME_UP',
|
|
800
|
+
VOLUME_DOWN: 'KEYCODE_VOLUME_DOWN',
|
|
801
|
+
VOLDOWN: 'KEYCODE_VOLUME_DOWN',
|
|
802
|
+
MUTE: 'KEYCODE_VOLUME_MUTE',
|
|
523
803
|
};
|
|
524
|
-
const enumName = normalized.startsWith('KEYCODE_')
|
|
804
|
+
const enumName = normalized.startsWith('KEYCODE_')
|
|
805
|
+
? normalized
|
|
806
|
+
: aliases[normalized] || `KEYCODE_${normalized}`;
|
|
525
807
|
const code = RemoteKeyCode[enumName];
|
|
526
808
|
if (code === undefined) {
|
|
527
809
|
this.log.warn(`Unbekannte Remote-Taste: ${input} (${enumName})`);
|
|
@@ -549,10 +831,14 @@ class AndroidTvRemoteAdapter extends utils.Adapter {
|
|
|
549
831
|
this.stopCastMonitor();
|
|
550
832
|
await this.setStateAsync('info.connection', false, true);
|
|
551
833
|
if (this.remote) {
|
|
552
|
-
try {
|
|
834
|
+
try {
|
|
835
|
+
this.remote.stop();
|
|
836
|
+
} catch {
|
|
837
|
+
// Ignore cleanup errors.
|
|
838
|
+
}
|
|
553
839
|
this.remote = null;
|
|
554
840
|
}
|
|
555
|
-
} catch
|
|
841
|
+
} catch {
|
|
556
842
|
// ignore during shutdown
|
|
557
843
|
} finally {
|
|
558
844
|
callback();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.androidtvremote",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "ioBroker adapter for Android TV / Google TV using the Android TV Remote v2 protocol",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Henrik Schönhofen",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"homepage": "https://github.com/backfisch88/ioBroker.androidtvremote",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"keywords": [
|
|
12
|
-
"
|
|
12
|
+
"ioBroker",
|
|
13
13
|
"android tv",
|
|
14
14
|
"google tv",
|
|
15
15
|
"remote",
|
|
@@ -37,5 +37,28 @@
|
|
|
37
37
|
},
|
|
38
38
|
"bugs": {
|
|
39
39
|
"url": "https://github.com/backfisch88/ioBroker.androidtvremote/issues"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "npm run test:package && npm run test:unit",
|
|
43
|
+
"lint": "eslint main.js",
|
|
44
|
+
"check": "node --check main.js",
|
|
45
|
+
"test:package": "node ./node_modules/@iobroker/testing/node_modules/mocha/bin/mocha.js test/package.test.js",
|
|
46
|
+
"test:integration": "node ./node_modules/@iobroker/testing/node_modules/mocha/bin/mocha.js test/integration.test.js",
|
|
47
|
+
"test:unit": "node --check main.js",
|
|
48
|
+
"typecheck": "tsc",
|
|
49
|
+
"release": "release-script",
|
|
50
|
+
"dev:build": "adapter-dev build"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@alcalzone/release-script": "^5.2.1",
|
|
54
|
+
"@alcalzone/release-script-plugin-iobroker": "^5.2.0",
|
|
55
|
+
"@alcalzone/release-script-plugin-license": "^5.2.2",
|
|
56
|
+
"@alcalzone/release-script-plugin-manual-review": "^5.2.0",
|
|
57
|
+
"@iobroker/adapter-dev": "^1.5.0",
|
|
58
|
+
"@iobroker/eslint-config": "^2.3.4",
|
|
59
|
+
"@iobroker/testing": "^6.3.0",
|
|
60
|
+
"@tsconfig/node22": "^22.0.6",
|
|
61
|
+
"@types/node": "^22.20.5",
|
|
62
|
+
"typescript": "^5.9.3"
|
|
40
63
|
}
|
|
41
64
|
}
|