[jellyfin] add play by id channels and update sdk (#13389)

* [jellyfin] add play by id channels and update sdk
* [jellyfin] add missed Playing Item Id channel

Signed-off-by: Miguel Álvarez <miguelwork92@gmail.com>
pull/13398/head
GiviMAD 2022-09-18 13:01:18 +02:00 committed by GitHub
parent b2d9fe5c0a
commit 4ebcb70c83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 214 additions and 53 deletions

View File

@ -4,6 +4,7 @@ This is the binding for [Jellyfin](https://jellyfin.org) the volunteer-built med
Stream to any device from your own server, with no strings attached.
Your media, your server, your way.
This binding allows connect to Jellyfin clients that supports remote control, it's build on top of the official Jellyfin kotlin sdk.
Compatible with Jellyfin servers in version 10.8.x.
## Supported Things
@ -48,6 +49,7 @@ In order to assist you with this process the binding expose a simple login form
|----------|--------|------------------------------|
| send-notification | String | Display message in client |
| media-control | Player | Control media playback |
| playing-item-id | String | Id of the item currently playing (readonly) |
| playing-item-name | String | Name of the item currently playing (readonly) |
| playing-item-series-name | String | Name of the item's series currently playing, only have value when item is an episode (readonly) |
| playing-item-season-name | String | Name of the item's season currently playing, only have value when item is an episode (readonly) |
@ -62,7 +64,10 @@ In order to assist you with this process the binding expose a simple login form
| play-next-by-terms | String | Add to playback queue as next by terms, works for series, episodes and movies; terms search is explained bellow |
| play-last-by-terms | String | Add to playback queue as last by terms, works for series, episodes and movies; terms search is explained bellow |
| browse-by-terms | String | Browse media by terms, works for series, episodes and movies; terms search is explained bellow |
| play-by-id | String | Play media by id, works for series, episodes and movies; id search is explained bellow |
| play-next-by-id | String | Add to playback queue as next by id, works for series, episodes and movies |
| play-last-by-id | String | Add to playback queue as last by id, works for series, episodes and movies |
| browse-by-id | String | Browse media by id, works for series, episodes and movies |
### Terms search:
The terms search has a default behavior that can be modified sending some predefined prefixes.
@ -106,6 +111,7 @@ Thing jellyfin:client:exampleServerId:<JELLYFIN_DEVICE_ID> "Jellyfin Android cli
```
String strJellyfinAndroidSendNotification { channel="jellyfin:client:exampleServerId:<JELLYFIN_DEVICE_ID>:send-notification " }
Player plJellyfinAndroidMediaControl { channel="jellyfin:client:exampleServerId:<JELLYFIN_DEVICE_ID>:media-control" }
String strJellyfinAndroidPlayingItemId { channel="jellyfin:client:exampleServerId:<JELLYFIN_DEVICE_ID>:playing-item-id" }
String strJellyfinAndroidPlayingItemName { channel="jellyfin:client:exampleServerId:<JELLYFIN_DEVICE_ID>:playing-item-name" }
String strJellyfinAndroidPlayingItemSeriesName { channel="jellyfin:client:exampleServerId:<JELLYFIN_DEVICE_ID>:playing-item-series-name" }
String strJellyfinAndroidPlayingItemSeasonName { channel="jellyfin:client:exampleServerId:<JELLYFIN_DEVICE_ID>:playing-item-season-name" }

View File

@ -21,17 +21,17 @@
<dependency>
<groupId>org.jellyfin.sdk</groupId>
<artifactId>jellyfin-core-jvm</artifactId>
<version>1.2.0</version>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>org.jellyfin.sdk</groupId>
<artifactId>jellyfin-api-jvm</artifactId>
<version>1.2.0</version>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>org.jellyfin.sdk</groupId>
<artifactId>jellyfin-model-jvm</artifactId>
<version>1.2.0</version>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
@ -90,7 +90,7 @@
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core-jvm</artifactId>
<version>1.6.1</version>
<version>1.6.2</version>
<scope>compile</scope>
</dependency>
<dependency>

View File

@ -37,6 +37,7 @@ public class JellyfinBindingConstants {
public static final String SEND_NOTIFICATION_CHANNEL = "send-notification";
public static final String MEDIA_CONTROL_CHANNEL = "media-control";
public static final String PLAYING_ITEM_PERCENTAGE_CHANNEL = "playing-item-percentage";
public static final String PLAYING_ITEM_ID_CHANNEL = "playing-item-id";
public static final String PLAYING_ITEM_NAME_CHANNEL = "playing-item-name";
public static final String PLAYING_ITEM_SERIES_NAME_CHANNEL = "playing-item-series-name";
public static final String PLAYING_ITEM_SEASON_NAME_CHANNEL = "playing-item-season-name";
@ -50,7 +51,10 @@ public class JellyfinBindingConstants {
public static final String PLAY_NEXT_BY_TERMS_CHANNEL = "play-next-by-terms";
public static final String PLAY_LAST_BY_TERMS_CHANNEL = "play-last-by-terms";
public static final String BROWSE_ITEM_BY_TERMS_CHANNEL = "browse-by-terms";
public static final String PLAY_BY_ID_CHANNEL = "play-by-id";
public static final String PLAY_NEXT_BY_ID_CHANNEL = "play-next-by-id";
public static final String PLAY_LAST_BY_ID_CHANNEL = "play-last-by-id";
public static final String BROWSE_ITEM_BY_ID_CHANNEL = "browse-by-id";
// Discovery
public static final int DISCOVERY_RESULT_TTL_SEC = 600;
}

View File

@ -28,6 +28,7 @@ import org.jellyfin.sdk.JellyfinOptions;
import org.jellyfin.sdk.api.client.exception.ApiClientException;
import org.jellyfin.sdk.api.operations.SystemApi;
import org.jellyfin.sdk.compatibility.JavaFlow;
import org.jellyfin.sdk.compatibility.JavaFlow.FlowJob;
import org.jellyfin.sdk.model.ClientInfo;
import org.jellyfin.sdk.model.DeviceInfo;
import org.jellyfin.sdk.model.api.PublicSystemInfo;
@ -53,7 +54,8 @@ import org.slf4j.LoggerFactory;
@Component(service = DiscoveryService.class, configurationPid = "discovery.jellyfin")
public class JellyfinServerDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(JellyfinServerDiscoveryService.class);
private JavaFlow.@Nullable FlowJob cancelDiscovery;
@Nullable
private FlowJob cancelDiscovery;
public JellyfinServerDiscoveryService() throws IllegalArgumentException {
super(Set.of(THING_TYPE_CLIENT), 60);

View File

@ -12,10 +12,12 @@
*/
package org.openhab.binding.jellyfin.internal.handler;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.BROWSE_ITEM_BY_ID_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.BROWSE_ITEM_BY_TERMS_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.MEDIA_CONTROL_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_EPISODE_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_GENRES_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_ID_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_NAME_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_PERCENTAGE_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_SEASON_CHANNEL;
@ -24,13 +26,18 @@ import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLA
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_SERIES_NAME_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_TOTAL_SECOND_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAYING_ITEM_TYPE_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAY_BY_ID_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAY_BY_TERMS_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAY_LAST_BY_ID_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAY_LAST_BY_TERMS_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAY_NEXT_BY_ID_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.PLAY_NEXT_BY_TERMS_CHANNEL;
import static org.openhab.binding.jellyfin.internal.JellyfinBindingConstants.SEND_NOTIFICATION_CHANNEL;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
@ -39,6 +46,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jellyfin.sdk.api.client.exception.ApiClientException;
import org.jellyfin.sdk.model.api.BaseItemDto;
import org.jellyfin.sdk.model.api.BaseItemKind;
import org.jellyfin.sdk.model.api.PlayCommand;
import org.jellyfin.sdk.model.api.PlayerStateInfo;
import org.jellyfin.sdk.model.api.PlaystateCommand;
@ -141,6 +149,55 @@ public class JellyfinClientHandler extends BaseThingHandler {
}
runItemSearch(command.toFullString(), null);
break;
case PLAY_BY_ID_CHANNEL:
if (command instanceof RefreshType) {
return;
}
UUID itemUUID;
try {
itemUUID = parseItemUUID(command);
} catch (NumberFormatException e) {
logger.warn("Thing {}: Unable to parse item UUID in command {}.", thing.getUID(), command);
return;
}
runItemById(itemUUID, PlayCommand.PLAY_NOW);
break;
case PLAY_NEXT_BY_ID_CHANNEL:
if (command instanceof RefreshType) {
return;
}
try {
itemUUID = parseItemUUID(command);
} catch (NumberFormatException e) {
logger.warn("Thing {}: Unable to parse item UUID in command {}.", thing.getUID(), command);
return;
}
runItemById(itemUUID, PlayCommand.PLAY_NEXT);
break;
case PLAY_LAST_BY_ID_CHANNEL:
if (command instanceof RefreshType) {
return;
}
try {
itemUUID = parseItemUUID(command);
} catch (NumberFormatException e) {
logger.warn("Thing {}: Unable to parse item UUID in command {}.", thing.getUID(), command);
return;
}
runItemById(itemUUID, PlayCommand.PLAY_LAST);
break;
case BROWSE_ITEM_BY_ID_CHANNEL:
if (command instanceof RefreshType) {
return;
}
try {
itemUUID = parseItemUUID(command);
} catch (NumberFormatException e) {
logger.warn("Thing {}: Unable to parse item UUID in command {}.", thing.getUID(), command);
return;
}
runItemById(itemUUID, null);
break;
case PLAYING_ITEM_SECOND_CHANNEL:
if (command instanceof RefreshType) {
refreshState();
@ -161,6 +218,7 @@ public class JellyfinClientHandler extends BaseThingHandler {
}
seekToPercentage(Integer.parseInt(command.toFullString()));
break;
case PLAYING_ITEM_ID_CHANNEL:
case PLAYING_ITEM_NAME_CHANNEL:
case PLAYING_ITEM_GENRES_CHANNEL:
case PLAYING_ITEM_SEASON_CHANNEL:
@ -183,6 +241,13 @@ public class JellyfinClientHandler extends BaseThingHandler {
}
}
private UUID parseItemUUID(Command command) throws NumberFormatException {
var itemId = command.toFullString().replace("-", "");
UUID itemUUID = new UUID(new BigInteger(itemId.substring(0, 16), 16).longValue(),
new BigInteger(itemId.substring(16), 16).longValue());
return itemUUID;
}
@Override
public void dispose() {
super.dispose();
@ -236,6 +301,14 @@ public class JellyfinClientHandler extends BaseThingHandler {
cleanChannel(PLAYING_ITEM_TOTAL_SECOND_CHANNEL);
}
}
if (isLinked(PLAYING_ITEM_ID_CHANNEL)) {
if (playingItem != null) {
updateState(new ChannelUID(this.thing.getUID(), PLAYING_ITEM_ID_CHANNEL),
new StringType(playingItem.getId().toString()));
} else {
cleanChannel(PLAYING_ITEM_ID_CHANNEL);
}
}
if (isLinked(PLAYING_ITEM_NAME_CHANNEL)) {
if (playingItem != null) {
updateState(new ChannelUID(this.thing.getUID(), PLAYING_ITEM_NAME_CHANNEL),
@ -253,7 +326,7 @@ public class JellyfinClientHandler extends BaseThingHandler {
}
}
if (isLinked(PLAYING_ITEM_SEASON_NAME_CHANNEL)) {
if (playingItem != null && "Episode".equals(playingItem.getType())) {
if (playingItem != null && BaseItemKind.EPISODE.equals(playingItem.getType())) {
updateState(new ChannelUID(this.thing.getUID(), PLAYING_ITEM_SEASON_NAME_CHANNEL),
new StringType(playingItem.getSeasonName()));
} else {
@ -261,7 +334,7 @@ public class JellyfinClientHandler extends BaseThingHandler {
}
}
if (isLinked(PLAYING_ITEM_SEASON_CHANNEL)) {
if (playingItem != null && "Episode".equals(playingItem.getType())) {
if (playingItem != null && BaseItemKind.EPISODE.equals(playingItem.getType())) {
updateState(new ChannelUID(this.thing.getUID(), PLAYING_ITEM_SEASON_CHANNEL),
new DecimalType(Objects.requireNonNull(playingItem.getParentIndexNumber())));
} else {
@ -269,7 +342,7 @@ public class JellyfinClientHandler extends BaseThingHandler {
}
}
if (isLinked(PLAYING_ITEM_EPISODE_CHANNEL)) {
if (playingItem != null && "Episode".equals(playingItem.getType())) {
if (playingItem != null && BaseItemKind.EPISODE.equals(playingItem.getType())) {
updateState(new ChannelUID(this.thing.getUID(), PLAYING_ITEM_EPISODE_CHANNEL),
new DecimalType(Objects.requireNonNull(playingItem.getIndexNumber())));
} else {
@ -287,7 +360,7 @@ public class JellyfinClientHandler extends BaseThingHandler {
if (isLinked(PLAYING_ITEM_TYPE_CHANNEL)) {
if (playingItem != null) {
updateState(new ChannelUID(this.thing.getUID(), PLAYING_ITEM_TYPE_CHANNEL),
new StringType(playingItem.getType()));
new StringType(playingItem.getType().toString()));
} else {
cleanChannel(PLAYING_ITEM_TYPE_CHANNEL);
}
@ -322,9 +395,10 @@ public class JellyfinClientHandler extends BaseThingHandler {
private void runItemSearchByType(String terms, @Nullable PlayCommand playCommand, boolean movieSearchEnabled,
boolean seriesSearchEnabled, boolean episodeSearchEnabled)
throws SyncCallback.SyncCallbackError, ApiClientException {
var seriesItem = seriesSearchEnabled ? getServerHandler().searchItem(terms, "Series", null) : null;
var movieItem = movieSearchEnabled ? getServerHandler().searchItem(terms, "Movie", null) : null;
var episodeItem = episodeSearchEnabled ? getServerHandler().searchItem(terms, "Episode", null) : null;
var seriesItem = seriesSearchEnabled ? getServerHandler().searchItem(terms, BaseItemKind.SERIES, null) : null;
var movieItem = movieSearchEnabled ? getServerHandler().searchItem(terms, BaseItemKind.MOVIE, null) : null;
var episodeItem = episodeSearchEnabled ? getServerHandler().searchItem(terms, BaseItemKind.EPISODE, null)
: null;
if (movieItem != null) {
logger.debug("Found movie: '{}'", movieItem.getName());
}
@ -337,30 +411,7 @@ public class JellyfinClientHandler extends BaseThingHandler {
if (movieItem != null) {
runItem(movieItem, playCommand);
} else if (seriesItem != null) {
if (playCommand != null) {
var resumeEpisodeItem = getServerHandler().getSeriesResumeItem(seriesItem.getId());
var nextUpEpisodeItem = getServerHandler().getSeriesNextUpItem(seriesItem.getId());
var firstEpisodeItem = getServerHandler().getSeriesEpisodeItem(seriesItem.getId(), 1, 1);
if (resumeEpisodeItem != null) {
logger.debug("Resuming series '{}' episode '{}'", seriesItem.getName(),
resumeEpisodeItem.getName());
playItem(resumeEpisodeItem, playCommand,
Objects.requireNonNull(resumeEpisodeItem.getUserData()).getPlaybackPositionTicks());
} else if (nextUpEpisodeItem != null) {
logger.debug("Playing next series '{}' episode '{}'", seriesItem.getName(),
nextUpEpisodeItem.getName());
playItem(nextUpEpisodeItem, playCommand);
} else if (firstEpisodeItem != null) {
logger.debug("Playing series '{}' first episode '{}'", seriesItem.getName(),
firstEpisodeItem.getName());
playItem(firstEpisodeItem, playCommand);
} else {
logger.warn("Unable to found episode for series");
}
} else {
logger.debug("Browse series '{}'", seriesItem.getName());
browseItem(seriesItem);
}
runSeriesItem(seriesItem, playCommand);
} else if (episodeItem != null) {
runItem(episodeItem, playCommand);
} else {
@ -368,10 +419,37 @@ public class JellyfinClientHandler extends BaseThingHandler {
}
}
private void runSeriesItem(BaseItemDto seriesItem, @Nullable PlayCommand playCommand)
throws SyncCallback.SyncCallbackError, ApiClientException {
if (playCommand != null) {
var resumeEpisodeItem = getServerHandler().getSeriesResumeItem(seriesItem.getId());
var nextUpEpisodeItem = getServerHandler().getSeriesNextUpItem(seriesItem.getId());
var firstEpisodeItem = getServerHandler().getSeriesEpisodeItem(seriesItem.getId(), 1, 1);
if (resumeEpisodeItem != null) {
logger.debug("Resuming series '{}' episode '{}'", seriesItem.getName(), resumeEpisodeItem.getName());
playItem(resumeEpisodeItem, playCommand,
Objects.requireNonNull(resumeEpisodeItem.getUserData()).getPlaybackPositionTicks());
} else if (nextUpEpisodeItem != null) {
logger.debug("Playing next series '{}' episode '{}'", seriesItem.getName(),
nextUpEpisodeItem.getName());
playItem(nextUpEpisodeItem, playCommand);
} else if (firstEpisodeItem != null) {
logger.debug("Playing series '{}' first episode '{}'", seriesItem.getName(),
firstEpisodeItem.getName());
playItem(firstEpisodeItem, playCommand);
} else {
logger.warn("Unable to found episode for series");
}
} else {
logger.debug("Browse series '{}'", seriesItem.getName());
browseItem(seriesItem);
}
}
private void runSeriesEpisode(String terms, int season, int episode, @Nullable PlayCommand playCommand)
throws SyncCallback.SyncCallbackError, ApiClientException {
logger.debug("{} series episode mode", playCommand != null ? "Play" : "Browse");
var seriesItem = getServerHandler().searchItem(terms, "Series", null);
var seriesItem = getServerHandler().searchItem(terms, BaseItemKind.SERIES, null);
if (seriesItem != null) {
logger.debug("Searching series {} episode {}x{}", seriesItem.getName(), season, episode);
var episodeItem = getServerHandler().getSeriesEpisodeItem(seriesItem.getId(), season, episode);
@ -388,8 +466,8 @@ public class JellyfinClientHandler extends BaseThingHandler {
private void runItem(BaseItemDto item, @Nullable PlayCommand playCommand)
throws SyncCallback.SyncCallbackError, ApiClientException {
var itemType = Objects.requireNonNull(item.getType());
logger.debug("{} {} '{}'", playCommand == null ? "Browsing" : "Playing", itemType.toLowerCase(),
"Episode".equals(itemType) ? item.getSeriesName() + ": " + item.getName() : item.getName());
logger.debug("{} {} '{}'", playCommand == null ? "Browsing" : "Playing", itemType.toString().toLowerCase(),
BaseItemKind.EPISODE.equals(itemType) ? item.getSeriesName() + ": " + item.getName() : item.getName());
if (playCommand == null) {
browseItem(item);
} else {
@ -423,6 +501,20 @@ public class JellyfinClientHandler extends BaseThingHandler {
getServerHandler().playItem(lastSessionId, playCommand, item.getId().toString(), startPositionTicks);
}
private void runItemById(UUID itemId, @Nullable PlayCommand playCommand)
throws SyncCallback.SyncCallbackError, ApiClientException {
var item = getServerHandler().getItem(itemId, null);
if (item == null) {
logger.warn("Unable to find item with id: {}", itemId);
return;
}
if (BaseItemKind.SERIES.equals(item.getType())) {
runSeriesItem(item, playCommand);
} else {
runItem(item, playCommand);
}
}
private void browseItem(BaseItemDto item) throws SyncCallback.SyncCallbackError, ApiClientException {
if (stopCurrentPlayback()) {
cancelDelayedCommand();
@ -517,10 +609,11 @@ public class JellyfinClientHandler extends BaseThingHandler {
}
private void cleanChannels() {
List.of(MEDIA_CONTROL_CHANNEL, PLAYING_ITEM_PERCENTAGE_CHANNEL, PLAYING_ITEM_NAME_CHANNEL,
PLAYING_ITEM_SERIES_NAME_CHANNEL, PLAYING_ITEM_SEASON_NAME_CHANNEL, PLAYING_ITEM_SEASON_CHANNEL,
PLAYING_ITEM_EPISODE_CHANNEL, PLAYING_ITEM_GENRES_CHANNEL, PLAYING_ITEM_TYPE_CHANNEL,
PLAYING_ITEM_SECOND_CHANNEL, PLAYING_ITEM_TOTAL_SECOND_CHANNEL).forEach(this::cleanChannel);
List.of(MEDIA_CONTROL_CHANNEL, PLAYING_ITEM_PERCENTAGE_CHANNEL, PLAYING_ITEM_ID_CHANNEL,
PLAYING_ITEM_NAME_CHANNEL, PLAYING_ITEM_SERIES_NAME_CHANNEL, PLAYING_ITEM_SEASON_NAME_CHANNEL,
PLAYING_ITEM_SEASON_CHANNEL, PLAYING_ITEM_EPISODE_CHANNEL, PLAYING_ITEM_GENRES_CHANNEL,
PLAYING_ITEM_TYPE_CHANNEL, PLAYING_ITEM_SECOND_CHANNEL, PLAYING_ITEM_TOTAL_SECOND_CHANNEL)
.forEach(this::cleanChannel);
}
private void cleanChannel(String channelId) {

View File

@ -40,6 +40,7 @@ import org.jellyfin.sdk.model.api.AuthenticateUserByName;
import org.jellyfin.sdk.model.api.AuthenticationResult;
import org.jellyfin.sdk.model.api.BaseItemDto;
import org.jellyfin.sdk.model.api.BaseItemDtoQueryResult;
import org.jellyfin.sdk.model.api.BaseItemKind;
import org.jellyfin.sdk.model.api.ItemFields;
import org.jellyfin.sdk.model.api.MessageCommand;
import org.jellyfin.sdk.model.api.PlayCommand;
@ -271,7 +272,7 @@ public class JellyfinServerHandler extends BaseBridgeHandler {
awaiter.awaitResponse();
}
public void browseToItem(String sessionId, String itemType, String itemId, String itemName)
public void browseToItem(String sessionId, BaseItemKind itemType, String itemId, String itemName)
throws SyncCallback.SyncCallbackError, ApiClientException {
var awaiter = new EmptySyncResponse();
new SessionApi(jellyApiClient).displayContent(sessionId, itemType, itemId, itemName, awaiter);
@ -287,7 +288,7 @@ public class JellyfinServerHandler extends BaseBridgeHandler {
throws SyncCallback.SyncCallbackError, ApiClientException {
var asyncContinuation = new SyncResponse<BaseItemDtoQueryResult>();
new TvShowsApi(jellyApiClient).getNextUp(jellyApiClient.getUserId(), null, limit, null, seriesId.toString(),
null, null, null, null, null, null, null, asyncContinuation);
null, null, null, null, null, null, null, null, null, asyncContinuation);
var result = asyncContinuation.awaitContent();
return Objects.requireNonNull(result.getItems());
}
@ -301,7 +302,8 @@ public class JellyfinServerHandler extends BaseBridgeHandler {
throws SyncCallback.SyncCallbackError, ApiClientException {
var asyncContinuation = new SyncResponse<BaseItemDtoQueryResult>();
new ItemsApi(jellyApiClient).getResumeItems(Objects.requireNonNull(jellyApiClient.getUserId()), null, limit,
null, seriesId, null, null, true, null, null, null, List.of("Episode"), null, null, asyncContinuation);
null, seriesId, null, null, true, null, null, null, List.of(BaseItemKind.EPISODE), null, null, null,
asyncContinuation);
var result = asyncContinuation.awaitContent();
return Objects.requireNonNull(result.getItems());
}
@ -320,21 +322,34 @@ public class JellyfinServerHandler extends BaseBridgeHandler {
return Objects.requireNonNull(result.getItems());
}
public @Nullable BaseItemDto searchItem(@Nullable String searchTerm, @Nullable String itemType,
public @Nullable BaseItemDto getItem(UUID id, @Nullable List<ItemFields> fields)
throws SyncCallback.SyncCallbackError, ApiClientException {
var asyncContinuation = new SyncResponse<BaseItemDtoQueryResult>();
new ItemsApi(jellyApiClient).getItems(jellyApiClient.getUserId(), null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, 1, true, null, null, null, fields, null, null, null,
null, null, null, null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null,
null, null, null, null, null, List.of(id), null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, false, false, asyncContinuation);
var response = asyncContinuation.awaitContent();
return Objects.requireNonNull(response.getItems()).stream().findFirst().orElse(null);
}
public @Nullable BaseItemDto searchItem(@Nullable String searchTerm, @Nullable BaseItemKind itemType,
@Nullable List<ItemFields> fields) throws SyncCallback.SyncCallbackError, ApiClientException {
return searchItems(searchTerm, itemType, fields, 1).stream().findFirst().orElse(null);
}
public List<BaseItemDto> searchItems(@Nullable String searchTerm, @Nullable String itemType,
public List<BaseItemDto> searchItems(@Nullable String searchTerm, @Nullable BaseItemKind itemType,
@Nullable List<ItemFields> fields, int limit) throws SyncCallback.SyncCallbackError, ApiClientException {
var asyncContinuation = new SyncResponse<BaseItemDtoQueryResult>();
var itemTypes = itemType != null ? List.of(itemType) : null;
new ItemsApi(jellyApiClient).getItems(jellyApiClient.getUserId(), null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, limit, true, searchTerm, null, null, fields, null, itemTypes, null, null, null, null,
null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, limit, true, searchTerm, null, null, fields, null,
itemTypes, null, null, null, null, null, null, null, null, null, null, null, 1, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, false, false, asyncContinuation);
null, null, null, null, null, null, null, null, null, false, false, asyncContinuation);
var response = asyncContinuation.awaitContent();
return Objects.requireNonNull(response.getItems());
}

View File

@ -29,18 +29,28 @@ thing-type.config.jellyfin.server.userId.description = The user id
# channel types
channel-type.jellyfin.browse-by-id-channel.label = Browse By Id
channel-type.jellyfin.browse-by-id-channel.description = Browse media by id
channel-type.jellyfin.browse-by-terms-channel.label = Browse By Terms
channel-type.jellyfin.browse-by-terms-channel.description = Browse media by terms, works for series, episodes and movies
channel-type.jellyfin.play-by-id-channel.label = Play By Id
channel-type.jellyfin.play-by-id-channel.description = Play media by id
channel-type.jellyfin.play-by-terms-channel.label = Play By Terms
channel-type.jellyfin.play-by-terms-channel.description = Play media by terms, works for series, episodes and movies
channel-type.jellyfin.play-last-by-id-channel.label = Play Last By Id
channel-type.jellyfin.play-last-by-id-channel.description = Add to playback queue as last by id
channel-type.jellyfin.play-last-by-terms-channel.label = Play Last By Terms
channel-type.jellyfin.play-last-by-terms-channel.description = Add to playback queue as last by terms; works for series, episodes and movies
channel-type.jellyfin.play-next-by-id-channel.label = Play Next By Id
channel-type.jellyfin.play-next-by-id-channel.description = Add to playback queue as next by id
channel-type.jellyfin.play-next-by-terms-channel.label = Play Next By Terms
channel-type.jellyfin.play-next-by-terms-channel.description = Add to playback queue as next by terms; works for series, episodes and movies
channel-type.jellyfin.playing-item-episode-channel.label = Playing Item Episode
channel-type.jellyfin.playing-item-episode-channel.description = Number of the episode item currently playing, only have value when item is an episode
channel-type.jellyfin.playing-item-genders-channel.label = Playing Item Genders
channel-type.jellyfin.playing-item-genders-channel.description = Coma separate list genders of the item currently playing
channel-type.jellyfin.playing-item-id-channel.label = Playing Item Id
channel-type.jellyfin.playing-item-id-channel.description = Id of the item currently playing
channel-type.jellyfin.playing-item-name-channel.label = Playing Item Name
channel-type.jellyfin.playing-item-name-channel.description = Name of the item currently playing
channel-type.jellyfin.playing-item-percentage-channel.label = Playing Item Percentage

View File

@ -55,6 +55,7 @@
<channels>
<channel id="send-notification" typeId="send-notification-channel"/>
<channel id="media-control" typeId="system.media-control"/>
<channel id="playing-item-id" typeId="playing-item-id-channel"/>
<channel id="playing-item-name" typeId="playing-item-name-channel"/>
<channel id="playing-item-series-name" typeId="playing-item-series-name-channel"/>
<channel id="playing-item-season-name" typeId="playing-item-season-name-channel"/>
@ -69,6 +70,10 @@
<channel id="play-next-by-terms" typeId="play-next-by-terms-channel"/>
<channel id="play-last-by-terms" typeId="play-last-by-terms-channel"/>
<channel id="browse-by-terms" typeId="browse-by-terms-channel"/>
<channel id="play-by-id" typeId="play-by-id-channel"/>
<channel id="play-next-by-id" typeId="play-next-by-id-channel"/>
<channel id="play-last-by-id" typeId="play-last-by-id-channel"/>
<channel id="browse-by-id" typeId="browse-by-id-channel"/>
</channels>
<config-description>
@ -81,6 +86,12 @@
<label>Send Notification</label>
<description>Send notification to the client</description>
</channel-type>
<channel-type id="playing-item-id-channel">
<item-type>String</item-type>
<label>Playing Item Id</label>
<description>Id of the item currently playing</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="playing-item-name-channel">
<item-type>String</item-type>
<label>Playing Item Name</label>
@ -159,4 +170,24 @@
<label>Browse By Terms</label>
<description>Browse media by terms, works for series, episodes and movies</description>
</channel-type>
<channel-type id="play-by-id-channel">
<item-type>String</item-type>
<label>Play By Id</label>
<description>Play media by id</description>
</channel-type>
<channel-type id="play-next-by-id-channel">
<item-type>String</item-type>
<label>Play Next By Id</label>
<description>Add to playback queue as next by id</description>
</channel-type>
<channel-type id="play-last-by-id-channel">
<item-type>String</item-type>
<label>Play Last By Id</label>
<description>Add to playback queue as last by id</description>
</channel-type>
<channel-type id="browse-by-id-channel">
<item-type>String</item-type>
<label>Browse By Id</label>
<description>Browse media by id</description>
</channel-type>
</thing:thing-descriptions>