2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.squeezebox.internal.handler;
15 import java.io.Closeable;
17 import org.openhab.binding.squeezebox.internal.utils.SqueezeBoxTimeoutException;
18 import org.openhab.core.library.types.StringType;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
23 * Utility class to play a notification message. The message is added
24 * to the playlist, played and the previous state of the playlist and the
27 * @author Mark Hilbush - Initial Contribution
28 * @author Patrik Gfeller - Utility class added reduce complexity and length of SqueezeBoxPlayerHandler.java
29 * @author Mark Hilbush - Convert sound notification volume from channel to config parameter
32 class SqueezeBoxNotificationPlayer implements Closeable {
33 private final Logger logger = LoggerFactory.getLogger(SqueezeBoxNotificationPlayer.class);
35 // An exception is thrown if we do not receive an acknowledge
36 // for a volume set command in the given amount of time [s].
37 private static final int VOLUME_COMMAND_TIMEOUT = 4;
39 // We expect the media server to acknowledge a playlist command.
40 // An exception is thrown if the playlist command was not processed
41 // after the defined amount in [s]
42 private static final int PLAYLIST_COMMAND_TIMEOUT = 5;
44 private final SqueezeBoxPlayerState playerState;
45 private final SqueezeBoxPlayerHandler squeezeBoxPlayerHandler;
46 private final SqueezeBoxServerHandler squeezeBoxServerHandler;
47 private final StringType uri;
48 private final String mac;
50 boolean playlistModified;
52 private int notificationMessagePlaylistsIndex;
54 SqueezeBoxNotificationPlayer(SqueezeBoxPlayerHandler squeezeBoxPlayerHandler,
55 SqueezeBoxServerHandler squeezeBoxServerHandler, StringType uri) {
56 this.squeezeBoxPlayerHandler = squeezeBoxPlayerHandler;
57 this.squeezeBoxServerHandler = squeezeBoxServerHandler;
58 this.mac = squeezeBoxPlayerHandler.getMac();
60 this.playerState = new SqueezeBoxPlayerState(squeezeBoxPlayerHandler);
63 void play() throws InterruptedException, SqueezeBoxTimeoutException {
64 if (squeezeBoxServerHandler == null) {
65 logger.warn("Server handler is null");
68 setupPlayerForNotification();
69 addNotificationMessageToPlaylist();
78 private void setupPlayerForNotification() throws InterruptedException, SqueezeBoxTimeoutException {
79 logger.debug("Setting up player for notification");
80 if (!playerState.isPoweredOn()) {
81 logger.debug("Powering on the player");
82 squeezeBoxServerHandler.powerOn(mac);
84 if (playerState.isShuffling()) {
85 logger.debug("Turning off shuffle");
86 squeezeBoxServerHandler.setShuffleMode(mac, 0);
88 if (playerState.isRepeating()) {
89 logger.debug("Turning off repeat");
90 squeezeBoxServerHandler.setRepeatMode(mac, 0);
92 if (playerState.isPlaying()) {
93 squeezeBoxServerHandler.stop(mac);
95 setVolume(squeezeBoxPlayerHandler.getNotificationSoundVolume().intValue());
99 * Sends a volume set command if target volume is not equal to the current volume.
101 * @param requestedVolume The requested volume value.
102 * @throws InterruptedException Thread interrupted during while we were waiting for an answer from the media server.
103 * @throws SqueezeBoxTimeoutException Volume command was not acknowledged by the media server.
105 private void setVolume(int requestedVolume) throws InterruptedException, SqueezeBoxTimeoutException {
106 if (playerState.getVolume() == requestedVolume) {
110 SqueezeBoxNotificationListener listener = new SqueezeBoxNotificationListener(mac);
111 listener.resetVolumeUpdated();
113 squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(listener);
114 squeezeBoxServerHandler.setVolume(mac, requestedVolume);
116 logger.trace("Waiting up to {} s for volume to be updated...", VOLUME_COMMAND_TIMEOUT);
119 int timeoutCount = 0;
121 while (!listener.isVolumeUpdated(requestedVolume)) {
123 if (timeoutCount++ > VOLUME_COMMAND_TIMEOUT * 10) {
124 throw new SqueezeBoxTimeoutException("Unable to update volume.");
128 squeezeBoxServerHandler.unregisterSqueezeBoxPlayerListener(listener);
132 private void addNotificationMessageToPlaylist() throws InterruptedException, SqueezeBoxTimeoutException {
133 logger.debug("Adding notification message to playlist");
134 SqueezeBoxNotificationListener listener = new SqueezeBoxNotificationListener(mac);
135 listener.resetPlaylistUpdated();
137 squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(listener);
138 squeezeBoxServerHandler.addPlaylistItem(mac, uri.toString(), "Notification");
141 updatePlaylist(listener);
142 this.playlistModified = true;
144 squeezeBoxServerHandler.unregisterSqueezeBoxPlayerListener(listener);
148 private void removeNotificationMessageFromPlaylist() throws InterruptedException, SqueezeBoxTimeoutException {
149 logger.debug("Removing notification message from playlist");
150 SqueezeBoxNotificationListener listener = new SqueezeBoxNotificationListener(mac);
151 listener.resetPlaylistUpdated();
153 squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(listener);
154 squeezeBoxServerHandler.deletePlaylistItem(mac, notificationMessagePlaylistsIndex);
157 updatePlaylist(listener);
159 squeezeBoxServerHandler.unregisterSqueezeBoxPlayerListener(listener);
164 * Monitor the number of playlist entries. When it changes, then we know the playlist
165 * has been updated with the notification URL. There's probably an edge case here where
166 * someone is updating the playlist at the same time, but that should be rare.
169 * @throws InterruptedException
170 * @throws SqueezeBoxTimeoutException
172 private void updatePlaylist(SqueezeBoxNotificationListener listener)
173 throws InterruptedException, SqueezeBoxTimeoutException {
174 logger.trace("Waiting up to {} s for playlist to be updated...", PLAYLIST_COMMAND_TIMEOUT);
176 int timeoutCount = 0;
178 while (!listener.isPlaylistUpdated()) {
180 if (timeoutCount++ > PLAYLIST_COMMAND_TIMEOUT * 10) {
181 logger.debug("Update playlist timed out after {} seconds", PLAYLIST_COMMAND_TIMEOUT);
182 throw new SqueezeBoxTimeoutException("Unable to update playlist.");
185 logger.debug("Playlist updated");
188 private void playNotification() throws InterruptedException, SqueezeBoxTimeoutException {
189 logger.debug("Playing notification");
191 notificationMessagePlaylistsIndex = squeezeBoxPlayerHandler.currentNumberPlaylistTracks() - 1;
192 SqueezeBoxNotificationListener listener = new SqueezeBoxNotificationListener(mac);
193 listener.resetStopped();
195 squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(listener);
196 squeezeBoxServerHandler.playPlaylistItem(mac, notificationMessagePlaylistsIndex);
199 int notificationTimeout = squeezeBoxPlayerHandler.getNotificationTimeout();
200 int timeoutCount = 0;
202 logger.trace("Waiting up to {} s for stop...", notificationTimeout);
203 while (!listener.isStopped()) {
205 if (timeoutCount++ > notificationTimeout * 10) {
206 logger.debug("Notification message timed out after {} seconds", notificationTimeout);
207 throw new SqueezeBoxTimeoutException("Notification message timed out");
211 squeezeBoxServerHandler.unregisterSqueezeBoxPlayerListener(listener);
215 private void restorePlayerState() {
216 logger.debug("Restoring player state");
218 // Mute the player to prevent any noise during the transition to saved state
219 // Don't wait for the volume acknowledge as there“s nothing to do about it at this point.
220 squeezeBoxServerHandler.setVolume(mac, 0);
222 if (playlistModified) {
224 removeNotificationMessageFromPlaylist();
225 } catch (InterruptedException | SqueezeBoxTimeoutException e) {
226 // Not much we can do here except log it and continue on
227 logger.debug("Exception while removing notification from playlist: {}", e.getMessage());
231 // Resume playing saved playlist item.
232 // Note that setting the time doesn't work for remote streams.
233 squeezeBoxServerHandler.playPlaylistItem(mac, playerState.getPlaylistIndex());
234 squeezeBoxServerHandler.setPlayingTime(mac, playerState.getPlayingTime());
236 switch (playerState.getPlayState()) {
238 logger.debug("Resuming last item playing");
242 * If the player was paused, stop it. We stop it because the LMS
243 * doesn't respond to a pause command while it's processing the
244 * above 'playPlaylist item' command. The consequence of this is
245 * we lose the ability to resume local music from saved playing time.
247 logger.debug("Stopping the player");
248 squeezeBoxServerHandler.stop(mac);
251 logger.debug("Stopping the player");
252 squeezeBoxServerHandler.stop(mac);
256 // Restore the saved volume level
257 squeezeBoxServerHandler.setVolume(mac, playerState.getVolume());
259 if (playerState.isShuffling()) {
260 logger.debug("Restoring shuffle mode");
261 squeezeBoxServerHandler.setShuffleMode(mac, playerState.getShuffle());
263 if (playerState.isRepeating()) {
264 logger.debug("Restoring repeat mode");
265 squeezeBoxServerHandler.setRepeatMode(mac, playerState.getRepeat());
267 if (playerState.isMuted()) {
268 logger.debug("Re-muting the player");
269 squeezeBoxServerHandler.mute(mac);
271 if (!playerState.isPoweredOn()) {
272 logger.debug("Powering off the player");
273 squeezeBoxServerHandler.powerOff(mac);