Skip to content

Commit

Permalink
[spotify] Various enhancements and fixes (openhab#11370)
Browse files Browse the repository at this point in the history
Enhancements:
- Added play actions to start a track or other via a rule.
- Added albumImageUrl with the url to the album image to not have to send the whole image via the openHAB eventbus.
- Added a parameter imageIndex to set the image size to use (0, 1 or 2) to channels albumImage and albumImageUrl.
- Added offset and limit parameters to playlists channel (Closes openhab#6843).

Fixes:
- Fixed invalid expire value set on ExpiringCache (Closes openhab#10490).
- Improved handling Spotify API error messages (Closes openhab#11308).
- Added TTL to discovery results to fix duplicated Spotify Connect devices reported in the inbox (Closes openhab#7400).
- Fixed device list update problem for devices. If no devices where configured as separate things (or the devices added where offline, but non added where not offline) the list was not updated.

Signed-off-by: Hilbrand Bouwkamp <[email protected]>
  • Loading branch information
Hilbrand authored and Nemer_Daud committed Jan 28, 2022
1 parent 336f1ed commit 4ebee9d
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 89 deletions.
64 changes: 51 additions & 13 deletions bundles/org.openhab.binding.spotify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,22 @@ __Common Channels:__
| playlistName | String | Read-write | The currently playing playlist. Or empty if no playing list is playing. |
| albumName | String | Read-only | Album Name of the currently playing track. |
| albumImage | RawType | Read-only | Album Image of the currently playing track. |
| albumImageUrl | String | Read-only | Url to the album Image of the currently playing track. |
| artistName | String | Read-only | Artist Name of the currently playing track. |

The `playlists` channel has 2 parameters:

| Parameter | Description |
|-----------|----------------------------------------------------------------------------|
| offset | The index of the first playlist to return. Default `0`, max `100.000` |
| limit | The maximum number of playlists to return. Default `20`, min `1`, max `50` |

The `albumImage` and `albumImageUrl` channels has 1 parameter:

| Parameter | Description |
|------------|--------------------------------------------------------------------------------------------|
| imageIndex | Index in list of to select size of the image to show. 0:large (default), 1:medium, 2:small |

Note: The `deviceName` and `playlist` channels are Selection channels.
They are dynamically populated by the binding with the user specific devices and playlists.

Expand Down Expand Up @@ -163,6 +177,19 @@ __Advanced Channels:__
| deviceActive | Switch | Read-only | Indicates if the device is active or not. Should be the same as Thing status ONLINE/OFFLINE. |
| deviceRestricted | Switch | Read-only | Indicates if this device allows to be controlled by the API or not. If restricted it cannot be controlled. |

### Actions

The bridge supports an action to play a track or other context uri.
The following actions are supported:

```
play(String context_uri)
play(String context_uri, int offset, int position_ms)
play(String context_uri, String device_id)
play(String context_uri, String device_id, int offset, int position_ms)
```


## Full Example

In this example there is a bridge configured with Thing ID __user1__ and illustrating that the bridge is authorized to play in the context of the Spotify user account __user1__.
Expand All @@ -174,24 +201,27 @@ Bridge spotify:player:user1 "Me" [clientId="<your client id>", clientSecret="<yo
Things:
device device1 "Device 1" [deviceName="<spotify device name>"]
device device2 "Device 2" [deviceName="<spotify device name>"]
Channels:
String : playlists [limit=50]
String : albumImageUrl [imageIndex=1]
}
```

spotify.items:

```
Player spotifyTrackPlayer "Player" {channel="spotify:player:user1:trackPlayer"}
String spotifyDevices "Active device [%s]" {channel="spotify:player:user1:devices"}
Switch spotifyDeviceShuffle "Shuffle mode" {channel="spotify:player:user1:deviceShuffle"}
String spotifyTrackRepeat "Repeat mode: [%s]" {channel="spotify:player:user1:trackRepeat"}
String spotifyTrackProgress "Track progress: [%s]" {channel="spotify:player:user1:trackProgress"}
String spotifyTrackDuration "Track duration: [%s]" {channel="spotify:player:user1:trackDuration"}
String spotifyTrackName "Track Name: [%s]" {channel="spotify:player:user1:trackName"}
String spotifyAlbumName "Album Name: [%s]" {channel="spotify:player:user1:albumName"}
String spotifyArtistName "Artist Name: [%s]" {channel="spotify:player:user1:artistName"}
Image spotifyAlbumImage "Album Art" {channel="spotify:player:user1:albumImage"}
String spotifyPlaylists "Playlists [%s]" {channel="spotify:player:user1:playlists"}
String spotifyPlayName "Playlist [%s]" {channel="spotify:player:user1:playlistName"}
Player spotifyTrackPlayer "Player" {channel="spotify:player:user1:trackPlayer"}
String spotifyDevices "Active device [%s]" {channel="spotify:player:user1:devices"}
Switch spotifyDeviceShuffle "Shuffle mode" {channel="spotify:player:user1:deviceShuffle"}
String spotifyTrackRepeat "Repeat mode: [%s]" {channel="spotify:player:user1:trackRepeat"}
String spotifyTrackProgress "Track progress: [%s]" {channel="spotify:player:user1:trackProgress"}
String spotifyTrackDuration "Track duration: [%s]" {channel="spotify:player:user1:trackDuration"}
String spotifyTrackName "Track Name: [%s]" {channel="spotify:player:user1:trackName"}
String spotifyAlbumName "Album Name: [%s]" {channel="spotify:player:user1:albumName"}
String spotifyArtistName "Artist Name: [%s]" {channel="spotify:player:user1:artistName"}
String spotifyAlbumImageUrl "Album Art" {channel="spotify:player:user1:albumImageUrl"}
String spotifyPlaylists "Playlists [%s]" {channel="spotify:player:user1:playlists"}
String spotifyPlayName "Playlist [%s]" {channel="spotify:player:user1:playlistName"}
String device1DeviceName {channel="spotify:device:user1:device1:deviceName"}
Player device1Player {channel="spotify:device:user1:device1:devicePlayer"}
Expand All @@ -217,7 +247,7 @@ sitemap spotify label="Spotify Sitemap" {
Text item=spotifyTrackProgress label="Track progress: [%s]"
Text item=spotifyTrackDuration label="Track duration: [%s]"
Text item=spotifyTrackName label="Track Name: [%s]"
Image item=spotifyAlbumImage label="Album Art"
Image item=spotifyAlbumImageUrl label="Album Art"
Text item=spotifyAlbumName label="Currently Played Album Name: [%s]"
Text item=spotifyArtistName label="Currently Played Artist Name: [%s]"
Selection item=spotifyPlaylists label="Playlist" icon="music"
Expand All @@ -239,6 +269,14 @@ sitemap spotify label="Spotify Sitemap" {
}
```

spotify.rules

```
val spotifyActions = getActions("spotify", "spotify:player:user1")
// play the song
spotifyActions.play("spotify:track:4cOdK2wGLETKBW3PvgPWqT")
```

## Binding model and Spotify Web API

The model of the binding is such that the bridge acts as a player in the context of a specific user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public class SpotifyBindingConstants {

public static final String CHANNEL_PLAYLISTS = "playlists";
public static final String CHANNEL_PLAYLISTNAME = "playlistName";
public static final String CHANNEL_PLAYLISTS_LIMIT = "limit";
public static final String CHANNEL_PLAYLISTS_OFFSET = "offset";

public static final String CHANNEL_PLAYED_TRACKID = "trackId";
public static final String CHANNEL_PLAYED_TRACKURI = "trackUri";
Expand All @@ -76,6 +78,8 @@ public class SpotifyBindingConstants {
public static final String CHANNEL_PLAYED_ALBUMNAME = "albumName";
public static final String CHANNEL_PLAYED_ALBUMTYPE = "albumType";
public static final String CHANNEL_PLAYED_ALBUMIMAGE = "albumImage";
public static final String CHANNEL_PLAYED_ALBUMIMAGEURL = "albumImageUrl";
public static final String CHANNEL_CONFIG_IMAGE_INDEX = "imageIndex";

public static final String CHANNEL_PLAYED_ARTISTID = "artistId";
public static final String CHANNEL_PLAYED_ARTISTURI = "artistUri";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.spotify.internal.actions;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.spotify.internal.handler.SpotifyBridgeHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;

/**
* Spotify Rule Actions.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@ThingActionsScope(name = "spotify")
@NonNullByDefault
public class SpotifyActions implements ThingActions, ThingHandlerService {

private @Nullable ThingHandler handler;

/**
* Play a context uri (track or other) on the current active device (if null is passed for deviceID) or the given
* device at the given offset and/or position in milliseconds.
*
* @param actions Spotify Actions object.
* @param contextUri context uri (track or other)
* @param deviceId Id of the device to play on, or current device if given null
* @param offset Offset in the list, default 0.
* @param positionMs position in the track in milliseconds, default 0,
*/
@RuleAction(label = "@text/actions.play.label", description = "@text/actions.play.description")
public void play(
@ActionInput(name = "contextUri", label = "@text/actions.play.context_uri.label", description = "@text/actions.play.context_uri.description", type = "java.lang.String", required = true) String contextUri,
@ActionInput(name = "deviceId", label = "@text/actions.play.device_id.label", description = "@text/actions.play.device_id.description", type = "java.lang.String", defaultValue = "") @Nullable String deviceId,
@ActionInput(name = "offset", label = "@text/actions.play.offset.label", description = "@text/actions.play.offset.description", type = "java.lang.Integer", defaultValue = "0") final int offset,
@ActionInput(name = "positionMs", label = "@text/actions.play.positions_ms.label", description = "@text/actions.play.positions_ms.description", type = "java.lang.Integer", defaultValue = "0") final int positionMs) {
((SpotifyBridgeHandler) getThingHandler()).getSpotifyApi().playTrack(deviceId == null ? "" : deviceId,
contextUri, offset, positionMs);
}

/**
* Play a context uri (track or other) on the current active device.
*
* @param actions Spotify Actions object.
* @param contextUri context uri (track or other)
*/
public static void play(ThingActions actions, String contextUri) {
((SpotifyActions) actions).play(contextUri, null, 0, 0);
}

/**
* Play a context uri (track or other) on the current active device at the given offset and/or position in
* milliseconds.
*
* @param actions Spotify Actions object.
* @param contextUri context uri (track or other)
* @param offset Offset in the list, default 0.
* @param positionMs position in the track in milliseconds, default 0,
*/
public static void play(ThingActions actions, String contextUri, final int offset, final int positionMs) {
((SpotifyActions) actions).play(contextUri, null, positionMs, positionMs);
}

/**
* Play a context uri (track or other) on the given device.
*
* @param actions Spotify Actions object.
* @param contextUri context uri (track or other)
* @param deviceId Id of the device to play on, or current device if given null
*/
public static void play(ThingActions actions, String contextUri, @Nullable String deviceId) {
((SpotifyActions) actions).play(contextUri, deviceId, 0, 0);
}

/**
* Play a context uri (track or other) on the current active device (if null is passed for deviceID) or the given
* device at the given offset and/or position in milliseconds.
*
* @param actions Spotify Actions object.
* @param contextUri context uri (track or other)
* @param deviceId Id of the device to play on, or current device if given null
* @param offset Offset in the list, default 0.
* @param positionMs position in the track in milliseconds, default 0,
*/
public static void play(ThingActions actions, String contextUri, @Nullable String deviceId, final int offset,
final int positionMs) {
((SpotifyActions) actions).play(contextUri, deviceId, positionMs, positionMs);
}

@Override
public void setThingHandler(ThingHandler handler) {
this.handler = handler;
}

@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
}
Loading

0 comments on commit 4ebee9d

Please sign in to comment.