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

logplex-player-react

v0.2.21

Published

A production React video player — Vidstack-based, HLS/MP4, fully skinned (RTL/LTR), with quality/audio/subtitle menus, pre/mid/post-roll ads, gestures, WebView-safe fullscreen, and optional built-in analytics + resume.

Readme

logplex-player-react

npm downloads bundle size types CI license GitHub stars

A production-ready React video player built on Vidstack + hls.js. HLS/MP4, a fully custom skin (dark, gold accent, RTL/Persian + LTR/English), responsive via container queries, with quality / audio / subtitle menus, pre/mid/post-roll ads, touch gestures, WebView-safe fullscreen, and optional built-in analytics + resume.

🎬 Live demo & docs

Analytics is completely optional — omit the analytics prop and it's a self-contained player with zero network calls.

Install

npm i logplex-player-react

Peer dependencies: react and react-dom (>= 18).

Quick start

import { LogplexPlayer } from 'logplex-player-react';
import 'logplex-player-react/styles.css';

export default function Watch() {
  return (
    <LogplexPlayer
      src="https://cdn.example.com/movie/master.m3u8"
      poster="https://cdn.example.com/poster.jpg"
      title="Sample Movie"
      locale="en" // 'fa' (RTL) | 'en' (LTR)
    />
  );
}

Features

  • HLS + MP4 — adaptive HLS via hls.js (auto quality from the manifest) or progressive MP4. Pass an array of MP4 renditions for a manual quality menu.
  • Custom skin — dark, gold-accented, light mode, RTL/LTR, fully responsive (container queries).
  • VOD providers — vodType exchanges an opaque play token for the real stream URL via a provider API (ABR Hamrahi, Poyan), so existing back-ends keep working.
  • Quality / speed / subtitles / audio menus — HLS-embedded subtitle and multi-language audio tracks are detected automatically; add external WebVTT subtitles too. qualityValidate filters out unwanted renditions; qualityPolicy trades data for picture (start on 1080p, never let Auto drop under 720p).
  • Ads — pre-roll, mid-rolls (at content seconds) and post-roll, with skip countdown and click-through. Ad playback is never counted in content analytics.
  • Playlist & seasons — episode list + prev/next, auto-advance, season headers (Episode.group), and an up-next card near the end of an episode.
  • Gestures — mobile double-tap ±10s, long-press 2×, brightness/volume swipe (correctly remapped in simulated-rotation fullscreen); YouTube-style on desktop.
  • WebView-safe fullscreen — native when available, CSS-simulated rotation fallback (keeps the skin) for locked WebViews.
  • Like / badge / operator notice / IP-restriction overlays (Like can be controlled).
  • Live-friendly skin — disabledControls drops the controls a live channel has no use for (speed, clock, scrub bar, ±10s, like), so viewers aren't offered actions that do the wrong thing.
  • Optional analytics + resume — emits canonical events to your ingest endpoint and offers a "continue watching" banner. Or drive resume from your own back-end with resolveResume, and report a watch heartbeat to a non-Logplex tracker with onWatchInterval.
  • TypeScript, tree-shakeable ESM + CJS, a single styles.css.

Theming & i18n

<LogplexPlayer
  appearance="dark"             // 'dark' | 'light'
  theme={{ accent: '#e8b84b' }} // CSS custom properties
  locale="fa"                   // RTL right-aligns text; layout stays physical
/>

The player inherits the host font, so Persian/Arabic UIs pick up your own font automatically.

Optional analytics

<LogplexPlayer
  src={src}
  analytics={{ baseUrl, apiKey, userId, contentId, contentType: 'movie' }}
  resume // continue-watching banner
/>

Omit analytics entirely to run the player standalone — no requests, nothing backend-specific.

VOD providers & your own back-end

You don't need the Logplex analytics integration to keep an existing back-end working. Resolve token-based sources, report a watch heartbeat, and feed the resume banner from your own API:

<LogplexPlayer
  src={playToken}                    // opaque token for the provider
  vodType="abr_hamrahi"              // 'standard' | 'abr_hamrahi' | 'poyan'
  vodCustomUrl={{ abr_hamrahi: '/vod/abrehamrahi/{token}' }}
  qualityValidate={h => h > 400}     // hide tiny renditions from the Auto menu
  qualityPolicy={{ minHeight: 720, startHeight: 1080 }} // quality over data

  // periodic "user watch" report to your current (non-Logplex) tracker
  onWatchInterval={async ({ playDuration, duration, quality, userWatchId }) =>
    reportWatch({ playDuration, duration, quality, userWatchId }) // return an id to chain
  }
  watchIntervalMs={5000}

  // "continue watching" banner sourced from your back-end (memoize it)
  resolveResume={async () => {
    const w = await getWatch(contentId);
    return w ? { positionSeconds: w.duration } : null;
  }}
/>

onWatchInterval reports the quality as "W*H" (e.g. "1920*1080"), the accumulated playDuration, the current duration (position), and chains the returned watch id into the next call.

Shad VOD (resolved-src)

For a Shad transcoder asset you don't need a vodType provider — Shad resolution must happen server-side (the play token is project-scoped and must never be exposed to the browser). Your own backend calls Shad's POST /v1/project/assets/{id}/play, which mints a fresh, short-lived, IP-bound player token and returns the ready-to-play player_endpoint (an HLS master URL carrying the token) plus a sprites VTT. Hand those straight to the player:

// your backend (server-side): mint a FRESH token per view — never cache/reuse it
// POST https://shad.example.com/v1/project/assets/{assetId}/play
// → { status: 'OK', player_endpoint: '.../index.m3u8?...', sprites: '.../sb/...' }

// your component: feed the resolved URLs to the player
<LogplexPlayer
  src={playback.player_endpoint}     // HLS master (token embedded by Shad)
  thumbnails={playback.sprites}      // seek-bar sprite sheet (VTT)
  appearance="dark"
  theme={{ accent: '#3b82f6' }}
  locale="fa"
/>

Mint a new token on every open and keep it only in transient state — don't persist, reuse, embed it in a shareable URL, or put it in client storage. Encrypted (AES-128) HLS works transparently: the player's hls.js fetches the AES key through the same tokened endpoint. This is exactly how the Shad back-office panel drives playback.

Playlist, seasons & up-next

const episodes = [
  { id: 'e1', src: '.../e1.m3u8', title: 'سریال', subtitle: 'قسمت اول', group: 'فصل اول', poster: '...' },
  { id: 'e2', src: '.../e2.m3u8', title: 'سریال', subtitle: 'قسمت دوم', group: 'فصل اول', poster: '...' },
];

<LogplexPlayer episodes={episodes} currentEpisodeId={current} onEpisodeChange={setCurrent} />

Episode.group renders sticky season headers in the playlist panel. Near the end of an episode that has a next one, an up-next card (cover + filling progress bar) appears; ignoring it auto-advances on end, clicking it jumps straight to the next episode.

Live channels

A live stream has no duration to display, nothing to scrub through, and no meaningful playback speed. Drop those controls rather than leaving them to misbehave:

<LogplexPlayer
  src="https://example.com/channel.m3u8"
  title="Channel one"
  disabledControls={['like', 'speed', 'time', 'progress', 'seek']}
/>

The bottom bar adapts to what's left: with the clock gone the title keeps its position, and a row emptied of everything collapses instead of leaving a strip above the buttons.

Two things worth knowing when you build a live page around it:

  • Rejoining beats resuming. Content that has rolled out of the live window can't be resumed, so after a drop or a long stall, re-mount the player (a changing key) — it re-reads the playlist and rejoins at the live edge.
  • Don't chase the live edge. Seeking to within a second or two of the edge lands inside the segment still being published and starves the buffer. If you correct drift yourself via onPlayerReady, land well behind the edge.

Props reference

| Prop | Type | Description | | --- | --- | --- | | src | string \| VideoSource[] | HLS/MP4 URL, or MP4 renditions for a manual quality menu. For a non-standard vodType, an opaque play token. | | vodType | 'standard' \| 'abr_hamrahi' \| 'poyan' | VOD provider. Non-standard exchanges src (a token) for the real stream via the provider API. Default standard. | | vodCustomUrl | Partial<Record<VodProvider, string>> | Override the provider API endpoint(s); {token} is substituted. | | qualityValidate | (height: number) => boolean | Hide auto (HLS) renditions whose height fails the predicate. Auto stays available. | | qualityPolicy | { minHeight?: number; startHeight?: number } | Rendition policy. minHeight is the floor for both Auto and the quality menu (a source with nothing that tall plays only its tallest rendition); startHeight is where playback starts (tallest rendition at or below it). Applies to hls.js and VideoSource[] lists, not to native HLS on iOS Safari. | | poster | string | Cover image (before play). | | title / episodeLabel | string | Shown above the scrubber. | | thumbnails | string | WebVTT thumbnails track for scrub previews. | | subtitles | SubtitleTrack[] | External subtitle files (HLS subtitles + audio tracks are auto-detected). | | locale / dir | 'fa' \| 'en' / 'rtl' \| 'ltr' | UI language and direction. fa → RTL. | | theme / appearance | ThemeOverrides / 'dark' \| 'light' | CSS-variable overrides and color scheme. | | episodes | Episode[] | Playlist; enables the panel + prev/next. Episode.group adds season headers; an up-next card appears near the end. | | currentEpisodeId / onEpisodeChange | string / (id) => void | Controlled episode selection. | | analytics | LogplexAnalyticsConfig | Enables built-in Logplex analytics + resume. | | resume | boolean | Show the continue-watching banner. Default true. | | resolveResume | () => Promise<ResumePoint \| null> | Feed the resume banner from your own back-end (no analytics needed). Memoize it. | | onWatchInterval | (info) => Promise<string \| void> | Periodic watch heartbeat to a non-Logplex tracker; return an id to chain. | | watchIntervalMs | number | Watch-interval cadence in ms. Default 5000. | | onPlayerReady | (player \| null) => void | Exposes the underlying Vidstack instance for imperative control. | | loading | boolean | Force the loading overlay (also shown while a provider source resolves). | | ad / ads | AdConfig / AdBreak[] | Pre-roll shorthand, or breaks at 'pre' \| 'post' \| seconds. | | notice / restriction / badge | PlayerNotice / PlayerRestriction / string | Operator notice, blocking network overlay, transient info pill. | | disabledControls | PlayerControl[] | Controls to leave out: 'like', 'speed', 'time', 'progress', 'seek'. See Live channels. | | onLike / liked | (liked) => void / boolean | Like button; liked makes it controlled. | | fullscreenMode | 'auto' \| 'native' \| 'simulated' | Fullscreen strategy. simulated forces a CSS rotation for locked WebViews. | | fullscreenOnPlay | boolean | Enter fullscreen when playback starts from the cover. | | persistSettings / settingsKey | boolean / string | Remember volume/mute/speed/brightness in localStorage. | | onBack | () => void | Show a back button in the top bar. | | autoPlay / muted | boolean | Start playing / start muted. Browsers generally only allow autoplay when muted. | | type | string | MIME type of src when it can't be inferred from the extension. | | className | string | Extra class on the player element. | | children | ReactNode | Rendered inside the controls layer, so it fades with them. |

See the documentation site for the live playground and the full events reference.

Browser support

Modern evergreen browsers (Chrome, Edge, Firefox, Safari) and WebViews. HLS plays via hls.js where MSE is available, and via native HLS on Safari/iOS. hls.js and dash.js ship with the package and are served from your own bundle — nothing is fetched from a CDN at runtime (dash.js is a lazy chunk, loaded only for .mpd sources).

Mobile & iOS notes

The player is built mobile-first (inline playback, touch gestures, responsive skin):

  • Inline playback — playsInline is set, so video plays in place on iPhone instead of being forced into the system player.
  • HLS everywhere — Android Chrome/WebView uses hls.js (MSE); iOS Safari/WKWebView has no MSE, so it falls back to native HLS automatically. MP4 works on both.
  • Fullscreen — fullscreenMode="auto" (default) uses native OS fullscreen when available (including iOS's native <video> fullscreen, which rotates correctly), and on Android forces landscape for a landscape video when the device hasn't auto-rotated. Where no native fullscreen exists — typically a locked Android WebView — it falls back to a CSS simulated rotation that keeps the custom skin and promotes to the browser top layer; its size is taken from window.inner* in pixels (more reliable than viewport units in some WebViews), and touch gestures are remapped into the rotated frame. Force the path with fullscreenMode="simulated" or "native".
  • Quality menu — fully controllable where hls.js runs (Android/desktop). On iOS native HLS, bitrate is auto-selected by the OS; for guaranteed manual quality on iOS, pass an array of MP4 renditions to src. Embedded audio/subtitle tracks are exposed via the native track API.
  • Autoplay — like all browsers, mobile requires muted or a user gesture; the tap-to-play cover handles this.
  • Touch hardening — long-press callout / text selection / double-tap zoom are disabled on the player surface so gestures don't conflict; the simulated-fullscreen overlay is sized in pixels from the live viewport so it fills the screen edge-to-edge even where dynamic viewport units misbehave.

Development

npm install
npm run dev          # docs/demo site with HMR
npm test             # unit tests (vitest)
npm run lint         # eslint
npm run build        # build the library to dist/
npm run docs:build   # build the docs site to site/

See CONTRIBUTING.md.

License

MIT © Morteza Safarishahi

Built on Vidstack and hls.js. The demo uses the Sprite Fight sample and the Vazirmatn font (SIL OFL).