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
|----------|--------|------------------------------|
| 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) |
| 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.
```
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" }
<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>
<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>
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";
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;
}
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;
@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);
*/
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;
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;
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;
}
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();
}
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:
}
}
+ 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();
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),
}
}
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 {
}
}
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 {
}
}
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 {
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);
}
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());
}
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 {
}
}
+ 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);
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 {
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();
}
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) {
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;
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);
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());
}
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());
}
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());
}
# 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
<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"/>
<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>
<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>
<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>