Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[squeezebox] Implement like/unlike for remote streaming services #7396

Merged
merged 5 commits into from
Jul 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 18 additions & 3 deletions bundles/org.openhab.binding.squeezebox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ All devices support some of the following channels:
| ircode | String | Received IR code |
| numberPlaylistTracks | Number | Number of playlist tracks |
| playFavorite | String | ID of Favorite to play (channel's state options contains available favorites) |
| rate | Switch | "Like" or "unlike" the currently playing song (if supported by the streaming service) |

## Playing Favorites

Expand Down Expand Up @@ -197,14 +198,28 @@ then
end
```

## Rating Songs

Some streaming services, such as Pandora and Slacker, all songs to be rated.
When playing from these streaming services, sending commands to the `rate` channel can be used to *like* or *unlike* the currently playing song.
Sending the ON command will *like* the song.
Sending the OFF command will *unlike* the song.
If the streaming service doesn't support rating, sending commands to the `rate` channel has no effect.

### Known Issues

- There are some versions of squeezelite that will not correctly play very short duration mp3 files. Versions of squeezelite after v1.7 and before v1.8.6 will not play very short duration mp3 files reliably. For example, if you're using piCorePlayer (which uses squeezelite), please check your version of squeezelite if you're having trouble playing notifications. This bug has been fixed in squeezelite version 1.8.6-985, which is included in piCorePlayer version 3.20.
- There are some versions of squeezelite that will not correctly play very short duration mp3 files.
Versions of squeezelite after v1.7 and before v1.8.6 will not play very short duration mp3 files reliably.
For example, if you're using piCorePlayer (which uses squeezelite), please check your version of squeezelite if you're having trouble playing notifications.
This bug has been fixed in squeezelite version 1.8.6-985, which is included in piCorePlayer version 3.20.

- When streaming from a remote service (such as Pandora or Spotify), after the notification plays, the Squeezebox Server starts playing a new track, instead of picking up from where it left off on the currently playing track.

- There have been reports that notifications do not play reliably, or do not play at all, when using Logitech Media Server (LMS) version 7.7.5. Therefore, it is recommended that the LMS be on a more current version than 7.7.5.
- There have been reports that notifications do not play reliably, or do not play at all, when using Logitech Media Server (LMS) version 7.7.5.
Therefore, it is recommended that the LMS be on a more current version than 7.7.5.

- There have been reports that the LMS does not play some WAV files reliably. If you're using a TTS service that produces WAV files, and the notifications are not playing, try using an MP3-formatted TTS notification.
This issue reportedly was [fixed in the LMS](https://github.com/Logitech/slimserver/issues/307) by accepting additional MIME types for WAV files.

- The LMS treats player MAC addresses as case-sensitive. Therefore, the case of MAC addresses in the Squeeze Player thing configuration must match the case displayed on the *Information* tab in the LMS Settings.
- The LMS treats player MAC addresses as case-sensitive.
Therefore, the case of MAC addresses in the Squeeze Player thing configuration must match the case displayed on the *Information* tab in the LMS Settings.
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,5 @@ public class SqueezeBoxBindingConstants {
public static final String CHANNEL_NAME = "name";
public static final String CHANNEL_MODEL = "model";
public static final String CHANNEL_FAVORITES_PLAY = "playFavorite";
public static final String CHANNEL_RATE = "rate";
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxServerHandler;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
Expand All @@ -65,16 +66,24 @@ public class SqueezeBoxHandlerFactory extends BaseThingHandlerFactory {

private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();

private AudioHTTPServer audioHTTPServer;
private NetworkAddressService networkAddressService;
private final AudioHTTPServer audioHTTPServer;
private final NetworkAddressService networkAddressService;
private final SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider;

private Map<String, ServiceRegistration<AudioSink>> audioSinkRegistrations = new ConcurrentHashMap<>();

private SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider;

// Callback url (scheme+server+port) to use for playing notification sounds
private String callbackUrl = null;

@Activate
public SqueezeBoxHandlerFactory(@Reference AudioHTTPServer audioHTTPServer,
@Reference NetworkAddressService networkAddressService,
@Reference SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider) {
this.audioHTTPServer = audioHTTPServer;
this.networkAddressService = networkAddressService;
this.stateDescriptionProvider = stateDescriptionProvider;
}

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
Expand Down Expand Up @@ -197,31 +206,4 @@ private String createCallbackUrl() {

return "http://" + ipAddress + ":" + port;
}

@Reference
protected void setAudioHTTPServer(AudioHTTPServer audioHTTPServer) {
this.audioHTTPServer = audioHTTPServer;
}

protected void unsetAudioHTTPServer(AudioHTTPServer audioHTTPServer) {
this.audioHTTPServer = null;
}

@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}

protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}

@Reference
protected void setDynamicStateDescriptionProvider(SqueezeBoxStateDescriptionOptionsProvider provider) {
this.stateDescriptionProvider = provider;
}

protected void unsetDynamicStateDescriptionProvider(SqueezeBoxStateDescriptionOptionsProvider provider) {
this.stateDescriptionProvider = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.eclipse.smarthome.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.eclipse.smarthome.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.eclipse.smarthome.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

Expand All @@ -29,14 +30,9 @@
@NonNullByDefault
public class SqueezeBoxStateDescriptionOptionsProvider extends BaseDynamicStateDescriptionProvider {

@Reference
protected void setChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
@Activate
public SqueezeBoxStateDescriptionOptionsProvider(
@Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}

protected void unsetChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public SqueezeBoxPlayerDiscoveryParticipant(SqueezeBoxServerHandler squeezeBoxSe
protected void startScan() {
logger.debug("startScan invoked in SqueezeBoxPlayerDiscoveryParticipant");
this.squeezeBoxServerHandler.requestPlayers();
this.squeezeBoxServerHandler.requestFavorites();
}

/*
Expand Down Expand Up @@ -206,4 +207,8 @@ public void updateFavoritesListEvent(List<Favorite> favorites) {
@Override
public void sourceChangeEvent(String mac, String source) {
}

@Override
public void buttonsChangeEvent(String mac, String likeCommand, String unlikeCommand) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 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.squeezebox.internal.dto;

import com.google.gson.annotations.SerializedName;

/**
* The {@link ButtonDTO} represents a custom button that overrides existing
* button functionality. For example, "like song" replaces the repeat button.
*
* @author Mark Hilbush - Initial contribution
*/
public class ButtonDTO {

/**
* Indicates whether button is standard or custom
*/
public Boolean custom;

/**
* Indicates if standard button is enabled or disabled
*/
public Boolean enabled;

/**
* Concatenation of elements of command array
*/
public String command;

/**
* Currently not used
*/
@SerializedName("icon")
public String icon;

/**
* Currently not used
*/
@SerializedName("jiveStyle")
public String jiveStyle;

/**
* Currently not used
*/
@SerializedName("tooltip")
public String toolTip;

public boolean isCustom() {
return custom == null ? Boolean.FALSE : custom;
}

public boolean isEnabled() {
return enabled == null ? Boolean.FALSE : enabled;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 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.squeezebox.internal.dto;

import java.lang.reflect.Type;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

/**
* The {@link ButtonDTODeserializer} is responsible for deserializing a button object, which
* can either be an Integer, or a custom button specification.
*
* @author Mark Hilbush - Initial contribution
*/
public class ButtonDTODeserializer implements JsonDeserializer<ButtonDTO> {

@Override
public ButtonDTO deserialize(JsonElement jsonElement, Type tyoeOfT, JsonDeserializationContext context)
throws JsonParseException {
ButtonDTO button = null;
if (jsonElement.isJsonPrimitive() && jsonElement.getAsJsonPrimitive().isNumber()) {
Integer value = jsonElement.getAsInt();
button = new ButtonDTO();
button.custom = false;
button.enabled = value != 0;
} else if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
button = new ButtonDTO();
button.custom = true;
button.icon = jsonObject.get("icon").getAsString();
button.jiveStyle = jsonObject.get("jiveStyle").getAsString();
button.toolTip = jsonObject.get("tooltip").getAsString();
button.command = StreamSupport.stream(jsonObject.getAsJsonArray("command").spliterator(), false)
.map(JsonElement::getAsString).collect(Collectors.joining(" "));
}
return button;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 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.squeezebox.internal.dto;

import com.google.gson.annotations.SerializedName;

/**
* The {@link ButtonsDTO} contains information about the forward, rewind, repeat,
* and shuffle buttons, including any custom definitions, such as replacing repeat
* and shuffle with like and unlike, respectively.
*
* @author Mark Hilbush - Initial contribution
*/
public class ButtonsDTO {

/**
* Indicates if forward button is enabled/disabled,
* or if there is a custom button definition.
*/
@SerializedName("fwd")
public ButtonDTO forward;

/**
* Indicates if rewind button is enabled/disabled,
* or if there is a custom button definition.
*/
@SerializedName("rew")
public ButtonDTO rewind;

/**
* Indicates if repeat button is enabled/disabled,
* or if there is a custom button definition.
*/
@SerializedName("repeat")
public ButtonDTO repeat;

/**
* Indicates if shuffle button is enabled/disabled,
* or if there is a custom button definition.
*/
@SerializedName("shuffle")
public ButtonDTO shuffle;
}