[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
parent
b2d9fe5c0a
commit
4ebcb70c83
|
@ -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" }
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue