32.0.0 • Published 2 months ago

pulsar-player-controller v32.0.0

Weekly downloads
-
License
UNLICENSED
Repository
-
Last release
2 months ago

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>;
};