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

@memberjunction/ng-media-player

v5.48.0

Published

World-class generic media (audio/video) player with transcript sync, plus an MJStorage-bound wrapper.

Readme

@memberjunction/ng-media-player

A world-class, zero-dependency Angular media (audio/video) player for MemberJunction applications, plus an MJStorage-bound wrapper that streams files stored in any MJ storage provider. The generic player has no MemberJunction-core dependency — it is pure Angular and safe to reuse in any app; all the MJ wiring lives in the separate wrapper.

Installation

npm install @memberjunction/ng-media-player

Overview

This package ships two standalone components:

| Component | Selector | Purpose | |-----------|----------|---------| | MJMediaPlayerComponent | mj-media-player | Generic, framework-agnostic player. You give it one or more MediaTracks (plain URLs) and an optional transcript. | | MJStorageMediaPlayerComponent | mj-storage-media-player | MJStorage-bound wrapper. You give it an MJ: Files FileID (or several); it resolves each to a short-lived authenticated streaming URL and hands them to the generic player. |

The generic player renders a custom transport bar (play/pause, click-and-drag scrubber, playback-rate menu, ±skip, volume + mute, fullscreen), a real audio waveform that doubles as the scrubber, and an optional time-synced transcript whose cues are clickable and auto-highlight as playback advances. For a single audio track it shows the transport + waveform; for multiple video tracks it renders a responsive grid.

flowchart TD
    subgraph Wrapper["MJStorageMediaPlayerComponent (mj-storage-media-player)"]
        F["FileID / FileIDs[]"] --> M["CreateMediaAccessToken mutation<br/>(per-user permission check)"]
        M --> U["/media/:fileId?token= streaming URL<br/>+ optional peaks.json peaks"]
    end
    subgraph Generic["MJMediaPlayerComponent (mj-media-player)"]
        T["MediaTrack[]"] --> P["Transport bar + waveform scrubber"]
        T --> W["Real waveform (supplied Peaks OR client-decoded)"]
        C["MediaTranscriptCue[]"] --> X["Click-to-seek transcript (auto-highlight)"]
    end
    U -->|"MediaTrack[] (Url + Peaks)"| Generic

    style Wrapper fill:#2d6a9f,stroke:#1a4971,color:#fff
    style Generic fill:#2d8659,stroke:#1a5c3a,color:#fff

Key features

  • Custom transport — play/pause, click-and-drag scrubber, playback-rate menu, ±skip (configurable seconds), volume slider + mute, fullscreen for video.
  • Real audio waveform — for audio-only tracks the player renders a true waveform (not a synthetic one) that IS the scrubber. Peaks are either supplied via MediaTrack.Peaks (rendered as-is, no decode) or extracted client-side by decoding the track URL through an AudioContext. On any decode failure it falls back gracefully to a plain progress bar.
  • Time-synced transcript — supply MediaTranscriptCue[] and the player renders a transcript panel; clicking a cue seeks to its start, and the active cue auto-highlights as playback advances. Defaults to below the player (TranscriptPosition='bottom') with a show/hide toggle in the transport bar.
  • Loading / buffering state — a MediaPlaybackState lifecycle (idle/loading/buffering/ready/playing/paused/ended/error) drives a stage busy-indicator overlay (the buffering spinner is debounced so instant seeks don't flash it) and a screen-reader-friendly status label.
  • Multi-track video grid — multiple video tracks render in a responsive grid; the first video leads the master timeline.
  • Keyboard support — space (play/pause), ←/→ (±5s), J/L (±10s), ↑/↓ (volume), M (mute), F (fullscreen); the scrubber itself is a role="slider" with arrow/Home/End support.
  • Cancelable Before* eventsBeforePlay/BeforePause/BeforeSeek/BeforeRateChange each pass an event object; set event.Cancel = true to abort the pending action (mirrors the ng-conversations beforeAgentTurn pattern).
  • Imperative APIPlay(), Pause(), SeekToMs(), SetPlaybackRate(), SeekToCue(), etc.
  • Reduced-motion aware — suppresses the waveform draw-in animation when the user prefers reduced motion.

Usage

Generic player (plain URLs)

import { MJMediaPlayerComponent, MediaTrack, MediaTranscriptCue } from '@memberjunction/ng-media-player';

@Component({
  standalone: true,
  imports: [MJMediaPlayerComponent],
  template: `
    <mj-media-player
      [Tracks]="tracks"
      [Transcript]="transcript"
      (Ended)="onEnded()">
    </mj-media-player>
  `,
})
export class MyComponent {
  tracks: MediaTrack[] = [
    { Id: 'a1', Kind: 'audio', Url: 'https://example.com/interview.wav' },
  ];
  transcript: MediaTranscriptCue[] = [
    { Id: 'c1', StartMs: 0,    SpeakerLabel: 'Host',  Text: 'Welcome to the show.' },
    { Id: 'c2', StartMs: 3200, SpeakerLabel: 'Guest', Text: 'Thanks for having me.' },
  ];
  onEnded() { /* ... */ }
}

Storage-backed player (MJ: Files)

import { MJStorageMediaPlayerComponent } from '@memberjunction/ng-media-player';

@Component({
  standalone: true,
  imports: [MJStorageMediaPlayerComponent],
  template: `
    <mj-storage-media-player
      [FileID]="recordingFileId"
      [Transcript]="transcript">
    </mj-storage-media-player>
  `,
})
export class RecordingPlaybackComponent {
  recordingFileId = '...';            // an MJ: Files row ID
  transcript: MediaTranscriptCue[] = [ /* ... */ ];
}

The wrapper resolves the file id to a streaming URL on init (and whenever FileID/FileIDs change), surfacing graceful loading / no-access / empty states while it does.

MJMediaPlayerComponent (mj-media-player)

Inputs

| Property | Type | Default | Description | |----------|------|---------|-------------| | Tracks | MediaTrack[] | [] | The track(s) to play. One audio track → transport bar + waveform; many videos → grid. | | Transcript | MediaTranscriptCue[] \| null | null | Transcript cues. When set (and ShowTranscript), a transcript panel renders. | | Autoplay | boolean | false | Begin playback automatically once media is ready. | | StartAtMs | number \| null | null | Start playback at this position (ms) once metadata loads. | | ShowTranscript | boolean | true | Master switch for the transcript panel (when a transcript is provided). | | ShowTranscriptToggle | boolean | true | Whether the transcript show/hide toggle renders in the transport bar. | | ShowSpeedControl | boolean | true | Whether the playback-rate menu is shown. | | ShowSkipControls | boolean | true | Whether the ±skip buttons are shown. | | ShowVolume | boolean | true | Whether the volume slider + mute are shown. | | ShowWaveform | boolean | true | Whether the real audio waveform (which doubles as the scrubber) is shown for audio-only tracks. | | WaveformBarCount | number | DEFAULT_WAVEFORM_BARS | Number of bars the waveform renders / downsamples to. | | ShowFullscreen | boolean | true | Whether the fullscreen button is shown for video. | | SkipSeconds | number | 30 | Seconds the ±skip buttons jump. | | PlaybackRates | number[] | [0.5, 1, 1.25, 1.5, 2] | Rates offered in the rate menu. | | InitialRate | number | 1 | Initial playback rate. | | InitialVolume | number | 1 | Initial volume (0..1). | | TranscriptPosition | 'bottom' \| 'side' | 'bottom' | Where the transcript panel sits relative to the media. |

Outputs

Before* events are cancelable — set event.Cancel = true in a handler to abort.

| Event | Type | Description | |-------|------|-------------| | BeforePlay | EventEmitter<MediaPlayerCancelableEvent> | Before playback starts (cancelable). | | AfterPlay | EventEmitter<void> | After playback starts. | | BeforePause | EventEmitter<MediaPlayerCancelableEvent> | Before pause (cancelable). | | AfterPause | EventEmitter<void> | After pause. | | BeforeSeek | EventEmitter<MediaSeekEvent> | Before a seek (cancelable; carries FromMs/ToMs). | | AfterSeek | EventEmitter<number> | After a seek (the new position in ms). | | BeforeRateChange | EventEmitter<MediaRateEvent> | Before a rate change (cancelable; carries FromRate/ToRate). | | AfterRateChange | EventEmitter<number> | After a rate change (the new rate). | | CueActivated | EventEmitter<MediaCueEvent> | When the active transcript cue changes during playback. | | CueClicked | EventEmitter<MediaCueEvent> | When a transcript cue is clicked. | | TimeUpdate | EventEmitter<number> | On every time update (current position in ms). | | DurationChange | EventEmitter<number> | When the media duration is resolved/changes (ms). | | Ended | EventEmitter<void> | When playback reaches the end. | | StateChanged | EventEmitter<MediaPlaybackState> | On every playback-lifecycle transition. | | TranscriptVisibilityChanged | EventEmitter<boolean> | When the transcript is toggled (true = shown). |

Methods (imperative API)

| Method | Returns | Description | |--------|---------|-------------| | Play() | void | Begin playback (honors BeforePlay). | | Pause() | void | Pause playback (honors BeforePause). | | TogglePlay() | void | Toggle play/pause. | | SeekToMs(ms) | void | Seek to an absolute position (honors BeforeSeek). | | SkipForward() / SkipBackward() | void | Skip ±SkipSeconds. | | SetPlaybackRate(rate) | void | Set the playback rate (honors BeforeRateChange). | | SeekToCue(index) | void | Seek to a transcript cue's start (continues playing if playing, repositions if paused). | | SetVolume(v) | void | Set volume 0..1 (unmutes if > 0). | | ToggleMute() | void | Toggle mute. | | EnterFullscreen() / ExitFullscreen() / ToggleFullscreen() | void | Fullscreen control for video. | | ToggleTranscript() | void | Show/hide the transcript panel (emits TranscriptVisibilityChanged). |

Read-only getters expose live state for template binding / inspection, including CurrentTimeMs, DurationMs, IsPlaying, PlaybackRate, ActiveCueIndex, MediaState, IsLoading, IsBuffering, and StatusLabel.

MJStorageMediaPlayerComponent (mj-storage-media-player)

Extends BaseAngularComponent, so it accepts the standard [Provider] input for multi-provider apps.

Inputs

| Property | Type | Default | Description | |----------|------|---------|-------------| | FileID | string \| null | null | Convenience single MJ: Files id. Combined with FileIDs; duplicates are de-duped. | | FileIDs | string[] | [] | Multi-track file ids (e.g. separate meeting streams). | | Transcript | MediaTranscriptCue[] \| null | null | Transcript cues forwarded to the generic player. | | ShowTranscript / ShowTranscriptToggle / ShowSpeedControl / ShowSkipControls / ShowVolume / ShowWaveform / ShowFullscreen | boolean | true | Pass-through feature flags, forwarded to the generic player. | | SkipSeconds | number | 30 | Forwarded skip step. | | PlaybackRates | number[] | [0.5, 1, 1.25, 1.5, 2] | Forwarded rate menu. | | Autoplay | boolean | false | Forwarded autoplay flag. | | StartAtMs | number \| null | null | Forwarded start position. | | TranscriptPosition | 'side' \| 'bottom' | 'bottom' | Forwarded transcript position. |

Read-only getters

| Getter | Type | Description | |--------|------|-------------| | ResolvedTracks | MediaTrack[] | The streaming tracks resolved from the file ids. | | IsLoading | boolean | True while minting access tokens / resolving tracks. | | AccessError | string \| null | A no-access / unavailable message, set only when nothing resolved. | | HasFileIds | boolean | Whether any file id is configured. |

Streaming & permission model (wrapper)

The wrapper resolves each file id by calling the CreateMediaAccessToken GraphQL mutation (server-side, in @memberjunction/server). The server:

  1. Runs a per-user permission check for the file.
  2. Mints a short-lived (HS256) signed token and returns the authenticated streaming URL GET /media/:fileId?token=….
  3. If a peaks.json sidecar sits beside the recording in storage, returns its precomputed waveform peaks alongside the URL.

The <audio> / <video> element then streams that URL natively over HTTP Range (progressive playback + seek-before-download for large video) instead of base64'ing the whole file over GraphQL. The /media route re-verifies the token's signature/expiry and that it matches the requested file, then range-streams the bytes from the underlying MJ storage provider via FileStorageBase.GetObjectStream (see @memberjunction/storage).

Waveform peaks: when the server supplies precomputed peaks (from the peaks.json sidecar), the wrapper sets them on MediaTrack.Peaks so the generic player renders the real waveform instantly with no client-side fetch/decode. When no sidecar exists, audio waveforms still decode client-side from the streaming URL; video shows the plain progress bar.

Public types

type MediaKind = 'audio' | 'video';

type MediaPlaybackState =
  | 'idle' | 'loading' | 'buffering' | 'ready'
  | 'playing' | 'paused' | 'ended' | 'error';

interface MediaTrack {
  Id: string;            // stable identifier (track-by + event correlation)
  Kind: MediaKind;       // audio vs. video
  Url: string;           // http(s) or blob: object URL
  MimeType?: string;     // optional MIME hint (e.g. 'audio/wav', 'video/mp4')
  Label?: string;        // optional label shown in multi-track layouts
  PosterUrl?: string;    // optional poster image for video
  Peaks?: number[];      // optional precomputed waveform peaks (normalized 0..1, one per bar).
                         // Supplied → rendered directly, NO client-side decode.
                         // Absent (audio + waveform on) → player decodes the URL client-side.
}

interface MediaTranscriptCue {
  Id: string;            // stable cue identifier
  StartMs: number;       // cue start (ms from media start)
  EndMs?: number;        // optional cue end; when absent, the next cue's start is used
  SpeakerLabel?: string; // optional speaker label (color-coded)
  Text: string;          // the spoken text
}

interface MediaPlayerCancelableEvent { Cancel: boolean; }          // set true to abort
interface MediaSeekEvent extends MediaPlayerCancelableEvent { FromMs: number; ToMs: number; }
interface MediaRateEvent extends MediaPlayerCancelableEvent { FromRate: number; ToRate: number; }
interface MediaCueEvent { Cue: MediaTranscriptCue; Index: number; }

Helper utilities (computeActiveCueIndex, downsamplePeaks, nextPlaybackState, transcript-layout resolvers) are also exported from the package public API for reuse and testing.

Dependencies

  • @angular/common / @angular/core (peer)
  • @memberjunction/core, @memberjunction/graphql-dataprovider, @memberjunction/ng-base-types — used only by the storage wrapper. The generic MJMediaPlayerComponent has no MemberJunction dependency.

Building & testing

npm run build   # ngc
npm run test    # vitest

License

ISC