@tplnet/homebridge-denon-heos-audio 1.2.0 → 1.2.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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes of this fork. The fork is based on [hov3rcraft/homebridge-denon-heos-audio](https://github.com/hov3rcraft/homebridge-denon-heos-audio) 1.0.0-beta.1.
4
4
 
5
+ ## 1.2.2
6
+
7
+ ### Fixed
8
+
9
+ - The tile in the Homebridge UI / Home app no longer jumps back to "off" while switching on takes a few seconds: HomeKit gets the answer immediately and switching continues in the background. If switching fails, the tile shows the real state again and the error is logged.
10
+ - A radio station that needs a few seconds to buffer is no longer replaced by the power on favorite: the plugin now waits up to 8 seconds (before: 3) for playback to start.
11
+
12
+ ## 1.2.1
13
+
14
+ ### Fixed
15
+
16
+ - When switching on took a few seconds (e.g. while the power on favorite was being started), the tile briefly showed "off" before it switched to "on". The requested state is now reported from the moment of the tap, and intermediate states of the device are ignored until switching has finished.
17
+ - The player list was requested twice per device at startup.
18
+
5
19
  ## 1.2.0
6
20
 
7
21
  ### New
package/README.md CHANGED
@@ -56,7 +56,7 @@ Not sure what your device supports? Turn on [device discovery](#1-find-your-devi
56
56
  | **Hybrid** | AV receivers and amplifiers with HEOS (e.g. Marantz Cinema series, Denon AVR-X, Denon Home Amp) | Real power on / standby | ✅ | ✅ |
57
57
 
58
58
  **Why does "power" mean "playback" with HEOS CLI?** HEOS speakers have no real standby command. The plugin therefore treats "playing" as on and "stopped/paused" as off.
59
- Switching on resumes the last source. If there is nothing to resume (e.g. after the speaker was restarted or a streaming session ended), or playback doesn't start within 3 seconds, the first HEOS favorite is started, so switching on also works in scenes and automations. You can choose another favorite or turn this off with `powerOnFavorite`.
59
+ Switching on resumes the last source. If there is nothing to resume (e.g. after the speaker was restarted or a streaming session ended), or playback doesn't start within 8 seconds, the first HEOS favorite is started, so switching on also works in scenes and automations. You can choose another favorite or turn this off with `powerOnFavorite`.
60
60
 
61
61
  > **Requirement:** at least one favorite in the HEOS app (*Music → Favorites*, e.g. a radio station). Without a favorite, a speaker that has nothing to resume can't be switched on from HomeKit.
62
62
 
@@ -204,7 +204,10 @@ export declare class DenonClientHeosCli extends DenonClient {
204
204
  private infoLog;
205
205
  private player_name;
206
206
  constructor(serialNumber: string, name: string, host: string, connect_timeout: number, response_timeout: number, debugLogCallback?: (message: string, ...parameters: any[]) => void, powerUpdateCallback?: (power: boolean) => void, muteUpdateCallback?: (mute: boolean) => void, volumeUpdateCallback?: (volume: number) => void, inputUpdateCallback?: (input: string) => void);
207
+ private playerIdLookup;
208
+ /** Looks up the player id; concurrent calls share one request. */
207
209
  private findPlayerId;
210
+ private findPlayerIdUncached;
208
211
  protected subscribeToChangeEvents(): Promise<void>;
209
212
  protected sendCommand(command: ProtocolCommand, commandMode: CommandMode, { value, passPayload }: {
210
213
  value?: string;
@@ -248,7 +248,8 @@ export class DenonClientHeosCli extends DenonClient {
248
248
  player_id;
249
249
  /** HEOS favorite (1, 2, …) that is started when switching on without something to resume; 0 = disabled. */
250
250
  powerOnFavorite = 1;
251
- static PLAY_VERIFY_CHECKS = 3;
251
+ // radio streams may need a few seconds of buffering before the device reports "play"
252
+ static PLAY_VERIFY_CHECKS = 8;
252
253
  playVerifyIntervalMs = 1000;
253
254
  /** Log for actions the user should see without debug logging (e.g. starting the power on favorite). */
254
255
  infoLog = (message) => this.debugLog(message);
@@ -275,7 +276,15 @@ export class DenonClientHeosCli extends DenonClient {
275
276
  }
276
277
  });
277
278
  }
278
- async findPlayerId() {
279
+ playerIdLookup;
280
+ /** Looks up the player id; concurrent calls share one request. */
281
+ findPlayerId() {
282
+ this.playerIdLookup ??= this.findPlayerIdUncached().finally(() => {
283
+ this.playerIdLookup = undefined;
284
+ });
285
+ return this.playerIdLookup;
286
+ }
287
+ async findPlayerIdUncached() {
279
288
  const payload_str = await super.sendCommand(DenonClientHeosCli.PROTOCOL.PLAYERS, CommandMode.GET, { passPayload: true });
280
289
  const payload = JSON.parse(payload_str);
281
290
  if (!Array.isArray(payload)) {
@@ -38,6 +38,9 @@ export declare class DenonAudioAccessory {
38
38
  private targetMediaState;
39
39
  private volumeChangeOngoing;
40
40
  private pendingActive;
41
+ private powerChangeInProgress;
42
+ /** The power change running in the background (for tests). */
43
+ private powerChange;
41
44
  private playingChangeOngoing;
42
45
  constructor(platform: DenonAudioPlatform, accessory: PlatformAccessory, config: DeviceConfig, log: Logger);
43
46
  /**
@@ -44,6 +44,9 @@ export class DenonAudioAccessory {
44
44
  targetMediaState;
45
45
  volumeChangeOngoing = false;
46
46
  pendingActive;
47
+ powerChangeInProgress = false;
48
+ /** The power change running in the background (for tests). */
49
+ powerChange;
47
50
  playingChangeOngoing = false;
48
51
  constructor(platform, accessory, config, log) {
49
52
  log.debug("Initializing DenonAudioAccessory...");
@@ -312,15 +315,27 @@ export class DenonAudioAccessory {
312
315
  async setActive(newValue) {
313
316
  this.log.debug(`setActive for ${this.name} set to ${newValue}`);
314
317
  const active = newValue === this.platform.Characteristic.Active.ACTIVE;
315
- try {
316
- await this.denonClient.setPower(active);
318
+ // Report the requested state right away: switching on can take a few seconds (e.g. when the power on favorite is
319
+ // started), and the device reports intermediate states during that time.
320
+ this.pendingActive = { value: active, until: Number.POSITIVE_INFINITY };
321
+ this.powerChangeInProgress = true;
322
+ // HomeKit gets the answer immediately; switching continues in the background. Otherwise HomeKit / the Homebridge UI
323
+ // would show the old state until switching on has finished (which can take several seconds).
324
+ this.powerChange = this.denonClient
325
+ .setPower(active)
326
+ .then(() => {
317
327
  this.pendingActive = { value: active, until: Date.now() + DenonAudioAccessory.POWER_STATE_SETTLE_TIME };
318
- }
319
- catch (error) {
328
+ })
329
+ .catch(async (error) => {
320
330
  this.pendingActive = undefined;
321
331
  this.log.error(`An error occured while setting power status for ${this.name}.`, error);
322
- throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
323
- }
332
+ // show the real state again
333
+ const deviceState = await this.denonClient.getPower().catch(() => !active);
334
+ this.tvService.updateCharacteristic(this.platform.Characteristic.Active, deviceState);
335
+ })
336
+ .finally(() => {
337
+ this.powerChangeInProgress = false;
338
+ });
324
339
  }
325
340
  async setActiveIdentifier(newValue) {
326
341
  this.log.debug(`setActiveIdentifier for ${this.name} set to ${newValue}`);
@@ -542,6 +557,10 @@ export class DenonAudioAccessory {
542
557
  * Handles characteristic updates from the API
543
558
  */
544
559
  callbackActive(active) {
560
+ if (this.powerChangeInProgress && this.pendingActive && this.pendingActive.value !== active) {
561
+ this.log.debug(`Ignoring intermediate power state ${active} of ${this.name} while switching.`);
562
+ return;
563
+ }
545
564
  if (this.pendingActive && this.pendingActive.value !== active) {
546
565
  // changed on the device (e.g. in the HEOS app) - the device state wins
547
566
  this.pendingActive = undefined;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tplnet/homebridge-denon-heos-audio",
3
3
  "displayName": "Homebridge Denon Heos Audio (tplnet)",
4
4
  "type": "module",
5
- "version": "1.2.0",
5
+ "version": "1.2.2",
6
6
  "description": "Control your Denon and Marantz HEOS speakers, amplifiers and AVRs via AVR Control and/or HEOS CLI",
7
7
  "author": "tplnet",
8
8
  "contributors": [