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

@scrippsproduct/shadowstream

v0.0.36

Published

<p align="center"> <img src="public/images/shadowstream-player-screenshot.png" alt="ShadowStream player screenshot" width="800" /> </p>

Downloads

426

Readme

ShadowStream Player

ShadowStream is a React + TypeScript video player library and embed surface for HLS playback, ad delivery, playlist management, and external site control.

What this repository contains

  • A standalone Vite app used for local development, testing, and deployment builds.
  • A distributable library package published as @scrippsproduct/shadowstream.
  • A global browser API exposed as window.shadowstream for playlist control, player control, and event subscriptions.

Current versions and runtime expectations

These versions were pulled from the repository at the time of this README update.

| Item | Current version | | --- | --- | | App package (package.json) | 0.0.10 | | Library package (package.lib.json) | 0.0.33 | | Node runtime used in current setup | v20.17.0 | | React / React DOM | 19.1.2 | | Vite | 6.2.1 | | TypeScript | 5.5.3 | | Storybook | 9.1.5 | | Vitest | 3.1.3 | | ESLint | 9.9.0 | | Playwright | 1.56.0 |

NVM / Node Version

This repo currently uses Node v20.17.0

Recommended setup:

nvm install 20.17.0
nvm use 20.17.0
npm install

Any current NVM release that can install Node 20.17.0 is sufficient.

Getting started

npm install

Primary development commands

| Command | Purpose | | --- | --- | | npm run storybook | Main local development environment at http://localhost:6006 | | npm run dev | Vite dev server | | npm run build | Production build | | npm run watch:full | TypeScript watch + Vite watch | | npm run lint | Lint the repository | | npm run lint:fix | Auto-fix lintable issues | | npm run lint:report | Write eslint-report.json | | npm run test | Run Vitest once | | npm run test:watch | Run Vitest in watch mode | | npm run test:coverage | Run Vitest with coverage | | npm run build-storybook | Static Storybook build |

Production build and release notes

App build output

The production and stage builds output compiled assets into dist/.

Typical generated assets:

  • dist/shadowstream-v<version>.umd.js

Versioning

The repo currently uses package version bumps during CI with:

npm version patch --no-git-tag-version

Library packaging

To build the library package for distribution:

npm run build to test locally but you can just push to stage to test qa -or- push to main for production

GitLab workflow and deployment map

This repository currently contains GitLab CI in .gitlab-ci.yml.

Branch behavior

Push to stage
  -> build_npm_stage
  -> npm install
  -> npm version patch --no-git-tag-version
  -> npm run build-stage
  -> upload dist/ to s3://$AWS_S3_BUCKET/shadowstream/
  -> deploy happens automatically

Push to main
  -> build_npm
  -> npm install
  -> npm version patch --no-git-tag-version
  -> npm run build
  -> deploy_prod job targets the same S3 path
  -> deploy is manual

GitLab environments

| Branch | Build job | Deploy job | Environment | Deploy behavior | | --- | --- | --- | --- | --- | | stage | build_npm_stage | deploy_stage | stage | Automatic | | main | build_npm | deploy_prod | prod | Manual |

Deploy target

Both deploy jobs publish dist/ to:

s3://$AWS_S3_BUCKET/shadowstream/

stage -> dev-cdn.scrippscloud.com bucket

-example-> https://dev-cdn.scrippscloud.com/shadowstream/shadowstream-v0.0.11.umd.js

main -> cdn.scrippscloud.com bucket

-example-> https://cdn.scrippscloud.com/shadowstream/shadowstream-v0.0.11.umd.js

Architecture overview

Main entry points

| File | Responsibility | | --- | --- | | src/App.tsx | Normalizes embed/incoming props, resolves privacy values, builds the initial playlist, registers early playlist controls, and swaps preview into the real player | | src/components/preview/Preview.tsx | Main Poster image with big play button to "start" the app | src/components/player/HLSPlayer.tsx | Core player orchestration: playback, HLS lifecycle, ads, playlist progression, external events, and player registration | | src/hooks/usePlaylistManager.ts | Playlist state, next/previous navigation, viewed tracking, preload/load-more behavior, replay handling | | src/hooks/useAdManager.ts | Ad request coordination for each video transition | | src/hooks/useUserSync.ts | Resolves SpringServe user sync data used for ad targeting | | src/lib/externalApi.ts | Builds and maintains the window.shadowstream API | | src/contexts/UserActivityContext.tsx | User activity/inactivity tracking for controls and interaction state |

Component and state flow maps

1. Embed and initialization flow

Incoming embed props
  -> src/App.tsx normalizes values
  -> privacy strings are merged into ads config
  -> initial video is converted into a PlaylistItem
  -> incoming playlist is normalized and combined with the initial video
  -> Preview renders first unless autoplay/live-autoplay activates the player
  -> HLSPlayer mounts with normalized PlayerProps

2. External API registration flow

App.tsx
  -> creates an instanceId from verizonId or a generated shadowstream_* fallback
  -> registers early playlist controls immediately
  -> allows outside code to queue playlist actions before HLSPlayer is mounted

HLSPlayer.tsx
  -> useExternalPlaylistControls registers the live playlist manager controls
  -> registerPlayer(instanceId, playerControls) exposes the full player instance
  -> shadowStreamAPI updates window.shadowstream

3. Playlist and continuous-play flow

App.tsx
  -> buildCombinedPlaylist(initial item, incoming playlist)

HLSPlayer.tsx
  -> usePlaylistManager({ initialPlaylist, callLetters, continuousPlay, onEnd, ... })
  -> currentItem/currentIndex become the active source of truth
  -> next/previous/jump update currentIndex
  -> preload checks run near the end of the available queue
  -> additional videos can be fetched from station data when callLetters is available
  -> handleVideoEnd decides whether to advance, replay, or fire the end callback

4. Ad flow

HLSPlayer currentItem changes
  -> useAdManager evaluates adsEnabled + player readiness
  -> ad options are built from player opts + user sync data
  -> bids.getBids(...) resolves bid payloads
  -> showAds is enabled
  -> AdPlayer / IMA playback runs
  -> adStart and adEnd events are emitted through window.shadowstream
  -> content playback resumes

5. User sync and activity flow

useUserSync
  -> src/lib/ssUserSync.ts fetches https://sync.springserve.com/usersync/json
  -> returned ID is attached to ad request config

UserActivityContext
  -> tracks activity/inactivity across pointer, keyboard, and touch events
  -> drives control visibility and idle behavior inside the player UI

Public parameter map

There are two practical ways to think about the public API:

  • Embed/incoming props handled by src/App.tsx
  • Library player props consumed by src/components/player/HLSPlayer.tsx

Incoming props handled by src/App.tsx

These are defined primarily in src/App.types.ts and normalized in src/App.tsx.

| Param | Accepted forms | Internal behavior | | --- | --- | --- | | m3u8 / streamUrl | string | Normalized into options.streamUrl and used as the primary HLS source | | mp4 | string | Normalized into options.mp4Url as fallback playback/source metadata | | video-title | string | Normalized into videoTitle, displayed in player state and carried into playlist items/events | | verizonId | string | Used as the stable instance ID for window.shadowstream[instanceId]; if omitted a generated shadowstream_* ID is used | | thumbnailUrl | string | Used by Preview before player activation and stored on playlist items | | vertical | boolean | Passed through to the player and playlist items for vertical-video layout behavior | | autoplay | boolean | Causes App to skip preview and activate HLSPlayer automatically | | disable-continuous-play | 'true' | Inverted into continuousPlay; when disabled the playlist manager will not auto-advance continuously | | disable-ads / disableAds | 'true' or boolean | Inverted into adsEnabled; controls whether useAdManager participates in the playback flow | | is-live / isLive / live | string or boolean | Coerced into isLive; affects load timing, autoplay logic, and live handling in player state | | live-autoplay / liveAutoplay | string or boolean | Allows live streams to activate immediately when isLive is true | | muteOnLoad | boolean | Passed to player startup state to begin muted | | width / height | number | Passed into player sizing and layout | | duration | number or numeric string | Normalized to a non-negative number and stored on the current item/player metadata | | callLetters | string | Used by playlist-loading logic to fetch additional videos for continuous play | | playlist | PlaylistItem[] | Normalized item-by-item, merged with the initial video, then handed to usePlaylistManager | | cbs.onVideoEnd | () => void | Wrapped by App as the end callback and fired when playback completes | | fname | string | Copied into options.ads.fname for ad configuration | | enableConvivaDebug | boolean | Passed through to HLSPlayer, but only enabled when environment debug is on | | enableConvivaTouchstone | boolean | Passed through to HLSPlayer, but only enabled when environment debug is on | | convivaCustomerKey | string | Passed to analytics/Conviva integration | | section | string | Forwarded into PlayerProps; useful for downstream analytics/context | | channel | string | Forwarded into PlayerProps; useful for downstream analytics/context |

Ad config fields

props.ads is merged with privacy strings resolved at runtime. These fields end up in options.ads and are used during bid creation and ad playback.

| Field | Internal behavior | | --- | --- | | iu | Ad unit identifier used in ad configuration | | sz | Creative size string passed into ad config | | fname | Ad metadata field; also backfilled from top-level fname when supplied | | pxconfig | Passed through for ad/provider configuration | | categories | Passed through for targeting/categories | | refdomain | Passed through for referrer/targeting context | | tfcd | Passed through for targeting/compliance | | url | Passed through into ad request configuration | | description_url | Passed through into ad request configuration | | us_privacy | Auto-populated from privacy helpers unless already provided | | gpp | Auto-populated from privacy helpers unless already provided | | gpp_sid | Auto-populated from privacy helpers unless already provided | | inv | Optional targeting field | | adUnit | Optional ad unit metadata | | preroll | Optional pre-roll behavior/config flag | | cust_params | Custom targeting object forwarded to ad request config |

Library-facing player props

The library exports HLSPlayer from src/index.ts, with the public declaration living in src/index.d.ts.

| Prop | What it does internally | | --- | --- | | verizonId | Defines the external API instance key when provided | | streamUrl | Primary content source for the current item/player load | | mp4Url | Fallback content URL metadata | | thumbnailUrl | Poster/preview metadata | | vttUrl | Caption/subtitle metadata attached to the current item | | vertical | Layout mode for portrait video | | autoplay | Starts playback automatically when the player is initialized | | continuousPlay | Enables continuous playlist progression in usePlaylistManager | | adsEnabled | Turns the ad manager flow on or off | | videoTitle | Used in UI metadata, playlist items, and emitted event payloads | | muteOnLoad | Initial mute state | | liveAutoplay | Live-stream startup behavior | | start | Gate for initial player loading in HLSPlayer | | end | Final end-of-playback callback | | width / height | Player layout dimensions | | duration | Metadata for the current item and external state | | callLetters | Enables dynamic playlist loading for station-driven continuous play | | ads | Ad request and targeting payload | | isLive | Live-stream handling and initial load timing | | playlist | Seed data for usePlaylistManager | | onPlaylistUpdate | Callback slot for playlist changes | | onItemChange | Callback slot for item transitions |

External API map

The player exposes a global API through window.shadowstream.

Instance lookup

const player = window.shadowstream['your-instance-id'];

Global helpers

window.shadowstream.getAllPlayers();
window.shadowstream.getPlayer('your-instance-id');
window.shadowstream.pauseAll();
window.shadowstream.playAll();

Playlist control methods

await player.playlist.jumpToVideoById('video-123');
await player.playlist.jumpToVideoByIndex(2);
await player.playlist.nextVideo();
await player.playlist.previousVideo();
await player.playlist.getCurrentPlaylist();
await player.playlist.getCurrentVideo();
await player.playlist.getCurrentIndex();

Player control methods

player.play();
player.pause();
player.togglePlayPause();
player.seek(30);
player.setVolume(0.8);
player.toggleMute();
player.toggleFullscreen();
player.nextVideo();
player.skipVideo();

Events emitted through the API

The current event types defined in src/types/externalApi.ts are:

  • play
  • pause
  • adStart
  • adEnd
  • videoChanged
  • playlistUpdated
  • videoEnded
  • videoStarted

For a longer API walkthrough, see EXTERNAL_API.md.

Key implementation notes before production

  • App.tsx currently acts as the compatibility/normalization layer for embed-style props.
  • HLSPlayer.tsx is the real orchestration center and is the best place to trace runtime behavior.
  • Privacy strings are merged at runtime before ads initialize.
  • User sync currently comes from SpringServe via https://sync.springserve.com/usersync/json.
  • There is no checked-in .nvmrc, so Node version discipline currently depends on documentation and local environment setup.
  • The repository currently uses GitLab CI/CD, not GitHub Actions, for build/deploy automation.

Suggested pre-production checklist

  1. Use Node 20.17.0 via NVM.
  2. Run npm install.
  3. Run npm run lint.
  4. Run npm run test.
  5. Run npm run build for a production validation build.
  6. Confirm the intended versions in package.json and package.lib.json.
  7. Confirm whether the release should go to stage first or straight to main for the manual production deploy job.