Jellyfin REST API sensor

Hi there!
I am trying to poll the Jellyfin REST API directly, to detect playback status.

The /Sessions endpoint returns a JSON list of objects, one for each session. As I understand it, if that object has the NowPlayingItem key it means playback is active.
See Jellyfin API reference here (and yes, the “X-Emby-Authorization” header is incorrect in the docs, it’s supposed to be “X-MediaBrowser-Token”)

I’m trying to detect that key. What is wrong with my code?

binary_sensor:
- platform: rest
  name: Jellyfin playing
  device_class: running
  resource_template: "{{ states('input_text.jellyfin_url') }}/Sessions"
  headers:
    X-MediaBrowser-Token: !secret jellyfin_apikey
  value_template: >
    {% set bool = 'false' %}
    {% for item in value_json %}
      {% set bool = 'true' if item.NowPlayingItem is defined %}
    {% endfor %}
    {{ bool }}

I also tried a different condition: if 'NowPlayingItem' in item with no luck.

Just for debugging purposes, I created a REST sensor with the value:

value_template: "{{ value_json[0].NowPlayingItem.Name }}"

…and when there’s playback, I get the media name just fine.

In case anyone stumble on a similar problem…
I thought I might share my solution.

Step 1

It seems that the problem with my original sensor has something to do with the 255-char limitation on a RESTful sensor’s state. Even though my template’s final result is either true or false, it seems that passing the entire value_json for processing inthe state wasn’t working.
As a workaround, I used the sensor’s attributes (similar to the official example) to “dump” the JSON response I needed.

sensor:
- platform: rest
  ...
  # Dummy state; using the attributes which can hold >255 chars
  value_template: OK
  json_attributes_path: ...
  json_attributes:
    ...

Step 2

Now came a tough part - how to simulate the for loop I had in my template? That is, how to filter the JSON response, which includes an array of sessions, to find the only relevant one that contains NowPlayingItem?
This was solved using some JSONPath magic, with the help of online exp tutorial, dummy data and tester. value_json is filtered to give only that item which has a property key NowPlayingItem.

  json_attributes_path: "$[?(@.NowPlayingItem)]"
  json_attributes:
    - Id
    - PlayState
    - NowPlayingItem

Step 3

Now that I had the REST sensor, I used a template binary sensor to create the actual boolean value (and extract other useful info from the session object).
Final result:

sensor:
- platform: rest
  name: Jellyfin playing
  resource_template: "{{ states('input_text.jellyfin_url') }}/Sessions"
  headers:
    X-MediaBrowser-Token: !secret jellyfin_apikey
  # Dummy state; actually using the attributes which can hold >255 chars
  value_template: OK
  json_attributes_path: "$[?(@.NowPlayingItem)]"
  json_attributes:
    - Id
    - PlayState
    - NowPlayingItem

template:
- binary_sensor:
  - name: Jellyfin playing
    device_class: running
    state: "{{ iif(state_attr('sensor.jellyfin_playing', 'Id') != None, 'on', 'off') }}"
    attributes:
      session_id: "{{ state_attr('sensor.jellyfin_playing', 'Id') }}"
      is_paused: "{{ state_attr('sensor.jellyfin_playing', 'PlayState')['IsPaused'] }}"
      name: "{{ state_attr('sensor.jellyfin_playing', 'NowPlayingItem')['Name'] }}"
      rating: "{{ state_attr('sensor.jellyfin_playing', 'NowPlayingItem')['OfficialRating'] }}"

Multiple active sessions?

This does NOT address the case where there is more than one active playing session. In that case, the JSONPath $[?(@.NowPlayingItem)] will return an array of matches, but the json_attributes will populate only from the last match, I believe. If anyone has any ideas on how to solve THAT problem - lemme know!

1 Like

Maybe an old topic but i am using the webhook plugin to send over the playback status to a trigger template. Seems to work ok to tell me when playback starts, i can pass over the media content type, title etc… so i can run automations if a Movie starts playing for example.

Let me know if you want more info.

Yeah I’d like to know. The pull system of using rest is a worse setup than push with the Webhooks.

I found a workaround! Found someone on the forums that used command_line sensor with curl.

Basically, echo a json string into the command_line sensor, creating a new array.

 echo "{\"NowPlayingItems\":" $( CURL  OUTPUT HERE)  "} "

Then read this with template sensor.

I am trying to work out some nice yaml and post it on the forums

What I have now (mind you, the -k is ignore cert checks)

   - platform: command_line
    name: jellyfin_curl
    scan_interval: 30
    command: >
       echo "{\"NowPlayingItems\":" $(
       curl 
       -s 
       -k
       -H 'Authorization: Mediabrowser Token="XXXXXXXXXXXXX"' 
       -H 'accept: application/json' 
       'https://192.168.1.41:8920/Sessions?activeWithinSeconds=30'
       ) "}" 
    value_template: >
       {{ value_json.NowPlayingItems | length }}
    json_attributes:
      - NowPlayingItems

with the following template now works. My biggest realization was, that there can be sessions which have and have not NowPlayingItem.
Most of the time my script was failing because of the last item in the Sessions API was not having a NowPlayingItem

{% set fullJson = state_attr('sensor.jellyfin_curl', 'NowPlayingItems') %}
{%- for item in fullJson -%}
{%- if item.NowPlayingItem is defined -%}
    {{item.UserName}}: {{item.NowPlayingItem.Name}}
{%- endif %}
{% endfor -%}