Custom Component: Tautulli Active Streams

Tautulli Active Streams Integration for Home Assistant

:star: Like This Integration?

If you find the **Tautulli Active Streams** integration helpful, please take a moment to give it a ⭐ on [GitHub] Your feedback and support help improve the project and motivate further development. Thanks! 🚀



:pushpin: Features

A custom integration for Home Assistant that allows you to monitor active Plex streams using Tautulli Api.

  • Dynamically creates session sensors based on active Plex streams.
  • Custom Sensor Count – Choose how many active streams to display.
  • Adjustable Scan Interval – Set how often HA updates stream data.
  • Detailed Session Attributes – Each active stream sensor provides:
    • :film_strip: Media Title & Type (Movie, TV Show, etc.)
    • :bust_in_silhouette: User (Who is watching)
    • :earth_africa: IP Address & Device
    • :tv: Playback Progress & Quality
    • And so much more!

  • Kill All Active Streams:
    A new service that terminates every active stream, regardless of the user.
  • Kill Active Streams by Username:
    A new service that terminates all streams associated with a specified username.
  • Kill Active Streams by Session:
    A new service that terminates a Single stream.
➡️ Click Here 🛠️ How to Use the Kill Stream Services
Check Attributes in "plex_session_" for "user" or "session_id"

:one: Kill All Active Streams

Call the service from Developer Tools → Actions:

service: tautulli_active_streams.kill_all_streams
data:
  message: "Your Message Here"

:two: Kill Streams for a Specific User

Call the service for a specific user:

service: tautulli_active_streams.kill_user_stream
data:
  user: "john_doe"
  message: "Your Message Here."

:two: Kill Streams for a Specific Session

Call the service for a specific user:

service: tautulli_active_streams.kill_session_stream
data:
  session_id: "gxvzdoq4fjkjfmduq5dgf25hz"
  message: "Your Message Here."

These new features are designed to work seamlessly with automations and will become even more powerful as additional data becomes available in future updates.



:link: GitHub: Tautulli Active Streams

:link: Home-assistant Community: (https://community.home-assistant.io/t/custom-component-tautulli-active-streams)



:inbox_tray: Installation

  • Via HACS:

    • Open HACS in Home Assistant.
    • Click the three dots in the top right corner and select Custom repositories.
    • Add the repository URL for this integration and choose Integration as the category.
    • Download the integration from HACS.
  • Manual Installation:

    • Download the repository from GitHub.
    • Place the entire folder into your custom_components/tautulli_active_streams directory.
  • Setup in Home Assistant:

    • Go to Settings → Devices & Services and Add the integration.
    • Enter your Tautulli details (URL, API Key), and set the Session Sensor Count and Refresh Interval.

Don’t forget to add the Card below.

My usage: I have a chip card that displays the current active session count.
When clicked, It will trigger a bubble popup card where all active sessions can be viewed.


:rocket:Custom Lovelace card

Produced by @stratotally and Edited by me.
Dynamically displays active Plex sessions using Tautulli data fetched by the Tautulli Active Streams integration.

  1. Install Tautulli Active Streams integration via HACS or manually.
  2. Ensure Lovelace resources for auto-entities, button-card, bar-card, card-mod are loaded.
  3. Copy the YAML below into your Lovelace dashboard.
  4. Enjoy real-time Plex session monitoring! :clapper::fire:

➡️ Click Here Card YAML - Last updated 23.02.2025

type: custom:auto-entities
filter:
  exclude:
    - state: unknown
    - state: unavailable
    - state: "off"
    - state: "on"
  include:
    - entity_id: "*plex_session_*"
      options:
        entity: this.entity_id
        type: custom:button-card
        tap_action:
          action: none
        variables:
          entity: this.entity_id
        custom_fields:
          picture:
            card:
              type: picture
              image: |
                [[[
                  return states[variables.entity].attributes.image_url 
                    ? states[variables.entity].attributes.image_url 
                    : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAHCCAIAAADaUWPQAAABoElEQVR4nO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgL8BMCQAAX/csOsAAAAASUVORK5CYII=";
                ]]]
              card_mod:
                style: |
                  ha-card {
                    box-shadow: 0;
                    border-radius: 5px;
                    margin: 1px 3px -4px 3px;
                  }
                  ha-card img {
                    min-height: 80px;
                    min-width: 80px;
                  }
          bar:
            card:
              type: custom:bar-card
              entities:
                - entity: this.entity_id
              attribute: progress_percent
              unit_of_measurement: "%"
              positions:
                icon: "off"
                indicator: "off"
                name: inside
              height: 19px
              color: |
                [[[
                  if (states[variables.entity].state == 'playing') {
                    return '#2986cc';
                  } else if (states[variables.entity].state == 'paused') {
                    return '#e49f29; animation: blink 1.5s linear infinite;'; 
                  } else {
                    return '#000000'; // Default color if neither playing nor paused
                  }
                ]]]      
              name: |
                [[[
                  return states[variables.entity].state
                ]]]
              card_mod:
                style: |-
                  @keyframes blink {
                    50% {
                     opacity: 0;
                    }
                  }
                  ha-card {
                    --ha-card-background: rgba(0, 0, 0, 0.8) !important;
                    border: 0.02px solid rgba(70, 130, 180, 0.3);

                    box-shadow: none;
                  }
                  ha-card #states {
                    padding: 0;
                  }
                  bar-card-currentbar, bar-card-backgroundbar {
                    border-radius: 8px;
                    left: 0;
                  }
                  bar-card-name {
                    margin-left: 3%;
                    text-shadow: 1px 1px 1px #0003;
                  }
                  bar-card-value {
                    margin-right: 3%;
                    text-shadow: 1px 1px 1px #0003;
                  }
          user: |
            [[[
              return "<b>" + states[variables.entity].attributes.user + "</b>"
            ]]]
          title: |
            [[[
              if (states[variables.entity].state == 'playing') {
                return "<ha-icon icon='mdi:play' style='width: 15px; height: 15px; position: relative; top: -2px;'></ha-icon> " + states[variables.entity].attributes.full_title;
              } else {
                if (states[variables.entity].state == 'paused') {
                  return "<ha-icon icon='mdi:pause' style='width: 15px; height: 15px; position: relative; top: -2px;'></ha-icon> " + states[variables.entity].attributes.full_title;
                } else {
                  return states[variables.entity].attributes.full_title;
                }
              }

            ]]]
          stream: |
            [[[
              return states[variables.entity].attributes.transcode_decision + " - " + states[variables.entity].attributes.stream_video_resolution;
            ]]]
          product: |
            [[[
              var player = states[variables.entity].attributes.player;
              var product = states[variables.entity].attributes.product;
              return product + ' - ' + '<i>' + player + '</i>';
            ]]]
          media_detail: |
            [[[
              if(states[variables.entity].attributes.media_type == 'movie') {
                return "<ha-icon icon='mdi:filmstrip' style='width: 15px; height: 15px; position: relative; top: -2px;'></ha-icon> (" + states[variables.entity].attributes.year + ")";
              } else {
                return "<ha-icon icon='mdi:television-classic' style='width: 15px; height: 15px; position: relative; top: -2px;'></ha-icon> S" + states[variables.entity].attributes.parent_media_index + " • E" + states[variables.entity].attributes.media_index;
              }
            ]]]
          bandwidth: |
            [[[ 
              var bytes = states[variables.entity].attributes.bandwidth * 1000;
              var sizes = ['Bytes', 'Kbps', 'Mbps', 'Gbps', 'Tbps'];
              if (bytes == 0) return 'n/a';
              var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)));
              if (i == 0) return 'Bandwidth: ' + bytes + ' ' + sizes[i];
              return 'Bandwidth: ' + (bytes / Math.pow(1000, i)).toFixed(1) + ' ' + sizes[i];
            ]]]
        card_mod:
          style: |
            ha-card {
              padding: 0;
              margin: 0;
              border: 0.01px solid rgba(70, 130, 180, 0.5);
              box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.5); /* Add a box shadow effect */
              background: rgba(0.40, 1, 0, 0.5) !important;
            }
            ha-card #container {
            margin: 5px 0 0 0;

            }
            #name {
              display:none;
            }
        styles:
          card:
            - height: 100x
            - padding: 0
          custom_fields:
            bar:
              - text-transform: capitalize
              - font-size: 13px
              - padding-top: 2px
              - padding-bottom: 0px
            user:
              - text-transform: capitalize
              - text-align: end
              - font-size: 12px
              - font-family: Arial, sans-serif;
              - font-style: italic;
              - letter-spacing: 2px;
              - margin-left: "-60px;"
            title:
              - text-transform: capitalize
              - text-align: start
              - font-size: 26px
              - margin-top: "-5px"
              - margin-bottom: 2px
            stream:
              - text-transform: capitalize
              - text-align: start
              - font-size: 12px
            product:
              - text-transform: capitalize
              - text-align: start
              - font-size: 12px
            media_detail:
              - text-transform: uppercase
              - text-align: start
              - font-size: 15px
            bandwidth:
              - text-transform: capitalize
              - text-align: end
              - font-size: 12px
              - margin-left: "-60px;"
          grid:
            - grid-template-areas: |
                "picture product user"
                "picture title title"    
                "picture media_detail media_detail"
                "picture bar bar"
                "picture stream bandwidth"
            - grid-template-columns: 1fr 200px 3fr
            - grid-gap: 5px 3px
card:
  type: vertical-stack
card_param: cards


:hammer_and_wrench: Troubleshooting

No Data Appearing?

  • Ensure your Tautulli API key, Url are correct.
  • Restart Home Assistant after making changes.
  • Check Developer ToolsStates for sensor.plex_session_*.


:handshake: Contributing

Want to improve this integration? Feel free to submit a PR or open an issue on GitHub!


:scroll: License

This project is licensed under the MIT License.



2 Likes

:star: Like This Integration?

If you find the **Tautulli Active Streams** integration helpful, please take a moment to give it a ⭐ on [GitHub](https://github.com/Richardvaio/Tautulli_Active_Streams)! Your feedback and support help improve the project and motivate further development. Thanks! 🚀

I would add to your requirements the auto-entities in HACS is required too.

1 Like

@richardbeetwell i can’t get the picture part to work. Its giving me this.

Thanks

thank you. will update

1 Like

how are you connecting to tautulli? i will be posting another update shortly which includes updated to the way images are handled

I used the API and I.P. address, which worked Perfectly! Great addition to HA!

Hello! Thanks for all of your work on this!

Similar result, @richardbeetwell

Home Assistant

  • Core 2025.1.4
  • Supervisor 2025.02.0
  • Operating System 14.2
  • Frontend 20250109.2

Please Update. Enable the image proxy and this should resolve picture related issues.
please let me know and if you like this integration please STAR my github page.

Thank you all!

v2.0.4 - (13.02.2025)

Changes & Improvements:

  • Image Proxy Feature:
    • Introduced the Image Proxy option to route image requests through Home Assistant.
    • Protects your API key by removing it from image URLs exposed to clients.
    • Ensures that images remain accessible even if clients lack direct access to the Tautulli server (e.g., behind reverse proxies).

This may become a Default Option

Updated to 2.0.4. Different issue but still not pulling the picture. Any debugs i can provide? I did also try to activate the Proxy option

image

Have you tryed removing integrations and adding again?
Please let me know

Deleted the old integration and installed 2.1.0.

Same GUI issue.

1 Like

First of all, thanks for a very well designed card to go along with this great integration!

I just spent 6 hours poring through the Tautulli REST End Point and Custom Button Card thread, recreating each step and editing yamls all over the place. Then I got to the end and saw your release of this integration…I REALLY should have read that thread starting from the most recent posts.

This is so much quicker and easier, although I did learn a lot by going through the previous thread.

Anyway, on to my question: when I connect via http://[IP:PORT] everything works great and looks amazing.

(I wanted to show a screencap because it looked so good, but apparently I can only embed one image as a new user here)

However, if I try to connect via my reverse proxied internal domain, the config page spins for while then just says “unknown error occured”

Same thing happens whether I enable SSL verification or not. SSL cert is provided by LetsEncrypt and not self-signed.

Log entries show:

Logger: homeassistant.config_entries
Source: config_entries.py:637
First occurred: 1:14:23 AM (3 occurrences)
Last logged: 1:33:32 AM

Error setting up entry Tautulli Active Streams for tautulli_active_streams
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 637, in __async_setup_with_context
    result = await component.async_setup_entry(hass, self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/tautulli_active_streams/__init__.py", line 87, in async_setup_entry
    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 2285, in async_forward_entry_setups
    await self._async_forward_entry_setups_locked(entry, platforms)
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 2296, in _async_forward_entry_setups_locked
    await asyncio.gather(
    ...<11 lines>...
    )
asyncio.exceptions.CancelledError

and

Logger: homeassistant.config_entries
Source: config_entries.py:637
First occurred: 1:14:23 AM (3 occurrences)
Last logged: 1:33:32 AM

Error setting up entry Tautulli Active Streams for sensor
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 637, in __async_setup_with_context
    result = await component.async_setup_entry(hass, self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/sensor/__init__.py", line 96, in async_setup_entry
    return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 193, in async_setup_entry
    return await self._platforms[key].async_setup_entry(config_entry)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 333, in async_setup_entry
    return await self._async_setup_platform(async_create_setup_awaitable)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 374, in _async_setup_platform
    await asyncio.gather(*pending)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 658, in async_add_entities
    await add_func(coros, entities, timeout)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 567, in _async_add_and_update_entities
    results = await asyncio.gather(*tasks, return_exceptions=True)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
asyncio.exceptions.CancelledError

Those errors made me think it was because I had already installed the integration and connected with http, or maybe because of something conflicting with all my hatchet jobs going through the original Tautulli thread. However, after deleting everything and hard rebooting, I reinstalled the integration (2.1.0) and got the same errors.

Any thoughts?

Screencap of the working card when connected via http:

Forgot to mention in my original post that I can connect to the Tautulli API via the https://tautulli.mydomain.com in a browser and I do see the json result with all the correct info.

hi mate do you have in tautulli settings. web interface Public Tautulli Domain url set and Enable HTTP Proxy. enabled? please let me know if this solves for you otherwise i will look into this

I did not have HTTP proxy enabled but did have my public domain set.

After enabling HTTP proxy, though, no change to the integration setup. Same log errors.

Interestingly, all 7 of the tautulli sensors are created as well as the plex_session sensors 2 through 6 (albeit in an off state). The only one not created is sensor.plex_session_1_tautulli.

what do you have the scan interval set to ? try 4 seconds for this test

remove integration. restart home assistant
delete all logs and then add integration again.

let me know how you get on and if any errors are in the logs

Thanks for your time helping me out.

After removing the integration entirely, re-adding the custom repo and reinstalling the integration (2.1.1 this time) with scan interval 4 seconds, same log errors:

2025-02-17 20:55:17.518 INFO (MainThread) [homeassistant.setup] Setting up tautulli_active_streams
2025-02-17 20:55:28.521 INFO (MainThread) [homeassistant.components.sensor] Setting up tautulli_active_streams.sensor
2025-02-17 20:55:28.523 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.plex_session_2_tautulli
2025-02-17 20:55:28.525 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.plex_session_3_tautulli
2025-02-17 20:55:28.526 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.plex_session_4_tautulli
2025-02-17 20:55:28.530 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.plex_session_5_tautulli
2025-02-17 20:55:28.532 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.plex_session_6_tautulli
2025-02-17 20:55:28.533 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.stream_count
2025-02-17 20:55:28.536 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.stream_count_direct_play
2025-02-17 20:55:28.537 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.stream_count_direct_stream
2025-02-17 20:55:28.539 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.stream_count_transcode
2025-02-17 20:55:28.541 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.total_bandwidth
2025-02-17 20:55:28.542 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.lan_bandwidth
2025-02-17 20:55:28.544 INFO (MainThread) [homeassistant.helpers.entity_registry] Registered new sensor.tautulli_active_streams entity: sensor.wan_bandwidth
2025-02-17 20:55:36.623 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry Tautulli Active Streams for sensor
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 637, in __async_setup_with_context
    result = await component.async_setup_entry(hass, self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/sensor/__init__.py", line 96, in async_setup_entry
    return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 193, in async_setup_entry
    return await self._platforms[key].async_setup_entry(config_entry)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 333, in async_setup_entry
    return await self._async_setup_platform(async_create_setup_awaitable)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 374, in _async_setup_platform
    await asyncio.gather(*pending)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 658, in async_add_entities
    await add_func(coros, entities, timeout)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 567, in _async_add_and_update_entities
    results = await asyncio.gather(*tasks, return_exceptions=True)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
asyncio.exceptions.CancelledError
2025-02-17 20:55:36.633 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry Tautulli Active Streams for tautulli_active_streams
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 637, in __async_setup_with_context
    result = await component.async_setup_entry(hass, self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/tautulli_active_streams/__init__.py", line 87, in async_setup_entry
    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 2285, in async_forward_entry_setups
    await self._async_forward_entry_setups_locked(entry, platforms)
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 2296, in _async_forward_entry_setups_locked
    await asyncio.gather(
    ...<11 lines>...
    )
asyncio.exceptions.CancelledError

any idea why the bar car is getting right justified?
image