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 🙏

© 2024 – Pkg Stats / Ryan Hefner

pulsar-player-controller

v32.0.0

Published

React API for advanced video player interactions

Downloads

9

Readme

Pulsar-Player-Controller

Provides a standardized React API for efficiently reading, updating, and subscribing to video player state for Pulsar backends.

Usage

Pulsar also provides a number of custom hooks for controlling the video player and listening for player state updates.

To use these features, you'll need to render PlayerControllerRoot above PulsarCore in your application:

import type { FC } from 'react';
import { PulsarCore } from 'pulsar';
import { PlayerControllerRoot } from 'pulsar-player-controller';

const PlayerConsumer: FC = () => (
  <PlayerControllerRoot>
    <PulsarCore {/* ...props */} />
  </PlayerControllerRoot>
);

usePlayerController

This hook allows the consumer to control player state imperatively (play / pause, volume, muted, etc).

import type { FC } from 'react';
import { usePlayerController } from 'pulsar-player-controller';

const CustomControls: FC = () => {
  const controller = usePlayerController();
  const pauseHandler = () => {
    controller.pause();
  };

  return (
    <button disabled={!controller} onClick={pauseHandler}>
      Pause
    </button>
  );
};

Mocking usePlayerController For Testing

import {
  PlayerController,
  mockPlayerController,
} from 'pulsar-player-controller';

let mockedPlayerController: PlayerController;

jest.mock('pulsar-player-controller', () => ({
  ...jest.requireActual('pulsar-player-controller'),
  usePlayerController: () => mockedPlayerController,
}));

beforeEach(() => {
  mockedPlayerController = mockPlayerController({ pause: jest.fn() });
});

it('tells the player to pause', () => {
  // some interaction that uses the player controller
  expect(mockedPlayerController.pause).toHaveBeenCalledTimes(1);
});

usePlaybackState

Allows you to listen for player state changes: idle, ready, buffering, playing, ended.

import type { FC } from 'react';
import { PlayerState, usePLaybackState } from 'pulsar-player-controller';
import { Spinner } from './Spinner';

const PlayerOverlay: FC = () => {
  const state = usePLaybackState();
  const currentTime = usePlaybackTime();

  switch (state) {
    case PlayerState.BUFFERING:
      return <Spinner />;
    case PlayerState.ENDED:
      return <EndOfContentRecommendations />;
    case PlayerState.IDLE:
    case PlayerState.PLAYING:
    case PlayerState.READY:
      null;
    default:
      return state as never;
  }
};

usePlaybackTime

Allows you to efficiently listen for player time updates. The value returned is an integer number and will only be updated once per second. Due to the nature of the underlying timeupdate event, this will not be emitted exactly every second.

Consumers should use this hook as close to the consumer site as possible to limit the effects of every-second component updates. Use this hook in multiple components if necessary.

import type { FC } from 'react';
import { usePlaybackTime } from 'pulsar-player-controller';

const Seekbar: FC = () => {
  const controller = usePlayerController();
  const currentTime = usePlaybackTime();

  return <div>{`Progress ${currentTime} / ${controller.getDuration()}`}</div>;
};

useVolume / useMuted

Allows the consumer to efficiently handle Player volume / muted state changes as well as imperatively setting new values for both.

import type { FC } from 'react';
import { useMuted, useVolume } from 'pulsar-player-controller';

const VolumeControls: FC = () => {
  const { setVolume, volume } = useVolume();
  const { muted, setMuted } = useMuted();

  return (
    <>
      <button
        onClick={() => {
          setMuted(!muted);
        }}
      >
        {muted ? 'Unmute' : 'Mute'}
      </button>
      <VolumeSlider volume={muted ? 0.0 : volume} onChange={setVolume} />
    </>
  );
};

Captions (ClosedCaptionsRoot / useClosedCaptions)

Allows the consumer to efficiently detect, toggle, and render a custom Closed Captions UI.

import { FC, useEffect } from 'react';
import { TextCue, useClosedCaptions } from 'pulsar-player-controller';

const Captions: FC = () => {
  const { available, enabled, toggle, subscribe } = useClosedCaptions();
  const [captions, setCaptions] = useState<TextCue[]>([]);

  useEffect(() => {
    if (!enabled) {
      return;
    }

    return subscribe((newCaptions) => {
      setCaptions(newCaptions);
    });
  }, [enabled, subscribe]);

  return (
    <>
      <button disabled={!available} onClick={toggle}>
        {enabled ? 'Disable CC' : 'Enable CC'}
      </button>
      {captions.map((caption) => (
        <p>{caption.line}</p>
      ))}
    </>
  );
};

const PlayerUI: FC = () => (
  <PlayerControllerRoot>
    <ClosedCaptionsRoot contentKey={'<playing-content-id>'}>
      <Captions />
      <PulsarCore />
    </ClosedCaptionsRoot>
  </PlayerControllerRoot>
);

usePlayerError

import { usePlayerError } from 'pulsar-player-controller';

const Player: FC = () => {
  const playerError = usePlayerError();
  if (playerError) {
    return <div>{`Oh no, video error! Code: ${error.code}`}</div>;
  }

  return <PulsarCore />;
};

usePlaybackAd

import type { FC } from 'react';
import { usePlaybackAd } from 'pulsar-player-controller';

export const AdStatus: FC = () => {
  const ad = usePlaybackAd();
  if (!ad) {
    return null;
  }

  return (
    <p>{`Ad ${ad.cue.podPosition + 1} of ${ad.cue.podCount}. Remaining: ${
      ad.remainingTimeSeconds
    }`}</p>
  );
};

usePlaybackQuality

import type { FC } from 'react';
import { usePlaybackAd } from 'pulsar-player-controller';

export const QualityPicker: FC = () => {
  const qualityInfo = usePlaybackQuality();
  if (!qualityInfo) {
    return null;
  }

  const { options, quality, setQuality } = qualityInfo;
  return (
    <>
      <p>{`Current Quality: ${quality.name}`}</p>
      {options.map((o) => (
        <div
          onClick={() => {
            setQuality(o);
          }}
        >
          {o.name}
        </div>
      ))}
    </>
  );
};

usePlaybackAutoplayStatus

import type { FC } from 'react';
import { usePlaybackAutoplayStatus } from 'pulsar-player-controller';

export const TapToPlay: FC = () => {
  const controller = usePlayerController();

  const blockedStatus = usePlaybackAutoplayStatus();
  if (!blockedStatus) {
    return;
  }

  const playHandler = () => {
    controller.play();
  };

  const unmuteHandler = () => {
    controller.setMuted(false);
  };

  if (blockedStatus === 'autoplay-blocked') {
    return <button onClick={playHandler}>Tap To Play</button>;
  }

  return <button onClick={unmuteHandler}>Tap To Unmute</button>;
};