All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tinymediamanager.thirdparty.upnp.Upnp Maven / Gradle / Ivy

/*
 * Copyright 2012 - 2019 Manuel Laggner
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.tinymediamanager.thirdparty.upnp;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import org.fourthline.cling.DefaultUpnpServiceConfiguration;
import org.fourthline.cling.UpnpService;
import org.fourthline.cling.UpnpServiceImpl;
import org.fourthline.cling.binding.LocalServiceBindingException;
import org.fourthline.cling.binding.annotations.AnnotationLocalServiceBinder;
import org.fourthline.cling.binding.xml.DescriptorBindingException;
import org.fourthline.cling.controlpoint.ActionCallback;
import org.fourthline.cling.model.DefaultServiceManager;
import org.fourthline.cling.model.DiscoveryOptions;
import org.fourthline.cling.model.ValidationException;
import org.fourthline.cling.model.action.ActionInvocation;
import org.fourthline.cling.model.message.UpnpResponse;
import org.fourthline.cling.model.message.header.UDADeviceTypeHeader;
import org.fourthline.cling.model.meta.Device;
import org.fourthline.cling.model.meta.DeviceDetails;
import org.fourthline.cling.model.meta.DeviceIdentity;
import org.fourthline.cling.model.meta.Icon;
import org.fourthline.cling.model.meta.LocalDevice;
import org.fourthline.cling.model.meta.LocalService;
import org.fourthline.cling.model.meta.ManufacturerDetails;
import org.fourthline.cling.model.meta.ModelDetails;
import org.fourthline.cling.model.meta.Service;
import org.fourthline.cling.model.profile.RemoteClientInfo;
import org.fourthline.cling.model.types.DLNACaps;
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.UDAServiceId;
import org.fourthline.cling.model.types.UDN;
import org.fourthline.cling.registry.RegistrationException;
import org.fourthline.cling.support.avtransport.callback.Play;
import org.fourthline.cling.support.avtransport.callback.SetAVTransportURI;
import org.fourthline.cling.support.avtransport.callback.Stop;
import org.fourthline.cling.support.connectionmanager.ConnectionManagerService;
import org.fourthline.cling.support.contentdirectory.DIDLParser;
import org.fourthline.cling.support.model.DIDLContent;
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.fourthline.cling.transport.RouterException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tinymediamanager.ReleaseInfo;
import org.tinymediamanager.core.entities.MediaEntity;
import org.tinymediamanager.core.entities.MediaFile;
import org.tinymediamanager.core.movie.entities.Movie;
import org.tinymediamanager.core.tvshow.entities.TvShowEpisode;
import org.tinymediamanager.thirdparty.NetworkUtil;

public class Upnp {
  private static final Logger LOGGER         = LoggerFactory.getLogger(Upnp.class);
  public static final String  IP             = NetworkUtil.getMachineIPAddress();
  public static final int     UPNP_PORT      = 8008;
  public static final int     WEBSERVER_PORT = 8009;

  // ROOT is fix 0 , do not change!!
  public static final String  ID_ROOT        = "0";
  public static final String  ID_MOVIES      = "1";
  public static final String  ID_TVSHOWS     = "2";

  private static Upnp         instance;
  private UpnpService         upnpService    = null;
  private WebServer           webServer      = null;
  private Service             playerService  = null;
  private LocalDevice         localDevice    = null;

  private Upnp() {
  }

  public synchronized static Upnp getInstance() {
    if (Upnp.instance == null) {
      Upnp.instance = new Upnp();
    }
    return Upnp.instance;
  }

  public UpnpService getUpnpService() {
    return this.upnpService;
  }

  public LocalDevice getLocalDevice() {
    return this.localDevice;
  }

  /**
   * Starts out UPNP Service / Listener
   */
  public void createUpnpService() {
    if (this.upnpService == null) {
      this.upnpService = new UpnpServiceImpl(new DefaultUpnpServiceConfiguration(UPNP_PORT), UpnpListener.getListener());
      try {
        this.upnpService.getRouter().enable();
      }
      catch (RouterException e) {
        LOGGER.warn("Could not start UPNP router: {}", e);
      }
    }
  }

  private LocalDevice getDevice()
      throws ValidationException, LocalServiceBindingException, IOException, IllegalArgumentException, URISyntaxException {

    if (localDevice == null) {
      DeviceIdentity identity = new DeviceIdentity(UDN.uniqueSystemIdentifier("tinyMediaManager"), 600);
      DeviceType type = new UDADeviceType("MediaServer", 1);
      String hostname = NetworkUtil.getMachineHostname();
      if (hostname == null) {
        hostname = IP;
      }

      // @formatter:off
      DeviceDetails details = new DeviceDetails("tinyMediaManager",
        new ManufacturerDetails("tinyMediaManager", "https://www.tinymediamanager.org/"),
        new ModelDetails("tinyMediaManager", "tinyMediaManager - Media Server", ReleaseInfo.getVersion()), 
        // @Namespace default /dev//desc
        new URI("http://" + hostname + ":" + UPNP_PORT + "/dev/" + identity.getUdn().getIdentifierString() + "/desc"),
        new DLNADoc[] {
            new DLNADoc("DMS", DLNADoc.Version.V1_5), 
            new DLNADoc("M-DMS", DLNADoc.Version.V1_5) 
        },
        new DLNACaps(new String[] { "av-upload", "image-upload", "audio-upload" }));
      // @formatter:on

      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.
        }
      }

      LOGGER.info("Hello, i'm " + identity.getUdn().getIdentifierString());

      // Content Directory Service
      LocalService cds = new AnnotationLocalServiceBinder().read(ContentDirectoryService.class);
      cds.setManager(new DefaultServiceManager(cds, ContentDirectoryService.class));

      // Connection Manager Service
      LocalService cms = new AnnotationLocalServiceBinder().read(ConnectionManagerService.class);
      // cms.setManager(new DefaultServiceManager<>(cms, ConnectionManagerService.class));
      cms.setManager(new DefaultServiceManager(cms, ConnectionManagerService.class) {
        @Override
        protected ConnectionManagerService createServiceInstance() throws Exception {
          return new ConnectionManagerService(protocols, null);
        }
      });

      LocalService mss = new AnnotationLocalServiceBinder().read(MSMediaReceiverRegistrarService.class);
      mss.setManager(new DefaultServiceManager(mss, MSMediaReceiverRegistrarService.class));

      Icon icon = null;
      try {
        // only when deployed
        icon = new Icon("image/png", 128, 128, 24, new File("tmm.png"));
      }
      catch (Exception e) {
        // in eclipse
        try {
          icon = new Icon("image/png", 128, 128, 24, new File("AppBundler/tmm.png"));
        }
        catch (Exception e2) {
          LOGGER.warn("Did not find device icon...");
        }
      }

      this.localDevice = new LocalDevice(identity, type, details, new Icon[] { icon }, new LocalService[] { cds, cms, mss });
    }

    return this.localDevice;
  }

  /**
   * Sends a UPNP broadcast message, to find some players.
* Should be available shortly via getAvailablePlayers() */ public void sendPlayerSearchRequest() { createUpnpService(); this.upnpService.getControlPoint().search(new UDADeviceTypeHeader(new UDADeviceType("MediaRenderer"))); } /** * Finds all available Players (implementing the MediaRenderer stack)
* You might want to call sendPlayerSearchRequest() a few secs before, to populate freshly * * @return List of devices */ public List getAvailablePlayers() { createUpnpService(); List ret = new ArrayList<>(); for (Device device : this.upnpService.getRegistry().getDevices()) { if (device.getType().getType().equals("MediaRenderer")) { ret.add(device); } } return ret; } /** * Sets a device as our player for play/stop and other services
* Use getAvailablePlayers() for a list of them. * * @param device * device for playing */ public void setPlayer(Device device) { this.playerService = device.findService(new UDAServiceId("AVTransport")); if (this.playerService == null) { LOGGER.warn("Could not find AVTransportService on device " + device.getDisplayString()); } } /** * Plays a file/url * * @param me * the media entity * @param mf * the media file */ public void playFile(MediaEntity me, MediaFile mf) { if (this.playerService == null) { LOGGER.warn("No player set - did you call setPlayer(Device) ?"); return; } if (mf == null) { LOGGER.warn("parameters empty!"); return; } String url = ""; String meta = "NO METADATA"; if (me != null) { try { DIDLContent didl = new DIDLContent(); DIDLParser dip = new DIDLParser(); if (me instanceof Movie) { didl.addItem(Metadata.getUpnpMovie((Movie) me, true)); } else if (me instanceof TvShowEpisode) { didl.addItem(Metadata.getUpnpTvShowEpisode(((TvShowEpisode) me).getTvShow(), (TvShowEpisode) me, true)); } // get url from didl, no need to regenerate this url = didl.getItems().get(0).getResources().get(0).getValue(); meta = dip.generate(didl); } catch (Exception e) { LOGGER.warn("Could not generate metadata / url"); return; } } ActionCallback setAVTransportURIAction = new SetAVTransportURI(this.playerService, url, meta) { @Override public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) { LOGGER.warn("Setting URL for player failed! " + defaultMsg); } }; this.upnpService.getControlPoint().execute(setAVTransportURIAction); ActionCallback playAction = new Play(this.playerService) { @Override public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) { LOGGER.warn("Playing failed! " + defaultMsg); } }; this.upnpService.getControlPoint().execute(playAction); } /** * stop the player */ public void stopPlay() { if (this.playerService == null) { return; } ActionCallback stopAction = new Stop(this.playerService) { @Override public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) { LOGGER.warn("Stopping failed! " + defaultMsg); } }; this.upnpService.getControlPoint().execute(stopAction); } public String getDeviceDescriptorXML() { String xml = ""; try { xml = upnpService.getConfiguration().getDeviceDescriptorBinderUDA10().generate(localDevice, new RemoteClientInfo(), upnpService.getConfiguration().getNamespace()); } catch (DescriptorBindingException e) { LOGGER.warn("Could not generate UPNP device descriptor", e); } return xml; } /** * starts a WebServer for accessing MediaFiles over UPNP
* In /upnp/(movie|tvshow)/UUIDofMediaEntity/(folder)/file.ext format */ public void startWebServer() { try { if (this.webServer == null) { this.webServer = new WebServer(WEBSERVER_PORT); } } catch (IOException e) { LOGGER.warn("Could not start WebServer!", e); } } public void stopWebServer() { if (this.webServer != null) { this.webServer.closeAllConnections(); } } public void startMediaServer() { createUpnpService(); try { this.upnpService.getRegistry().addDevice(getDevice(), new DiscoveryOptions(true)); } catch (RegistrationException | LocalServiceBindingException | ValidationException | IOException | IllegalArgumentException | URISyntaxException e) { LOGGER.warn("could not start UPNP MediaServer!", e); } } public void stopMediaServer() { if (this.upnpService != null) { this.upnpService.getRegistry().removeAllLocalDevices(); } } public void shutdown() { stopPlay(); stopWebServer(); stopMediaServer(); if (this.upnpService != null) { try { this.upnpService.getRouter().shutdown(); } catch (RouterException e) { LOGGER.warn("Could not shutdown the UPNP router."); } this.upnpService.shutdown(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy