npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

media-player-element

v2.1.0

Published

A media player you write in HTML — one custom element over the <audio> or <video> you already wrote

Readme

<media-player> npm version ci gzip size license mit

A media player you write in HTML — one custom element over the <audio> or <video> you already wrote.

Most media player libraries take the player away from you: you hand over a <video> and a config object, and get back someone else's control bar. Changing the arrangement means learning a controls array, or passing an HTML string into a config option.

This one works the other way. You write the controls. Buttons and range inputs in your page, styled by your stylesheet, in the order you put them. The element wires them by name.

It is a rewrite of the media player I built at GitHub in 2022 as a passion project — the one still playing on long forgotten github.com pages, it was written in Catalyst and TypeScript, before the React era. Now it is given a new life as open source, dogfooding hydrargyri and book-of-elementals.

<media-player>
  <audio controls src="/episode.mp3"></audio>

  <toolbar-elemental
    class="media-player-controls"
    aria-label="Playback"
    bind="isReady:if"
  >
    <slider-elemental class="media-player-scrubber">
      <input
        type="range"
        min="0"
        step="any"
        aria-label="Seek"
        bind="duration:attr#max;currentTime:prop#value"
        on="pointerdown:beginScrub;keydown:beginScrub;input:scrub;change:seek;pointerup@document:endScrub;keyup:endScrub"
      />
    </slider-elemental>
    <button on="click:togglePlay" bind="playLabel:attr#aria-label">▶</button>
    <span bind="currentTime|time">00:00</span>
  </toolbar-elemental>
</media-player>

Delete the <script> and the page still plays: the controls attribute you wrote stays on the media element until this one upgrades and takes over.

Keyboard shortcuts work the same way. key="k" on your button is the whole binding, and on="keydown@document:onKeyDown" on the player is what makes the page answer it, so a shortcut can never name an action no visible control names. The one exception is keys="ArrowUp:volumeUp;ArrowDown:volumeDown" on the player, for the pair a volume slider gives no button to. Nothing is bound by default; bind keydown:onKeyDown instead and the keys only answer while focus is inside the player.

One element for both media. It reads which element you wrapped and turns on the video half (poster, click-to-play overlay, click-to-pause on the picture, captions, fullscreen, fading controls) only for a <video>. A custom element that speaks the media API is the third thing it wraps, marked class="media-player-media": <video-background>, which answers for YouTube, Vimeo and a video file from one tag, or one of media-elements for the formats it does not carry. The manual has both recipes, live, with what each costs.

The scrubber and volume are <slider-elemental>, the buffered bar is <progress-elemental>, the control row is <toolbar-elemental>, and the binding is hydrargyri.

Where it comes from

The original needed 24 @target declarations to reach its own controls, and a VideoPlayer extends AudioPlayer class pair that had to talk to itself through a custom event because the base class owned connectedCallback. Declarative binds took 16 of those targets, listeners on the node they belong to took 4, three turned out to be state rather than nodes, and one survived. The inheritance went with them: which media element you wrapped is read off the child, so there is one class and one tag.

Install

npm install media-player-element

The package and the repository are media-player-element; the element they define is <media-player>.

Stylesheets are separate, and bundled: bundle.css is the structure, bundle-theme.css the optional look, each with the elemental sheets folded in. The manual has the CDN block and the à-la-carte sheets.

Everything else

https://stamat.github.io/media-player-element/ — the whole reference, the comparison against Plyr, media-chrome, Vidstack and Video.js, and what this deliberately does not do. This README is the pitch; that page is the manual.

Development

script/bootstrap # npm ci, from a fresh clone
script/server    # build + serve with live reload, http://localhost:4040
script/build     # compile dist/ and index.html
script/test      # jest
script/lint      # eslint + stylelint (the authority; CI runs it)

CONTRIBUTING.md says what belongs here and what a pull request needs.

License

MIT © Stamat