Skip to content

Commit

Permalink
Issue 7: Stream Content Length (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
calne-ca authored Nov 23, 2024
1 parent aef9973 commit 7553300
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 83 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## [0.6.0] - 2024-11-23

### Added

- Support for *estimateContentLength* parameter in stream operation
- Can be set either globally in the *SubsonicPreferences* or passed with a *StreamParams* object to the stream method.
Params passed directly to a method will override the global preferences though, so be careful when doing so.

### Breaking Changes

- All signatures of the methods in *media()* have changed:
- All methods now return an instance of a new class *MediaStream* instead if an InputStream or URL.
- URL methods have been removed all-together because the logic is now implemented in *MediaStream*.
- Before:
````java
InputStream stream = subsonic.media().stream(1);
URL streamUrl = subsonic.media().streamUrl(1);
````
- Now:
````java
MediaStream stream = subsonic.media().stream(1);

InputStream inputStream = stream.getInputStream();
URL streamUrl = stream.getUrl();
int contentLength = stream.getContentLength(); // This value changes based on the estimateContentLength param
````

## [0.5.1] - 2024-08-24

### Fixed
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ A Java Client for the [Subsonic API](http://www.subsonic.org/pages/api.jsp).
<dependency>
<groupId>net.beardbot</groupId>
<artifactId>subsonic-client</artifactId>
<version>0.5.1</version>
<version>0.6.0</version>
</dependency>
```

Expand Down Expand Up @@ -175,8 +175,8 @@ while (child.isDir()){
child = directory.getchildren().get(0);
}

InputStream stream = subsonic.media().stream(child.getId());
someAudioPlayer.play(stream);
MediaStream stream = subsonic.media().stream(child.getId());
someAudioPlayer.play(stream.getInputStream());
```

```java
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>net.beardbot</groupId>
<artifactId>subsonic-client</artifactId>
<version>0.6.0-SNAPSHOT</version>
<version>0.6.0</version>

<name>${project.groupId}:${project.artifactId}</name>
<description>Java client for the Subsonic API</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class SubsonicPreferences {
private String clientName = "SubsonicJavaClient";
private int streamBitRate = 192;
private String streamFormat = "mp3";
private boolean estimateContentLength = false;

private final SubsonicAuthentication authentication;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,8 @@
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.beardbot.subsonic.client.Subsonic;
import net.beardbot.subsonic.client.base.*;
import net.beardbot.subsonic.client.utils.JaxbUtil;
import org.subsonic.restapi.ErrorCode;
import org.subsonic.restapi.SubsonicResponse;

import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;

import static net.beardbot.subsonic.client.utils.SubsonicResponseErrorHandler.handleError;
import java.util.Collections;

@Slf4j
public class MediaService {
Expand All @@ -42,85 +31,65 @@ public MediaService(Subsonic subsonic) {
}

@SneakyThrows
public InputStream stream(String id){
var url = streamUrl(id);
log.debug("Fetching audio stream '{}'.", url);

return safeOpenStream(url);
}

@SneakyThrows
public URL streamUrl(String id){
public MediaStream stream(String id){
var params = StreamParams.create()
.format(subsonic.getPreferences().getStreamFormat())
.maxBitRate(subsonic.getPreferences().getStreamBitRate()).getParamMap();
.maxBitRate(subsonic.getPreferences().getStreamBitRate())
.estimateContentLength(subsonic.getPreferences().isEstimateContentLength());

return stream(id, params);
}

public MediaStream stream(String id, StreamParams streamParams){
var params = streamParams.getParamMap();
params.put("id", Collections.singletonList(id));

return subsonic.createUrl("stream", params);
var url = subsonic.createUrl("stream", params);

log.debug("Fetching audio stream '{}'.", url);

return new MediaStream(url);
}

@SneakyThrows
public InputStream download(String id){
public MediaStream download(String id){
var params = DownloadParams.create().id(id);
log.debug("Downloading song with params '{}'.", params.getParamMapForLogging());

var url = subsonic.createUrl("download", params.getParamMap());

return safeOpenStream(url);
log.debug("Fetching audio stream '{}'.", url);

return new MediaStream(url);
}

public InputStream getCoverArt(String id){
public MediaStream getCoverArt(String id){
return getCoverArt(id, CoverArtParams.create());
}

@SneakyThrows
public InputStream getCoverArt(String id, CoverArtParams coverArtParams){
var url = getCoverArtUrl(id, coverArtParams);
log.debug("Fetching cover art with params '{}'.", coverArtParams);
return safeOpenStream(url);
}

public URL getCoverArtUrl(String id){
return getCoverArtUrl(id, CoverArtParams.create());
}

public URL getCoverArtUrl(String id, CoverArtParams coverArtParams){
public MediaStream getCoverArt(String id, CoverArtParams coverArtParams){
var params = coverArtParams.getParamMap();
params.put("id",Collections.singletonList(id));

return subsonic.createUrl("getCoverArt", params);
var url = subsonic.createUrl("getCoverArt", params);

log.debug("Fetching cover art with params '{}'.", coverArtParams);
return new MediaStream(url);
}

public InputStream getAvatar(){
public MediaStream getAvatar(){
return getAvatar(subsonic.getPreferences().getUsername());
}

@SneakyThrows
public InputStream getAvatar(String username){
public MediaStream getAvatar(String username){
var params = AvatarParams.create().username(username);

log.debug("Downloading avatar with params '{}'.", params.getParamMapForLogging());

var url = subsonic.createUrl("getAvatar", params.getParamMap());

return safeOpenStream(url);
}

private InputStream safeOpenStream(URL url){
log.debug("Downloading resource {}", url);

try {
var connection = url.openConnection();
var inputStream = connection.getInputStream();
if (connection.getContentType().contains("xml")){
handleError(JaxbUtil.unmarshall(inputStream, SubsonicResponse.class));
}
return new BufferedInputStream(inputStream);
} catch (FileNotFoundException e) {
throw new SubsonicException(ErrorCode.DATA_NOT_FOUND, "The requested data was not found.");
} catch (IOException e) {
throw new SubsonicException(ErrorCode.GENERIC_ERROR, "Unknown error.");
}
return new MediaStream(url);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2020 Joscha Düringer
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.beardbot.subsonic.client.api.media;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.beardbot.subsonic.client.base.SubsonicException;
import net.beardbot.subsonic.client.utils.JaxbUtil;
import org.subsonic.restapi.ErrorCode;
import org.subsonic.restapi.SubsonicResponse;

import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import static net.beardbot.subsonic.client.utils.SubsonicResponseErrorHandler.handleError;

@RequiredArgsConstructor
public class MediaStream {
@Getter private final URL url;
@Getter private Integer contentLength;
private InputStream inputStream;

public InputStream getInputStream(){
if (inputStream == null) {
inputStream = createInputStream();
}

return inputStream;
}

private InputStream createInputStream() {
try {
var connection = url.openConnection();
var inputStream = connection.getInputStream();
if (connection.getContentType().contains("xml")){
handleError(JaxbUtil.unmarshall(inputStream, SubsonicResponse.class));
}
contentLength = connection.getContentLength();
return new BufferedInputStream(inputStream);
} catch (FileNotFoundException e) {
throw new SubsonicException(ErrorCode.DATA_NOT_FOUND, "The requested data was not found.");
} catch (IOException e) {
throw new SubsonicException(ErrorCode.GENERIC_ERROR, "Unknown error.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import net.beardbot.subsonic.client.base.ApiParams;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
class StreamParams extends ApiParams {
public class StreamParams extends ApiParams {
public static StreamParams create(){
return new StreamParams();
}
Expand All @@ -35,4 +35,9 @@ public StreamParams maxBitRate(int maxBitRate) {
setParam("maxBitRate", String.valueOf(maxBitRate));
return this;
}

public StreamParams estimateContentLength(boolean estimateContentLength) {
setParam("estimateContentLength", String.valueOf(estimateContentLength));
return this;
}
}
Loading

0 comments on commit 7553300

Please sign in to comment.