]> git.basschouten.com Git - openhab-addons.git/blob
31d1ec9df7255a8eafcce07ca41b68384a58ce60
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.squeezebox.internal;
14
15 import static org.openhab.binding.squeezebox.internal.SqueezeBoxBindingConstants.*;
16
17 import java.util.Dictionary;
18 import java.util.HashMap;
19 import java.util.Hashtable;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25
26 import org.openhab.binding.squeezebox.internal.discovery.SqueezeBoxPlayerDiscoveryParticipant;
27 import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxPlayerEventListener;
28 import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxPlayerHandler;
29 import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxServerHandler;
30 import org.openhab.core.audio.AudioHTTPServer;
31 import org.openhab.core.audio.AudioSink;
32 import org.openhab.core.config.discovery.DiscoveryService;
33 import org.openhab.core.net.HttpServiceUtil;
34 import org.openhab.core.net.NetworkAddressService;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.ThingUID;
39 import org.openhab.core.thing.binding.BaseThingHandlerFactory;
40 import org.openhab.core.thing.binding.ThingHandler;
41 import org.openhab.core.thing.binding.ThingHandlerFactory;
42 import org.osgi.framework.ServiceRegistration;
43 import org.osgi.service.component.ComponentContext;
44 import org.osgi.service.component.annotations.Activate;
45 import org.osgi.service.component.annotations.Component;
46 import org.osgi.service.component.annotations.Reference;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51  * The {@link SqueezeBoxHandlerFactory} is responsible for creating things and
52  * thing handlers.
53  *
54  * @author Dan Cunningham - Initial contribution
55  * @author Mark Hilbush - Cancel request player job when handler removed
56  * @author Mark Hilbush - Add callbackUrl
57  */
58 @Component(service = ThingHandlerFactory.class, configurationPid = "binding.squeezebox")
59 public class SqueezeBoxHandlerFactory extends BaseThingHandlerFactory {
60     private final Logger logger = LoggerFactory.getLogger(SqueezeBoxHandlerFactory.class);
61
62     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
63             .concat(SqueezeBoxServerHandler.SUPPORTED_THING_TYPES_UIDS.stream(),
64                     SqueezeBoxPlayerHandler.SUPPORTED_THING_TYPES_UIDS.stream())
65             .collect(Collectors.toSet());
66
67     private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
68
69     private final AudioHTTPServer audioHTTPServer;
70     private final NetworkAddressService networkAddressService;
71     private final SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider;
72
73     private Map<String, ServiceRegistration<AudioSink>> audioSinkRegistrations = new ConcurrentHashMap<>();
74
75     // Callback url (scheme+server+port) to use for playing notification sounds
76     private String callbackUrl = null;
77
78     @Activate
79     public SqueezeBoxHandlerFactory(@Reference AudioHTTPServer audioHTTPServer,
80             @Reference NetworkAddressService networkAddressService,
81             @Reference SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider) {
82         this.audioHTTPServer = audioHTTPServer;
83         this.networkAddressService = networkAddressService;
84         this.stateDescriptionProvider = stateDescriptionProvider;
85     }
86
87     @Override
88     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
89         return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
90     }
91
92     @Override
93     protected void activate(ComponentContext componentContext) {
94         super.activate(componentContext);
95         Dictionary<String, Object> properties = componentContext.getProperties();
96         callbackUrl = (String) properties.get("callbackUrl");
97     }
98
99     @Override
100     protected ThingHandler createHandler(Thing thing) {
101         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
102
103         if (thingTypeUID.equals(SQUEEZEBOXSERVER_THING_TYPE)) {
104             logger.trace("creating handler for bridge thing {}", thing);
105             SqueezeBoxServerHandler bridge = new SqueezeBoxServerHandler((Bridge) thing);
106             registerSqueezeBoxPlayerDiscoveryService(bridge);
107             return bridge;
108         }
109
110         if (thingTypeUID.equals(SQUEEZEBOXPLAYER_THING_TYPE)) {
111             logger.trace("creating handler for player thing {}", thing);
112             SqueezeBoxPlayerHandler playerHandler = new SqueezeBoxPlayerHandler(thing, createCallbackUrl(),
113                     stateDescriptionProvider);
114
115             // Register the player as an audio sink
116             logger.trace("Registering an audio sink for player thing {}", thing.getUID());
117             SqueezeBoxAudioSink audioSink = new SqueezeBoxAudioSink(playerHandler, audioHTTPServer, callbackUrl);
118             @SuppressWarnings("unchecked")
119             ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
120                     .registerService(AudioSink.class.getName(), audioSink, new Hashtable<>());
121             audioSinkRegistrations.put(thing.getUID().toString(), reg);
122
123             return playerHandler;
124         }
125
126         return null;
127     }
128
129     /**
130      * Adds SqueezeBoxServerHandlers to the discovery service to find SqueezeBox
131      * Players
132      *
133      * @param squeezeBoxServerHandler
134      */
135     private synchronized void registerSqueezeBoxPlayerDiscoveryService(
136             SqueezeBoxServerHandler squeezeBoxServerHandler) {
137         logger.trace("registering player discovery service");
138
139         SqueezeBoxPlayerDiscoveryParticipant discoveryService = new SqueezeBoxPlayerDiscoveryParticipant(
140                 squeezeBoxServerHandler);
141
142         // Register the PlayerListener with the SqueezeBoxServerHandler
143         squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(discoveryService);
144
145         // Register the service, then add the service to the ServiceRegistration map
146         discoveryServiceRegs.put(squeezeBoxServerHandler.getThing().getUID(),
147                 bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
148     }
149
150     @Override
151     protected synchronized void removeHandler(ThingHandler thingHandler) {
152         if (thingHandler instanceof SqueezeBoxServerHandler serverHandler) {
153             logger.trace("removing handler for bridge thing {}", thingHandler.getThing());
154
155             ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.get(thingHandler.getThing().getUID());
156             if (serviceReg != null) {
157                 logger.trace("unregistering player discovery service");
158
159                 // Get the discovery service object and use it to cancel the RequestPlayerJob
160                 SqueezeBoxPlayerDiscoveryParticipant discoveryService = (SqueezeBoxPlayerDiscoveryParticipant) bundleContext
161                         .getService(serviceReg.getReference());
162                 discoveryService.cancelRequestPlayerJob();
163
164                 // Unregister the PlayerListener from the SqueezeBoxServerHandler
165                 serverHandler.unregisterSqueezeBoxPlayerListener(
166                         (SqueezeBoxPlayerEventListener) bundleContext.getService(serviceReg.getReference()));
167
168                 // Unregister the PlayerListener service
169                 serviceReg.unregister();
170
171                 // Remove the service from the ServiceRegistration map
172                 discoveryServiceRegs.remove(thingHandler.getThing().getUID());
173             }
174         }
175
176         if (thingHandler instanceof SqueezeBoxPlayerHandler playerHandler) {
177             SqueezeBoxServerHandler bridge = playerHandler.getSqueezeBoxServerHandler();
178             if (bridge != null) {
179                 // Unregister the player's audio sink
180                 logger.trace("Unregistering the audio sync service for player thing {}",
181                         thingHandler.getThing().getUID());
182                 ServiceRegistration<AudioSink> reg = audioSinkRegistrations
183                         .get(thingHandler.getThing().getUID().toString());
184                 if (reg != null) {
185                     reg.unregister();
186                 }
187
188                 logger.trace("removing handler for player thing {}", thingHandler.getThing());
189                 bridge.removePlayerCache(playerHandler.getMac());
190             }
191         }
192     }
193
194     private String createCallbackUrl() {
195         final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
196         if (ipAddress == null) {
197             logger.warn("No network interface could be found.");
198             return null;
199         }
200
201         final int port = HttpServiceUtil.getHttpServicePort(bundleContext);
202         if (port == -1) {
203             logger.warn("Cannot find port of the http service.");
204             return null;
205         }
206
207         return "http://" + ipAddress + ":" + port;
208     }
209 }