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

hls-streamer

v4.6.2

Published

Lightweight HLS streaming from media files (MP3, AAC, M4A, OGG, FLAC, WAV, MP4, MOV, M4V) on demand, without temporary files or ffmpeg dependency

Readme

HLS Streamer

HLS Streamer

npm version License: MIT TypeScript

HLS Streamer converts audio and video files (MP3, AAC, M4A, OGG Vorbis, FLAC, WAV, MP4, MOV, M4V) into HTTP Live Streaming (HLS) playlists on the fly. It analyses the source file in-memory, builds frame-aligned byte ranges, and streams them without temporary files, native bindings, or external binaries like ffmpeg.


Why HLS Streamer?

  • Multi-format support – handles MP3, AAC, M4A, OGG Vorbis, FLAC, WAV, MP4, MOV, and M4V with automatic format detection.
  • Audio + Video – audio files produce standard HLS v6 playlists; video files produce HLS v7 fMP4 playlists with an EXT-X-MAP init segment and keyframe-aligned boundaries.
  • Zero dependencies – no shared libraries, no ffmpeg, no native compilation. Pure TypeScript parsers for all formats. Drop it into Docker, serverless, or edge runtimes.
  • Remote storage – stream directly from S3, MinIO, or any compatible object storage via the IStorageProvider interface — no local file required.
  • Accurate segments – real frame/packet parsing provides true durations, #EXTINF metadata, and target durations that match playback.
  • Frame-aligned byte ranges – every segment begins and ends on verified frame boundaries; video segments snap to keyframes to prevent decoding artifacts.
  • No temp files – streams straight from the source file using byte-range reads.
  • Fast-start aware – optional smaller first segments improve startup latency for constrained networks.
  • TypeScript first – authored in TypeScript with full type definitions for your tooling and IDEs.

How It Works

  1. Storage abstraction – the source is either a local file (via filePath) or any object storage (via storageProvider). Both expose the same byte-range interface internally.
  2. Format detection – automatically detects format from file content (magic bytes / ftyp brand) or extension, with optional manual override.
  3. Metadata analysis – format-specific parsers extract frame/packet tables with offsets, durations, and (for video) keyframe markers.
  4. Segment planning – boundaries are calculated from the frame table so each segment contains whole frames while respecting your target size. Video segments snap to I-frame boundaries.
  5. Playlist generationcreateM3U8() emits an #EXTM3U playlist. Audio files use HLS v6; video files use HLS v7 with EXT-X-MAP pointing to the moov init segment.
  6. On-demand byte rangesgetFileBuffer(start, end) reads only the requested bytes — from disk or object storage — without buffering the full file.
┌──────────────┐     ┌─────────────────┐     ┌─────────────────┐     ┌──────────────────────┐
│ Media Source │ ──▶ │ Format Detector │ ──▶ │ Format Parser   │ ──▶ │ Segment Planner      │
│ (any format) │     │ (magic bytes)   │     │ (MP3/MP4/etc.)  │     │ (frame/keyframe)     │
└──────────────┘     └─────────────────┘     └─────────────────┘     └──────────┬───────────┘
                                                                                  │
                                                                                  ▼
                                                                     ┌──────────────────────┐
                                                                     │ HLS Playlist & Bytes │
                                                                     └──────────────────────┘

Quick Start

import { HlsStreamer } from 'hls-streamer';

// Audio file — auto-detected
const audioStreamer = new HlsStreamer({
  filePath: '/media/library/song.mp3', // or .aac, .m4a, .ogg, .flac, .wav
  segmentSizeKB: 512,
  fileName: 'track',
  baseUrl: 'audio/stream/session-42',
  enableFastStart: true,
});

const audioPlaylist = await audioStreamer.createM3U8();

// Video file — same API
const videoStreamer = new HlsStreamer({
  filePath: '/media/library/movie.mp4', // or .mov, .m4v
  segmentSizeKB: 2048,
  fileName: 'segment',
  baseUrl: 'video/stream/session-42',
});

const videoPlaylist = await videoStreamer.createM3U8();

// Detect media type at runtime
const type = await videoStreamer.getMediaType(); // 'video' | 'audio'

Serving Over HTTP

The example below shows an Express setup that handles both audio and video:

import express from 'express';
import { HlsStreamer } from 'hls-streamer';

const app = express();

app.get('/streams/:id/playlist.m3u8', async (req, res, next) => {
  try {
    const streamer = new HlsStreamer({
      filePath: resolveMediaPath(req.params.id),
      baseUrl: `streams/${req.params.id}`,
      enableFastStart: true,
    });

    res.type('application/vnd.apple.mpegurl');
    res.send(await streamer.createM3U8());
  } catch (error) {
    next(error);
  }
});

app.get('/streams/:id/:start/:end/:filename', async (req, res, next) => {
  try {
    const streamer = new HlsStreamer({
      filePath: resolveMediaPath(req.params.id),
      baseUrl: `streams/${req.params.id}`,
    });

    const start = Number(req.params.start);
    const end = Number(req.params.end);
    const mediaType = await streamer.getMediaType();

    res.type(mediaType === 'video' ? 'video/mp4' : 'audio/mpeg');
    res.set('Accept-Ranges', 'bytes');
    res.send(await streamer.getFileBuffer(start, end));
  } catch (error) {
    next(error);
  }
});

Segment URL Contract

Generated playlists follow this pattern:

/{baseUrl}/{startByte}/{endByte}/{fileName}{index}.{ext}

For video files, an additional init segment URL is emitted as EXT-X-MAP:

/{baseUrl}/{moovOffset}/{moovEnd}/{fileName}init.mp4
  • startByte is inclusive, endByte is exclusive.
  • index is zero-padded to three digits (000, 001, ...).
  • Serve the exact byte range from the original file — no transcoding needed.

Remote Storage (S3 / MinIO)

HLS Streamer can stream directly from object storage — no local file needed. Pass a storageProvider instead of filePath. The built-in S3Provider works with AWS S3, MinIO, Wasabi, Backblaze B2, and any S3-compatible service.

Installation

The AWS SDK is a peer dependency and only required when using S3Provider. Install it separately:

npm install @aws-sdk/client-s3

AWS S3

import { HlsStreamer, S3Provider } from 'hls-streamer';

const provider = new S3Provider({
  bucket: 'my-media-bucket',
  key: 'podcasts/episode-42.mp3',
  clientConfig: {
    region: 'us-east-1',
    // credentials resolved automatically from env / IAM role
  },
});

const streamer = new HlsStreamer({
  storageProvider: provider,
  fileName: 'episode',
  baseUrl: 'streams/ep42',
  segmentSizeKB: 512,
});

const playlist = await streamer.createM3U8();

Passing explicit credentials:

import { S3Client } from '@aws-sdk/client-s3';
import { HlsStreamer, S3Provider } from 'hls-streamer';

const s3Client = new S3Client({
  region: 'eu-west-1',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

const provider = new S3Provider({
  bucket: 'my-media-bucket',
  key: 'videos/clip.mp4',
  client: s3Client, // reuse an existing client
});

const streamer = new HlsStreamer({ storageProvider: provider });

MinIO

MinIO is fully S3-compatible. Point endpoint at your MinIO server and set forcePathStyle: true:

import { S3Client } from '@aws-sdk/client-s3';
import { HlsStreamer, S3Provider } from 'hls-streamer';

const minioClient = new S3Client({
  endpoint: 'http://localhost:9000',   // your MinIO server
  region: 'us-east-1',                 // any non-empty string
  forcePathStyle: true,                // required for MinIO
  credentials: {
    accessKeyId: 'minioadmin',
    secretAccessKey: 'minioadmin',
  },
});

const provider = new S3Provider({
  bucket: 'media',
  key: 'audio/track.mp3',
  client: minioClient,
});

const streamer = new HlsStreamer({
  storageProvider: provider,
  fileName: 'track',
  baseUrl: 'streams/track',
});

const playlist = await streamer.createM3U8();

Serving S3 / MinIO streams over HTTP (Express)

The HTTP handler is identical to the local-file version — only the streamer construction changes:

import express from 'express';
import { S3Client } from '@aws-sdk/client-s3';
import { HlsStreamer, S3Provider } from 'hls-streamer';

const app = express();

// Reuse one client for the lifetime of the process
const s3 = new S3Client({ region: 'us-east-1' });

function makeStreamer(bucket: string, key: string, sessionId: string) {
  return new HlsStreamer({
    storageProvider: new S3Provider({ bucket, key, client: s3 }),
    baseUrl: `streams/${sessionId}`,
    fileName: 'segment',
  });
}

app.get('/streams/:session/playlist.m3u8', async (req, res, next) => {
  try {
    const streamer = makeStreamer('my-bucket', resolveKey(req.params.session), req.params.session);
    res.type('application/vnd.apple.mpegurl');
    res.send(await streamer.createM3U8());
  } catch (err) { next(err); }
});

app.get('/streams/:session/:start/:end/:filename', async (req, res, next) => {
  try {
    const streamer = makeStreamer('my-bucket', resolveKey(req.params.session), req.params.session);
    const mediaType = await streamer.getMediaType();
    res.type(mediaType === 'video' ? 'video/mp4' : 'audio/mpeg');
    res.set('Accept-Ranges', 'bytes');
    res.send(await streamer.getFileBuffer(Number(req.params.start), Number(req.params.end)));
  } catch (err) { next(err); }
});

Custom storage provider

Implement IStorageProvider to connect any storage backend (GCS, Azure Blob, HTTP, etc.):

import { IStorageProvider } from 'hls-streamer';

class MyProvider implements IStorageProvider {
  readonly resourceId = 'custom://my-source';

  async getSize(): Promise<number> { /* return total file size */ }
  async getRange(start: number, end: number): Promise<Buffer> { /* [start, end) bytes */ }
  async getHeader(): Promise<Buffer> { return this.getRange(0, 64); }
  async getBuffer(): Promise<Buffer> { /* full file */ }
}

const streamer = new HlsStreamer({ storageProvider: new MyProvider() });

Tip: getRange is the hot path — use HTTP byte-range requests (Range: bytes=start-end) to avoid downloading the full file for each segment.

Error handling

import { StorageProviderError } from 'hls-streamer';

try {
  const playlist = await streamer.createM3U8();
} catch (err) {
  if (err instanceof StorageProviderError) {
    console.error(`Storage error for ${err.resourceId}:`, err.message);
  }
}

Configuration Reference

Provide either filePath or storageProvider — not both.

| Option | Type | Default | Description | | ------------------- | --------------------- | ------------ | ----------- | | filePath | string | — | Path to a local media file. Supports: MP3, AAC, M4A, OGG, FLAC, WAV, MP4, MOV, M4V. | | storageProvider | IStorageProvider | — | Remote storage provider (e.g. S3Provider). Mutually exclusive with filePath. | | segmentSizeKB | number | 512 | Target segment size in kilobytes. | | fileName | string | "file" | Base name for generated segment URLs. | | baseUrl | string | "" | URL prefix inserted before each segment path. | | enableFastStart | boolean | false | Smaller first two segments for faster playback start. | | format | MediaFormat | auto-detect | Optional override: 'mp3', 'aac', 'm4a', 'ogg', 'flac', 'wav', 'mp4', 'mov', 'm4v'. |

API Surface

  • createM3U8(): Promise<string> – Full HLS playlist with frame-accurate durations. Audio → HLS v6; Video → HLS v7 + EXT-X-MAP.
  • getFileBuffer(start: number, end: number): Promise<Buffer> – Byte-range read from the source file (used for both segments and the init segment). Validates the range against file size only — does not trigger a full parse.
  • getSegmentDuration(index: number): Promise<number> – Duration in seconds for a specific segment.
  • getMediaType(): Promise<'audio' | 'video'> – Returns 'video' for MP4/MOV/M4V, 'audio' for everything else. Reads only the file header (~64 bytes) when possible.
  • getFileInfo(): Promise<MediaFileInfo> – Parsed metadata (size, duration, frame table, etc.). Cached on the instance.
  • restoreFileInfo(fileInfo: MediaFileInfo): void – Hydrate a previously-parsed MediaFileInfo into a fresh instance, skipping the underlying parse. Pair with an external metadata cache (LRU, Redis) to avoid re-downloading + re-parsing the same file across short-lived instances. Safe to round-trip via JSON.stringify / JSON.parse.

Custom error classes: FileNotFoundError, InvalidFileError, InvalidRangeError, InvalidParameterError, UnsupportedFormatError, StorageProviderError.

Supported Formats

| Format | Extensions | Container | Codec | Frame Parsing | | ------- | ------------------- | --------- | ------------ | ------------------------------- | | MP3 | .mp3 | — | MPEG Layer 3 | ✅ Full frame table | | AAC | .aac | ADTS | AAC | ✅ ADTS frames | | M4A | .m4a, .m4b | MP4 | AAC | ✅ MP4 box structure | | OGG | .ogg, .oga | OGG | Vorbis | ✅ OGG pages | | FLAC| .flac | — | FLAC | ✅ FLAC frames | | WAV | .wav | RIFF | PCM | ⚠️ Synthetic 1-second frames | | MP4 | .mp4 | fMP4 | H.264/H.265 | ✅ ISOBMFF box parse + keyframes | | MOV | .mov | QuickTime | H.264/H.265 | ✅ ISOBMFF box parse + keyframes | | M4V | .m4v | MP4 | H.264/H.265 | ✅ ISOBMFF box parse + keyframes |

Playlist Anatomy

Audio (HLS v6)

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:5.973,
/audio/session/0/260736/track000.mp3
#EXTINF:5.994,
/audio/session/260736/521472/track001.mp3
...
#EXT-X-ENDLIST

Video (HLS v7 fMP4)

#EXTM3U
#EXT-X-VERSION:7
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-MAP:URI="/video/session/0/28672/fileinit.mp4"
#EXTINF:4.000,
/video/session/28672/2125824/file000.mp4
#EXTINF:3.967,
/video/session/2125824/4194304/file001.mp4
...
#EXT-X-ENDLIST
  • EXT-X-MAP points to the moov box (init segment) inside the original file — no remuxing.
  • Video segment boundaries are snapped to keyframes for seamless decoding.
  • #EXTINF retains millisecond precision for smooth playback.

Operational Tips

  • Caching – Construct the streamer once per unique file and reuse it. Segment planning caches metadata after the first call.

  • External metadata cache – If you cannot keep HlsStreamer instances alive between requests (e.g. serverless, multi-process workers), use getFileInfo() to capture the parsed MediaFileInfo once and restoreFileInfo() to hydrate fresh instances on subsequent requests. Subsequent calls to createM3U8(), getMediaType(), and getFileBuffer() then issue zero parse-related reads against your storage backend:

    const cached = lruCache.get(s3Key); // your cache
    const streamer = new HlsStreamer({ storageProvider: new S3Provider({ bucket, key: s3Key, client: s3 }) });
    if (cached) {
      streamer.restoreFileInfo(cached);
    } else {
      lruCache.set(s3Key, await streamer.getFileInfo());
    }
    // From here, segment requests do one ranged GET, nothing more.
  • Video segment size – Use a larger segmentSizeKB (1024–4096) for video to avoid excessive HTTP requests. Audio works well at 512.

  • CDN friendliness – Segment URLs are deterministic byte ranges, making them ideal for edge caching. Use Cache-Control: public, max-age=86400.

  • Serverless – Zero-dependency design works in Lambda/Cloud Functions. getFileBuffer reads only the bytes needed, keeping memory usage low.

  • Content-Type – Serve audio segments as audio/mpeg (or the appropriate codec MIME type) and video segments as video/mp4. Use getMediaType() to branch at runtime.

  • S3 client reuse – Create one S3Client for the lifetime of your process and pass it to each S3Provider. This avoids per-request connection overhead and respects connection pool limits.

  • MinIO in Docker – Set endpoint to your MinIO container URL and forcePathStyle: true. The bucket must exist and the credentials must have s3:GetObject and s3:HeadObject permissions.

  • Troubleshooting – Inspect FileLib.analyzeMediaFile() to review parsing warnings and format-specific metadata.

Development

npm install
npm test
npm run build

To run a single test file:

npx jest tests/Parsers/Mp4Parser.test.ts

Support

Contributing

Contributions are welcome! Please open an issue to discuss substantial changes before submitting a pull request. Make sure npm test and npm run build pass prior to filing the PR.


Release Notes

For the complete history, see CHANGELOG.md.

Version 4.6.0

New Features

  • HlsStreamer.getFileInfo() is now public — returns the parsed MediaFileInfo (size, duration, frame table, etc.). Result is cached on the instance.
  • HlsStreamer.restoreFileInfo(fileInfo) hydrates a previously-parsed MediaFileInfo into a fresh instance, skipping the underlying parse. Designed to pair with an external cache (LRU, Redis) so short-lived instances avoid redundant downloads + re-parsing of the same file. Round-trips cleanly through JSON.stringify / JSON.parse.

Performance

  • getMediaType() no longer triggers a full file read in the common path. It now reads only the file header (~64 bytes) and classifies via magic bytes; falls back to a full parse only when the header is inconclusive. With a format override, classification happens with no read at all.
  • getFileBuffer() no longer triggers a full file parse. It validates the requested range using only provider.getSize() (a HEAD-equivalent), then issues the ranged read. The size is memoized on the instance — repeated segment requests against the same HlsStreamer issue exactly one getSize() call regardless of how many ranges are fetched.

Migration

Fully backward compatible — no signatures or behaviors changed. All additions are additive.

Full details: CHANGELOG.md.


Version 4.5.0

New Features

  • Remote storage support – stream directly from S3, MinIO, or any S3-compatible service without downloading files locally. Pass a storageProvider to HlsStreamerOptions instead of filePath.
  • S3Provider – built-in provider for AWS S3 and S3-compatible services (MinIO, Wasabi, Backblaze B2, etc.). Accepts a pre-configured S3Client or creates one from clientConfig. Uses HTTP byte-range requests for segments, avoiding full file downloads. @aws-sdk/client-s3 is an optional peer dependency — only install it if you use S3Provider.
  • IStorageProvider interface – implement this to connect any custom storage backend (GCS, Azure Blob, HTTP, etc.).
  • StorageProviderError – new typed error thrown when a storage provider operation fails, carrying the resourceId of the failing resource.
  • LocalFileProvider – the existing filesystem logic is now a first-class provider. Exported for use in custom wrappers or testing.

Migration

No changes needed for existing code. filePath continues to work exactly as before.

// v4.0 — unchanged, still works
new HlsStreamer({ filePath: '/local/audio.mp3' });

// v4.5 — new: remote storage
import { S3Provider } from 'hls-streamer';
new HlsStreamer({ storageProvider: new S3Provider({ bucket: 'b', key: 'audio.mp3', client: s3 }) });

Version 4.0.0

Major release adding video support and completing the audio→media rename.

New Features

  • Video support – MP4, MOV, and M4V files are now fully supported via a pure-JS ISOBMFF (MP4 box) parser. No ffmpeg, no native bindings, no vendored libraries.
  • HLS v7 fMP4 playlists – video files produce EXT-X-MAP init segments and keyframe-aligned byte-range segments, fully compatible with Safari, Chrome, and HLS.js.
  • Keyframe-aware segmentation – video segment boundaries snap to I-frames, preventing decoding artifacts.
  • getMediaType() – new method returning 'audio' | 'video' to simplify route/MIME-type logic.
  • MediaFormat / MediaFileInfo / MediaFrameInfo – public types renamed from Audio* to Media* to reflect the broader scope.

Breaking Changes (from v3)

  • AudioFormat, AudioFileInfo, AudioFrameInfo, IAudioParser are now deprecated aliases — they still compile but will be removed in v5.
  • FileLib.analyzeAudioFile() and analyzeAudioBuffer() are deprecated; use analyzeMediaFile() / analyzeMediaBuffer().
  • UnsupportedFormatError message changed from "Unsupported audio format" to "Unsupported media format".
  • format option type widened from AudioFormat to MediaFormat (superset — no change needed for existing callers).

Migration Guide

// v3.x
import { AudioFormat, AudioFileInfo } from 'hls-streamer';
const type: AudioFormat = 'mp3';

// v4.x — preferred
import { MediaFormat, MediaFileInfo } from 'hls-streamer';
const type: MediaFormat = 'mp3'; // same values, broader type

// v3.x names still compile in v4 (deprecated, removed in v5)
import { AudioFormat } from 'hls-streamer'; // ⚠️ deprecated alias

// Video — new in v4
const streamer = new HlsStreamer({ filePath: 'movie.mp4' });
const playlist = await streamer.createM3U8(); // HLS v7 + EXT-X-MAP
const type = await streamer.getMediaType();   // 'video'

Version 3.1.0

  • HLS compliance: #EXTINF lines now include the required trailing comma.
  • AAC ADTS detection: ADTS buffers no longer misclassified as MP3.
  • Extensionless files: constructor now accepts extensionless files when magic bytes identify a supported format.

Version 3.0.0

  • Added AAC, M4A, OGG Vorbis, FLAC, and WAV support.
  • Refactored MP3 parsing into a modular Parsers/ directory.
  • InvalidFileError for non-MP3 files replaced by UnsupportedFormatError.

Made with ❤️ by LordVersA