diff --git a/airsonic-main/pom.xml b/airsonic-main/pom.xml
index d79e4b500..83b8b95d2 100644
--- a/airsonic-main/pom.xml
+++ b/airsonic-main/pom.xml
@@ -49,10 +49,12 @@
io.dropwizard.metrics
metrics-core
+ 4.2.25
io.dropwizard.metrics
metrics-jmx
+ 4.2.25
@@ -464,7 +466,7 @@
org.apache.maven
maven-artifact
- 3.9.6
+ 3.9.7
diff --git a/airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java b/airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java
index efa2adc1e..4d30fcef7 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java
@@ -114,12 +114,13 @@ public ResponseEntity handleRequest(Authentication authentication,
@RequestParam(required = false, name = "offsetSeconds") Double offsetSeconds,
ServletWebRequest swr) throws Exception {
- User user = securityService.getCurrentUser(swr.getRequest());
+ String username = securityService.getCurrentUsername(swr.getRequest());
+ User user = securityService.getUserByName(username);
if (!(authentication instanceof JWTAuthenticationToken) && !user.isStreamRole()) {
- throw new AccessDeniedException("Streaming is forbidden for user " + user.getUsername());
+ throw new AccessDeniedException("Streaming is forbidden for user " + username);
}
- Player player = playerService.getPlayer(swr.getRequest(), swr.getResponse(), user.getUsername(), false, true);
+ Player player = playerService.getPlayer(swr.getRequest(), swr.getResponse(), username, false, true);
Long expectedSize = null;
@@ -154,8 +155,8 @@ public ResponseEntity handleRequest(Authentication authentication,
if (isSingleFile) {
if (!(authentication instanceof JWTAuthenticationToken)
- && !securityService.isFolderAccessAllowed(file, user.getUsername())) {
- throw new AccessDeniedException("Access to file " + file.getId() + " is forbidden for user " + user.getUsername());
+ && !securityService.isFolderAccessAllowed(file, username)) {
+ throw new AccessDeniedException("Access to file " + file.getId() + " is forbidden for user " + username);
}
// Update the index of the currently playing media file. At
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/SecurityService.java b/airsonic-main/src/main/java/org/airsonic/player/service/SecurityService.java
index 0ee0f0bcf..878463902 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/SecurityService.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/SecurityService.java
@@ -52,6 +52,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
@@ -307,7 +308,6 @@ public List getCredentials(String username, App... apps) {
return userRepository.findByUsername(username).map(user -> {
return userCredentialRepository.findByUserAndAppIn(user, List.of(apps));
}).orElseGet(() -> {
- LOG.warn("Can't get credentials for a non-existent user {}", username);
return Collections.emptyList();
});
}
@@ -420,7 +420,8 @@ public String getCurrentUsername(HttpServletRequest request) {
* @param username The username used when logging in.
* @return The user, or null
if not found.
*/
- public User getUserByName(String username) {
+ @Nullable
+ public User getUserByName(@Nullable String username) {
return getUserByName(username, true);
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/UPnPService.java b/airsonic-main/src/main/java/org/airsonic/player/service/UPnPService.java
index 94ae697b1..a54792dce 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/UPnPService.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/UPnPService.java
@@ -14,15 +14,15 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2016 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.airsonic.player.service;
+import org.airsonic.player.service.upnp.ApacheUpnpServiceConfiguration;
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;
@@ -36,13 +36,6 @@
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.impl.apache.StreamClientConfigurationImpl;
-import org.fourthline.cling.transport.impl.apache.StreamClientImpl;
-import org.fourthline.cling.transport.impl.apache.StreamServerConfigurationImpl;
-import org.fourthline.cling.transport.impl.apache.StreamServerImpl;
-import org.fourthline.cling.transport.spi.NetworkAddressFactory;
-import org.fourthline.cling.transport.spi.StreamClient;
-import org.fourthline.cling.transport.spi.StreamServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -51,7 +44,9 @@
import jakarta.annotation.PostConstruct;
+import java.io.IOException;
import java.io.InputStream;
+import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@@ -67,9 +62,14 @@ public class UPnPService {
private static final Logger LOG = LoggerFactory.getLogger(UPnPService.class);
+ private final static int MIN_ADVERTISEMENT_AGE_SECONDS = 60 * 60 * 24;
+
@Autowired
private SettingsService settingsService;
+ @Autowired
+ private VersionService versionService;
+
private UpnpService upnpService;
@Autowired
@@ -103,20 +103,15 @@ public void ensureServiceStarted() {
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;
+ if (upnpService != null && bo) {
+ LOG.info("Disabling UPnP/DLNA media server");
+ upnpService.getRegistry().removeAllLocalDevices();
+ LOG.info("Shutting down UPnP service...");
+ upnpService.shutdown();
+ LOG.info("Shutting down UPnP service - Done!");
}
+ return false;
});
-
}
private void startService() {
@@ -132,7 +127,8 @@ private void startService() {
private synchronized void createService() {
upnpService = new UpnpServiceImpl(new ApacheUpnpServiceConfiguration(settingsService.getUPnpPort()));
- // Asynch search for other devices (most importantly UPnP-enabled routers for port-mapping)
+ // Asynch search for other devices (most importantly UPnP-enabled routers for
+ // port-mapping)
upnpService.getControlPoint().search();
}
@@ -153,26 +149,9 @@ public void setMediaServerEnabled(boolean enabled) {
private LocalDevice createMediaServerDevice() throws Exception {
- String serverName = settingsService.getDlnaServerName();
- String serverId = settingsService.getDlnaServerId();
- if (serverId == null) {
- serverId = UUID.randomUUID().toString();
- settingsService.setDlnaServerId(serverId);
- }
- DeviceIdentity identity = new DeviceIdentity(UDN.valueOf(serverId));
- 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);
+ @SuppressWarnings("unchecked")
+ LocalService contentDirectoryservice = new AnnotationLocalServiceBinder()
+ .read(CustomContentDirectory.class);
contentDirectoryservice.setManager(new DefaultServiceManager(contentDirectoryservice) {
@Override
@@ -193,25 +172,61 @@ protected CustomContentDirectory createServiceInstance() {
}
}
- LocalService connetionManagerService = new AnnotationLocalServiceBinder().read(ConnectionManagerService.class);
- connetionManagerService.setManager(new DefaultServiceManager(connetionManagerService) {
- @Override
- protected ConnectionManagerService createServiceInstance() {
- return new ConnectionManagerService(protocols, null);
- }
- });
+ @SuppressWarnings("unchecked")
+ 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);
+ @SuppressWarnings("unchecked")
+ 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});
+ Icon icon = null;
+ try (InputStream in = getClass().getResourceAsStream("logo-512.png")) {
+ icon = new Icon("image/png", 512, 512, 32, "logo-512", in);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ String serverName = settingsService.getDlnaServerName();
+ String serverId = settingsService.getDlnaServerId();
+ String serialNumber = versionService.getLocalBuildNumber();
+ if (serverId == null) {
+ serverId = UUID.randomUUID().toString();
+ settingsService.setDlnaServerId(serverId);
+ }
+
+ // TODO: DLNACaps
+ DLNADoc[] dlnaDocs = new DLNADoc[] { new DLNADoc("DMS", DLNADoc.Version.V1_5) };
+ URI modelURI = URI.create("https://airsonic.github.io/");
+ URI manufacturerURI = URI.create("https://github.com/kagemomiji/airsonic-advanced");
+ URI presentaionURI = URI.create(settingsService.getDlnaBaseLANURL());
+ ManufacturerDetails manufacturerDetails = new ManufacturerDetails(serverName, modelURI);
+ ModelDetails modelDetails = new ModelDetails(serverName, null, versionService.getLocalVersion().toString(),
+ manufacturerURI);
+ DeviceDetails details = new DeviceDetails(serverName, manufacturerDetails, modelDetails, serialNumber, null,
+ presentaionURI, dlnaDocs, null);
+ DeviceIdentity identity = new DeviceIdentity(UDN.uniqueSystemIdentifier(serverName),
+ MIN_ADVERTISEMENT_AGE_SECONDS);
+ DeviceType type = new UDADeviceType("MediaServer", 1);
+
+ 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"))) {
+ 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) {
@@ -221,36 +236,4 @@ public List getSonosControllerHosts() {
}
return result;
}
-
- public UpnpService getUpnpService() {
- return upnpService;
- }
-
- public void setSettingsService(SettingsService settingsService) {
- this.settingsService = settingsService;
- }
-
- public void setCustomContentDirectory(CustomContentDirectory customContentDirectory) {
- this.dispatchingContentDirectory = customContentDirectory;
- }
-
- /**
- * Note the different packages on similarly named classes from the parent
- *
- */
- public static class ApacheUpnpServiceConfiguration extends DefaultUpnpServiceConfiguration {
- public ApacheUpnpServiceConfiguration(int streamListenPort) {
- super(streamListenPort);
- }
-
- @Override
- public StreamClient> createStreamClient() {
- return new StreamClientImpl(new StreamClientConfigurationImpl(getSyncProtocolExecutorService()));
- }
-
- @Override
- public StreamServer> createStreamServer(NetworkAddressFactory networkAddressFactory) {
- return new StreamServerImpl(new StreamServerConfigurationImpl(networkAddressFactory.getStreamListenPort()));
- }
- }
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/AlbumUpnpProcessor.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/AlbumUpnpProcessor.java
index 749813998..c79cf5739 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/AlbumUpnpProcessor.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/AlbumUpnpProcessor.java
@@ -14,6 +14,7 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2017 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
@@ -21,12 +22,12 @@
import com.google.common.primitives.Ints;
import org.airsonic.player.domain.Album;
-import org.airsonic.player.domain.CoverArtScheme;
import org.airsonic.player.domain.MediaFile;
import org.airsonic.player.domain.MusicFolder;
-import org.airsonic.player.domain.User;
+import org.airsonic.player.domain.ParamSearchResult;
import org.airsonic.player.service.AlbumService;
import org.airsonic.player.service.MediaFileService;
+import org.airsonic.player.service.MediaFolderService;
import org.airsonic.player.service.SearchService;
import org.fourthline.cling.support.model.BrowseResult;
import org.fourthline.cling.support.model.DIDLContent;
@@ -36,7 +37,6 @@
import org.fourthline.cling.support.model.container.MusicAlbum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.ArrayList;
@@ -61,8 +61,17 @@ public class AlbumUpnpProcessor extends UpnpContentProcessor
@Autowired
private MediaFileService mediaFileService;
+ @Autowired
+ private MediaFolderService mediaFolderService;
+
+ @Autowired
+ private UpnpProcessorRouter router;
+
+ @Autowired
+ private UpnpUtil upnpUtil;
+
public AlbumUpnpProcessor() {
- setRootId(DispatchingContentDirectory.CONTAINER_ID_ALBUM_PREFIX);
+ setRootId(ProcessorType.ALBUM);
setRootTitle("Albums");
}
@@ -73,7 +82,7 @@ public AlbumUpnpProcessor() {
public BrowseResult browseRoot(String filter, long firstResult, long maxResults, SortCriterion[] orderBy) throws Exception {
DIDLContent didl = new DIDLContent();
- List allFolders = getDispatchingContentDirectory().getMediaFolderService().getAllMusicFolders();
+ List allFolders = mediaFolderService.getAllMusicFolders();
List selectedItems = albumService.getAlphabeticalAlbums(Ints.saturatedCast(firstResult), Ints.saturatedCast(maxResults), false, true, allFolders);
for (Album item : selectedItems) {
addItem(didl, item);
@@ -89,7 +98,7 @@ public Container createContainer(Album album) {
container.setId(getRootId() + DispatchingContentDirectory.SEPARATOR + album.getComment());
} else {
container.setId(getRootId() + DispatchingContentDirectory.SEPARATOR + album.getId());
- container.setAlbumArtURIs(new URI[] { getAlbumArtURI(album.getId()) });
+ container.setAlbumArtURIs(new URI[] { upnpUtil.getAlbumArtURI(album.getId()) });
container.setDescription(album.getComment());
}
container.setParentID(getRootId());
@@ -103,7 +112,7 @@ public Container createContainer(Album album) {
@Override
public List getAllItems() {
- List allFolders = getDispatchingContentDirectory().getMediaFolderService().getAllMusicFolders();
+ List allFolders = mediaFolderService.getAllMusicFolders();
return albumService.getAlphabeticalAlbums(false, true, allFolders);
}
@@ -126,10 +135,10 @@ public List getChildren(Album album) {
if (album.getId() == -1) {
List albumList = null;
if (album.getComment().startsWith(ALL_BY_ARTIST)) {
- ArtistUpnpProcessor ap = getDispatcher().getArtistProcessor();
+ ArtistUpnpProcessor ap = router.getArtistProcessor();
albumList = ap.getChildren(ap.getItemById(album.getComment().replaceAll(ALL_BY_ARTIST + "_", "")));
} else if (album.getComment().equalsIgnoreCase(ALL_RECENT)) {
- albumList = getDispatcher().getRecentAlbumProcessor().getAllItems();
+ albumList = router.getRecentProcessor().getAllItems();
} else {
albumList = new ArrayList<>();
}
@@ -146,32 +155,37 @@ public List getChildren(Album album) {
@Override
public int getAllItemsSize() {
- List allFolders = getDispatchingContentDirectory().getMediaFolderService().getAllMusicFolders();
+ List allFolders = mediaFolderService.getAllMusicFolders();
return albumService.getAlbumCount(allFolders);
}
@Override
public void addChild(DIDLContent didl, MediaFile child) {
- didl.addItem(getDispatcher().getMediaFileProcessor().createItem(child));
- }
-
- public URI getAlbumArtURI(int albumId) {
- return UriComponentsBuilder
- .fromUriString(getDispatcher().getBaseUrl())
- .uriComponents(getDispatcher().getJwtSecurityService()
- .addJWTToken(
- User.USERNAME_ANONYMOUS,
- UriComponentsBuilder.fromUriString("ext/coverArt.view")
- .queryParam("id", albumId)
- .queryParam("size", CoverArtScheme.LARGE.getSize()))
- .build())
- .build().encode().toUri();
+ didl.addItem(router.getMediaFileProcessor().createItem(child));
}
public PersonWithRole[] getAlbumArtists(String artist) {
return new PersonWithRole[] { new PersonWithRole(artist) };
}
+ public BrowseResult searchByName(String name,
+ long firstResult, long maxResults,
+ SortCriterion[] orderBy) {
+ DIDLContent didl = new DIDLContent();
+ try {
+ List allFolders = mediaFolderService.getAllMusicFolders();
+ ParamSearchResult result = searchService.searchByName(name, Ints.saturatedCast(firstResult), Ints.saturatedCast(maxResults), allFolders, Album.class);
+ List selectedItems = result.getItems();
+ for (Album item : selectedItems) {
+ addItem(didl, item);
+ }
+
+ return createBrowseResult(didl, didl.getCount(), result.getTotalHits());
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ApacheUpnpServiceConfiguration.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ApacheUpnpServiceConfiguration.java
new file mode 100644
index 000000000..3a263a161
--- /dev/null
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ApacheUpnpServiceConfiguration.java
@@ -0,0 +1,50 @@
+/*
+ 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 2024 (C) Y.Tory
+ Copyright 2017 (C) Airsonic Authors
+ Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
+*/
+package org.airsonic.player.service.upnp;
+
+import org.fourthline.cling.DefaultUpnpServiceConfiguration;
+import org.fourthline.cling.transport.impl.apache.StreamClientConfigurationImpl;
+import org.fourthline.cling.transport.impl.apache.StreamClientImpl;
+import org.fourthline.cling.transport.impl.apache.StreamServerConfigurationImpl;
+import org.fourthline.cling.transport.impl.apache.StreamServerImpl;
+import org.fourthline.cling.transport.spi.NetworkAddressFactory;
+import org.fourthline.cling.transport.spi.StreamClient;
+import org.fourthline.cling.transport.spi.StreamServer;
+
+/**
+ * Note the different packages on similarly named classes from the parent
+ *
+ */
+public class ApacheUpnpServiceConfiguration extends DefaultUpnpServiceConfiguration {
+ public ApacheUpnpServiceConfiguration(int streamListenPort) {
+ super(streamListenPort);
+ }
+
+ @Override
+ public StreamClient> createStreamClient() {
+ return new StreamClientImpl(new StreamClientConfigurationImpl(getSyncProtocolExecutorService()));
+ }
+
+ @Override
+ public StreamServer> createStreamServer(NetworkAddressFactory networkAddressFactory) {
+ return new StreamServerImpl(new StreamServerConfigurationImpl(networkAddressFactory.getStreamListenPort()));
+ }
+}
\ No newline at end of file
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ArtistUpnpProcessor.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ArtistUpnpProcessor.java
index fcc8956f8..7f801d26e 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ArtistUpnpProcessor.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ArtistUpnpProcessor.java
@@ -14,17 +14,24 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2017 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.airsonic.player.service.upnp;
+import com.google.common.primitives.Ints;
import org.airsonic.player.domain.Album;
import org.airsonic.player.domain.Artist;
import org.airsonic.player.domain.MusicFolder;
+import org.airsonic.player.domain.ParamSearchResult;
import org.airsonic.player.repository.AlbumRepository;
import org.airsonic.player.repository.ArtistRepository;
+import org.airsonic.player.service.MediaFolderService;
+import org.airsonic.player.service.SearchService;
+import org.fourthline.cling.support.model.BrowseResult;
import org.fourthline.cling.support.model.DIDLContent;
+import org.fourthline.cling.support.model.SortCriterion;
import org.fourthline.cling.support.model.container.Container;
import org.fourthline.cling.support.model.container.MusicArtist;
import org.springframework.beans.factory.annotation.Autowired;
@@ -50,8 +57,17 @@ public class ArtistUpnpProcessor extends UpnpContentProcessor {
@Autowired
private AlbumRepository albumRepository;
+ @Autowired
+ private MediaFolderService mediaFolderService;
+
+ @Autowired
+ private UpnpProcessorRouter router;
+
+ @Autowired
+ private SearchService searchService;
+
public ArtistUpnpProcessor() {
- setRootId(DispatchingContentDirectory.CONTAINER_ID_ARTIST_PREFIX);
+ setRootId(ProcessorType.ARTIST);
setRootTitle("Artists");
}
@@ -68,7 +84,7 @@ public Container createContainer(Artist artist) {
@Override
public List getAllItems() {
- List allFolders = getDispatcher().getMediaFolderService().getAllMusicFolders();
+ List allFolders = mediaFolderService.getAllMusicFolders();
if (CollectionUtils.isEmpty(allFolders)) {
return Collections.emptyList();
}
@@ -90,7 +106,7 @@ public Artist getItemById(String id) {
@Override
public List getChildren(Artist artist) {
- List allFolders = getDispatcher().getMediaFolderService().getAllMusicFolders();
+ List allFolders = mediaFolderService.getAllMusicFolders();
List allAlbums = albumRepository.findByArtistAndFolderInAndPresentTrue(artist.getName(), allFolders);
if (allAlbums.size() > 1) {
// if the artist has more than one album, add in an option to
@@ -106,10 +122,26 @@ public List getChildren(Artist artist) {
@Override
public void addChild(DIDLContent didl, Album album) {
- didl.addContainer(getAlbumProcessor().createContainer(album));
+ didl.addContainer(router.getAlbumProcessor().createContainer(album));
}
- public AlbumUpnpProcessor getAlbumProcessor() {
- return getDispatcher().getAlbumProcessor();
+
+ public BrowseResult searchByName(String name,
+ long firstResult, long maxResults,
+ SortCriterion[] orderBy) {
+ DIDLContent didl = new DIDLContent();
+ try {
+ List allFolders = mediaFolderService.getAllMusicFolders();
+ ParamSearchResult result = searchService.searchByName(name, Ints.saturatedCast(firstResult), Ints.saturatedCast(maxResults), allFolders, Artist.class);
+ List selectedItems = result.getItems();
+ for (Artist item : selectedItems) {
+ addItem(didl, item);
+ }
+
+ return createBrowseResult(didl, didl.getCount(), result.getTotalHits());
+ } catch (Exception e) {
+ return null;
+ }
}
+
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/CustomContentDirectory.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/CustomContentDirectory.java
index d14d2a729..ecaa6cab5 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/CustomContentDirectory.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/CustomContentDirectory.java
@@ -14,33 +14,21 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2016 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.airsonic.player.service.upnp;
-import com.google.common.collect.Lists;
-import org.airsonic.player.domain.MediaFile;
-import org.airsonic.player.domain.Player;
-import org.airsonic.player.domain.User;
-import org.airsonic.player.service.JWTSecurityService;
-import org.airsonic.player.service.PlayerService;
-import org.airsonic.player.service.SecurityService;
-import org.airsonic.player.service.SettingsService;
-import org.airsonic.player.service.TranscodingService;
-import org.airsonic.player.util.StringUtil;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.StringUtils;
import org.fourthline.cling.support.contentdirectory.AbstractContentDirectoryService;
import org.fourthline.cling.support.contentdirectory.ContentDirectoryException;
import org.fourthline.cling.support.contentdirectory.DIDLParser;
import org.fourthline.cling.support.model.BrowseResult;
import org.fourthline.cling.support.model.DIDLContent;
-import org.fourthline.cling.support.model.Res;
import org.fourthline.cling.support.model.SortCriterion;
-import org.seamless.util.MimeType;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.util.UriComponentsBuilder;
+
+import java.util.Arrays;
+import java.util.Collections;
/**
* @author Sindre Mehus
@@ -50,60 +38,8 @@ public abstract class CustomContentDirectory extends AbstractContentDirectorySer
protected static final String CONTAINER_ID_ROOT = "0";
- @Autowired
- protected SettingsService settingsService;
- @Autowired
- private PlayerService playerService;
- @Autowired
- private TranscodingService transcodingService;
- @Autowired
- protected JWTSecurityService jwtSecurityService;
- @Autowired
- private SecurityService securityService;
-
public CustomContentDirectory() {
- super(Lists.newArrayList("*"), Lists.newArrayList());
- }
-
- protected Res createResourceForSong(MediaFile song) {
- // Create a guest user if necessary
- securityService.createGuestUserIfNotExists();
- Player player = playerService.getGuestPlayer(null);
-
- UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("ext/stream")
- .queryParam("id", song.getId())
- .queryParam("player", player.getId());
-
- if (song.isVideo()) {
- builder.queryParam("format", TranscodingService.FORMAT_RAW);
- }
-
- builder = jwtSecurityService.addJWTToken(User.USERNAME_ANONYMOUS, builder);
-
- String url = getBaseUrl() + builder.toUriString();
-
- String suffix = song.isVideo() ? FilenameUtils.getExtension(song.getPath()) : transcodingService.getSuffix(player, song, null);
- String mimeTypeString = StringUtil.getMimeType(suffix);
- MimeType mimeType = mimeTypeString == null ? null : MimeType.valueOf(mimeTypeString);
-
- Res res = new Res(mimeType, null, url);
- res.setDuration(formatDuration(song.getDuration()));
- return res;
- }
-
- private String formatDuration(Double seconds) {
- if (seconds == null) {
- return null;
- }
- return StringUtil.formatDuration((long) (seconds * 1000), true);
- }
-
- protected String getBaseUrl() {
- String dlnaBaseLANURL = settingsService.getDlnaBaseLANURL();
- if (StringUtils.isBlank(dlnaBaseLANURL)) {
- throw new RuntimeException("DLNA Base LAN URL is not set correctly");
- }
- return StringUtils.appendIfMissing(dlnaBaseLANURL, "/");
+ super(Arrays.asList("*"), Collections.emptyList());
}
protected BrowseResult createBrowseResult(DIDLContent didl, int count, int totalMatches) throws Exception {
@@ -119,19 +55,4 @@ public BrowseResult search(String containerId,
return super.search(containerId, searchCriteria, filter, firstResult, maxResults, orderBy);
}
- public void setPlayerService(PlayerService playerService) {
- this.playerService = playerService;
- }
-
- public void setTranscodingService(TranscodingService transcodingService) {
- this.transcodingService = transcodingService;
- }
-
- public void setSettingsService(SettingsService settingsService) {
- this.settingsService = settingsService;
- }
-
- public void setJwtSecurityService(JWTSecurityService jwtSecurityService) {
- this.jwtSecurityService = jwtSecurityService;
- }
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/DispatchingContentDirectory.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/DispatchingContentDirectory.java
index 6eb2df9ff..2e80c1145 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/DispatchingContentDirectory.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/DispatchingContentDirectory.java
@@ -14,29 +14,20 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2016 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.airsonic.player.service.upnp;
-import org.airsonic.player.domain.CoverArtScheme;
-import org.airsonic.player.domain.MediaFile;
-import org.airsonic.player.domain.User;
-import org.airsonic.player.service.*;
import org.fourthline.cling.support.contentdirectory.ContentDirectoryErrorCode;
import org.fourthline.cling.support.contentdirectory.ContentDirectoryException;
import org.fourthline.cling.support.model.*;
-import org.fourthline.cling.support.model.item.Item;
-import org.fourthline.cling.support.model.item.MusicTrack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
-import org.springframework.web.util.UriComponentsBuilder;
-import java.net.URI;
-import java.util.Arrays;
/**
* @author Allen Petersen
@@ -48,62 +39,19 @@ public class DispatchingContentDirectory extends CustomContentDirectory {
private static final Logger LOG = LoggerFactory.getLogger(DispatchingContentDirectory.class);
- public static final String CONTAINER_ID_ROOT = "0";
- public static final String CONTAINER_ID_PLAYLIST_PREFIX = "playlist";
- public static final String CONTAINER_ID_FOLDER_PREFIX = "folder";
- public static final String CONTAINER_ID_ALBUM_PREFIX = "album";
- public static final String CONTAINER_ID_ARTIST_PREFIX = "artist";
- public static final String CONTAINER_ID_ARTISTALBUM_PREFIX = "artistalbum";
- public static final String CONTAINER_ID_GENRE_PREFIX = "genre";
- public static final String CONTAINER_ID_RECENT_PREFIX = "recent";
-
protected static final String SEPARATOR = "-";
- @Lazy
- @Autowired
- private PlaylistUpnpProcessor playlistProcessor;
- @Lazy
- @Autowired
- private MediaFileUpnpProcessor mediaFileProcessor;
- //@Autowired can't autowire because of the subclassing :P
- @Lazy
- @Autowired//first checks type then field name to autowire
- private AlbumUpnpProcessor albumUpnpProcessor;
- //@Autowired can't autowire because of the subclassing :P
- @Lazy
- @Autowired//first checks type then field name to autowire
- private RecentAlbumUpnpProcessor recentAlbumUpnpProcessor;
- @Lazy
- @Autowired
- private ArtistUpnpProcessor artistProcessor;
- @Lazy
- @Autowired
- private GenreUpnpProcessor genreProcessor;
- @Lazy
- @Autowired
- private RootUpnpProcessor rootProcessor;
-
- @Autowired
- private MediaFileService mediaFileService;
- @Autowired
- private MediaFolderService mediaFolderService;
@Autowired
- private PlaylistService playlistService;
-
- @Autowired
- private MusicIndexService musicIndexService;
-
- @Autowired
- private SearchService searchService;
-
+ private UpnpProcessorRouter router;
@Override
public BrowseResult browse(String objectId, BrowseFlag browseFlag,
- String filter, long firstResult,
- long maxResults, SortCriterion[] orderBy)
- throws ContentDirectoryException {
+ String filter, long firstResult,
+ long maxResults, SortCriterion[] orderBy)
+ throws ContentDirectoryException {
- LOG.info("UPnP request - objectId: " + objectId + ", browseFlag: " + browseFlag + ", filter: " + filter + ", firstResult: " + firstResult + ", maxResults: " + maxResults);
+ LOG.info("UPnP request - objectId: " + objectId + ", browseFlag: " + browseFlag + ", filter: " + filter
+ + ", firstResult: " + firstResult + ", maxResults: " + maxResults);
if (objectId == null)
throw new ContentDirectoryException(ContentDirectoryErrorCode.CANNOT_PROCESS, "objectId is null");
@@ -119,18 +67,20 @@ public BrowseResult browse(String objectId, BrowseFlag browseFlag,
String browseRoot = splitId[0];
String itemId = splitId.length == 1 ? null : splitId[1];
- UpnpContentProcessor processor = findProcessor(browseRoot);
+ UpnpContentProcessor, ?> processor = router.findProcessor(ProcessorType.toEnum(browseRoot));
if (processor == null) {
// if it's null then assume it's a file, and that the id
// is all that's there.
itemId = browseRoot;
- processor = getMediaFileProcessor();
+ processor = router.findProcessor(ProcessorType.MEDIAFILE);
}
if (itemId == null) {
- returnValue = browseFlag == BrowseFlag.METADATA ? processor.browseRootMetadata() : processor.browseRoot(filter, firstResult, maxResults, orderBy);
+ returnValue = browseFlag == BrowseFlag.METADATA ? processor.browseRootMetadata()
+ : processor.browseRoot(filter, firstResult, maxResults, orderBy);
} else {
- returnValue = browseFlag == BrowseFlag.METADATA ? processor.browseObjectMetadata(itemId) : processor.browseObject(itemId, filter, firstResult, maxResults, orderBy);
+ returnValue = browseFlag == BrowseFlag.METADATA ? processor.browseObjectMetadata(itemId)
+ : processor.browseObject(itemId, filter, firstResult, maxResults, orderBy);
}
return returnValue;
} catch (Throwable x) {
@@ -141,174 +91,24 @@ public BrowseResult browse(String objectId, BrowseFlag browseFlag,
@Override
public BrowseResult search(String containerId,
- String searchCriteria, String filter,
- long firstResult, long maxResults,
- SortCriterion[] orderBy) throws ContentDirectoryException {
+ String searchCriteria, String filter,
+ long firstResult, long maxResults,
+ SortCriterion[] orderBy) throws ContentDirectoryException {
// i don't see a parser for upnp search criteria anywhere, so this will
// have to do
String upnpClass = searchCriteria.replaceAll("^.*upnp:class\\s+[\\S]+\\s+\"([\\S]*)\".*$", "$1");
String titleSearch = searchCriteria.replaceAll("^.*dc:title\\s+[\\S]+\\s+\"([\\S]*)\".*$", "$1");
BrowseResult returnValue = null;
if ("object.container.person.musicArtist".equalsIgnoreCase(upnpClass)) {
- returnValue = getArtistProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy);
+ returnValue = router.getArtistProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy);
} else if ("object.item.audioItem".equalsIgnoreCase(upnpClass)) {
- returnValue = getMediaFileProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy);
+ returnValue = router.getMediaFileProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy);
} else if ("object.container.album.musicAlbum".equalsIgnoreCase(upnpClass)) {
- returnValue = getAlbumProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy);
+ returnValue = router.getAlbumProcessor().searchByName(titleSearch, firstResult, maxResults, orderBy);
}
- return returnValue != null ? returnValue : super.search(containerId, searchCriteria, filter, firstResult, maxResults, orderBy);
- }
-
-
- private UpnpContentProcessor findProcessor(String type) {
- switch (type) {
- case CONTAINER_ID_ROOT:
- return getRootProcessor();
- case CONTAINER_ID_PLAYLIST_PREFIX:
- return getPlaylistProcessor();
- case CONTAINER_ID_FOLDER_PREFIX:
- return getMediaFileProcessor();
- case CONTAINER_ID_ALBUM_PREFIX:
- return getAlbumProcessor();
- case CONTAINER_ID_RECENT_PREFIX:
- return getRecentAlbumProcessor();
- case CONTAINER_ID_ARTIST_PREFIX:
- return getArtistProcessor();
- case CONTAINER_ID_GENRE_PREFIX:
- return getGenreProcessor();
- }
- return null;
- }
-
- public Item createItem(MediaFile song) {
- MediaFile parent = mediaFileService.getParentOf(song);
- MusicTrack item = new MusicTrack();
- item.setId(String.valueOf(song.getId()));
- item.setParentID(String.valueOf(parent.getId()));
- item.setTitle(song.getTitle());
- item.setAlbum(song.getAlbumName());
- if (song.getArtist() != null) {
- item.setArtists(new PersonWithRole[]{new PersonWithRole(song.getArtist())});
- }
- Integer year = song.getYear();
- if (year != null) {
- item.setDate(year + "-01-01");
- }
- item.setOriginalTrackNumber(song.getTrackNumber());
- if (song.getGenre() != null) {
- item.setGenres(new String[]{song.getGenre()});
- }
- item.setResources(Arrays.asList(createResourceForSong(song)));
- item.setDescription(song.getComment());
- item.addProperty(new DIDLObject.Property.UPNP.ALBUM_ART_URI(getAlbumArtUrl(parent.getId())));
-
- return item;
- }
-
- public URI getAlbumArtUrl(int id) {
- return UriComponentsBuilder
- .fromUriString(getBaseUrl())
- .uriComponents(jwtSecurityService
- .addJWTToken(
- User.USERNAME_ANONYMOUS,
- UriComponentsBuilder.fromUriString("ext/coverArt.view")
- .queryParam("id", id)
- .queryParam("size", CoverArtScheme.LARGE.getSize()))
- .build())
- .build().encode().toUri();
- }
-
- public PlaylistUpnpProcessor getPlaylistProcessor() {
- return playlistProcessor;
- }
- public void setPlaylistProcessor(PlaylistUpnpProcessor playlistProcessor) {
- this.playlistProcessor = playlistProcessor;
- }
-
- public MediaFileUpnpProcessor getMediaFileProcessor() {
- return mediaFileProcessor;
- }
- public void setMediaFileProcessor(MediaFileUpnpProcessor mediaFileProcessor) {
- this.mediaFileProcessor = mediaFileProcessor;
- }
-
- public AlbumUpnpProcessor getAlbumProcessor() {
- return albumUpnpProcessor;
- }
- public void setAlbumProcessor(AlbumUpnpProcessor albumProcessor) {
- this.albumUpnpProcessor = albumProcessor;
- }
-
- public RecentAlbumUpnpProcessor getRecentAlbumProcessor() {
- return recentAlbumUpnpProcessor;
- }
- public void setRecentAlbumProcessor(RecentAlbumUpnpProcessor recentAlbumProcessor) {
- this.recentAlbumUpnpProcessor = recentAlbumProcessor;
- }
-
- public ArtistUpnpProcessor getArtistProcessor() {
- return artistProcessor;
- }
- public void setArtistProcessor(ArtistUpnpProcessor artistProcessor) {
- this.artistProcessor = artistProcessor;
- }
-
- public GenreUpnpProcessor getGenreProcessor() {
- return genreProcessor;
- }
- public void setGenreProcessor(GenreUpnpProcessor genreProcessor) {
- this.genreProcessor = genreProcessor;
- }
-
- public RootUpnpProcessor getRootProcessor() {
- return rootProcessor;
- }
- public void setRootProcessor(RootUpnpProcessor rootProcessor) {
- this.rootProcessor = rootProcessor;
- }
-
- public MediaFileService getMediaFileService() {
- return mediaFileService;
- }
- public void setMediaFileService(MediaFileService mediaFileService) {
- this.mediaFileService = mediaFileService;
+ return returnValue != null ? returnValue
+ : super.search(containerId, searchCriteria, filter, firstResult, maxResults, orderBy);
}
- public MediaFolderService getMediaFolderService() {
- return mediaFolderService;
- }
-
- public void setMediaFolderService(MediaFolderService mediaFolderService) {
- this.mediaFolderService = mediaFolderService;
- }
-
- public SettingsService getSettingsService() {
- return settingsService;
- }
-
- public PlaylistService getPlaylistService() {
- return playlistService;
- }
- public void setPlaylistService(PlaylistService playlistService) {
- this.playlistService = playlistService;
- }
-
- public JWTSecurityService getJwtSecurityService() {
- return jwtSecurityService;
- }
-
- public MusicIndexService getMusicIndexService() {
- return this.musicIndexService;
- }
- public void setMusicIndexService(MusicIndexService musicIndexService) {
- this.musicIndexService = musicIndexService;
- }
-
- public SearchService getSearchService() {
- return this.searchService;
- }
- public void setSearchService(SearchService searchService) {
- this.searchService = searchService;
- }
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/FolderBasedContentDirectory.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/FolderBasedContentDirectory.java
index 4157441cd..907c4a46b 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/FolderBasedContentDirectory.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/FolderBasedContentDirectory.java
@@ -14,6 +14,7 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2016 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
@@ -38,7 +39,6 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Arrays;
@@ -63,6 +63,8 @@ public class FolderBasedContentDirectory extends CustomContentDirectory {
private PlaylistService playlistService;
@Autowired
private IndexManager indexManager;
+ @Autowired
+ private UpnpUtil upnpUtil;
@Override
public BrowseResult browse(String objectId, BrowseFlag browseFlag, String filter, long firstResult,
@@ -212,9 +214,9 @@ private Item createItem(MediaFile song) {
if (song.getGenre() != null) {
item.setGenres(new String[]{song.getGenre()});
}
- item.setResources(Arrays.asList(createResourceForSong(song)));
+ item.setResources(Arrays.asList(upnpUtil.createResourceForSong(song)));
item.setDescription(song.getComment());
- item.addProperty(new DIDLObject.Property.UPNP.ALBUM_ART_URI(getAlbumArtUrl(parent)));
+ item.addProperty(new DIDLObject.Property.UPNP.ALBUM_ART_URI(upnpUtil.getAlbumArtURI(parent.getId())));
return item;
}
@@ -238,7 +240,7 @@ private Container createContainer(MediaFile mediaFile) {
private Container createAlbumContainer(MediaFile album) {
MusicAlbum container = new MusicAlbum();
- container.setAlbumArtURIs(new URI[]{getAlbumArtUrl(album)});
+ container.setAlbumArtURIs(new URI[]{upnpUtil.getAlbumArtURI(album.getId())});
// TODO: correct artist?
if (album.getArtist() != null) {
@@ -271,24 +273,4 @@ private Container createPlaylistContainer(Playlist playlist) {
return container;
}
- private URI getAlbumArtUrl(MediaFile album) {
- return UriComponentsBuilder
- .fromUriString(getBaseUrl())
- .uriComponents(jwtSecurityService
- .addJWTToken(
- User.USERNAME_ANONYMOUS,
- UriComponentsBuilder.fromUriString("ext/coverArt.view")
- .queryParam("id", album.getId())
- .queryParam("size", CoverArtScheme.LARGE.getSize()))
- .build())
- .build().encode().toUri();
- }
-
- public void setMediaFileService(MediaFileService mediaFileService) {
- this.mediaFileService = mediaFileService;
- }
-
- public void setPlaylistService(PlaylistService playlistService) {
- this.playlistService = playlistService;
- }
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/GenreUpnpProcessor.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/GenreUpnpProcessor.java
index e03fd3b3b..6a1fac6e0 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/GenreUpnpProcessor.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/GenreUpnpProcessor.java
@@ -14,6 +14,7 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2017 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
@@ -22,12 +23,15 @@
import org.airsonic.player.domain.Genre;
import org.airsonic.player.domain.MediaFile;
import org.airsonic.player.domain.MusicFolder;
+import org.airsonic.player.service.MediaFileService;
+import org.airsonic.player.service.MediaFolderService;
import org.airsonic.player.util.Util;
import org.fourthline.cling.support.model.BrowseResult;
import org.fourthline.cling.support.model.DIDLContent;
import org.fourthline.cling.support.model.SortCriterion;
import org.fourthline.cling.support.model.container.Container;
import org.fourthline.cling.support.model.container.GenreContainer;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -40,10 +44,19 @@
public class GenreUpnpProcessor extends UpnpContentProcessor {
public GenreUpnpProcessor() {
- setRootId(DispatchingContentDirectory.CONTAINER_ID_GENRE_PREFIX);
+ setRootId(ProcessorType.GENRE);
setRootTitle("Genres");
}
+ @Autowired
+ private UpnpProcessorRouter router;
+
+ @Autowired
+ private MediaFileService mediaFileService;
+
+ @Autowired
+ private MediaFolderService mediaFolderService;
+
/**
* Browses the top-level content of a type.
*/
@@ -84,7 +97,7 @@ public Container createContainer(Genre item, int index) {
@Override
public List getAllItems() {
- return getDispatcher().getMediaFileService().getGenres(false);
+ return mediaFileService.getGenres(false);
}
@Override
@@ -99,12 +112,12 @@ public Genre getItemById(String id) {
@Override
public List getChildren(Genre item) {
- List allFolders = getDispatcher().getMediaFolderService().getAllMusicFolders();
- return getDispatcher().getMediaFileProcessor().getMediaFileService().getSongsByGenre(0, Integer.MAX_VALUE, item.getName(), allFolders);
+ List allFolders = mediaFolderService.getAllMusicFolders();
+ return mediaFileService.getSongsByGenre(0, Integer.MAX_VALUE, item.getName(), allFolders);
}
@Override
public void addChild(DIDLContent didl, MediaFile child) {
- didl.addItem(getDispatcher().getMediaFileProcessor().createItem(child));
+ didl.addItem(router.getMediaFileProcessor().createItem(child));
}
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/MediaFileUpnpProcessor.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/MediaFileUpnpProcessor.java
index 63903497d..bbdfb7c89 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/MediaFileUpnpProcessor.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/MediaFileUpnpProcessor.java
@@ -20,16 +20,22 @@
*/
package org.airsonic.player.service.upnp;
+import com.google.common.primitives.Ints;
import org.airsonic.player.domain.MediaFile;
import org.airsonic.player.domain.MusicFolder;
+import org.airsonic.player.domain.ParamSearchResult;
import org.airsonic.player.service.MediaFileService;
+import org.airsonic.player.service.MediaFolderService;
+import org.airsonic.player.service.SearchService;
import org.fourthline.cling.support.model.BrowseResult;
import org.fourthline.cling.support.model.DIDLContent;
import org.fourthline.cling.support.model.DIDLObject;
+import org.fourthline.cling.support.model.SortCriterion;
import org.fourthline.cling.support.model.container.Container;
import org.fourthline.cling.support.model.container.MusicAlbum;
import org.fourthline.cling.support.model.item.Item;
import org.fourthline.cling.support.model.item.MusicTrack;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.net.URI;
@@ -46,10 +52,25 @@
public class MediaFileUpnpProcessor extends UpnpContentProcessor {
public MediaFileUpnpProcessor() {
- setRootId(DispatchingContentDirectory.CONTAINER_ID_FOLDER_PREFIX);
+ setRootId(ProcessorType.FOLDER);
setRootTitle("Folders");
}
+ @Autowired
+ private UpnpProcessorRouter router;
+
+ @Autowired
+ private MediaFileService mediaFileService;
+
+ @Autowired
+ private MediaFolderService mediaFolderService;
+
+ @Autowired
+ private UpnpUtil upnpUtil;
+
+ @Autowired
+ private SearchService searchService;
+
@Override
// overriding for the case of browsing a file
public BrowseResult browseObjectMetadata(String id) throws Exception {
@@ -63,49 +84,49 @@ public BrowseResult browseObjectMetadata(String id) throws Exception {
public Container createContainer(MediaFile item) {
MusicAlbum container = new MusicAlbum();
if (item.isAlbum()) {
- container.setAlbumArtURIs(new URI[] { getDispatcher().getAlbumProcessor().getAlbumArtURI(item.getId())});
+ container.setAlbumArtURIs(new URI[] { upnpUtil.getAlbumArtURI(item.getId())});
if (item.getArtist() != null) {
- container.setArtists(getDispatcher().getAlbumProcessor().getAlbumArtists(item.getArtist()));
+ container.setArtists(router.getAlbumProcessor().getAlbumArtists(item.getArtist()));
}
container.setDescription(item.getComment());
}
- container.setId(DispatchingContentDirectory.CONTAINER_ID_FOLDER_PREFIX + DispatchingContentDirectory.SEPARATOR + item.getId());
+ container.setId(getRootId() + DispatchingContentDirectory.SEPARATOR + item.getId());
container.setTitle(item.getName());
List children = getChildren(item);
container.setChildCount(children.size());
- if (! getMediaFileService().isRoot(item)) {
- MediaFile parent = getMediaFileService().getParentOf(item);
+ if (! mediaFileService.isRoot(item)) {
+ MediaFile parent = mediaFileService.getParentOf(item);
if (parent != null) {
container.setParentID(String.valueOf(parent.getId()));
}
} else {
- container.setParentID(DispatchingContentDirectory.CONTAINER_ID_FOLDER_PREFIX);
+ container.setParentID(getRootId());
}
return container;
}
@Override
public List getAllItems() {
- List allFolders = getDispatcher().getMediaFolderService().getAllMusicFolders();
+ List allFolders = mediaFolderService.getAllMusicFolders();
if (allFolders.size() == 1) {
// if there's only one root folder just return it
- return getChildren(getMediaFileService().getMediaFile("", allFolders.get(0)));
+ return getChildren(mediaFileService.getMediaFile("", allFolders.get(0)));
} else {
- return allFolders.stream().map(f -> getMediaFileService().getMediaFile("", f)).collect(toList());
+ return allFolders.stream().map(f -> mediaFileService.getMediaFile("", f)).collect(toList());
}
}
@Override
public MediaFile getItemById(String id) {
- return getMediaFileService().getMediaFile(Integer.parseInt(id));
+ return mediaFileService.getMediaFile(Integer.parseInt(id));
}
@Override
public List getChildren(MediaFile item) {
- List children = getMediaFileService().getVisibleChildrenOf(item, true, true);
+ List children = mediaFileService.getVisibleChildrenOf(item, true, true);
children.sort((MediaFile o1, MediaFile o2) -> o1.getPath().replaceAll("\\W", "").compareToIgnoreCase(o2.getPath().replaceAll("\\W", "")));
return children;
}
@@ -129,14 +150,14 @@ public void addChild(DIDLContent didl, MediaFile child) {
}
public Item createItem(MediaFile song) {
- MediaFile parent = getMediaFileService().getParentOf(song);
+ MediaFile parent = mediaFileService.getParentOf(song);
MusicTrack item = new MusicTrack();
item.setId(String.valueOf(song.getId()));
item.setParentID(String.valueOf(parent.getId()));
item.setTitle(song.getTitle());
item.setAlbum(song.getAlbumName());
if (song.getArtist() != null) {
- item.setArtists(getDispatcher().getAlbumProcessor().getAlbumArtists(song.getArtist()));
+ item.setArtists(router.getAlbumProcessor().getAlbumArtists(song.getArtist()));
}
Integer year = song.getYear();
if (year != null) {
@@ -146,15 +167,29 @@ public Item createItem(MediaFile song) {
if (song.getGenre() != null) {
item.setGenres(new String[]{song.getGenre()});
}
- item.setResources(Arrays.asList(getDispatcher().createResourceForSong(song)));
+ item.setResources(Arrays.asList(upnpUtil.createResourceForSong(song)));
item.setDescription(song.getComment());
- item.addProperty(new DIDLObject.Property.UPNP.ALBUM_ART_URI(getDispatcher().getAlbumProcessor().getAlbumArtURI(parent.getId())));
+ item.addProperty(new DIDLObject.Property.UPNP.ALBUM_ART_URI(upnpUtil.getAlbumArtURI(parent.getId())));
return item;
}
- public MediaFileService getMediaFileService() {
- return getDispatchingContentDirectory().getMediaFileService();
+ public BrowseResult searchByName(String name,
+ long firstResult, long maxResults,
+ SortCriterion[] orderBy) {
+ DIDLContent didl = new DIDLContent();
+ try {
+ List allFolders = mediaFolderService.getAllMusicFolders();
+ ParamSearchResult result = searchService.searchByName(name, Ints.saturatedCast(firstResult), Ints.saturatedCast(maxResults), allFolders, MediaFile.class);
+ List selectedItems = result.getItems();
+ for (MediaFile item : selectedItems) {
+ addItem(didl, item);
+ }
+
+ return createBrowseResult(didl, didl.getCount(), result.getTotalHits());
+ } catch (Exception e) {
+ return null;
+ }
}
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/PlaylistUpnpProcessor.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/PlaylistUpnpProcessor.java
index 1658fb3fa..d972740fd 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/PlaylistUpnpProcessor.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/PlaylistUpnpProcessor.java
@@ -14,6 +14,7 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2017 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
@@ -39,8 +40,11 @@ public class PlaylistUpnpProcessor extends UpnpContentProcessor getAllItems() {
- return getPlaylistService().getAllPlaylists();
+ return playlistService.getAllPlaylists();
}
public Playlist getItemById(String id) {
- return getDispatcher().getPlaylistService().getPlaylist(Integer.parseInt(id));
+ return playlistService.getPlaylist(Integer.parseInt(id));
}
public List getChildren(Playlist item) {
- return getPlaylistService().getFilesInPlaylist(item.getId());
+ return playlistService.getFilesInPlaylist(item.getId());
}
public void addChild(DIDLContent didl, MediaFile child) {
- didl.addItem(getDispatchingContentDirectory().createItem(child));
- }
-
- public PlaylistService getPlaylistService() {
- return this.playlistService;
- }
- public void setPlaylistService(PlaylistService playlistService) {
- this.playlistService = playlistService;
+ didl.addItem(router.getMediaFileProcessor().createItem(child));
}
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ProcessorType.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ProcessorType.java
new file mode 100644
index 000000000..e49c5e30d
--- /dev/null
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/ProcessorType.java
@@ -0,0 +1,57 @@
+/*
+ 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 2024 (C) Y.Tory
+ Copyright 2017 (C) Airsonic Authors
+ Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
+*/
+package org.airsonic.player.service.upnp;
+
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+
+public enum ProcessorType {
+
+ ROOT("0"),
+ PLAYLIST("playlist"),
+ FOLDER("folder"),
+ ALBUM("album"),
+ ARTIST("artist"),
+ ARTISTALBUM("artistalbum"),
+ GENRE("genre"),
+ RECENT("recent"),
+ MEDIAFILE("mediafile"),
+ UNKNOWN("unknown");
+
+ private String keyType;
+
+ private ProcessorType(String keyType) {
+ this.keyType = keyType;
+ }
+
+ public String getKeyType() {
+ return this.keyType;
+ }
+
+ @Nonnull
+ public static ProcessorType toEnum(@Nullable String keyType) {
+ for (ProcessorType pt: values()) {
+ if (pt.getKeyType().equalsIgnoreCase(keyType)) return pt;
+ }
+ return UNKNOWN;
+ }
+
+}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/RecentAlbumUpnpProcessor.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/RecentAlbumUpnpProcessor.java
index 1ce7e31dd..41a5cb2fc 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/RecentAlbumUpnpProcessor.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/RecentAlbumUpnpProcessor.java
@@ -14,6 +14,7 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2017 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
@@ -21,6 +22,7 @@
import org.airsonic.player.domain.Album;
import org.airsonic.player.domain.MusicFolder;
import org.airsonic.player.service.AlbumService;
+import org.airsonic.player.service.MediaFolderService;
import org.airsonic.player.util.Util;
import org.fourthline.cling.support.model.BrowseResult;
import org.fourthline.cling.support.model.DIDLContent;
@@ -41,8 +43,11 @@ public class RecentAlbumUpnpProcessor extends AlbumUpnpProcessor {
@Autowired
private AlbumService albumService;
+ @Autowired
+ private MediaFolderService mediaFolderService;
+
public RecentAlbumUpnpProcessor() {
- setRootId(DispatchingContentDirectory.CONTAINER_ID_RECENT_PREFIX);
+ setRootId(ProcessorType.RECENT);
setRootTitle("RecentAlbums");
}
@@ -71,7 +76,7 @@ public BrowseResult browseRoot(String filter, long firstResult, long maxResults,
@Override
public List getAllItems() {
- List allFolders = getDispatchingContentDirectory().getMediaFolderService().getAllMusicFolders();
+ List allFolders = mediaFolderService.getAllMusicFolders();
List recentAlbums = albumService.getRecentlyAddedAlbums(0, RECENT_COUNT, allFolders);
if (recentAlbums.size() > 1) {
// if there is more than one recent album, add in an option to
@@ -87,7 +92,7 @@ public List getAllItems() {
@Override
public int getAllItemsSize() {
- List allFolders = getDispatchingContentDirectory().getMediaFolderService().getAllMusicFolders();
+ List allFolders = mediaFolderService.getAllMusicFolders();
int allAlbumCount = albumService.getAlbumCount(allFolders);
return Math.min(allAlbumCount, RECENT_COUNT);
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/RootUpnpProcessor.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/RootUpnpProcessor.java
index 77221c7e7..831e2f3dc 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/RootUpnpProcessor.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/RootUpnpProcessor.java
@@ -41,9 +41,12 @@ public class RootUpnpProcessor extends UpnpContentProcessor getAllItems() throws Exception {
ArrayList allItems = new ArrayList();
- allItems.add(getDispatchingContentDirectory().getAlbumProcessor().createRootContainer());
- allItems.add(getDispatchingContentDirectory().getArtistProcessor().createRootContainer());
- allItems.add(getDispatchingContentDirectory().getMediaFileProcessor().createRootContainer());
- allItems.add(getDispatchingContentDirectory().getGenreProcessor().createRootContainer());
- allItems.add(getDispatchingContentDirectory().getPlaylistProcessor().createRootContainer());
- allItems.add(getDispatchingContentDirectory().getRecentAlbumProcessor().createRootContainer());
+ allItems.add(router.getAlbumProcessor().createRootContainer());
+ allItems.add(router.getArtistProcessor().createRootContainer());
+ allItems.add(router.getMediaFileProcessor().createRootContainer());
+ allItems.add(router.getGenreProcessor().createRootContainer());
+ allItems.add(router.getPlaylistProcessor().createRootContainer());
+ allItems.add(router.getRecentProcessor().createRootContainer());
return allItems;
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpContentProcessor.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpContentProcessor.java
index cb1e58bf3..b0acddd98 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpContentProcessor.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpContentProcessor.java
@@ -14,14 +14,12 @@
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see .
+ Copyright 2024 (C) Y.Tory
Copyright 2017 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.airsonic.player.service.upnp;
-import com.google.common.primitives.Ints;
-import org.airsonic.player.domain.MusicFolder;
-import org.airsonic.player.domain.ParamSearchResult;
import org.airsonic.player.util.Util;
import org.fourthline.cling.support.contentdirectory.DIDLParser;
import org.fourthline.cling.support.model.BrowseResult;
@@ -29,9 +27,7 @@
import org.fourthline.cling.support.model.SortCriterion;
import org.fourthline.cling.support.model.container.Container;
import org.fourthline.cling.support.model.container.StorageFolder;
-import org.springframework.beans.factory.annotation.Autowired;
-import java.lang.reflect.ParameterizedType;
import java.util.List;
/**
@@ -40,11 +36,8 @@
*/
public abstract class UpnpContentProcessor {
- @Autowired
- private DispatchingContentDirectory dispatchingContentDirectory;
-
protected String rootTitle;
- protected String rootId;
+ protected ProcessorType rootId;
/**
* Browses the root metadata for a type.
@@ -62,7 +55,7 @@ public Container createRootContainer() throws Exception {
int childCount = getAllItemsSize();
container.setChildCount(childCount);
- container.setParentID(DispatchingContentDirectory.CONTAINER_ID_ROOT);
+ container.setParentID(ProcessorType.ROOT.getKeyType());
return container;
}
@@ -121,36 +114,7 @@ protected BrowseResult createBrowseResult(DIDLContent didl, long count, long tot
return new BrowseResult(new DIDLParser().generate(didl), count, totalMatches);
}
- public BrowseResult searchByName(String name,
- long firstResult, long maxResults,
- SortCriterion[] orderBy) {
- DIDLContent didl = new DIDLContent();
-
- Class clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
- try {
- List allFolders = getDispatchingContentDirectory().getMediaFolderService().getAllMusicFolders();
- ParamSearchResult result = getDispatcher().getSearchService().searchByName(name, Ints.saturatedCast(firstResult), Ints.saturatedCast(maxResults), allFolders, clazz);
- List selectedItems = result.getItems();
- for (T item : selectedItems) {
- addItem(didl, item);
- }
-
- return createBrowseResult(didl, didl.getCount(), result.getTotalHits());
- } catch (Exception e) {
- return null;
- }
- }
-
- public DispatchingContentDirectory getDispatchingContentDirectory() {
- return dispatchingContentDirectory;
- }
- public void setDispatchingContentDirectory(DispatchingContentDirectory dispatchingContentDirectory) {
- this.dispatchingContentDirectory = dispatchingContentDirectory;
- }
- public DispatchingContentDirectory getDispatcher() {
- return getDispatchingContentDirectory();
- }
public void addItem(DIDLContent didl, T item) {
didl.addContainer(createContainer(item));
@@ -178,10 +142,9 @@ public void setRootTitle(String rootTitle) {
this.rootTitle = rootTitle;
}
public String getRootId() {
- return rootId;
+ return rootId.getKeyType();
}
- public void setRootId(String rootId) {
+ public void setRootId(ProcessorType rootId) {
this.rootId = rootId;
}
}
-
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpProcessorRouter.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpProcessorRouter.java
new file mode 100644
index 000000000..36335db4c
--- /dev/null
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpProcessorRouter.java
@@ -0,0 +1,39 @@
+/*
+ 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 2024 (C) Y.Tory
+ Copyright 2017 (C) Airsonic Authors
+ Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
+*/
+package org.airsonic.player.service.upnp;
+
+public interface UpnpProcessorRouter {
+
+ public UpnpContentProcessor, ?> findProcessor(ProcessorType type);
+
+ public AlbumUpnpProcessor getAlbumProcessor();
+
+ public ArtistUpnpProcessor getArtistProcessor();
+
+ public MediaFileUpnpProcessor getMediaFileProcessor();
+
+ public RecentAlbumUpnpProcessor getRecentProcessor();
+
+ public PlaylistUpnpProcessor getPlaylistProcessor();
+
+ public GenreUpnpProcessor getGenreProcessor();
+
+}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpProcessorRouterImpl.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpProcessorRouterImpl.java
new file mode 100644
index 000000000..42f1f52fc
--- /dev/null
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpProcessorRouterImpl.java
@@ -0,0 +1,107 @@
+/*
+ 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 2024 (C) Y.Tory
+ Copyright 2017 (C) Airsonic Authors
+ Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
+*/
+package org.airsonic.player.service.upnp;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+@Component
+public class UpnpProcessorRouterImpl implements UpnpProcessorRouter {
+
+ @Autowired
+ @Lazy
+ private RootUpnpProcessor rootUpnpProcessor;
+ @Lazy
+ @Autowired
+ private AlbumUpnpProcessor albumUpnpProcessor;
+ @Lazy
+ @Autowired
+ private ArtistUpnpProcessor artistUpnpProcessor;
+ @Lazy
+ @Autowired
+ private GenreUpnpProcessor genreUpnpProcessor;
+ @Lazy
+ @Autowired
+ private MediaFileUpnpProcessor mediaFileUpnpProcessor;
+ @Lazy
+ @Autowired
+ private PlaylistUpnpProcessor playlistUpnpProcessor;
+ @Lazy
+ @Autowired
+ private RecentAlbumUpnpProcessor recentAlbumUpnpProcessor;
+
+ @Override
+ public UpnpContentProcessor, ?> findProcessor(ProcessorType type) {
+ switch (type) {
+ case ROOT:
+ return rootUpnpProcessor;
+ case PLAYLIST:
+ return playlistUpnpProcessor;
+ case FOLDER:
+ case MEDIAFILE:
+ return mediaFileUpnpProcessor;
+ case ALBUM:
+ return albumUpnpProcessor;
+ case RECENT:
+ return recentAlbumUpnpProcessor;
+ case ARTIST:
+ return artistUpnpProcessor;
+ case GENRE:
+ return genreUpnpProcessor;
+ case ARTISTALBUM:
+ case UNKNOWN:
+ return null;
+ }
+ return null;
+ }
+
+ @Override
+ public MediaFileUpnpProcessor getMediaFileProcessor() {
+ return mediaFileUpnpProcessor;
+ }
+
+ @Override
+ public ArtistUpnpProcessor getArtistProcessor() {
+ return artistUpnpProcessor;
+ }
+
+ @Override
+ public AlbumUpnpProcessor getAlbumProcessor() {
+ return albumUpnpProcessor;
+ }
+
+ @Override
+ public RecentAlbumUpnpProcessor getRecentProcessor() {
+ return recentAlbumUpnpProcessor;
+ }
+
+ @Override
+ public GenreUpnpProcessor getGenreProcessor() {
+ return genreUpnpProcessor;
+ }
+
+ @Override
+ public PlaylistUpnpProcessor getPlaylistProcessor() {
+ return playlistUpnpProcessor;
+ }
+
+}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpUtil.java b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpUtil.java
new file mode 100644
index 000000000..66dad5ea5
--- /dev/null
+++ b/airsonic-main/src/main/java/org/airsonic/player/service/upnp/UpnpUtil.java
@@ -0,0 +1,115 @@
+/*
+ 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 2024 (C) Y.Tory
+ Copyright 2017 (C) Airsonic Authors
+ Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
+*/
+package org.airsonic.player.service.upnp;
+
+import org.airsonic.player.domain.CoverArtScheme;
+import org.airsonic.player.domain.MediaFile;
+import org.airsonic.player.domain.Player;
+import org.airsonic.player.domain.User;
+import org.airsonic.player.service.JWTSecurityService;
+import org.airsonic.player.service.PlayerService;
+import org.airsonic.player.service.SecurityService;
+import org.airsonic.player.service.SettingsService;
+import org.airsonic.player.service.TranscodingService;
+import org.airsonic.player.util.StringUtil;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.fourthline.cling.support.model.Res;
+import org.seamless.util.MimeType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.net.URI;
+
+@Component
+public class UpnpUtil {
+
+ @Autowired
+ private SecurityService securityService;
+
+ @Autowired
+ private PlayerService playerService;
+
+ @Autowired
+ private JWTSecurityService jwtSecurityService;
+
+ @Autowired
+ private SettingsService settingsService;
+
+ @Autowired
+ private TranscodingService transcodingService;
+
+ public Res createResourceForSong(MediaFile song) {
+ // Create a guest user if necessary
+ securityService.createGuestUserIfNotExists();
+ Player player = playerService.getGuestPlayer(null);
+
+ UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("ext/stream")
+ .queryParam("id", song.getId())
+ .queryParam("player", player.getId());
+
+ if (song.isVideo()) {
+ builder.queryParam("format", TranscodingService.FORMAT_RAW);
+ }
+
+ builder = jwtSecurityService.addJWTToken(User.USERNAME_ANONYMOUS, builder);
+
+ String url = getBaseUrl() + builder.toUriString();
+
+ String suffix = song.isVideo() ? FilenameUtils.getExtension(song.getPath()) : transcodingService.getSuffix(player, song, null);
+ String mimeTypeString = StringUtil.getMimeType(suffix);
+ MimeType mimeType = mimeTypeString == null ? null : MimeType.valueOf(mimeTypeString);
+
+ Res res = new Res(mimeType, null, url);
+ res.setDuration(formatDuration(song.getDuration()));
+ return res;
+ }
+
+ private String formatDuration(Double seconds) {
+ if (seconds == null) {
+ return null;
+ }
+ return StringUtil.formatDuration((long) (seconds * 1000), true);
+ }
+
+ public String getBaseUrl() {
+ String dlnaBaseLANURL = settingsService.getDlnaBaseLANURL();
+ if (StringUtils.isBlank(dlnaBaseLANURL)) {
+ throw new RuntimeException("DLNA Base LAN URL is not set correctly");
+ }
+ return StringUtils.appendIfMissing(dlnaBaseLANURL, "/");
+ }
+
+ public URI getAlbumArtURI(int albumId) {
+ return UriComponentsBuilder
+ .fromUriString(getBaseUrl())
+ .uriComponents(jwtSecurityService
+ .addJWTToken(
+ User.USERNAME_ANONYMOUS,
+ UriComponentsBuilder.fromUriString("ext/coverArt.view")
+ .queryParam("id", albumId)
+ .queryParam("size", CoverArtScheme.LARGE.getSize()))
+ .build())
+ .build().encode().toUri();
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index f9f8b15d9..49283d8b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -251,7 +251,7 @@
com.puppycrawl.tools
checkstyle
- 10.16.0
+ 10.17.0