Custom UI: Mini video player (HACK)

Finally, a video player with good information that takes significantly less screen real estate:

iPhone X screenshot:

(new mini player on top, older players beneath) - Idle state comparison:

(new mini player on top, older players beneath) - Playing Netflix video comparison:

(new mini player on top, older players beneath) - Plex video using media_player.apple_tv player:

(new mini player on top, older players beneath) - Plex video using media_player.plex player:

Popup controls:

state-card-mini-video-player.html:

<!--
https://github.com/c727/home-assistant-mini-media-player
version: 20180129.2
-->
<dom-module id='state-card-mini-video-player'>
  <template>
    <style include='iron-flex iron-flex-alignment'></style>
    <style>
      state-badge {
        float: left;

        /* Force round badge to square movie poster size */
        width: 80px !important;
        height: 120px !important;
        border-radius: 0% !important;
        background-size: 80px !important;
        background-repeat: no-repeat !important;
        background-color: black !important;
        background-position: center !important;
        margin-right: 15px !important;
      }
      .info {
        margin-left: 56px;
      }
      .state,
      .playnername {
        /* line-height: 40px; */
        /* font-size: large; */
        font-weight: bolder;
        white-space: nowrap;
      }
      .state {
        font-weight: lighter;
      }
      .playnername[has-info] {

      }
      .mediainfo {
        color: var(--secondary-text-color);
        font-weight: medium;
        white-space: nowrap;
      }
      paper-dialog {
        padding: 20px 8px;
        border-radius: 2px;
        font-weight: 500;
        min-width: 500px;
      }
      @media screen and (max-width: 600px) {
        paper-dialog {
          position: absolute !important;
          bottom: 0; left: 0; right: 0;
          border-bottom-left-radius: 0;
          border-bottom-right-radius: 0;
          margin: 0;
        }
      }
      paper-button-group {
        display: grid;
        grid-template-columns: repeat(3, 120px);
        grid-gap: 4px;
        margin-top: 8px;
        padding: 0;
      }
      paper-button {
        background-color: var(--primary-color);
        color: #FFF;
        font-weight: 500;
        width: 100%;
        margin: 0;
      }
    </style>

    <template is='dom-if' if='[[!playerObj]]'>
      <div class='horizontal justified layout'>
        <state-info state-obj='[[stateObj]]' in-dialog='[[dialog]]'></state-info>
        <div class='state'>Nothing Playing</div>
      </div>
    </template>

    <template is='dom-if' if='[[playerObj]]'>
      <div class='horizontal justified layout' on-tap='openInfoCard'>
        <div class='state-info'>
          <state-badge state-obj='[[playerObj]]'></state-badge>
          <div class='info'>
            <div class='mediainfo'>[[computeStateName(playerObj)]]</div>
            <br>
            <div class='playnername' hidden='[[!playerObj.hasMediaInfo]]'>[[playerObj.mediaInfo]]</div>
            <div class='mediainfo' hidden='[[!playerObj.hasShowInfo]]'>[[playerObj.showInfo]]</div>
            <div class='mediainfo' hidden='[[!playerObj.hasRating]]'>[[playerObj.rating]]</div>
            <div class='mediainfo' hidden='[[!playerObj.hasDuration]]'>[[playerObj.duration]]</div>
            <div class='mediainfo' hidden='[[!playerObj.hasSource]]'>[[playerObj.source]]</div>
          </div>
        </div>
        <div class='horizontal layout'>
          <template is='dom-if' if='[[playerObj.isNormalView]]'>
            <paper-icon-button icon='mdi:power' on-tap='handlePowerTap'></paper-icon-button>
          </template>
          <template is='dom-if' if='[[playerObj.isOnAndCompactView]]'>
            <paper-icon-button icon='mdi:skip-backward' on-tap='handlePreviousTap'></paper-icon-button>
            <paper-icon-button icon='mdi:play' on-tap='handlePlayPauseTap' hidden='[[playerObj.isPlaying]]'></paper-icon-button>
            <paper-icon-button icon='mdi:pause' on-tap='handlePlayPauseTap' hidden='[[!playerObj.isPlaying]]'></paper-icon-button>
            <paper-icon-button icon='mdi:skip-forward' on-tap='handleNextTap'></paper-icon-button>
          </template>
        </div>
        </template>
      </div>

      <paper-dialog id='infoCard'>
        <template is='dom-if' if='[[playerObj]]'>
          <div class='mediainfo'>[[computeStateName(playerObj)]]</div>
        </template>
        <more-info-media_player
          hass='[[hass]]'
          state-obj='[[playerObj]]'
        ></more-info-media_player>
      </paper-dialog>
    </template>
  </template>
</dom-module>

<script>
class StateCardMiniVideoPlayer extends Polymer.Element {
  static get is() { return 'state-card-mini-video-player'; }

  static get properties() {
    return {
      hass: Object,
      stateObj: Object,
      config: {
        type: Object,
        computed: 'computeConfig(stateObj)',
      },
      playerObj: {
        type: Object,
        computed: 'computePlayerObj(hass, config)',
      },
      dialog: {
        type: Boolean,
        value: false,
      },
    };
  }

  computeConfig(stateObj) {
    return stateObj.attributes.config;
  }

  computePlayerObj(hass, config) {
    if (config && config.player && hass.states[this.config.player]) {
      var playerObj = hass.states[this.config.player];
      playerObj.isOff = playerObj.state === 'off';
      playerObj.isPlaying = playerObj.state === 'playing';
      playerObj.isIdle = playerObj.state === 'idle';

      if (playerObj.isIdle) {
        return null;
      } else {
        playerObj.hasMediaInfo = (playerObj.attributes.media_title) ? true : false;
        if (playerObj.hasMediaInfo) {
          if (playerObj.attributes.media_title) {
            playerObj.mediaInfo = playerObj.attributes.media_title;
          }
        }

        playerObj.hasDuration = (playerObj.attributes.media_duration);
        if (playerObj.hasDuration) {
          var duration = parseInt(playerObj.attributes.media_duration);
          /* if plex */
          if (playerObj.attributes.media_library_name) {
            /* adjust plex time */
            duration = (duration / 1000);
          }
          var hours = parseInt(duration / 3600);
          duration = duration - hours * 3600;
          var minutes = parseInt(duration / 60);
          var duration_string = '';
          if (hours == 1) {
            duration_string = hours.toString() + ' hour ';
          } else if (hours > 1) {
            duration_string = hours.toString() + ' hours ';
          }
          if (minutes > 0){
            duration_string = duration_string + minutes.toString() + ' min';
          }
          playerObj.duration = duration_string;
        }

        playerObj.hasRating = (playerObj.attributes.media_content_rating);
        if (playerObj.hasRating) {
          playerObj.rating = 'Rated: ' + playerObj.attributes.media_content_rating;
        }

        playerObj.showInfo = '';
        /* if plex */
        if (playerObj.attributes.media_library_name) {
          if (playerObj.attributes.media_content_type == 'tvshow'){
            if (playerObj.attributes.media_season && playerObj.attributes.media_episode) {
              playerObj.showInfo = playerObj.attributes.media_series_title + ' - S' + playerObj.attributes.media_season + 'E' + playerObj.attributes.media_episode;
            }
          }
        }

        /* TODO: Reformat TV show titles from Apple TV media player titles
        Format 1 (Plex): S4 • E5: The Signature
          var re1 = ^S\d+.+E\d+:.+;

        Format 2 (Netflix): S3: E3 "The Coup"
          var re2 = ^S\d*:.E\d*.".*"
        */

        playerObj.hasShowInfo = (playerObj.showInfo) ? true : false;
      }

      playerObj.hasSource = (playerObj.attributes.source && playerObj.attributes.source != playerObj.mediaInfo);
      playerObj.source = playerObj.attributes.source;
      playerObj.isMuted = playerObj.attributes.is_volume_muted;
      playerObj.volumeSliderValue = playerObj.attributes.volume_level * 100;
      playerObj.isNormalView = !this.config.compact_view;
      playerObj.isOnAndNormalView = !playerObj.isOff && !this.config.compact_view;
      playerObj.isOnAndCompactView = !playerObj.isOff && this.config.compact_view;
      return playerObj;
    }
    return null;
  }

  computeStateName(stateObj) {
    return window.hassUtil.computeStateName(stateObj);
  }

  handlePreviousTap(ev) {
    ev.stopPropagation();
    this.callService('media_previous_track');
  }

  handlePlayPauseTap(ev) {
    ev.stopPropagation();
    this.callService('media_play_pause');
  }

  handleNextTap(ev) {
    ev.stopPropagation();
    this.callService('media_next_track');
  }

  handlePowerTap(ev) {
    ev.stopPropagation();
    this.callService('toggle');
  }

  callService(service) {
    this.hass.callService('media_player', service, { 'entity_id': this.playerObj.entity_id });
  }

  fireScript(ev) {
    this.hass.callService('script', ev.model.item.script.split('.')[1], { 'entity_id': this.playerObj.entity_id });
  }

  handleVolumeTap() {
    this.hass.callService('media_player', 'volume_mute', { 'entity_id': this.playerObj.entity_id, 'is_volume_muted': !this.playerObj.isMuted });
  }

  volumeSliderChanged(ev) {
    var volPercentage = parseFloat(ev.target.value);
    var vol = volPercentage > 0 ? volPercentage / 100 : 0;
    this.hass.callService('media_player', 'volume_set', { 'entity_id': this.playerObj.entity_id, 'volume_level': vol });
  }

  openInfoCard() {
    this.root.getElementById('infoCard').open();
  }
}
customElements.define(StateCardMiniVideoPlayer.is, StateCardMiniVideoPlayer);
</script>

configuration.yaml:

homeassistant:
  customize_glob:
    "*.*":
      custom_ui_state_card: state-card-custom-ui

    # give all mini video players the same look and feel
    "input_text.mvp_*":
      custom_ui_state_card: state-card-mini-video-player
      icon: mdi:movie
      friendly_name: Video

  customize:
    input_text.mvp_living_room_atv:
      friendly_name: Apple TV Mini Player
      config:
        player: media_player.living_room_apple_tv

    input_text.mvp_living_room_plex:
      friendly_name: Plex Mini Player
      config:
        player: media_player.plex_living_room_apple_tv

    media_player.plex_living_room_apple_tv:
      friendly_name: Plex Player

    media_player.living_room_apple_tv:
      friendly_name: Apple TV Player

discovery:

config:

media_player:
  - platform: plex
    entity_namespace: 'plex'
    include_non_clients: true
    scan_interval: 5
    show_all_controls: false
    use_episode_art: false

  - platform: apple_tv
    name: Apple TV Player
    host: !secret appletv_living_room_host
    login_id: !secret appletv_login_id
    start_off: true

frontend:
  javascript_version: latest

  extra_html_url:
    - /local/custom_ui/state-card-custom-ui.html
    - /local/custom_ui/state-card-mini-video-player.html
  extra_html_url_es5:
    - /local/custom_ui/state-card-custom-ui-es5.html
    - /local/custom_ui/state-card-mini-video-player_es5.html

input_text:
  mvp_living_room_plex:
    name: ' '
  mvp_living_room_atv:
    name: ' '

group:
  default_view:
    name: Home
    view: yes
    icon: mdi:home
    entities:
      - group.living_room_apple_tv
      - group.living_room_plex
      # - media_player.living_room_apple_tv
      # - media_player.plex_living_room_apple_tv

  living_room_apple_tv:
    name: ' ' # hide group heading
    entities:
      - input_text.mvp_living_room_atv

  living_room_plex:
    name: ' ' # hide group heading
    entities:
      - input_text.mvp_living_room_plex

automation:

  # The HA plex player displays detailed, properly formatted media information
  # Ex.
  #   Lawnmower Dog
  #   Rick and Morty - S01E02
  #   Rated: TV-MA
  # The HA apple tv player shows minimal, improperly formatted media information
  # Ex.
  #   S1 • E2: Lawnmower Dog
  #
  # Use this automation to show the plex player when plex videos are being played
  # Otherwise show the apple tv player (even if nothing is being played)
  - alias: Show Hide Living Room Video Players
    trigger:
      - platform: homeassistant
        event: start
      - platform: state
        entity_id:
          - media_player.plex_living_room_apple_tv
          - media_player.living_room_apple_tv
    action:
      # only show plex player when its playing
      - service: group.set_visibility
        entity_id: group.living_room_plex
        data_template:
          visible: >
            {% if is_state("media_player.plex_living_room_apple_tv","playing") %}
              True
            {% else  %}
              False
            {% endif %}
      # otherwise always show apple tv player
      - service: group.set_visibility
        entity_id: group.living_room_apple_tv
        data_template:
          visible: >
            {% if is_state("media_player.plex_living_room_apple_tv","playing") %}
              False
            {% else  %}
              True
            {% endif %}

To create this, I merely hacked away at the existing mini media player. Ideally @eddi89 incorporates my hacks into his player.

KNOWN ISSUE: I can only get this to work on Apple IOS by setting frontend: to javascript_version: latest. Any help you can give on fixing this issue or improving this component is appreciated.

9 Likes

This is great. I have sooo many Plex players up to 25. This will make that much nicer.

1 Like

Thanks! Will use this for Kodi, how hard would it be to change the aspect ration of the preview picture? I think Kodis didnt supply cover art with the normal media player card but captures in the aspect ration of the video file.

1 Like

And Your version should be the default version for players! Great Job!!!

Just modify the html file (width, height, and background-size)

VERSION 2: Simplifies the dynamic showing of one player and the hiding of another. For example, I want my living room Plex player shown when its playing something, otherwise show my living room Apple TV player.

/www/custom_ui/state-card-mini-video-player.html

<!--
Based on:
https://github.com/c727/home-assistant-mini-media-player
version: 20180129.2
-->
<dom-module id='state-card-mini-video-player'>
  <template>
    <style include='iron-flex iron-flex-alignment'></style>
    <style>
      state-badge {
        float: left;

        /* Force round badge to square movie poster size */
        width: 80px !important;
        height: 120px !important;
        border-radius: 0% !important;
        background-size: 80px !important;
        background-repeat: no-repeat !important;
        background-color: black !important;
        background-position: center !important;
        margin-right: 15px !important;
      }
      .info {
        margin-left: 56px;
      }
      .state,
      .playnername {
        /* line-height: 40px; */
        /* font-size: large; */
        font-weight: bolder;
        white-space: nowrap;
      }
      .state {
        font-weight: lighter;
      }
      .playnername[has-info] {

      }
      .mediainfo {
        color: var(--secondary-text-color);
        font-weight: medium;
        white-space: nowrap;
      }
      paper-dialog {
        padding: 20px 8px;
        border-radius: 2px;
        font-weight: 500;
        min-width: 500px;
      }
      @media screen and (max-width: 600px) {
        paper-dialog {
          position: absolute !important;
          bottom: 0; left: 0; right: 0;
          border-bottom-left-radius: 0;
          border-bottom-right-radius: 0;
          margin: 0;
        }
      }
      paper-button-group {
        display: grid;
        grid-template-columns: repeat(3, 120px);
        grid-gap: 4px;
        margin-top: 8px;
        padding: 0;
      }
      paper-button {
        background-color: var(--primary-color);
        color: #FFF;
        font-weight: 500;
        width: 100%;
        margin: 0;
      }
    </style>

    <template is='dom-if' if='[[!playerObj]]'>
      <div class='horizontal justified layout'>
        <state-info state-obj='[[stateObj]]' in-dialog='[[dialog]]'></state-info>
        <div class='state'>Nothing Playing</div>
      </div>
    </template>

    <template is='dom-if' if='[[playerObj]]'>
      <div class='horizontal justified layout' on-tap='openInfoCard'>
        <div class='state-info'>
          <state-badge state-obj='[[playerObj]]'></state-badge>
          <div class='info'>
            <div class='mediainfo'>[[computeStateName(playerObj)]]</div>
            <br>
            <div class='playnername' hidden='[[!playerObj.hasMediaInfo]]'>[[playerObj.mediaInfo]]</div>
            <div class='mediainfo' hidden='[[!playerObj.hasShowInfo]]'>[[playerObj.showInfo]]</div>
            <div class='mediainfo' hidden='[[!playerObj.hasRating]]'>[[playerObj.rating]]</div>
            <div class='mediainfo' hidden='[[!playerObj.hasDuration]]'>[[playerObj.duration]]</div>
            <div class='mediainfo' hidden='[[!playerObj.hasSource]]'>[[playerObj.source]]</div>
          </div>
        </div>
        <div class='horizontal layout'>
          <template is='dom-if' if='[[playerObj.isNormalView]]'>
            <paper-icon-button icon='mdi:power' on-tap='handlePowerTap'></paper-icon-button>
          </template>
          <template is='dom-if' if='[[playerObj.isOnAndCompactView]]'>
            <paper-icon-button icon='mdi:skip-backward' on-tap='handlePreviousTap'></paper-icon-button>
            <paper-icon-button icon='mdi:play' on-tap='handlePlayPauseTap' hidden='[[playerObj.isPlaying]]'></paper-icon-button>
            <paper-icon-button icon='mdi:pause' on-tap='handlePlayPauseTap' hidden='[[!playerObj.isPlaying]]'></paper-icon-button>
            <paper-icon-button icon='mdi:skip-forward' on-tap='handleNextTap'></paper-icon-button>
          </template>
        </div>
        </template>
      </div>

      <paper-dialog id='infoCard'>
        <template is='dom-if' if='[[playerObj]]'>
          <div class='mediainfo'>[[computeStateName(playerObj)]]</div>
        </template>
        <more-info-media_player
          hass='[[hass]]'
          state-obj='[[playerObj]]'
        ></more-info-media_player>
      </paper-dialog>
    </template>
  </template>
</dom-module>

<script>
class StateCardMiniVideoPlayer extends Polymer.Element {
  static get is() { return 'state-card-mini-video-player'; }

  static get properties() {
    return {
      hass: Object,
      stateObj: Object,
      config: {
        type: Object,
        computed: 'computeConfig(stateObj)',
      },
      playerObj: {
        type: Object,
        computed: 'computePlayerObj(hass, config)',
      },
      dialog: {
        type: Boolean,
        value: false,
      },
    };
  }

  computeConfig(stateObj) {
    return stateObj.attributes.config;
  }

  computePlayerObj(hass, config) {
    if (config && config.player && hass.states[this.config.player]) {
      var playerObj = hass.states[this.config.player];
      playerObj.isOff = playerObj.state === 'off';
      playerObj.isPlaying = playerObj.state === 'playing';
      playerObj.isIdle = playerObj.state === 'idle';

      if (playerObj.isIdle) {
        return null;
      } else {
        playerObj.hasMediaInfo = (playerObj.attributes.media_title) ? true : false;
        if (playerObj.hasMediaInfo) {
          if (playerObj.attributes.media_title) {
            playerObj.mediaInfo = playerObj.attributes.media_title;
          }
        }

        playerObj.hasDuration = (playerObj.attributes.media_duration);
        if (playerObj.hasDuration) {
          var duration = parseInt(playerObj.attributes.media_duration);
          /* if plex */
          if (playerObj.attributes.media_library_name) {
            /* adjust plex time */
            duration = (duration / 1000);
          }
          var hours = parseInt(duration / 3600);
          duration = duration - hours * 3600;
          var minutes = parseInt(duration / 60);
          var duration_string = '';
          if (hours == 1) {
            duration_string = hours.toString() + ' hour ';
          } else if (hours > 1) {
            duration_string = hours.toString() + ' hours ';
          }
          if (minutes > 0){
            duration_string = duration_string + minutes.toString() + ' min';
          }
          playerObj.duration = duration_string;
        }

        playerObj.hasRating = (playerObj.attributes.media_content_rating);
        if (playerObj.hasRating) {
          playerObj.rating = 'Rated: ' + playerObj.attributes.media_content_rating;
        }

        playerObj.showInfo = '';
        /* if plex */
        if (playerObj.attributes.media_library_name) {
          if (playerObj.attributes.media_content_type == 'tvshow'){
            if (playerObj.attributes.media_season && playerObj.attributes.media_episode) {
              playerObj.showInfo = playerObj.attributes.media_series_title + ' - S' + playerObj.attributes.media_season + 'E' + playerObj.attributes.media_episode;
            }
          }
        }

        /* TODO: Reformat TV show titles from Apple TV media player titles
        Format 1 (Plex): S4 • E5: The Signature
          var re1 = ^S\d+.+E\d+:.+;

        Format 2 (Netflix): S3: E3 "The Coup"
          var re2 = ^S\d*:.E\d*.".*"
        */

        playerObj.hasShowInfo = (playerObj.showInfo) ? true : false;
      }

      playerObj.hasSource = (playerObj.attributes.source && playerObj.attributes.source != playerObj.mediaInfo);
      playerObj.source = playerObj.attributes.source;
      playerObj.isMuted = playerObj.attributes.is_volume_muted;
      playerObj.volumeSliderValue = playerObj.attributes.volume_level * 100;
      playerObj.isNormalView = !this.config.compact_view;
      playerObj.isOnAndNormalView = !playerObj.isOff && !this.config.compact_view;
      playerObj.isOnAndCompactView = !playerObj.isOff && this.config.compact_view;
      return playerObj;
    }
    return null;
  }

  computeStateName(stateObj) {
    return window.hassUtil.computeStateName(stateObj);
  }

  handlePreviousTap(ev) {
    ev.stopPropagation();
    this.callService('media_previous_track');
  }

  handlePlayPauseTap(ev) {
    ev.stopPropagation();
    this.callService('media_play_pause');
  }

  handleNextTap(ev) {
    ev.stopPropagation();
    this.callService('media_next_track');
  }

  handlePowerTap(ev) {
    ev.stopPropagation();
    this.callService('toggle');
  }

  callService(service) {
    this.hass.callService('media_player', service, { 'entity_id': this.playerObj.entity_id });
  }

  fireScript(ev) {
    this.hass.callService('script', ev.model.item.script.split('.')[1], { 'entity_id': this.playerObj.entity_id });
  }

  handleVolumeTap() {
    this.hass.callService('media_player', 'volume_mute', { 'entity_id': this.playerObj.entity_id, 'is_volume_muted': !this.playerObj.isMuted });
  }

  volumeSliderChanged(ev) {
    var volPercentage = parseFloat(ev.target.value);
    var vol = volPercentage > 0 ? volPercentage / 100 : 0;
    this.hass.callService('media_player', 'volume_set', { 'entity_id': this.playerObj.entity_id, 'volume_level': vol });
  }

  openInfoCard() {
    this.root.getElementById('infoCard').open();
  }
}
customElements.define(StateCardMiniVideoPlayer.is, StateCardMiniVideoPlayer);
</script>

/configuration.yaml

homeassistant:
  name: Home
  customize_glob:
    "*.*":
      custom_ui_state_card: state-card-custom-ui

    # give all mini video players the same look and feel
    "input_text.mvp_*":
      custom_ui_state_card: state-card-mini-video-player
      icon: mdi:movie
      friendly_name: Video

  customize:
    input_text.mvp_living_room:
      friendly_name: Living Room
      config:
        # start off with the apple tv player
        player: media_player.living_room_apple_tv

    media_player.plex_living_room_apple_tv:
      friendly_name: Living Room

    media_player.living_room_apple_tv:
      friendly_name: Living Room

discovery:

config:

media_player:
  - platform: plex
    entity_namespace: 'plex'
    include_non_clients: true
    scan_interval: 5
    show_all_controls: false
    use_episode_art: false

  - platform: apple_tv
    name: Apple TV Player
    host: !secret appletv_living_room_host
    login_id: !secret appletv_login_id
    start_off: true

frontend:
  javascript_version: latest

  extra_html_url:
    - /local/custom_ui/state-card-mini-video-player.html
    - /local/custom_ui/state-card-custom-ui.html
  extra_html_url_es5:
    - /local/custom_ui/state-card-mini-video-player_es5.html
    - /local/custom_ui/state-card-custom-ui-es5.html

input_text:
  mvp_living_room:
    name: ' '

# required for the automation that hides/shows players
python_script:

group:
  default_view:
    name: Home
    view: yes
    icon: mdi:home
    entities:
      - group.living_room_card

  living_room_card:
    name: 'LIVING ROOM'
    entities:
      - input_text.mvp_living_room

automation:

  # The HA plex player displays detailed, properly formatted media information
  # Ex.
  #   Lawnmower Dog
  #   Rick and Morty - S01E02
  #   Rated: TV-MA
  # The HA apple tv player shows minimal, improperly formatted media information
  # Ex.
  #   S1 • E2: Lawnmower Dog
  #
  # Use this automation to show the plex player when plex videos are being played
  # Otherwise show the apple tv player (even if nothing is being played)
  - alias: Show Hide Living Room Video Players
    trigger:
      - platform: homeassistant
        event: start
      - platform: state
        entity_id:
          - media_player.plex_living_room_apple_tv
          - media_player.living_room_apple_tv
    action:
      # only show plex player when its playing
      # otherwise always show apple tv player
      - service: python_script.set_mini_video_player
        data_template:
          entity_id: "input_text.mvp_living_room"
          player: >
            {% if is_state("media_player.plex_living_room_apple_tv","playing") %}
              media_player.plex_living_room_apple_tv
            {% else  %}
              media_player.living_room_apple_tv
            {% endif %}

/python_scripts/set_mini_video_player.py

entity_id = str(data.get('entity_id'))
player = str(data.get('player'))

state = hass.states.get(entity_id)

friendly_name = ""
if "friendly_name" in state.attributes:
    friendly_name = state.attributes['friendly_name']
icon = ""
if "icon" in state.attributes:
    icon = state.attributes['icon']

hass.states.set(entity_id, str(state.state), {
    'min': 0,
    'max': 100,
    'pattern': 'null',
    'mode': 'text',
    'custom_ui_state_card': 'state-card-mini-video-player',
    'friendly_name': friendly_name,
    'icon': icon,
    'config': {
        "player": player
        }
})

How can I fix? It’s not showing the media information.
image
image