From 69e2b1e5e052abee0c313d9a2d33c5b26d718371 Mon Sep 17 00:00:00 2001 From: poblouin Date: Mon, 25 Apr 2022 22:01:22 -0400 Subject: [PATCH] Allow speaker with no associated playlist ID --- README.md | 18 +++++++++------- config.schema.json | 3 +-- package-lock.json | 4 ++-- package.json | 2 +- src/platform.ts | 6 ++++++ src/spotify-speaker-accessory.ts | 37 +++++++++++++++++++++++++++----- 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 62d12b7..19f9b89 100644 --- a/README.md +++ b/README.md @@ -36,14 +36,14 @@ This is Homekit's behaviour to set back the brightness to 100% when a device is To use this plugin you must provide some authentication information to Spotify and those steps has to be done manually. 1. Create a Spotify application in the developer's dashboard - - To do so, go to https://developer.spotify.com/dashboard and create an app. Once this is done you will have your clientId and clientSecret. + + To do so, go to [Spotify Dev Dashboard](https://developer.spotify.com/dashboard) and create an app. Once this is done you will have your clientId and clientSecret. 2. Obtain the auth code that this plugin will need to generate the access and refresh tokens To do so, you need to allow access to the app you created at the previous step. You only need to do this once. - ``` + ```md https://accounts.spotify.com/authorize?client_id={clientId}&response_type=code&redirect_uri=https://example.com/callback&scope={scopes} ``` @@ -57,11 +57,12 @@ To use this plugin you must provide some authentication information to Spotify a - You will have a small agreement form, simply accept it. - Then you will be redirected and you will find your code in the URL - ``` + ```md Example, you will get an URL that looks like the following. The code is everything that follows `code=`. https://example.com/callback?code=AQDPqT0ctdUm-uE2JRwbAoaWA-iRm0OuGY7wI17zQUlTxw7JfRma6id1mq-m8xKH6vJVNutJSqQcBrPZ__81uF-hrSJ-q_AX2yUEwERQKTnaPLDFCIE-c_qBjg81JSd5FqmEpJ5j9ddgKvkWUJ6WK5Kj-npTypCrUoQWRn9Vkn33DlYOfU7BxgPAPQBXQtqIfub3S576-gdUOGUAGPd6Ud5esSNMeI2lFKb-sj4eMiQJJJb35VI__EkRuFFJNCZkFagr3rBI-GGzfQA - ``` + ``` + 3. Take the code obtained at step #2 and put it in your homebridge `config.json` as the value of the attribute `spotifyAuthCode`. Once that is done, restart Homebridge and you should be up and running. Look at the logs for ay errors. For more details, see the [official auth documentation](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow) @@ -95,9 +96,10 @@ If you run into issues or you need help please use the [issues template](https:/ If you haven't done it already, start by reading [this section](#distinction-between-spotify-connect-and-spotify-connect-api). Common issues related to that though could be: - - The Homebridge instance on which this plugin is installed is not on the same network as the Spotify device - - The Spotify device is currently tied to another user account. Example, you authenticated this plugin using your account, and the Spotify device was last played with your SO's account. - - The device is in sleep mode. Any devices that sleeps (e.g. a computer) won't be available via the API. + +- The Homebridge instance on which this plugin is installed is not on the same network as the Spotify device +- The Spotify device is currently tied to another user account. Example, you authenticated this plugin using your account, and the Spotify device was last played with your SO's account. +- The device is in sleep mode. Any devices that sleeps (e.g. a computer) won't be available via the API. ## Contributors diff --git a/config.schema.json b/config.schema.json index 286cecd..7350076 100644 --- a/config.schema.json +++ b/config.schema.json @@ -55,8 +55,7 @@ "spotifyPlaylistUrl": { "title": "Spotify Playlist URL", "description": "The URL of the Spotify playlist that you want to play when this device will be set to ON in Homekit.", - "type": "string", - "required": true + "type": "string" } } } diff --git a/package-lock.json b/package-lock.json index 8268e32..29553fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@poblouin/homebridge-spotify-speaker", - "version": "1.1.1", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@poblouin/homebridge-spotify-speaker", - "version": "1.1.1", + "version": "1.2.0", "license": "MIT", "dependencies": { "spotify-web-api-node": "^5.0.2" diff --git a/package.json b/package.json index 78a5be2..e69e393 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "Homebridge Spotify Speaker", "name": "@poblouin/homebridge-spotify-speaker", - "version": "1.1.1", + "version": "1.2.0", "description": "Homebridge plugin that creates a speaker that plays a specific Spotify playlist", "license": "MIT", "author": "Pierre-Olivier Blouin ", diff --git a/src/platform.ts b/src/platform.ts index e71b478..2a789a1 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -99,6 +99,12 @@ export class HomebridgeSpotifySpeakerPlatform implements DynamicPlatformPlugin { private extractPlaylistId(playlistUrl: string): string | null { try { + // Empty playlist ID is allowed for cases where one wants to only + // play or pause one speaker started from an external source. + if (!playlistUrl) { + return null; + } + const url = new URL(playlistUrl); const playlistId = url.pathname.split('/')[2]; this.log.debug(`Found playlistId: ${playlistId}`); diff --git a/src/spotify-speaker-accessory.ts b/src/spotify-speaker-accessory.ts index d188834..243f072 100644 --- a/src/spotify-speaker-accessory.ts +++ b/src/spotify-speaker-accessory.ts @@ -111,19 +111,46 @@ export class SpotifySpeakerAccessory { private async setCurrentStates() { const state = await this.platform.spotifyApiWrapper.getPlaybackState(); + const playingHref = state?.body?.context?.href; + const playingDeviceId = state?.body?.device?.id; - // Make sure that this accessory is the one playing i.e. the playlist ID - // playing is the one from this accessory. - if (!state?.body?.context?.href?.includes(this.accessory.context.playlistId)) { + if (!state || state.statusCode !== 200) { + this.activeState = false; + this.currentVolume = 0; return; } - if (state?.statusCode === 200) { + if (state.body.is_playing && this.isPlaying(playingHref, playingDeviceId)) { this.activeState = state.body.is_playing; this.currentVolume = state.body.device.volume_percent; - } else if (!state || state.statusCode === 204) { + } else { this.activeState = false; this.currentVolume = 0; } } + + /** + * Finds which speaker should be synced. + * + * Speakers tied to a playlist will have priority over lone + * speakers (no playlist ID associated). + * + * @param playingHref The href of the currently playing Spotify playlist + * @param playingDeviceId The spotify device ID + */ + private isPlaying(playingHref: string | undefined, playingDeviceId: string | undefined) { + const contextPlaylistId = this.accessory.context.playlistId; + + if (contextPlaylistId && playingHref?.includes(contextPlaylistId)) { + return true; + } + + const currentDevicePlaying = this.device.spotifyDeviceId === playingDeviceId; + const hasHigherPrioritySpeaker = this.platform.accessories.find((a) => playingHref?.includes(a.context.playlistId)); + if (currentDevicePlaying && !contextPlaylistId && !hasHigherPrioritySpeaker) { + return true; + } + + return false; + } }