Download
109 lines 3.4 KiBJavaScriptModified 2026-08-01 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
// Media player widget (MPRIS via the backend; divebar://media events).
// Shows album art (or a note icon) + title/artist. Click toggles
// play/pause, middle click skips to the next track. Hides itself when no
// player is running.

const NOTE_SVG = `
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
       stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
    <path d="M9 18 V6 L19 4 V16"/>
    <circle cx="6.6" cy="18" r="2.4"/>
    <circle cx="16.6" cy="16" r="2.4"/>
  </svg>
`;

export default {
  name: "media",
  css: `
    .widget-media { display: none; cursor: pointer; }
    .widget-media.present { display: flex; }
    .widget-media .art {
      display: none;
      width: calc(var(--chip-height) * 0.68);
      height: calc(var(--chip-height) * 0.68);
      border-radius: 5px;
      object-fit: cover;
    }
    .widget-media .track {
      min-width: 0;
      overflow: hidden;
      white-space: nowrap;
      -webkit-mask-image: linear-gradient(to right, black calc(100% - 20px), transparent);
      mask-image: linear-gradient(to right, black calc(100% - 20px), transparent);
    }
    .widget-media .artist { color: var(--bar-fg-dim); }
    .widget-media.paused .icon,
    .widget-media.paused .track { opacity: 0.55; }
  `,
  settings: [
    {
      key: "maxWidth",
      label: "Max text width (ch)",
      type: "number",
      min: 8,
      max: 60,
      default: 24,
    },
    {
      key: "showArtist",
      label: "Show artist",
      type: "boolean",
      default: true,
    },
  ],
  create({ options, onEvent, invoke }) {
    const el = document.createElement("div");
    el.className = "chip clickable widget-media";
    el.innerHTML = `
      <span class="icon">${NOTE_SVG}</span>
      <img class="art" alt=""/>
      <span class="track"></span>
    `;
    const iconEl = el.querySelector(".icon");
    const artEl = el.querySelector(".art");
    const trackEl = el.querySelector(".track");
    trackEl.style.maxWidth = `${options.maxWidth}ch`;

    function control(action) {
      invoke("media_control", { action }).catch((err) =>
        console.error("divebar: media_control failed", err),
      );
    }
    el.addEventListener("click", () => control("playPause"));
    el.addEventListener("auxclick", (e) => {
      if (e.button === 1) control("next");
    });

    const unsubscribe = onEvent("divebar://media", (media) => {
      const present = media && (media.title || media.artist);
      el.classList.toggle("present", Boolean(present));
      if (!present) return;

      el.classList.toggle("paused", media.status !== "Playing");

      trackEl.replaceChildren();
      trackEl.append(document.createTextNode(media.title || "Unknown"));
      if (options.showArtist && media.artist) {
        const artist = document.createElement("span");
        artist.className = "artist";
        artist.textContent = ` — ${media.artist}`;
        trackEl.append(artist);
      }

      if (media.art) {
        if (artEl.src !== media.art) artEl.src = media.art;
        artEl.style.display = "block";
        iconEl.style.display = "none";
      } else {
        artEl.style.display = "none";
        iconEl.style.display = "";
      }

      el.title =
        `${media.title || "Unknown"}${media.artist ? ` — ${media.artist}` : ""}` +
        ` (${media.status}, ${media.player}) · click to play/pause, middle-click for next`;
    });

    return { el, destroy: unsubscribe };
  },
};