iobroker.youtube-remote 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -12,25 +12,31 @@ In the tab **Devices** of the settings:
12
12
  - **Search** finds devices with the YouTube app in the network (DIAL). **Add** pairs a found device; it reports
13
13
  its YouTube screen mostly only while the app runs, so the adapter tries to start it - if that does not work, open
14
14
  YouTube on the device once.
15
- - **Pair with TV code** works for every device: the YouTube app shows the code under Settings > Link with TV code.
15
+ - **Pair with TV code** works for every device: the YouTube app shows the code under **Settings** > **Link with TV code**.
16
+
17
+ The card of a device shows whether the YouTube app is open, what plays (title, channel) and has buttons for previous,
18
+ play, pause, next, the volume and mute. **More** shows the picture and the position of the video.
16
19
 
17
20
  ## States
18
21
 
19
22
  Per device `youtube-remote.0.<device>`, in the form of the type-detector type `mediaPlayer`, so visualizations and the
20
23
  devices adapter show it as a player:
21
24
 
22
- | State | Role | Description |
23
- | ------------------------------- | ------------------------ | -------------------------------------------------------------------------------------- |
24
- | `youtube` | text | play a video ID, a playlist (`PL…`), the uploads of a channel (`UC…`) or a YouTube URL |
25
- | `play`, `pause`, `next`, `prev` | button.* | player commands |
26
- | `state` | media.state | 0 pause, 1 play, 2 stop |
27
- | `playerState` | value | unstarted, ended, playing, paused, buffering, cued, advertisement |
28
- | `title`, `artist`, `cover` | media.* | title, channel and picture of the video (oEmbed of YouTube, can be switched off) |
29
- | `videoId`, `url` | text | the playing video |
30
- | `duration`, `elapsed` | media.* | length and position in seconds, as last reported by the TV |
31
- | `seek` | media.seek | jump to a position in seconds |
32
- | `volume`, `muted` | level.volume, media.mute | volume of the YouTube player |
33
- | `connected` | indicator.reachable | the session to the YouTube app is up |
25
+ | State | Role | Description |
26
+ |----------------------------------|---------------------------|-----------------------------------------------------------------------------------------|
27
+ | `youtube` | text | play a video ID, a playlist (`PL…`), the uploads of a channel (`UC…`) or a YouTube URL |
28
+ | `play`, `pause`, `next`, `prev` | button.* | player commands |
29
+ | `state` | media.state | 0 pause, 1 play, 2 stop |
30
+ | `playerState` | value | unstarted, ended, playing, paused, buffering, cued, advertisement |
31
+ | `title`, `artist`, `cover` | media.* | title, channel and picture of the video (oEmbed of YouTube, can be switched off) |
32
+ | `videoId`, `url` | text | the playing video |
33
+ | `duration`, `elapsed` | media.* | length and position in seconds, as last reported by the TV |
34
+ | `seek` | media.seek | jump to a position in seconds |
35
+ | `volume`, `muted` | level.volume, media.mute | volume of the YouTube player |
36
+ | `connected` | indicator.reachable | the YouTube app of the device is open |
37
+
38
+ A video waits up to 30 s for the YouTube app, so it can be written right after switching the TV on; player commands
39
+ wait 5 s. If the app does not come, `youtube` stays unacknowledged and a warning is logged.
34
40
 
35
41
  Handles (`@name`) cannot be resolved without the YouTube Data API: use the channel ID `UC…` from the address of the
36
42
  channel page.
@@ -48,8 +54,12 @@ stored encrypted.
48
54
  -->
49
55
 
50
56
  ## Changelog
57
+ ### 0.0.2 (2026-10-25)
58
+
59
+ - (@GermanBluefox) Device cards show state, title and channel, with play, pause, next, previous, volume and mute
60
+ - (@GermanBluefox) Commands wait for the YouTube app to open; `connected` shows whether it is open
51
61
 
52
- ### **WORK IN PROGRESS**
62
+ ### 0.0.1 (2026-10-24)
53
63
 
54
64
  - (@GermanBluefox) Initial release
55
65
 
@@ -29,6 +29,10 @@
29
29
  help: 'Look up titles help',
30
30
  default: true,
31
31
  xs: 12,
32
+ sm: 12,
33
+ md: 12,
34
+ lg: 12,
35
+ xl: 12,
32
36
  },
33
37
  discoveryTimeout: {
34
38
  type: 'number',
@@ -40,6 +44,8 @@
40
44
  xs: 12,
41
45
  sm: 6,
42
46
  md: 4,
47
+ lg: 4,
48
+ xl: 4,
43
49
  },
44
50
  _cloud: {
45
51
  type: 'infoBox',
@@ -19,7 +19,12 @@ export declare class YouTubeDeviceManagement extends DeviceManagement {
19
19
  constructor(adapter: AdapterInstance, service: YouTubeService);
20
20
  getInstanceInfo(): InstanceDetails;
21
21
  loadDevices(context: DeviceLoadContext<string>): Promise<void>;
22
- getDeviceDetails(id: string): DeviceDetails<string> | null;
22
+ /**
23
+ * Title and channel live on the card, the player buttons, volume and mute. They write the states of the device,
24
+ * so they work like a script writing them.
25
+ */
26
+ private controlsOf;
27
+ getDeviceDetails(id: string): Promise<DeviceDetails<string> | null>;
23
28
  private discover;
24
29
  private pairCode;
25
30
  private addFound;
@@ -33,7 +33,25 @@ const T = {
33
33
  },
34
34
  paired: { en: 'Paired devices', de: 'Gekoppelte Geräte' },
35
35
  discovered: { en: 'Found devices', de: 'Gefundene Geräte' },
36
+ title: { en: 'Title', de: 'Titel' },
37
+ channel: { en: 'Channel', de: 'Kanal' },
38
+ previous: { en: 'Previous', de: 'Zurück' },
39
+ pause: { en: 'Pause', de: 'Pause' },
40
+ next: { en: 'Next', de: 'Weiter' },
41
+ volume: { en: 'Volume', de: 'Lautstärke' },
42
+ muted: { en: 'Muted', de: 'Stumm' },
43
+ playing: { en: 'Playing', de: 'Spielt' },
44
+ paused: { en: 'Paused', de: 'Pausiert' },
45
+ stopped: { en: 'Stopped', de: 'Gestoppt' },
46
+ position: { en: 'Position', de: 'Position' },
36
47
  };
48
+ /** `m:ss` or `h:mm:ss` */
49
+ function duration(seconds) {
50
+ const total = Math.max(0, Math.round(Number(seconds) || 0));
51
+ const [h, m, sec] = [Math.floor(total / 3600), Math.floor(total / 60) % 60, total % 60];
52
+ const two = (n) => String(n).padStart(2, '0');
53
+ return h ? `${h}:${two(m)}:${two(sec)}` : `${m}:${two(sec)}`;
54
+ }
37
55
  function withError(text, error) {
38
56
  const message = error instanceof Error ? error.message : String(error);
39
57
  return typeof text === 'string'
@@ -83,6 +101,21 @@ class YouTubeDeviceManagement extends dm_utils_1.DeviceManagement {
83
101
  mapping: { true: 'connected', false: 'disconnected' },
84
102
  },
85
103
  },
104
+ indicators: [
105
+ {
106
+ id: 'state',
107
+ value: { stateId: `${namespace}.${device.id}.state` },
108
+ levels: [
109
+ { value: 1, icon: 'play', color: 'ok', text: T.playing },
110
+ { value: 0, icon: 'pause', color: 'warning', text: T.paused },
111
+ { icon: 'stop', color: 'inactive', text: T.stopped },
112
+ ],
113
+ hideIfEmpty: false,
114
+ order: 1,
115
+ },
116
+ ],
117
+ controls: this.controlsOf(device.id),
118
+ hasDetails: true,
86
119
  actions: [
87
120
  { id: 'play', icon: 'play', description: T.play, handler: (id, ctx) => this.play(id, ctx) },
88
121
  { id: 'rename', icon: 'rename', description: T.rename, handler: (id, ctx) => this.rename(id, ctx) },
@@ -122,17 +155,96 @@ class YouTubeDeviceManagement extends dm_utils_1.DeviceManagement {
122
155
  context.setTotalDevices(devices.length);
123
156
  devices.forEach(device => context.addDevice(device));
124
157
  }
125
- getDeviceDetails(id) {
158
+ /**
159
+ * Title and channel live on the card, the player buttons, volume and mute. They write the states of the device,
160
+ * so they work like a script writing them.
161
+ */
162
+ controlsOf(id) {
163
+ const base = `${this.adapter.namespace}.${id}`;
164
+ const handler = async (_deviceId, control, value) => {
165
+ try {
166
+ await this.adapter.setForeignStateAsync(`${base}.${control}`, value, false);
167
+ }
168
+ catch (error) {
169
+ return { error: { code: 500, message: error instanceof Error ? error.message : String(error) } };
170
+ }
171
+ return { val: value, ack: true, ts: Date.now(), lc: Date.now(), from: this.adapter.namespace };
172
+ };
173
+ const getStateHandler = async (_deviceId, control) => (await this.adapter.getForeignStateAsync(`${base}.${control}`)) ?? {
174
+ val: null,
175
+ ack: true,
176
+ ts: Date.now(),
177
+ lc: Date.now(),
178
+ from: this.adapter.namespace,
179
+ };
180
+ const button = (control, icon, label) => ({
181
+ id: control,
182
+ type: 'button',
183
+ icon,
184
+ label,
185
+ handler: (deviceId, controlId) => handler(deviceId, controlId, true),
186
+ });
187
+ const controls = [
188
+ { id: 'title', type: 'info', label: T.title, stateId: `${base}.title`, noTranslation: true },
189
+ { id: 'artist', type: 'info', label: T.channel, stateId: `${base}.artist`, noTranslation: true },
190
+ button('prev', 'previous', T.previous),
191
+ button('play', 'play', T.play),
192
+ button('pause', 'pause', T.pause),
193
+ button('next', 'next', T.next),
194
+ {
195
+ id: 'volume',
196
+ type: 'slider',
197
+ label: T.volume,
198
+ stateId: `${base}.volume`,
199
+ min: 0,
200
+ max: 100,
201
+ step: 1,
202
+ unit: '%',
203
+ controlDelay: 500,
204
+ handler,
205
+ },
206
+ { id: 'muted', type: 'switch', label: T.muted, stateId: `${base}.muted`, handler },
207
+ ];
208
+ return controls.map(control => ({ ...control, getStateHandler }));
209
+ }
210
+ async getDeviceDetails(id) {
126
211
  const device = this.service.devices().find(entry => entry.id === id);
127
- return device
128
- ? {
129
- id,
130
- schema: {
131
- type: 'panel',
132
- items: { name: { type: 'staticInfo', label: T.name, data: device.name } },
212
+ if (!device) {
213
+ return null;
214
+ }
215
+ const base = `${this.adapter.namespace}.${id}`;
216
+ const value = async (state) => (await this.adapter.getForeignStateAsync(`${base}.${state}`))?.val ?? null;
217
+ const [title, artist, url, cover, elapsed, length] = await Promise.all(['title', 'artist', 'url', 'cover', 'elapsed', 'duration'].map(value));
218
+ return {
219
+ id,
220
+ schema: {
221
+ type: 'panel',
222
+ items: {
223
+ name: { type: 'staticInfo', label: T.name, data: device.name, xs: 12 },
224
+ title: { type: 'staticInfo', label: T.title, data: String(title || '-'), xs: 12 },
225
+ artist: { type: 'staticInfo', label: T.channel, data: String(artist || '-'), xs: 12 },
226
+ position: {
227
+ type: 'staticInfo',
228
+ label: T.position,
229
+ data: length ? `${duration(elapsed)} / ${duration(length)}` : '-',
230
+ xs: 12,
231
+ },
232
+ // a click on the picture opens the video
233
+ ...(cover
234
+ ? {
235
+ cover: {
236
+ type: 'staticImage',
237
+ src: String(cover),
238
+ href: url ? String(url) : undefined,
239
+ xs: 12,
240
+ sm: 8,
241
+ md: 6,
242
+ },
243
+ }
244
+ : {}),
133
245
  },
134
- }
135
- : null;
246
+ },
247
+ };
136
248
  }
137
249
  async discover(context) {
138
250
  try {
package/build/main.js CHANGED
@@ -44,10 +44,16 @@ const playerStates_1 = require("./lib/playerStates");
44
44
  const registry_1 = require("./lib/registry");
45
45
  /** How long adding a found device waits for the YouTube app to report its screen, in ms */
46
46
  const SCREEN_WAIT = 15_000;
47
+ /** How long a video waits for the YouTube app to join the lounge, e.g. while the TV starts, in ms */
48
+ const APP_WAIT = 30_000;
49
+ /** The same for player commands */
50
+ const COMMAND_WAIT = 5_000;
47
51
  class YouTubeRemote extends utils.Adapter {
48
52
  registry;
49
53
  sessions = new Map();
50
54
  videoInfo = new Map();
55
+ /** Per device the last video asked for, so an older one still waiting for the app is dropped */
56
+ lastTarget = new Map();
51
57
  foundDevices = [];
52
58
  deviceManagement;
53
59
  constructor(options = {}) {
@@ -96,7 +102,9 @@ class YouTubeRemote extends utils.Adapter {
96
102
  await this.registry.remove(id);
97
103
  await this.delObjectAsync(id, { recursive: true });
98
104
  },
99
- play: (id, target) => this.send(id, lounge => lounge.play((0, youtube_lounge_1.parseYouTubeTarget)(target))),
105
+ play: async (id, target) => {
106
+ await this.playTarget(id, target);
107
+ },
100
108
  };
101
109
  }
102
110
  /** Pair with the screen ID the found device reports; the YouTube app is started over DIAL if it has to */
@@ -139,17 +147,13 @@ class YouTubeRemote extends utils.Adapter {
139
147
  void this.setStateAsync(`${device.id}.volume`, volume, true);
140
148
  void this.setStateAsync(`${device.id}.muted`, muted, true);
141
149
  });
142
- session.on('disconnected', () => void this.setStateAsync(`${device.id}.connected`, false, true));
143
- session.on('error', (e) => {
144
- this.log.debug(`${device.name}: ${e.message}`);
145
- void this.setStateAsync(`${device.id}.connected`, false, true);
146
- });
150
+ session.on('screen', (online) => void this.setStateAsync(`${device.id}.connected`, online, true));
151
+ session.on('error', (e) => this.log.debug(`${device.name}: ${e.message}`));
147
152
  this.sessions.set(device.id, session);
148
153
  session.subscribe();
149
154
  }
150
155
  async onNowPlaying(device, playing) {
151
156
  const base = device.id;
152
- await this.setStateAsync(`${base}.connected`, true, true);
153
157
  await this.syncToken(device);
154
158
  await this.setStateAsync(`${base}.state`, (0, playerStates_1.mediaStateOf)(playing), true);
155
159
  await this.setStateAsync(`${base}.playerState`, playing?.state ?? -1, true);
@@ -184,15 +188,33 @@ class YouTubeRemote extends utils.Adapter {
184
188
  await this.registry.save();
185
189
  }
186
190
  }
187
- async send(id, action) {
191
+ /**
192
+ * Send when the YouTube app is in the lounge: Google takes commands without it too, and they get lost.
193
+ *
194
+ * @returns false if `stillWanted` says the command is out of date after waiting
195
+ */
196
+ async send(id, action, wait, stillWanted = () => true) {
188
197
  const lounge = this.sessions.get(id);
189
198
  const device = this.registry.find(id);
190
199
  if (!lounge || !device) {
191
200
  throw new Error(`Unknown device ${id}`);
192
201
  }
202
+ if (!(await lounge.waitForScreen(wait))) {
203
+ throw new Error('the YouTube app is not open');
204
+ }
205
+ if (!stillWanted()) {
206
+ return false;
207
+ }
193
208
  await action(lounge);
194
- await this.setStateAsync(`${id}.connected`, true, true);
195
209
  await this.syncToken(device);
210
+ return true;
211
+ }
212
+ /** A video waits for the app as long as the TV may take to start; a newer one replaces it meanwhile */
213
+ playTarget(id, target) {
214
+ const parsed = (0, youtube_lounge_1.parseYouTubeTarget)(target);
215
+ const call = (this.lastTarget.get(id) ?? 0) + 1;
216
+ this.lastTarget.set(id, call);
217
+ return this.send(id, lounge => lounge.play(parsed), APP_WAIT, () => this.lastTarget.get(id) === call);
196
218
  }
197
219
  onStateChange(id, state) {
198
220
  if (!state || state.ack || !id.startsWith(`${this.namespace}.`)) {
@@ -203,35 +225,33 @@ class YouTubeRemote extends utils.Adapter {
203
225
  return;
204
226
  }
205
227
  this.execute(deviceId, command, state.val)
206
- .then(() => this.acknowledge(id, command, state.val))
228
+ .then(done => (done ? this.acknowledge(id, command, state.val) : undefined))
207
229
  .catch(e => this.log.warn(`${command} on ${this.registry.find(deviceId)?.name}: ${String(e)}`));
208
230
  }
231
+ /** @returns false if a newer command took its place, then the newer one is acknowledged */
209
232
  async execute(id, command, value) {
210
233
  switch (command) {
211
234
  case 'youtube':
212
- if (typeof value === 'string' && value.trim()) {
213
- await this.send(id, lounge => lounge.play((0, youtube_lounge_1.parseYouTubeTarget)(value)));
214
- }
215
- return;
235
+ return typeof value === 'string' && value.trim() ? this.playTarget(id, value) : true;
216
236
  case 'play':
217
237
  case 'pause':
218
238
  case 'next':
219
239
  case 'prev':
220
240
  if (value) {
221
- await this.send(id, lounge => lounge.play({ kind: 'command', command: command === 'prev' ? 'previous' : command }));
241
+ await this.send(id, lounge => lounge.play({ kind: 'command', command: command === 'prev' ? 'previous' : command }), COMMAND_WAIT);
222
242
  }
223
- return;
243
+ return true;
224
244
  case 'seek':
225
- await this.send(id, lounge => lounge.seekTo(Number(value)));
226
- return;
245
+ await this.send(id, lounge => lounge.seekTo(Number(value)), COMMAND_WAIT);
246
+ return true;
227
247
  case 'volume':
228
- await this.send(id, lounge => lounge.setVolume(Number(value)));
229
- return;
248
+ await this.send(id, lounge => lounge.setVolume(Number(value)), COMMAND_WAIT);
249
+ return true;
230
250
  case 'muted':
231
- await this.send(id, lounge => lounge.setMuted(!!value));
232
- return;
251
+ await this.send(id, lounge => lounge.setMuted(!!value), COMMAND_WAIT);
252
+ return true;
233
253
  default:
234
- return;
254
+ return true;
235
255
  }
236
256
  }
237
257
  /** Buttons and the target go back to their rest value, the others keep what was written */
package/io-package.json CHANGED
@@ -1,109 +1,131 @@
1
1
  {
2
- "common": {
3
- "name": "youtube-remote",
4
- "version": "0.0.1",
5
- "news": {
6
- "0.0.1": {
7
- "en": "initial release",
8
- "de": "Erste Version",
9
- "ru": "Первый выпуск",
10
- "pt": "Lançamento inicial",
11
- "nl": "Eerste release",
12
- "fr": "Version initiale",
13
- "it": "Prima versione",
14
- "es": "Versión inicial",
15
- "pl": "Pierwsze wydanie",
16
- "uk": "Перший випуск",
17
- "zh-cn": "首次发布"
18
- }
19
- },
20
- "titleLang": {
21
- "en": "YouTube remote",
22
- "de": "YouTube-Fernbedienung",
23
- "ru": "Пульт YouTube",
24
- "pt": "Controle remoto do YouTube",
25
- "nl": "YouTube-afstandsbediening",
26
- "fr": "Télécommande YouTube",
27
- "it": "Telecomando YouTube",
28
- "es": "Mando de YouTube",
29
- "pl": "Pilot YouTube",
30
- "uk": "Пульт YouTube",
31
- "zh-cn": "YouTube 遥控器"
32
- },
33
- "desc": {
34
- "en": "Controls the YouTube app of TVs, Android TV, Fire TV and consoles: plays videos, playlists and channels and shows what plays",
35
- "de": "Steuert die YouTube-App von Fernsehern, Android TV, Fire TV und Konsolen: spielt Videos, Playlists und Kanäle ab und zeigt, was läuft",
36
- "ru": "Управляет приложением YouTube на телевизорах, Android TV, Fire TV и консолях: воспроизводит видео, плейлисты и каналы и показывает, что играет",
37
- "pt": "Controla o app YouTube de TVs, Android TV, Fire TV e consoles: reproduz vídeos, playlists e canais e mostra o que está tocando",
38
- "nl": "Bedient de YouTube-app van tv's, Android TV, Fire TV en consoles: speelt video's, afspeellijsten en kanalen af en toont wat er speelt",
39
- "fr": "Commande l'application YouTube des téléviseurs, Android TV, Fire TV et consoles : lit des vidéos, playlists et chaînes et affiche ce qui est lu",
40
- "it": "Controlla l'app YouTube di TV, Android TV, Fire TV e console: riproduce video, playlist e canali e mostra cosa è in riproduzione",
41
- "es": "Controla la app de YouTube de televisores, Android TV, Fire TV y consolas: reproduce vídeos, listas y canales y muestra lo que suena",
42
- "pl": "Steruje aplikacją YouTube na telewizorach, Android TV, Fire TV i konsolach: odtwarza filmy, playlisty i kanały oraz pokazuje, co jest odtwarzane",
43
- "uk": "Керує застосунком YouTube на телевізорах, Android TV, Fire TV і консолях: відтворює відео, плейлисти й канали та показує, що грає",
44
- "zh-cn": "控制电视、Android TV、Fire TV 和游戏机上的 YouTube 应用:播放视频、播放列表和频道,并显示正在播放的内容"
45
- },
46
- "authors": ["bluefox <dogafox@gmail.com>"],
47
- "keywords": ["youtube", "tv", "lounge", "dial", "media"],
48
- "licenseInformation": {
49
- "type": "free",
50
- "license": "MIT"
51
- },
52
- "platform": "Javascript/Node.js",
53
- "icon": "youtube-remote.svg",
54
- "enabled": true,
55
- "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.youtube-remote/main/admin/youtube-remote.svg",
56
- "readme": "https://github.com/ioBroker/ioBroker.youtube-remote/blob/main/README.md",
57
- "loglevel": "info",
58
- "tier": 2,
59
- "mode": "daemon",
60
- "type": "multimedia",
61
- "compact": true,
62
- "connectionType": "cloud",
63
- "dataSource": "push",
64
- "adminUI": {
65
- "config": "json"
66
- },
67
- "supportedMessages": {
68
- "deviceManager": true
69
- },
70
- "dependencies": [
71
- {
72
- "js-controller": ">=6.0.11"
73
- }
74
- ],
75
- "globalDependencies": [
76
- {
77
- "admin": ">=7.8.23"
78
- }
79
- ]
2
+ "common": {
3
+ "name": "youtube-remote",
4
+ "version": "0.0.2",
5
+ "news": {
6
+ "0.0.2": {
7
+ "en": "Device cards show state, title and channel, with play, pause, next, previous, volume and mute\nCommands wait for the YouTube app to open; `connected` shows whether it is open",
8
+ "de": "Gerätekarten zeigen Status, Titel und Kanal mit Abspielen, Pause, Zurück, Lautstärke und Stumm\nBefehle warten auf den Start der YouTube-App; `connected` zeigt, ob sie geöffnet ist",
9
+ "ru": "На карточках устройства отображаются состояние, название и канал, а также воспроизведение, пауза, следующее, предыдущее, громкое и отключенное\nКоманды дожидаются открытия приложения YouTube; `connected` показывает, открыто ли оно",
10
+ "pt": "As placas mostram estado, título e canal, com play, pause, next, previous, volume e mute\nComandos aguardam o app do YouTube abrir; `conect` mostra se está aberto",
11
+ "nl": "Apparaatkaarten tonen status, titel en kanaal, met afspelen, pauzeren, volgende, vorige, volume en dempen\nOpdrachten wachten tot de YouTube-app wordt geopend; met `connected` wordt weergegeven of de app is geopend",
12
+ "fr": "Les cartes indiquent l'état, le titre et la chaîne, avec lecture, pause, suivant, volume et sourdine\nLes commandes attendent l'ouverture de l'app YouTube ; « Connected » indique si elle est ouverte",
13
+ "it": "Le schede mostrano stato, titolo e canale, con play, pausa, next, previous, volume e mute\nI comandi attendono l'app YouTube; `connesso` mostra se è aperta",
14
+ "es": "Las tarjetas muestran el estado, el título y el canal, con reproducir, pausar, siguiente, anterior, volumen y silencio\nLos comandos esperan a que se abra la aplicación YouTube; «conectado» muestra si está abierta",
15
+ "pl": "Karty urządzeń pokazują stan, tytuł i kanał, z odtwarzaniem, pauzą, następną, poprzednią, głośnością i wyciszaniem\nPolecenia czekają na otwarcie aplikacji YouTube; `connected` pokazuje, czy jest otwarta",
16
+ "uk": "Картки пристроїв показують стан, назву та канал, з відтворенням, паузою, наступним, попереднім, гучністю та вимкненням звуку\nКоманди чекають відкриття програми YouTube; `connected` показує, чи відкрито воно",
17
+ "zh-cn": "设备卡片显示状态、标题和频道,包括播放、暂停、下一首、上一首、音量和静音\n命令等待 YouTube 应用打开;`connected` 显示它是否已打开"
18
+ },
19
+ "0.0.1": {
20
+ "en": "initial release",
21
+ "de": "Erste Version",
22
+ "ru": "Первый выпуск",
23
+ "pt": "Lançamento inicial",
24
+ "nl": "Eerste release",
25
+ "fr": "Version initiale",
26
+ "it": "Prima versione",
27
+ "es": "Versión inicial",
28
+ "pl": "Pierwsze wydanie",
29
+ "uk": "Перший випуск",
30
+ "zh-cn": "首次发布"
31
+ }
80
32
  },
81
- "native": {
82
- "lookUpTitles": true,
83
- "discoveryTimeout": 4
33
+ "titleLang": {
34
+ "en": "YouTube remote",
35
+ "de": "YouTube-Fernbedienung",
36
+ "ru": "Пульт YouTube",
37
+ "pt": "Controle remoto do YouTube",
38
+ "nl": "YouTube-afstandsbediening",
39
+ "fr": "Télécommande YouTube",
40
+ "it": "Telecomando YouTube",
41
+ "es": "Mando de YouTube",
42
+ "pl": "Pilot YouTube",
43
+ "uk": "Пульт YouTube",
44
+ "zh-cn": "YouTube 遥控器"
84
45
  },
85
- "objects": [],
86
- "instanceObjects": [
87
- {
88
- "_id": "info",
89
- "type": "channel",
90
- "common": {
91
- "name": "Information"
92
- },
93
- "native": {}
94
- },
95
- {
96
- "_id": "info.deviceManager",
97
- "type": "state",
98
- "common": {
99
- "name": "Device Manager communication",
100
- "type": "string",
101
- "role": "json",
102
- "read": true,
103
- "write": false,
104
- "expert": true
105
- },
106
- "native": {}
107
- }
46
+ "desc": {
47
+ "en": "Controls the YouTube app of TVs, Android TV, Fire TV and consoles: plays videos, playlists and channels and shows what plays",
48
+ "de": "Steuert die YouTube-App von Fernsehern, Android TV, Fire TV und Konsolen: spielt Videos, Playlists und Kanäle ab und zeigt, was läuft",
49
+ "ru": "Управляет приложением YouTube на телевизорах, Android TV, Fire TV и консолях: воспроизводит видео, плейлисты и каналы и показывает, что играет",
50
+ "pt": "Controla o app YouTube de TVs, Android TV, Fire TV e consoles: reproduz vídeos, playlists e canais e mostra o que está tocando",
51
+ "nl": "Bedient de YouTube-app van tv's, Android TV, Fire TV en consoles: speelt video's, afspeellijsten en kanalen af en toont wat er speelt",
52
+ "fr": "Commande l'application YouTube des téléviseurs, Android TV, Fire TV et consoles : lit des vidéos, playlists et chaînes et affiche ce qui est lu",
53
+ "it": "Controlla l'app YouTube di TV, Android TV, Fire TV e console: riproduce video, playlist e canali e mostra cosa è in riproduzione",
54
+ "es": "Controla la app de YouTube de televisores, Android TV, Fire TV y consolas: reproduce vídeos, listas y canales y muestra lo que suena",
55
+ "pl": "Steruje aplikacją YouTube na telewizorach, Android TV, Fire TV i konsolach: odtwarza filmy, playlisty i kanały oraz pokazuje, co jest odtwarzane",
56
+ "uk": "Керує застосунком YouTube на телевізорах, Android TV, Fire TV і консолях: відтворює відео, плейлисти й канали та показує, що грає",
57
+ "zh-cn": "控制电视、Android TV、Fire TV 和游戏机上的 YouTube 应用:播放视频、播放列表和频道,并显示正在播放的内容"
58
+ },
59
+ "authors": [
60
+ "bluefox <dogafox@gmail.com>"
61
+ ],
62
+ "keywords": [
63
+ "youtube",
64
+ "tv",
65
+ "lounge",
66
+ "dial",
67
+ "media"
68
+ ],
69
+ "licenseInformation": {
70
+ "type": "free",
71
+ "license": "MIT"
72
+ },
73
+ "platform": "Javascript/Node.js",
74
+ "icon": "youtube-remote.svg",
75
+ "enabled": true,
76
+ "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.youtube-remote/main/admin/youtube-remote.svg",
77
+ "readme": "https://github.com/ioBroker/ioBroker.youtube-remote/blob/main/README.md",
78
+ "loglevel": "info",
79
+ "tier": 2,
80
+ "mode": "daemon",
81
+ "type": "multimedia",
82
+ "compact": true,
83
+ "connectionType": "cloud",
84
+ "dataSource": "push",
85
+ "nogit": true,
86
+ "adminUI": {
87
+ "config": "json"
88
+ },
89
+ "supportedMessages": {
90
+ "deviceManager": true
91
+ },
92
+ "dependencies": [
93
+ {
94
+ "js-controller": ">=6.0.11"
95
+ }
96
+ ],
97
+ "globalDependencies": [
98
+ {
99
+ "admin": ">=7.8.23"
100
+ }
108
101
  ]
102
+ },
103
+ "native": {
104
+ "lookUpTitles": true,
105
+ "discoveryTimeout": 4
106
+ },
107
+ "objects": [],
108
+ "instanceObjects": [
109
+ {
110
+ "_id": "info",
111
+ "type": "channel",
112
+ "common": {
113
+ "name": "Information"
114
+ },
115
+ "native": {}
116
+ },
117
+ {
118
+ "_id": "info.deviceManager",
119
+ "type": "state",
120
+ "common": {
121
+ "name": "Device Manager communication",
122
+ "type": "string",
123
+ "role": "json",
124
+ "read": true,
125
+ "write": false,
126
+ "expert": true
127
+ },
128
+ "native": {}
129
+ }
130
+ ]
109
131
  }
package/package.json CHANGED
@@ -1,68 +1,67 @@
1
1
  {
2
- "name": "iobroker.youtube-remote",
3
- "version": "0.0.1",
4
- "description": "Control the YouTube app of TVs, Android TV, Fire TV and consoles: play videos, playlists and channels, follow what plays",
5
- "author": {
6
- "name": "bluefox",
7
- "email": "dogafox@gmail.com"
8
- },
9
- "homepage": "https://github.com/ioBroker/ioBroker.youtube-remote",
10
- "license": "MIT",
11
- "keywords": [
12
- "ioBroker",
13
- "youtube",
14
- "tv",
15
- "lounge",
16
- "dial",
17
- "media"
18
- ],
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/ioBroker/ioBroker.youtube-remote.git"
22
- },
23
- "engines": {
24
- "node": ">=20"
25
- },
26
- "main": "build/main.js",
27
- "files": [
28
- "admin/",
29
- "build/",
30
- "!build/**/*.map",
31
- "!build/**/*.test.*",
32
- "io-package.json",
33
- "package.json",
34
- "README.md",
35
- "LICENSE"
36
- ],
37
- "dependencies": {
38
- "@iobroker/adapter-core": "^3.4.3",
39
- "@iobroker/dm-utils": "^3.2.1",
40
- "@iobroker/youtube-lounge": "^0.1.0"
41
- },
42
- "devDependencies": {
43
- "@alcalzone/release-script": "^5.2.1",
44
- "@alcalzone/release-script-plugin-iobroker": "^5.2.0",
45
- "@alcalzone/release-script-plugin-license": "^5.2.2",
46
- "@alcalzone/release-script-plugin-manual-review": "^5.2.0",
47
- "@iobroker/adapter-dev": "^1.5.0",
48
- "@iobroker/eslint-config": "^2.3.4",
49
- "@iobroker/testing": "^6.3.0",
50
- "@tsconfig/node22": "^22.0.6",
51
- "@types/mocha": "^10.0.10",
52
- "@types/node": "^22.20.5",
53
- "globals": "^17.13.0",
54
- "typescript": "~5.9.3"
55
- },
56
- "scripts": {
57
- "build": "tsc -p tsconfig.build.json",
58
- "check": "tsc -p tsconfig.json --noEmit",
59
- "pretest": "npm run build",
60
- "test:unit": "node scripts/run-mocha.js --config test/mocharc.custom.json \"build/**/*.test.js\" --exit",
61
- "test:package": "node scripts/run-mocha.js test/package --exit",
62
- "test:integration": "node scripts/run-mocha.js test/integration --exit",
63
- "test": "npm run test:unit && npm run test:package",
64
- "lint": "eslint -c eslint.config.mjs .",
65
- "translate": "translate-adapter",
66
- "release": "release-script"
67
- }
2
+ "name": "iobroker.youtube-remote",
3
+ "version": "0.0.2",
4
+ "description": "Control the YouTube app of TVs, Android TV, Fire TV and consoles: play videos, playlists and channels, follow what plays",
5
+ "author": {
6
+ "name": "bluefox",
7
+ "email": "dogafox@gmail.com"
8
+ },
9
+ "homepage": "https://github.com/ioBroker/ioBroker.youtube-remote",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "ioBroker",
13
+ "youtube",
14
+ "tv",
15
+ "lounge",
16
+ "dial",
17
+ "media"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ioBroker/ioBroker.youtube-remote.git"
22
+ },
23
+ "engines": {
24
+ "node": ">=22"
25
+ },
26
+ "main": "build/main.js",
27
+ "files": [
28
+ "admin/",
29
+ "build/",
30
+ "!build/**/*.map",
31
+ "!build/**/*.test.*",
32
+ "io-package.json",
33
+ "package.json",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "dependencies": {
38
+ "@iobroker/adapter-core": "^3.4.3",
39
+ "@iobroker/dm-utils": "^3.2.1",
40
+ "@iobroker/youtube-lounge": "^0.2.0"
41
+ },
42
+ "devDependencies": {
43
+ "@alcalzone/release-script": "^5.2.1",
44
+ "@alcalzone/release-script-plugin-iobroker": "^5.2.0",
45
+ "@alcalzone/release-script-plugin-license": "^5.2.2",
46
+ "@alcalzone/release-script-plugin-manual-review": "^5.2.0",
47
+ "@iobroker/adapter-dev": "^1.5.0",
48
+ "@iobroker/eslint-config": "^2.3.4",
49
+ "@iobroker/testing": "^6.3.0",
50
+ "@tsconfig/node22": "^22.0.6",
51
+ "@types/node": "^22.20.5",
52
+ "globals": "^17.13.0",
53
+ "typescript": "~5.9.3"
54
+ },
55
+ "scripts": {
56
+ "build": "tsc -p tsconfig.build.json",
57
+ "check": "tsc -p tsconfig.json --noEmit",
58
+ "pretest": "npm run build",
59
+ "test:unit": "node scripts/run-mocha.js --config test/mocharc.custom.json \"build/**/*.test.js\" --exit",
60
+ "test:package": "node scripts/run-mocha.js test/package --exit",
61
+ "test:integration": "node scripts/run-mocha.js test/integration --exit",
62
+ "test": "npm run test:unit && npm run test:package",
63
+ "lint": "eslint -c eslint.config.mjs .",
64
+ "translate": "translate-adapter",
65
+ "release": "release-script"
66
+ }
68
67
  }