[Mon Dashboard] - @Dapolux

Bonjour @Dapolux je trouve ton dashboard génial crois tu que tu pourrais me montrer ton code Lovelace? Merxi

Bravo, mais ya un truc qui me choque, tes goûts musicaux!! :laughing:

image

1 « J'aime »

Hello, oui pas de soucis, je vais ajouter dans le port initial les différents codes utilisés par section pour s’y retrouver. En attendant de l’avoir fait n’hésites pas s’il y a des sujets particuliers que tu souhaites, je prioriserai en conséquences.

Merci!

C’est marrant j’ai hésité a changer avant de faire mon screenshot car je soupçonnai une potentielle remarque :), j’aurais peut être du mettre Patrick Sébastien à la place.

Pas grave, j’assume! :slight_smile:

2 « J'aime »

Bonjour @Dapolux,

Peux tu nous en dire plus comment tu gères toute ta musique ? J’ai actuellement plusieurs enceintes reliées chacune à un rapsberry avec max2play d’installer dessus. Je diffuse soit en airplay soit via Spotify dessus. J’aimerais beaucoup pourvoir voir chaque enceinte et savoir si il y a de la lecture en cours dessus et voir qui est synchro avec qui et pourvoir changer cette synchro. Plus tard j’aimerais pouvoir les utiliser pour du TTS.

Hello @Arnault,

Si tes enceintes sont déjà sous max2play, le plus dur est déjà fait :).
Comme tu le sais probablement déjà, la synchronisation des players squeezebox est une fonctionnalité native dans LMS:
image

C’est cette même fonctionnalité qui est reprise sous HA, une fois l’intégration squeezebox faite, tu as toutes tes enceinte déclarée en tant que « media_player », avec la fonctionnalité de synchronisation.
Pour avoir accès à la possibilité de le faire visuellement, j’utilise dans lovelace la carte mini-media-player, en paramétrant mes enceintes dans « speaker_group », exemple:

  - type: 'custom:mini-media-player'
    entity: media_player.rpi_salon
    speaker_group: 
      platform: squeezebox
      show_group_count: true
      entities: 
        - entity_id: media_player.rpi_salon
          name: Salon  
        - entity_id: media_player.pc_de_greg
          name: Pc de Grég   
        - entity_id: media_player.rpi_nomade
          name: Nomade 
        - entity_id: media_player.google_home
          name: Google Home  
        - entity_id: media_player.notre_chambre
          name: Notre Chambre     

Par contre, pour spotify, je n’utilise pas du tout l’application native spotify, tout se fais par LMS, du coup pour les lectures spotify, j’utilise le plugin LMS Spotty.

Pour la navigation dans lms depuis lovelace, je t’invite à regarder de plus près le skin lms « Material », tu trouveras plus d’infos sur ce post:
https://forum.hacf.fr/t/visualisation-de-lms/2529/4

N’hésite pas si tu as des questions!

Merci beaucoup pour toutes ses informations. Je vais regarder tout ça et essayer de mettre ça en place. :+1:t2:

Très jolie interface. Merci @Dapolux pour le partage.
Entre les templates / custom cards et les popup via le browser_mod, il y a pas mal de yaml spécifique. Or en mode UI, sauf erreur on ne peut ni faire des include, ni mettre des commentaires. Cela conduit vite a de gros fichiers peu lisibles.
Utilises tu Lovelace en mode UI ou en mode YAML ? Comment as tu structuré ton code?

Bonjour @Argonaute,

Je suis en mode YAML, qui justement permet entres autres les includes et les commentaires.

D’un point de vue structure, coté configuration.yaml, j’ai mes 2 dashboards YMAL (je suis en mode « storage », comme ça j’ai aussi un dashboard UI pour faire des tests).

lovelace:

  mode: storage
  # Add yaml dashboards
  dashboards:
    smartphone-yaml:
      mode: yaml
      title: Smartphone
      icon: mdi:cellphone-text
      show_in_sidebar: true
      filename: dashboards/01_Smartphone.yaml
    dashboard-yaml:
      mode: yaml
      title: Desktop
      icon: mdi:laptop
      show_in_sidebar: true
      filename: dashboards/02_Desktop.yaml

En suite, j’ai 1 fichier yaml par dashboard, qui ont eux même 1 yaml par vue (et incluent aussi des fichiers globaux, comme le fichier des templates ou celles de popup), qui elles mêmes incluent parfaoit d’autres fichiers réutilisés à plusieurs endroits (exemple: mes groupes de speakers).

En gros voici mes fichiers pour lovelace:
image

image

Ps: Pour info, pour les includes, je n’ai pas réussi à inclure « n’importe quel code », je n’arrive que a include une « sous section » dans la structure Yaml. Je ne sais pas si c’est possible sinon.
Exemple avec le source suivant:

################################################################
#                     RDC
################################################################

  - title: rdc
    path: rdc
    icon: 'mdi:home-floor-0'
    panel: true
    cards:
      - type: custom:layout-card
        max_width: 620px
        cards:
          - !include includes/05_map_rdc.yaml
    popup_cards: !include includes/99_popup_cards.yaml

En gros je sais externaliser dans un autre include tout ce qui est sous « cards: », mais je ne sais pas externaliser imaginons les 2 lignes icon et panel dans un seul include.

Tu devrais trouver ton bonheur (et plus !) :wink:

Merci @Dapolux pour le retour ! Très propre et cela permet d’avoir de ne pas copier-coller les cards pour les 2 dashboard. J’imagine que tu as des fichiers spécifiques pour les templates.
La limitation sur les include ne me choque pas.

Je vais regarder cela avec attention! Merci

Effectivement. Dans mon dashboard « Desktop » j’ai une vue qui affiche directement les 3 plans du smartphone.

Non, j’ai tout dans le même, notamment par ce que je n’ai pas tant de templates que ça (une dizaine), et comme plusieurs templates utilisent eux même d’autre templates, c’est plus simple pour moi d’avoir tout directement dans le même fichier.

Bon alors tout d’abord, un grand merci car je crois que je vais pouvoir alléger considérablement mon code, cet addon est top!.

Par contre je ne vois pas comment cela solution mon questionnement initial.

Prenons par exemple le code suivant avec 2 boutons:

      - type: vertical-stack
        title: Test 3
        cards:
        - type: button
          entity: light.eclairage_rdc
          tap_action:
            action: toggle
        - type: button
          entity: light.eclairage_rdc
          tap_action:
            action: toggle

Je peux par exemple découper et faire:

      - type: vertical-stack
        title: Test 3
        cards:
        - !include test.yaml
        - !include test.yaml

avec dans test.yaml:

# lovelace_gen                  
  type: button
  entity: light.eclairage_rdc
  tap_action:
    action: toggle

mais imaginons que je souhaite faire le découpage sur l’instruction suivante, du style:

      - type: vertical-stack
        title: Test 3
        cards:
        - type: button
          !include test.yaml
        - type: button
          !include test.yaml

avec dans tests.yaml:

# lovelace_gen                  
  entity: light.eclairage_rdc
  tap_action:
    action: toggle

Alors ça ne marche pas, ou alors je ne vois pas bien comment je dois faire.

Peut être placer l’inclusion plus haut : GitHub - thomasloven/hass-lovelace_gen: 🔹 Improve the lovelace yaml parser for Home Assistant

- type: vertical-stack
        title: Test 3
        cards:
        - !include
          - type: button
          - test.yaml
        - !include
          - type: button
          - test.yaml

Je n’arrive pas à le faire fonctionner comme cela (Unknown error).

J’avais bien vu ce passage dans le doc, cependant de ce que je comprends, c’est plutot une utilisation d’un include au quel on passerais des paramètres (en l’occurence dans les exemples de la doc, on vois bien qu’il passe le yaml inclu, puis en suite les arguments), dans mon cas, l’idée est plus d’insérer un bout de code d’un autre fichier, peu importe ce code (là pour le moment ça ce limite aux « sous items »).

Bonjour,
J’ai commencé à travailler sur mon floorplan.
Dans le template cover_map, il renvoit à un template base_force_on. pourrais tu nous le montrer? merci

Hello,

Oui biensur, voici mon fichier complet avec tous mes templates, en date d’aujourd’hui:

################################################################
################################################################
#                     Templates for cards
################################################################
################################################################


#####   Base for all the cards (normal by default)   ###########
# Ratio, generic colours, name, format, font 
  base:
    show_state: false
    show_icon: false
    aspect_ratio: 1/1
    state:
      ## Specific style when state is "On"
      - value: 'on'
        styles:
          card:
            - background-color: 'rgba(255, 255, 255, 0.8)'
            - border: 1px rgba(80, 80, 80) solid
          name:
            - color: 'rgba(0, 0, 0, 0.6)'
          custom_fields:
            temperature:
              - color: "#8d8e90"
    double_tap_action:
      action: more-info
    styles:
      name:
        - top: 74%
        - left: 11%
        - position: absolute
      card:
        - font-family: Passion One
        - letter-spacing: 0px
        - color: 'rgba(255, 255, 255, 0.3)'
        - font-size: 100%
        - background-color: 'rgba(80, 80, 80, 0.7)'
        - border-radius: 15%
        - box-shadow: none
        - transition: none
        - border: 1px rgba(255, 255, 255) solid

  base_forced_on:  #Always on
    template:
      - base
    styles:
      card: [background-color: 'rgba(255, 255, 255, 0.8)', border: '1px rgba(80, 80, 80) solid']
      name: [color: 'rgba(0, 0, 0, 0.6)']
      state: [color: 'rgba(0, 0, 0, 0.6)']    
      custom_fields:
        temperature:
          - color: "#8d8e90"          

###############   Specific for map tiles   ################        
  base_map:
    template: base
    show_name: false
    styles:
      custom_fields:
        temperature:
          - top: 73%
          - width: 100%
          - justify-self: center
          - align-self: center
          - font-size: >
              [[[
              var window_width = window.screen.width;
              if (window_width > 600) {return '17px';}
              else {return '13px';}
              ]]]
          - color: white
          - font-weight: black
          - position: absolute   
  base_map_forced_on:
    template:
      - base_map
      - base_forced_on

  light_map:
    template:
      - base_map
      - light_icon        
    styles: 
      custom_fields:
        icon_hue:
          [top: 20%, left: 27%, width: 46%, position: absolute]    

  cover_map:
    template:
      - base_map
      - base_forced_on
      - cover_icon
    show_name: false        
    styles:
      custom_fields:
        icon_cover:
          [top: 20%, left: 20%, width: 120%, position: absolute]  

  
###############   Specific for normal tiles   ################  
  light:
    template:
      - base
      - light_circle
      - light_icon    
    styles: 
      custom_fields:
        icon_hue:
          [top: 18%, left: 6%, width: 40%, position: absolute]      
        circle_light:
          [top: 10%, left: 50%, width: 45%, position: absolute, letter-spacing: 0px]

  cover:
    template:
      - base
      - cover_circle
      - cover_icon
    styles: 
      custom_fields:
        cover_icon:
          [top: 18%, left: 6%, width: 40%, position: absolute]      
        circle_cover:
          [top: 10%, left: 50%, width: 45%, position: absolute, letter-spacing: 0px]             


################################################################
#                       Lights
################################################################

##################  Global light  ################
  light_circle:
    custom_fields:
      circle_light: >
        [[[ if (entity.state === 'on' && entity.attributes.brightness) { const
        brightness = Math.round(entity.attributes.brightness / 2.54); const
        radius = 20.5; const circumference = radius * 2 * Math.PI;  return `<svg
        viewBox="0 0 50 50"><circle cx="25" cy="25" r="${radius}"
        stroke="#b2b2b2" stroke-width="1.5" fill="none" style=" transform:
        rotate(-90deg); transform-origin: 50% 50%; stroke-dasharray:
        ${circumference}; stroke-dashoffset: ${circumference - brightness / 100
        * circumference};" /> <text x="50%" y="54%" fill="#8d8e90"
        font-size="14" text-anchor="middle"
        alignment-baseline="middle">${brightness}<tspan
        font-size="10">%</tspan></text></svg>`; } ]]]

  light_icon:
    custom_fields:

      icon_hue: >
        [[[ 
        const state = entity.state === 'on' ? 'animate' : null;
        return `
        <svg viewBox="0 0 50 50">
        <style>@keyframes animate{0%{transform: scale(0.85);}20%{transform: scale(1.1);}40%{transform: scale(0.95);}60%{transform: scale(1.03);}80%{transform: scale(0.97);}100%{transform: scale(1);}}.animate{animation: animate 0.8s; transform-origin: center;}</style>
        <path fill="rgba(180, 180, 180)" d="M 29.147 48.105 L 21.281 48.105 C 21.281 48.105 20.157 48.173 20.157 48.629 C 20.157 49.087 20.801 49.218 21.281 49.218 L 29.147 49.218 C 29.629 49.218 30.271 49.152 30.271 48.629 C 30.271 48.105 29.147 48.105 29.147 48.105 Z M 34.446 46.209 L 16.144 46.209 C 16.144 46.209 14.857 46.209 14.857 46.733 C 14.857 47.256 15.822 47.319 16.144 47.319 L 34.606 47.319 C 34.927 47.319 35.89 47.256 35.89 46.733 C 35.729 46.209 34.446 46.209 34.446 46.209 Z M 34.446 44.248 L 16.144 44.248 C 16.144 44.248 14.857 44.248 14.857 44.771 C 14.857 45.295 15.822 45.359 16.144 45.359 L 34.606 45.359 C 34.927 45.359 35.89 45.295 35.89 44.771 C 35.729 44.248 34.446 44.248 34.446 44.248 Z M 34.446 42.351 L 25.295 42.351 L 16.144 42.351 C 16.144 42.351 14.857 42.351 14.857 42.874 C 14.857 43.397 15.822 43.463 16.144 43.463 L 34.606 43.463 C 34.927 43.463 35.89 43.397 35.89 42.874 C 35.89 42.351 34.446 42.351 34.446 42.351 Z M 42.794 27.181 C 37.494 23.714 31.556 23.519 25.294 23.519 C 19.194 23.519 11.809 23.781 7.796 27.181 C 7.635 27.246 7.635 27.379 7.956 27.639 C 8.597 28.161 13.252 32.346 13.094 39.932 C 13.094 40.651 12.932 40.978 13.094 41.044 C 13.094 41.109 13.094 41.5 14.857 41.5 L 35.729 41.5 C 37.334 41.5 37.334 41.174 37.494 41.044 L 37.494 39.932 C 37.334 32.346 41.99 28.161 42.633 27.639 C 42.794 27.379 42.794 27.31 42.794 27.181" style=""/>
        <path fill="rgba(150, 150, 150)" d="M 6.828 35.24 C 14.261 41.681 38.81 39.96 43.204 35.24 C 43.872 34.362 44.375 33.484 44.707 32.825 C 48.372 20.309 48.215 15.922 48.215 14.824 C 47.874 5.16 34.192 3.846 28.021 3.846 L 22.347 3.846 C 16.172 3.846 2.321 4.943 2.152 14.824 C 2.152 15.922 1.983 20.309 5.66 32.825 C 5.66 33.484 6.158 34.582 6.828 35.24 Z" style=""/>
        <path class="${state}" fill="var(--button-card-light-color-no-temperature)" d="M 7.783 34.174 C 14.751 40.14 37.764 38.546 41.882 34.174 C 42.509 33.361 42.981 32.547 43.292 31.937 C 46.727 20.344 46.58 16.28 46.58 15.265 C 46.26 6.312 33.433 5.093 27.65 5.093 L 22.332 5.093 C 16.543 5.093 3.559 6.11 3.4 15.265 C 3.4 16.28 3.241 20.344 6.688 31.937 C 6.688 32.547 7.156 33.564 7.783 34.174 Z" style=""/>
        </svg>
        `; ]]]

      icon_hue_old2: >
        [[[ 
        const state = entity.state === 'on' ? 'animate' : null;
        return `
        <svg viewBox="0 0 50 50">
        <style>@keyframes animate{0%{transform: scale(0.85);}20%{transform: scale(1.1);}40%{transform: scale(0.95);}60%{transform: scale(1.03);}80%{transform: scale(0.97);}100%{transform: scale(1);}}.animate{animation: animate 0.8s; transform-origin: center;}</style>
        <path fill="rgba(180, 180, 180)" d="M 29.616 48.105 L 20.805 48.105 C 20.805 48.105 19.546 48.173 19.546 48.629 C 19.546 49.087 20.267 49.218 20.805 49.218 L 29.616 49.218 C 30.156 49.218 30.875 49.152 30.875 48.629 C 30.875 48.105 29.616 48.105 29.616 48.105 Z M 35.552 46.209 L 15.051 46.209 C 15.051 46.209 13.61 46.209 13.61 46.733 C 13.61 47.256 14.69 47.319 15.051 47.319 L 35.731 47.319 C 36.091 47.319 37.169 47.256 37.169 46.733 C 36.989 46.209 35.552 46.209 35.552 46.209 Z M 35.552 44.248 L 15.051 44.248 C 15.051 44.248 13.61 44.248 13.61 44.771 C 13.61 45.295 14.69 45.359 15.051 45.359 L 35.731 45.359 C 36.091 45.359 37.169 45.295 37.169 44.771 C 36.989 44.248 35.552 44.248 35.552 44.248 Z M 35.552 42.351 L 25.301 42.351 L 15.051 42.351 C 15.051 42.351 13.61 42.351 13.61 42.874 C 13.61 43.397 14.69 43.463 15.051 43.463 L 35.731 43.463 C 36.091 43.463 37.169 43.397 37.169 42.874 C 37.169 42.351 35.552 42.351 35.552 42.351 Z M 44.902 27.181 C 38.966 23.714 32.314 23.519 25.3 23.519 C 18.467 23.519 10.195 23.781 5.7 27.181 C 5.52 27.246 5.52 27.379 5.879 27.639 C 6.598 28.161 11.812 32.346 11.635 39.932 C 11.635 40.651 11.453 40.978 11.635 41.044 C 11.635 41.109 11.635 41.5 13.61 41.5 L 36.989 41.5 C 38.787 41.5 38.787 41.174 38.966 41.044 L 38.966 39.932 C 38.787 32.346 44.002 28.161 44.722 27.639 C 44.902 27.379 44.902 27.31 44.902 27.181" style=""/>
        <path fill="rgba(150, 150, 150)" d="M 6.451 28.691 C 14.107 34.485 39.389 32.937 43.915 28.691 C 44.603 27.901 45.121 27.111 45.463 26.518 C 49.237 15.258 49.075 11.311 49.075 10.323 C 48.724 1.629 34.633 0.446 28.278 0.446 L 22.434 0.446 C 16.075 0.446 1.81 1.433 1.636 10.323 C 1.636 11.311 1.462 15.258 5.248 26.518 C 5.248 27.111 5.761 28.099 6.451 28.691 Z" style=""/>
        <path class="${state}" fill="var(--button-card-light-color-no-temperature)" d="M 7.476 27.969 C 14.707 33.421 38.591 31.964 42.866 27.969 C 43.516 27.226 44.006 26.482 44.329 25.925 C 47.894 15.33 47.741 11.616 47.741 10.688 C 47.41 2.506 34.097 1.392 28.095 1.392 L 22.575 1.392 C 16.567 1.392 3.091 2.321 2.927 10.688 C 2.927 11.616 2.762 15.33 6.339 25.925 C 6.339 26.482 6.824 27.411 7.476 27.969 Z" style=""/>
        </svg>
        `; ]]]

      icon_hue_old: >
        [[[ const state = entity.state === 'on' ? 'animate' : null;
        return `<svg viewBox="0 0 50 50"><style>@keyframes animate{0%{transform: scale(0.85);}20%{transform: scale(1.1);}40%{transform: scale(0.95);}60%{transform: scale(1.03);}80%{transform: scale(0.97);}100%{transform: scale(1);}}.animate{animation: animate 0.8s; transform-origin: center;}</style>
        <path fill="#9da0a2" d="M27.4 47.3h-4.9s-.7.1-.7.8.4.9.7.9h4.9c.3 0 .7-.1.7-.9s-.7-.8-.7-.8zm3.3-2.9H19.3s-.8 0-.8.8.6.9.8.9h11.5c.2 0 .8-.1.8-.9-.1-.8-.9-.8-.9-.8zm0-3H19.3s-.8 0-.8.8.6.9.8.9h11.5c.2 0 .8-.1.8-.9-.1-.8-.9-.8-.9-.8zm0-2.9H19.3s-.8 0-.8.8.6.9.8.9h11.5c.2 0 .8-.1.8-.9s-.9-.8-.9-.8zm5.2-23.2c-3.3-5.3-7-5.6-10.9-5.6-3.8 0-8.4.4-10.9 5.6-.1.1-.1.3.1.7.4.8 3.3 7.2 3.2 18.8 0 1.1-.1 1.6 0 1.7 0 .1 0 .7 1.1.7h13c1 0 1-.5 1.1-.7v-1.7c-.1-11.6 2.8-18 3.2-18.8.1-.4.1-.5.1-.7"/>
        <path class="${state}" fill="var(--button-card-light-color-no-temperature)" d="M14.1 15.3c3.4-.3 7-.4 10.9-.4 3.8 0 7.5.2 10.9.4.4-.4.7-.8.9-1.1C39 8.5 38.9 6.5 38.9 6c-.2-4.4-8.4-5-12.1-5h0-3.4c-3.7 0-12 .5-12.1 5 0 .5-.1 2.5 2.1 8.2 0 .3.3.8.7 1.1z"/></svg>`; ]]]

################################################################
#                       Covers
################################################################          
          
##################  Global Cover ################    

  cover_circle:
    custom_fields: 
      circle_cover: >
        [[[  
        const position = Math.round(entity.attributes.current_position); 
        const radius = 20.5; 
        const circumference = radius * 2 * Math.PI;  
        return `<svg viewBox="0 0 50 50"><circle cx="25" cy="25" r="${radius}" stroke="#b2b2b2" stroke-width="1.5" fill="none" style=" transform:rotate(-90deg); transform-origin: 50% 50%; stroke-dasharray: ${circumference};
        stroke-dashoffset: ${circumference - position / 100 * circumference};" /> <text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle">${position}<tspan font-size="10">%</tspan></text></svg>`; 
         ]]]

  cover_icon:
    styles:
      custom_fields:
        icon_cover:
          [top: 20%, left: 10%, width: 50px, position: absolute]     
    custom_fields: 
      icon_cover: >
        [[[ 
        const position = Math.round(entity.attributes.current_position); 
        if (position < 5) {return `<svg viewBox="0 0 50 50"><path d="M3 4H21V8H19V20H17V8H7V20H5V8H3V4M8 9H16V11H8V9M8 12H16V14H8V12M8 15H16V17H8V15M8 18H16V20H8V18Z" fill="#8d8e90"/></svg>`;}
        else if ((position > 5) && (position < 35)) {return `<svg viewBox="0 0 50 50"><path d="M 3 4 L 21 4 L 21 8 L 19 8 L 19 20 L 17 20 L 17 8 L 7 8 L 7 20 L 5 20 L 5 8 L 3 8 L 3 4 M 8 9 L 16 9 L 16 11 L 8 11 L 8 9 M 8 12 L 16 12 L 16 14 L 8 14 L 8 12 M 8 15 L 16 15 L 16 17 L 8 17 L 8 15" fill="#8d8e90"/></svg>`;}
        else if ((position >= 35) && (position < 65)) {return `<svg viewBox="0 0 50 50"><path d="M 3 4 L 21 4 L 21 8 L 19 8 L 19 20 L 17 20 L 17 8 L 7 8 L 7 20 L 5 20 L 5 8 L 3 8 L 3 4 M 8 9 L 16 9 L 16 11 L 8 11 L 8 9 M 8 12 L 16 12 L 16 14 L 8 14 L 8 12" fill="#8d8e90"/></svg>`;}
        else if ((position >= 65) && (position < 95))  {return `<svg viewBox="0 0 50 50"><path d="M 3 4 L 21 4 L 21 8 L 19 8 L 19 20 L 17 20 L 17 8 L 7 8 L 7 20 L 5 20 L 5 8 L 3 8 L 3 4 M 8 9 L 16 9 L 16 11 L 8 11 L 8 9" fill="#8d8e90"/></svg>`;}
        else {return `<svg viewBox="0 0 50 50"><path d="M 3 4 L 21 4 L 21 8 L 19 8 L 19 20 L 17 20 L 17 8 L 7 8 L 7 20 L 5 20 L 5 8 L 3 8 L 3 4" fill="#8d8e90"/></svg>`;}
        ]]]

grand merci je vais regarder.

@Dapolux Bonjour désolé de te déranger, tout de doucement j’avance sur mon floorplan. Actuellement je bloque sur la carte media. Je n’arrive pas à trouver comme lire les fichiers MP3 qui sont dans mon fichier media. merci

7 messages ont été scindés en un nouveau sujet : Grouper les players - LMS