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

@elementmints/veedeeoh

v1.0.0

Published

Production-grade, framework-agnostic, accessible HTML5 video player — vanilla JS (ES modules) + SCSS + SVG sprite. Custom controls, lazy load, HLS, captions, i18n/RTL, gestures, plugins.

Readme

@elementmints/veedeeoh

A production-grade, framework-agnostic, modular HTML5 video player written in vanilla JS (ES modules) + SCSS + an SVG sprite. No Shadow DOM; fully custom controls; CSP-safe. Works with any bundler/framework (and is consumed by the MG Motors AEM site as a local source package).

Install

npm install @elementmints/veedeeoh

Peer/runtime deps hls.js, focus-trap and @floating-ui/dom are installed automatically.

Quick start

import VideoPlayer, { setSpritePath } from '@elementmints/veedeeoh';
import '@elementmints/veedeeoh/styles.css';

// Point at the bundled icon sprite (serve it from your app, or set a CDN URL).
setSpritePath('/assets/veedeeoh-sprite.svg'); // ships at @elementmints/veedeeoh/sprite.svg

const player = new VideoPlayer({
    root: document.querySelector('.my-video'),
    sources: { desktop: [{ src: '/clip-720.mp4', type: 'video/mp4', quality: 720 }] },
    poster: { desktop: '/poster.jpg', alt: 'Clip' }
});

The root element should contain (or the player will create) a .cmp-video-player__stage > video.cmp-video-player__video; a <picture class="cmp-video-player__poster"> is optional.

Architecture

@elementmints/veedeeoh/src/
  index.js              Public barrel (default export: VideoPlayer)
  types.d.ts            Public TypeScript declarations
  constants.js          Events, DI tokens, default keybindings, class/attr names
  core/                 VideoPlayer, Config, EventBus, StateStore, Ticker,
                        ServiceContainer (DI), PluginManager, PlayerElement,
                        mediaEvents, fullscreen
  providers/            Provider contract + Html5/NativeHls/Hls + ProviderFactory
                        + capabilities (UA/feature detection)
  controls/             ControlBar, ControlRegistry, BaseControl, primitives
                        (Button/Slider/Menu) and one module per control
  tooltip/              Keyboard-accessible TooltipManager (@floating-ui)
  accessibility/        A11yManager, LiveRegion, focusTrap (focus-trap)
  keyboard/             KeyboardManager + configurable keymap
  gestures/             GestureManager + recognizers (double-tap/swipe/pinch/hold)
  visibility/           VisibilityManager (pause out of view)
  lazyload/             LazyLoader (IntersectionObserver, configurable)
  single-active/        ActiveVideoRegistry (page-wide single active video)
  slider/               SliderManager + Swiper/Slick/Splide/Embla/Modal/Custom
  i18n/                 I18nManager + localeLoader + locales (en/hi/ar)
  icons/                IconRegistry + sprite resolver + default icon map
  analytics/            AnalyticsManager + dataLayer/console adapters
  plugins/              Plugin contract + example plugins
  security/             dom.js (the only DOM builder) + security.js (sanitize/URL)
  styles/               SCSS: tokens, player, controls, slider, menu, tooltip,
                        themes, a11y, rtl

Principles: SOLID, composition over inheritance, an event bus for all intra-player messaging, a plugin architecture with lifecycle hooks, and a lightweight DI container so managers are swappable and unit-testable.

Usage

Full configuration example

import VideoPlayer from '@elementmints/veedeeoh';

const player = new VideoPlayer({
    root: document.querySelector('.video'),
    lazyLoad: true,
    lazyThreshold: 0.25,
    pauseOutOfView: true,
    resumeInView: false,
    singleActiveVideo: true,
    language: 'en',
    poster: { desktop: '/poster.jpg', mobile: '/poster-m.jpg', alt: 'MG Comet' },
    sources: {
        desktop: [
            { src: '/comet-1080.mp4', type: 'video/mp4', quality: 1080 },
            { src: '/comet-720.mp4', type: 'video/mp4', quality: 720 }
        ],
        mobile: [{ src: '/comet-m.mp4', type: 'video/mp4' }]
    },
    controls: {
        position: 'bottom',
        order: ['play', 'seek', 'current-time', 'duration', 'mute', 'volume',
                'spacer', 'settings', 'fullscreen']
    },
    gestures: true,
    keyboard: true
});

player.on('play', () => {});

VideoPlayer.init(root) initialises every [data-cmp="media"] under root, reading per-element config from a data-vp-config JSON attribute.

Events

Emitted on the internal bus and mirrored as vp:<name> DOM CustomEvents: ready, mounted, play, playing, pause, ended, timeupdate, progress, waiting, canplay, seeking, seeked, ratechange, volumechange, qualitychange, levelsupdated, fullscreenchange, pipchange, captionschange, visibilitychange, languagechange, gesture, slidechange, next, previous, error, destroyed.

Plugins

import { createPlugin } from '@elementmints/veedeeoh';

player.use(createPlugin({
    name: 'my-plugin',
    beforeInit(ctx) { ctx.registerControl('badge', (c) => myControl(c)); },
    onReady(ctx) {}
}));

Hooks: beforeInit / afterInit / onReady / onDestroy. Registration surface: registerControl, registerProvider, registerSliderAdapter, registerAnalyticsAdapter.

Slider integration

Auto-detects Swiper, Slick, Splide, Embla, Bootstrap modal and a configurable custom slider. Only the active, fully-visible slide may play; the rest pause. Set slider.adapter to force one, or 'custom' with slider.event/slider.activeClass.

Strategies

  • Accessibility (WCAG 2.2 AA): semantic roles, ARIA labels, full keyboard operability, visible focus rings, fullscreen focus trap, a polite live region, reduced-motion and forced-colors support, RTL.
  • Security (VAPT): no internal innerHTML (all DOM via security/dom.js), source-URL protocol allow-list, author HTML sanitised via DOMPurify when present else a DOMParser allow-list, CSS-variable theming (CSP-safe).
  • Performance: one shared rAF Ticker, passive listeners, IntersectionObserver lazy load + visibility, requestIdleCallback for deferrable work, GPU-friendly compositing, full teardown (destroy()) that removes every listener/observer and releases media buffers; hls.js is a lazy chunk fetched only for HLS sources.

Browser support

| Browser | Notes | |---|---| | Chrome / Edge (last 2) | Full support | | Firefox (last 2) | Full support | | Safari 14+ macOS | Native HLS; element fullscreen for video | | iOS Safari 14+ | playsinline, muted autoplay, native HLS, element fullscreen, no PiP button | | Android Chrome | Full support |

Build, test & publish

npm install
npm test            # Jest (jsdom) — co-located src/**/*.test.js
npm run build       # dist/veedeeoh.js (ESM) + .umd.cjs + veedeeoh.css
npm publish --access public   # requires `npm login` + @elementmints org access

exports: . → built ESM/UMD (+ types), ./styles.css → compiled CSS, ./scss → SCSS source, ./sprite.svg → the icon sprite.

Demo

A runnable demo covering every scenario lives in demo/ (npx vite from there).

Using it inside the MG AEM monorepo

The AEM app (ui.frontend) consumes this package's source via a webpack + Jest alias (@elementmints/veedeeohpackages/veedeeoh/src), so no build step is needed for the site build. To switch to the published registry version, drop the alias and add "@elementmints/veedeeoh": "^1.0.0" to ui.frontend's deps.