New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@profullstack/player

Package Overview
Dependencies
Maintainers
2
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@profullstack/player

One web player for every source a Profullstack site serves: MP4, HLS, MPEG-2 transport streams and audio, with one control bar, on desktop, mobile, PWA and television.

Source
npmnpm
Version
0.3.1
Version published
Weekly downloads
97
-80.44%
Maintainers
2
Weekly downloads
 
Created
Source

@profullstack/player

One web player for every source we serve — MP4, HLS, MPEG-2 transport streams and audio — with one control bar, on desktop, mobile, PWA and television.

It exists because we had two players and neither could play what the other did. PairUX handed every URL straight to the browser, so it played MP4 and nothing else. tipoffwatch and genrewatch always built an mpegts.js demuxer, so they played IPTV channels and nothing else — an HLS playlist was answered with 415, 'that channel is an HLS playlist'. Both were right about their own content and wrong about everything else.

Install

pnpm add @profullstack/player

Use

import { createPlayer } from '@profullstack/player';
import '@profullstack/player/player.css';

const player = createPlayer(document.getElementById('stage'), {
  src: 'https://example.com/talk.mp4',
  mediaId: 'talk-42', // what a resume position is filed under; omit to remember nothing
});

// later
player.destroy();

React:

import { Player } from '@profullstack/player/react';
import '@profullstack/player/player.css';

<Player src={url} mediaId={`session:${code}`} className="aspect-video rounded-2xl" />;

The source decides the rest.

What plays, and how

SourceEngineNotes
.mp4, .webm, .movnativeThe browser decodes it; no library is loaded.
.mp3, .m4a, .aac, .wav, .flacnativeCompact audio bar, no stage.
.m3u8hls.js, or native on iOSQuality ladder, live-playlist detection.
.ts, .m2tsmpegts.jsStall-restart, TV buffering profiles, codec diagnosis.
anything elsenativeAn unmarked URL is assumed progressive, which is what one usually is.

Detection reads a declared mimeType first, then the URL path — never the query string, because a signed URL routinely ends ?response-content-disposition=...mp4. Pass kind to skip it entirely.

Engines are loaded on demand. A page that plays an MP4 never downloads hls.js or mpegts.js; between them they are over half a megabyte. Verified: an MP4 page fetches only the native chunk.

The HLS ordering, which is not the obvious one

Media Source first, native second — the opposite of what you would write.

canPlayType('application/vnd.apple.mpegurl') lies. Chrome answers "maybe" on builds that cannot play a playlist at all; it is a claim about a MIME type, not about a decoder. Trusting it sends every Chrome user down a path that silently plays nothing. This was caught here exactly that way — a headless Chrome claimed native HLS, was handed the stream, and the quality ladder came back empty because no hls.js had ever loaded.

So hls.js runs wherever Media Source exists (Chrome, Firefox, Edge, Android, desktop Safari). Native is the fallback for iOS, which has no MediaSource at all and is the one browser that genuinely does HLS properly.

Three shapes, decided by the source

  • VOD — scrub with buffered range, ±10s, speed, resume, chapters, ?t= timestamps, quality.
  • Live — none of those, because each is a lie about a stream with no end: nothing to scrub towards, no position worth remembering, no speed but 1. A LIVE badge instead. HLS flips into this from the playlist, after the bar has already been drawn.
  • Audio — a compact bar in normal flow: no stage, no fullscreen, no picture-in-picture. Still a full transport.

Everything else it does

  • Keyboard: space/k, ← →, j/l, ↑ ↓, m, f, p, 0–9, Home/End, < >.
  • Televisions: Fire TV, Android TV, Tizen, webOS, Roku and the rest are detected from one list, kept identical to the one genrewatch and tipoffwatch already use so a device is a TV in all of them or none. Controls grow, the auto-hide slows, the seek step doubles, and what a D-pad cannot use is dropped.
  • Remembers volume, mute and speed across sources, and a position per mediaId (60 of them, least-recently-touched evicted). Every storage access is guarded — some browsers throw on merely touching localStorage.
  • Explains failures. A blocked media load is a console-only event; the element's error code is the only in-page evidence. A CSP-refused load, a dropped connection and an undecodable codec each get their own sentence.

Already have a player?

Three of our apps do — p0dcasters and rssamplifier each run a queue-aware dock, and media-streamer has a modal per source. Replacing those with this bar would delete working features to gain a nicer-looking one. What they still need is the delivery half: which engine plays this source.

import { attachSource } from '@profullstack/player';

const attached = await attachSource(audioEl, { src: episode.enclosureUrl });
// attached.engine -> 'native' | 'hls' | 'mpegts'
// attached.unplayable -> a sentence, when nothing here can play it
attached.destroy();

No DOM is created, nothing is styled, and your UI is untouched. createPlayer uses exactly this internally, so there is one engine ladder rather than two that drift.

Options

OptionMeaning
srcRequired.
kind, mimeTypeSkip or steer source detection.
mediaIdResume key. Omit and nothing is stored.
liveForce live. Usually unnecessary.
chapters{ start, title }[]; marks on the bar and the current one named.
startAtSeconds to begin at. Beats a saved position.
shareUrl(seconds) => string. Omit to drop the copy-link button.
poster, autoplayAs you would expect.
mediaDrive an existing element instead of building one.
unplayableAdviceAppended to a codec failure, e.g. "VLC can — the button is beside Play."
withCredentialsSend cookies with stream requests; IPTV proxies authenticate that way.
capabilities, enginesOverride detection or inject an engine. Mostly for tests.

Every control carries a data-control name (play, back, rate, quality, …) — style or hide them without depending on the order buttons sit in.

Development

pnpm install
pnpm test        # 71 unit tests, jsdom
pnpm typecheck
pnpm build

The unit tests inject a fake engine, which is the design under test: the bar is meant to work identically whatever put the bytes there. Real formats are verified in a headless browser against real streams — see harness/.

Licence

MIT

Keywords

video

FAQs

Package last updated on 29 Aug 2026

Related posts