/* This file is part of Airsonic. Airsonic is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Airsonic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Airsonic. If not, see . Copyright 2016 (C) Airsonic Authors Based upon Subsonic, Copyright 2009 (C) Sindre Mehus */ package org.airsonic.player.service; import org.airsonic.player.service.upnp.CustomContentDirectory; import org.airsonic.player.service.upnp.MSMediaReceiverRegistrarService; import org.airsonic.player.util.FileUtil; import org.fourthline.cling.DefaultUpnpServiceConfiguration; import org.fourthline.cling.UpnpService; import org.fourthline.cling.UpnpServiceImpl; import org.fourthline.cling.binding.annotations.AnnotationLocalServiceBinder; import org.fourthline.cling.model.DefaultServiceManager; import org.fourthline.cling.model.meta.*; import org.fourthline.cling.model.types.DLNADoc; import org.fourthline.cling.model.types.DeviceType; import org.fourthline.cling.model.types.UDADeviceType; import org.fourthline.cling.model.types.UDN; import org.fourthline.cling.support.connectionmanager.ConnectionManagerService; import org.fourthline.cling.support.model.ProtocolInfos; import org.fourthline.cling.support.model.dlna.DLNAProfiles; import org.fourthline.cling.support.model.dlna.DLNAProtocolInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** * @author Sindre Mehus * @version $Id$ */ @Service public class UPnPService { private static final Logger LOG = LoggerFactory.getLogger(UPnPService.class); @Autowired private SettingsService settingsService; private UpnpService upnpService; @Autowired @Qualifier("dispatchingContentDirectory") private CustomContentDirectory dispatchingContentDirectory; private AtomicReference running = new AtomicReference<>(false); @PostConstruct public void init() { if (settingsService.isDlnaEnabled() || settingsService.isSonosEnabled()) { ensureServiceStarted(); if (settingsService.isDlnaEnabled()) { // Start DLNA media server? setMediaServerEnabled(true); } } Runtime.getRuntime().addShutdownHook(new Thread(() -> ensureServiceStopped())); } public void ensureServiceStarted() { running.getAndUpdate(bo -> { if (!bo) { startService(); return true; } else { return true; } }); } public void ensureServiceStopped() { running.getAndUpdate(bo -> { if (bo) { if (upnpService != null) { LOG.info("Disabling UPnP/DLNA media server"); upnpService.getRegistry().removeAllLocalDevices(); System.err.println("Shutting down UPnP service..."); upnpService.shutdown(); System.err.println("Shutting down UPnP service - Done!"); } return false; } else { return false; } }); } private void startService() { try { LOG.info("Starting UPnP service..."); createService(); LOG.info("Successfully started UPnP service on port {}!", SettingsService.getDefaultUPnPPort()); } catch (Throwable x) { LOG.error("Failed to start UPnP service: " + x, x); } } private synchronized void createService() { upnpService = new UpnpServiceImpl(new DefaultUpnpServiceConfiguration(SettingsService.getDefaultUPnPPort())); // Asynch search for other devices (most importantly UPnP-enabled routers for port-mapping) upnpService.getControlPoint().search(); } public void setMediaServerEnabled(boolean enabled) { if (enabled) { ensureServiceStarted(); try { upnpService.getRegistry().addDevice(createMediaServerDevice()); LOG.info("Enabling UPnP/DLNA media server"); } catch (Exception x) { LOG.error("Failed to start UPnP/DLNA media server: " + x, x); } } else { ensureServiceStopped(); } } private LocalDevice createMediaServerDevice() throws Exception { String serverName = settingsService.getDlnaServerName(); DeviceIdentity identity = new DeviceIdentity(UDN.uniqueSystemIdentifier(serverName)); DeviceType type = new UDADeviceType("MediaServer", 1); // TODO: DLNACaps DeviceDetails details = new DeviceDetails(serverName, new ManufacturerDetails(serverName), new ModelDetails(serverName), new DLNADoc[]{new DLNADoc("DMS", DLNADoc.Version.V1_5)}, null); InputStream in = getClass().getResourceAsStream("logo-512.png"); Icon icon = new Icon("image/png", 512, 512, 32, "logo-512", in); FileUtil.closeQuietly(in); LocalService contentDirectoryservice = new AnnotationLocalServiceBinder().read(CustomContentDirectory.class); contentDirectoryservice.setManager(new DefaultServiceManager(contentDirectoryservice) { @Override protected CustomContentDirectory createServiceInstance() { return dispatchingContentDirectory; } }); final ProtocolInfos protocols = new ProtocolInfos(); for (DLNAProfiles dlnaProfile : DLNAProfiles.values()) { if (dlnaProfile == DLNAProfiles.NONE) { continue; } try { protocols.add(new DLNAProtocolInfo(dlnaProfile)); } catch (Exception e) { // Silently ignored. } } LocalService connetionManagerService = new AnnotationLocalServiceBinder().read(ConnectionManagerService.class); connetionManagerService.setManager(new DefaultServiceManager(connetionManagerService) { @Override protected ConnectionManagerService createServiceInstance() { return new ConnectionManagerService(protocols, null); } }); // For compatibility with Microsoft LocalService receiverService = new AnnotationLocalServiceBinder().read(MSMediaReceiverRegistrarService.class); receiverService.setManager(new DefaultServiceManager<>(receiverService, MSMediaReceiverRegistrarService.class)); return new LocalDevice(identity, type, details, new Icon[]{icon}, new LocalService[]{contentDirectoryservice, connetionManagerService, receiverService}); } public List getSonosControllerHosts() { ensureServiceStarted(); List result = new ArrayList(); for (Device device : upnpService.getRegistry().getDevices(new DeviceType("schemas-upnp-org", "ZonePlayer"))) { if (device instanceof RemoteDevice) { URL descriptorURL = ((RemoteDevice) device).getIdentity().getDescriptorURL(); if (descriptorURL != null) { result.add(descriptorURL.getHost()); } } } return result; } public UpnpService getUpnpService() { return upnpService; } public void setSettingsService(SettingsService settingsService) { this.settingsService = settingsService; } public void setCustomContentDirectory(CustomContentDirectory customContentDirectory) { this.dispatchingContentDirectory = customContentDirectory; } }