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

@cinesend/atlas-player-react

v0.3.0

Published

React player for the CineSend Atlas Runtime API, with DRM-protected HLS and DASH playback.

Readme

@cinesend/atlas-player-react

The CineSend Atlas player for React. DRM-protected HLS and DASH, playing directly in your app — no iframe.

npm i @cinesend/atlas-player-react

Quick start

import { AtlasPlayer } from '@cinesend/atlas-player-react'

<AtlasPlayer
  apiBase="https://atlas-api.cinesend.com"
  envKey={envKey}
  userSession={session}            // omit for anonymous viewers
  target={{ kind: 'asset', id: 'ast_…' }}
  accentColor="#e50914"
  onEnded={() => …}
/>

That renders Atlas's own player — the same controls the hosted iframe ships, from the same code. Give the container a size; the player fills it.

The UI

chrome picks what AtlasPlayer renders. The default is atlas:

| chrome | What you get | | --- | --- | | atlas (default) | Atlas's controls, and one stylesheet injected into the document | | native | The browser's <video controls> | | none | A bare <video> and a Play button. No chrome, no stylesheet, nothing injected |

chrome="atlas" is:

  • Transport — play/pause, ±10s skip, a seek bar with a remaining-time label that reads Live when the manifest has no duration.
  • Volume — hidden on iOS, where video.volume is ignored and hardware buttons own loudness, because a slider that changes nothing reads as a broken player.
  • Fullscreen — the whole player where a browser allows it, the media element where it does not, WebKit's video-only fullscreen on iPhone, with the icon in sync across all three.
  • A spinner that waits out a 400ms stall, so live HLS at the live edge does not flash it over frames that are painting fine.
  • Chrome that fades after 5s idle and pins itself while paused. The tap that brings it back does not also pause the film.
  • Keyboard — space/k, ←/→, ↑/↓, f, m, bound to the player, not to your page: they work when the player has focus and are invisible to the rest of your app. (The iframe binds to the document, because there it is the page.)
  • "Tap for sound" when playback started muted.
  • Branding — accentColor drives the seek fill and control hover; logoUrl puts a watermark in the corner that fades with the rest of the chrome. In the iframe these come from the play session's style; here they are props, because the React path has no equivalent field.

Opting out

chrome="none" is the old behaviour and is still a first-class choice: if you have a design system, you should not pay for controls you would replace. It renders the <video>, a Play button (it has to — see below) and nothing else, and injects no stylesheet.

<AtlasPlayer chrome="none" … />

It does not shrink your bundle, though: chrome is a runtime prop, so AtlasPlayer carries the chrome, the icons and the stylesheet whichever value you pass. To pay nothing for them, import the hook rather than the component — useAtlasPlayer pulls in none of it, and a bundler with tree-shaking drops the rest.

For full control, drive the hook yourself. PlayerChrome is exported separately if you want Atlas's controls around your own layout:

const { videoRef, controls } = useAtlasPlayer({ … })

<PlayerChrome videoRef={videoRef} controls={controls} accentColor="#00b0ff">
  <video ref={videoRef} className="atlas-video" playsInline />
</PlayerChrome>

The stylesheet

chrome="atlas" injects one <style id="atlas-player-chrome"> the first time it renders, and never again. Every rule is prefixed atlas- and scoped to the player, so nothing of yours is touched.

If your CSP forbids inline styles, or you would rather the rules went through your own build, pass injectStyles={false} and ship them yourself:

import { chromeStyles } from '@cinesend/atlas-player-core'

The old promise still holds where it mattered: chrome="none" ships no stylesheet, and nothing is injected into a document that never renders the chrome.

The one thing to get right: starting playback

Call controls.play() synchronously from your own click handler.

(With chrome="atlas" its own transport button already does this for you — it calls the hook's controls.play(), inside the gesture, not video.play() and not from an effect. The rest of this section is for when you build your own.)

const { videoRef, status, controls, mutedFallback } = useAtlasPlayer({ … })

return (
  <>
    <video ref={videoRef} playsInline />
    <button onClick={controls.play}>Play</button>
  </>
)

Browsers grant transient user activation — it expires, and it is consumed. Starting a play session involves a network call, a dynamic engine import, a DRM certificate fetch and a manifest load, so a play() issued after all of that is no longer authorized by the click that began it.

controls.play() handles this by blessing the media element synchronously, before anything awaits. That only works if you call it inside the gesture handler — not from an effect, not after your own await. Get it wrong and playback still works, but muted, which is why it is easy to miss. mutedFallback goes true when that happens, so you can render an unmute affordance instead of leaving the viewer wondering.

There is no autoplay mode that starts unmuted. An unmuted start is a call you make.

status

A union, so you render each state deliberately rather than inferring it:

| status | Meaning | | --- | --- | | idle / loading | Session being created, media attaching | | ready | Media attached, not yet playing | | playing / paused / ended | Self-explanatory | | unavailable | Nothing to play — usually not encoded yet. Not an error; don't render it as one | | error | Playback failed; see error |

Errors

Failures throw PlaybackError carrying Atlas's machine-readable code, so you can branch instead of matching on prose:

if (err.code === 'concurrent_stream_limit') …
if (err.code === 'media_not_ready') …

Server rendering

AtlasPlayer renders to markup on a server without reaching for the document: the platform probes answer false where there is no browser, and the stylesheet goes in from an insertion effect on the client. Playback itself is a client concern and starts after hydration.

Browser support

DRM playback needs a secure context (HTTPS). Safari uses FairPlay over HLS; everything else uses Widevine over DASH. The package detects which and picks the right manifest.

If you render inside an iframe, the parent must grant allow="encrypted-media", or EME is blocked regardless of anything here.

Bundle size

shaka-player is dynamically imported and lands as a separate chunk, fetched only when a title actually has an encoded stream. hls.js is the same, fetched only for a live channel on a browser with no native HLS — so Safari never downloads it, and neither does a viewer who only opens encoded titles. react is a peer dependency.

Licence

MIT

Sound without a Play button

Unmuted playback needs a user gesture. If your "Play" control is on a different screen from the player, that activation is gone by the time the player mounts — the navigation, the playback call, the engine import and the manifest load all happen in between — so the video starts muted.

You cannot transfer activation, but you can bless an element during the gesture and use it later:

  1. Mount the <video> above your router, so one element outlives the route. PlayerChrome renders the <video> as its child, so it has to live above the router too — use the hook and your own markup for this pattern.
  2. Pass target: null while there is nothing to play. The hook stays idle.
  3. In the click handler that navigates, call controls.play() synchronously.
  4. Set the real target. The load path sees the blessed element and starts unmuted.
// in the click handler that navigates — synchronously, before any await
function onWatch() {
  controls.play()
  navigate(`/watch?kind=asset&id=${id}`)
}

Chrome often allows sound anyway, via sticky activation or a high Media Engagement Index. Safari on iOS does not: blessing the element is the only way to avoid a second tap there.