An open API service indexing awesome lists of open source software.

https://github.com/alessiofrittoli/react-media-player

Handle media players with ease
https://github.com/alessiofrittoli/react-media-player

audio-player mediaplayer mediaplayer-api mediasession mediasession-api react-media-player video-player

Last synced: 2 months ago
JSON representation

Handle media players with ease

Awesome Lists containing this project

README

          

React Media Player 🎥



Handle media players with ease




Latest version


Test coverage


Socket Security score


npm downloads


Dependencies


Dependencies status




minified


minizipped


Tree shakable




Fund this package

[sponsor-badge]: https://img.shields.io/static/v1?label=Fund%20this%20package&message=%E2%9D%A4&logo=GitHub&color=%23DB61A2
[sponsor-url]: https://github.com/sponsors/alessiofrittoli

### Table of Contents

- [Getting started](#getting-started)
- [API Reference](#api-reference)
- [React Hooks](#react-hooks)
- [`useAudioPlayer`](#useaudioplayer)
- [`useAudioPlayerStore`](#useaudioplayerstore)
- [`useVideoPlayer`](#usevideoplayer)
- [`useVideoPlayerStore`](#usevideoplayerstore)
- [`useMediaPlayer`](#usemediaplayer)
- [`useVolume`](#usevolume)
- [`useVolumeStore`](#usevolumestore)
- [`useMediaPlayerController`](#usemediaplayercontroller)
- [`useMediaPlayerLoading`](#usemediaplayerloading)
- [`useMediaPreload`](#usemediapreload)
- [`useMediaSession`](#usemediasession)
- [`useMediaSessionPiP`](#usemediasessionpip)
- [React Components](#react-components)
- [``](#audioplayer-)
- [``](#videoplayer-)
- [``](#audioplayerprovider-)
- [``](#videoplayerprovider-)
- [``](#volumeprovider-)
- [Utils](#utils)
- [Development](#development)
- [Install dependencies](#install-dependencies)
- [Build the source code](#build-the-source-code)
- [ESLint](#eslint)
- [Jest](#jest)
- [Contributing](#contributing)
- [Security](#security)
- [Credits](#made-with-)

---

### Getting started

Run the following command to start using `react-media-player` in your projects:

```bash
npm i @alessiofrittoli/react-media-player
```

or using `pnpm`

```bash
pnpm i @alessiofrittoli/react-media-player
```

---

### API Reference

#### Defining the queue

```ts
import { addItemsUUID } from "@alessiofrittoli/react-media-player/utils";
import type { Media, Queue } from "@alessiofrittoli/react-media-player";

interface PlaylistMedia extends Media {
customProp: boolean;
}

interface Playlist extends Queue {
name?: string;
}

const queue: Playlist = {
name: "Playlist name",
items: addItemsUUID([
{
src: "/song.mp3",
type: "audio",
title: "Song title",
album: "Album name",
artist: "Artist name",
customProp: true,
fade: { in: 1000, out: 1000 },
artwork: [
{ src: "/artwork-96.png", sizes: 96, type: "image/png" },
{ src: "/artwork-128.png", sizes: 128, type: "image/png" },
{ src: "/artwork-192.png", sizes: 192, type: "image/png" },
{ src: "/artwork-256.png", sizes: 256, type: "image/png" },
{ src: "/artwork-384.png", sizes: 384, type: "image/png" },
{ src: "/artwork-512.png", sizes: 512, type: "image/png" },
],
videoArtwork: [{ src: "/video-artwork.mp4", type: "video/mp4" }],
},
{
src: "/song-2.mp3",
type: "audio",
title: "Song title 2",
album: "Album name",
artist: "Artist name",
customProp: true,
fade: { in: 1000, out: 1000 },
artwork: [
{ src: "/artwork-96.png", sizes: 96, type: "image/png" },
{ src: "/artwork-128.png", sizes: 128, type: "image/png" },
{ src: "/artwork-192.png", sizes: 192, type: "image/png" },
{ src: "/artwork-256.png", sizes: 256, type: "image/png" },
{ src: "/artwork-384.png", sizes: 384, type: "image/png" },
{ src: "/artwork-512.png", sizes: 512, type: "image/png" },
],
videoArtwork: [{ src: "/video-artwork.mp4", type: "video/mp4" }],
},
]),
};
```

---

#### React Hooks

##### `useAudioPlayer`

Easily handle React audio players.

This hook acts as a wrapper around [`useMediaPlayer`](#usemediaplayer) and it automatically creates `Audio` resource for you.

Please refer to [`useMediaPlayer`](#usemediaplayer) doc section for API reference.

Usage

```ts
import { useAudioPlayer } from "@alessiofrittoli/react-media-player";
import type {
MediaChangeHandler,
PlaybackErrorHandler,
} from "@alessiofrittoli/react-media-player";

useAudioPlayer({
queue,
initialMedia: queue.items.at(2),
normalizeVolume: true,
playPauseFadeDuration: 500,
preload: true,
repeat: true,
restartThreshold: 6000,
volume: 1,
onMediaChange: useCallback>((media) => {}, []),
onPlaybackError: useCallback((error) => {}, []),
});
```

- See [Defining the queue](#defining-the-queue) for more info.

---

##### `useAudioPlayerStore`

Access [`useAudioPlayer`](#useaudioplayer) API exposed by [``](#audioplayerprovider-) Component.

---

##### `useVideoPlayer`

Easily handle React video players.

This hook acts as a wrapper around [`useMediaPlayer`](#usemediaplayer) and it automatically creates a `React.RefObject` that
needs to be attached to a `` JSX node.

Please refer to [`useMediaPlayer`](#usemediaplayer) doc section for API reference.

Usage

```tsx
"use client";

import { useVideoPlayer } from "@alessiofrittoli/react-media-player";
import type {
MediaChangeHandler,
PlaybackErrorHandler,
} from "@alessiofrittoli/react-media-player";

export const VideoPlayer: React.FC = () => {
const { videoRef } = useVideoPlayer({
queue,
initialMedia: queue.items.at(2),
normalizeVolume: true,
playPauseFadeDuration: 500,
preload: true,
repeat: true,
restartThreshold: 6000,
volume: 1,
onMediaChange: useCallback>((media) => {}, []),
onPlaybackError: useCallback((error) => {}, []),
});

return ;
};
```

- See [Defining the queue](#defining-the-queue) for more info.

---

##### `useVideoPlayerStore`

Access [`useVideoPlayer`](#usevideoplayer) API exposed by [``](#videoplayerprovider-) Component.

---

##### `useMediaPlayer`

Easily handle React media players.

Type parameters

| Parameter | Type | Description |
| --------- | ------------------------- | ---------------------- |
| `T` | `T extends Queue = Queue` | The type of the queue. |

---

Parameters

| Parameter | Type | Default | Description |
| ----------------- | -------------------------- | ------- | ------------------------------------------------------------------------------------------ |
| `options` | `UseMediaPlayerOptions` | - | An object defining media player options. |
| | | | - extends [`UseVolumeOptions`](#usevolumeoptions) interface. |
| | | | - extends [`UseMediaPlayerControllerOptions`](#usemediaplayercontrolleroptions) interface. |
| `options.preload` | `boolean` | `true` | Indicates whether to preload next media when current media is about to end. |

---

Returns

Type: `UseMediaPlayer`

An object defining media player state and utilities.

- extends [`UseVolume`](#usevolume-interface) interface.
- extends [`UseMediaPlayerController`](#usemediaplayercontroller-interface) interface.
- extends [`UseMediaPreload`](#usemediapreload-interface) interface.
- extends [`UseMediaPlayerLoading`](#usemediaplayerloading-interface) interface.

| Property | Type | Description |
| -------- | ------------------ | ----------------------------- |
| `media` | `HTMLMediaElement` | The given `HTMLMediaElement`. |

---

Usage

```ts
import { useRef } from "react";
import { useMediaPlayer } from "@alessiofrittoli/react-media-player";
import type {
MediaChangeHandler,
PlaybackErrorHandler,
} from "@alessiofrittoli/react-media-player";

const media = useRef(typeof window !== "undefined" ? new Audio() : undefined);

useMediaPlayer({
media: media.current,
queue,
initialMedia: queue.items.at(2),
normalizeVolume: true,
playPauseFadeDuration: 500,
preload: true,
repeat: true,
restartThreshold: 6000,
volume: 1,
onMediaChange: useCallback>((media) => {}, []),
onPlaybackError: useCallback((error) => {}, []),
});
```

- See [Defining the queue](#defining-the-queue) for more info.

---

##### `useVolume`

Manage audio volume control.

Please note that this hook doesn't update states to avoid useless overloads. This hook only handles `media` volume updates and relative normalizations.

UI state updates can be managed using [`useVolumeStore`](#usevolumestore) accessible inside [``](#volumeprovider-) Component children.

###### `UseVolumeOptions`

Parameters

| Parameter | Type | Default | Description |
| ------------------------- | ------------------ | ------- | ------------------------------------------------------- |
| `options` | `UseVolumeOptions` | - | Configuration options for the volume hook. |
| `options.media` | `HTMLMediaElement` | - | The `HTMLMediaElement`. |
| `options.volume` | `number` | `1` | The master volume [0-1]. |
| `options.normalizeVolume` | `boolean` | `true` | Normalize master volume. |
| `options.fade` | `number` | `200` | Volume fade in milliseconds applied when toggling mute. |

---

###### `UseVolume` interface

Returns

Type: `UseVolume`

An object providing volume control functionality including volume management, mute toggling, and volume normalization for media players.

| Property | Type | Description |
| ----------------- | ------------------------- | --------------------------------------------------------------------------------------- |
| `volumeRef` | `React.RefObject` | A React RefObject that stores the master volume value [0-1]. |
| | | This value may stores the normalized value if `normalizeVolume` has been set to `true`. |
| `initialVolume` | `number` | The initial master volume [0-1]. |
| `normalizeVolume` | `boolean` | Indicates whether volume normalization is applied. |
| `setVolume` | `ChangeHandler` | Set volume. |
| `toggleMute` | `ToggleMuteHandler` | Toggle mute. |
| | | Returns `0` if muting, otherwise the volume value before the mute was activated. |

---

Usage

```tsx
import { useVolume } from "@alessiofrittoli/react-media-player";

const { setVolume, toggleMute, volumeRef } = useVolume({
media: HTMLAudioElement | HTMLVideoElement,
volume: 0.8,
normalizeVolume: true,
fade: 300,
});
```

---

##### `useVolumeStore`

Access [`useVolume`](#usevolume) API exposed by [``](#volumeprovider-) Component.

---

##### `useMediaPlayerController`

React media player controller state.

Type parameters

| Parameter | Type | Description |
| --------- | ------------------------- | ---------------------- |
| `T` | `T extends Queue = Queue` | The type of the queue. |

---

###### `UseMediaPlayerControllerOptions`

Parameters

| Parameter | Type | Default | Description |
| ------------------------------- | ------------------------------------ | ------- | -------------------------------------------------------------------------------------------- |
| `options` | `UseMediaPlayerControllerOptions` | - | An object defining media player controller options. |
| `options.volumeRef` | `React.RefObject` | - | A React RefObject that stores the master volume value [0-1]. |
| | | - | Compatible with `volumeRef` returned by [`useVolume`](#usevolume) hook. |
| `options.repeat` | `boolean` | `true` | Indicates whether repeatition of the given queue is initially active. |
| `options.media` | `HTMLMediaElement` | - | The `HTMLMediaElement`. |
| `options.queue` | `T` | - | An object describing the queue. See [Defining the queue](#defining-the-queue) for more info. |
| `options.initialMedia` | `InitialMedia>` | - | Defines the initial queue media to load. |
| `options.restartThreshold` | `number\|false` | `5000` | Indicates time in milliseconds after that the media restart to `0` |
| | | | rather than playing the previous one. |
| | | | This only take effect when `previous()` method is called. |
| | | | You can opt-out by this functionality by setting `restartThreshold` to `false` or `0`. |
| `options.playPauseFadeDuration` | `number` | `200` | Volume fade in milliseconds applied when soundtrack start playing/get paused. |
| `options.onMediaChange` | `MediaChangeHandler` | - | A callback executed when media player is playing and transitioning to another media. |
| `options.onPlaybackError` | `PlaybackErrorHandler` | - | A callback executed when an error occurs when playing a media. |

---

###### `UseMediaPlayerController` interface

Returns

Type: `UseMediaPlayerController`

An object defining media player state and utilities.

- extends and exposes [`useQueue`](https://npmjs.com/package/@alessiofrittoli/react-hooks#usequeue) APIs.
it may be worthy to take a look at undocumented returned properties in the [`useQueue`](https://npmjs.com/package/@alessiofrittoli/react-hooks#usequeue) documentation.

| Properties | Type | Description |
| ----------------- | ---------------------------- | ------------------------------------------------------------------------------------ |
| `state` | `PlayerState` | Defines the current media player state. |
| | | It could be one of the following: |
| | | - `playing` \| The media player is currently playing. |
| | | - `paused` \| The media player is currently paused. |
| | | - `stopped` \| The media player hasn't been started yet or has been stopped. |
| `isPlaying` | `boolean` | Defines whether the media player is currently playing. |
| `playPause` | `PlayPauseHandler` | Play/pause/stop the media player or start another media. |
| | | - Returns: The queued item being played. |
| `togglePlayPause` | `UtilityPlayPauseHandler` | Toggle play/pause. |
| | | - Returns: The queued item being played/paused. |
| `stop` | `UtilityPlayPauseHandler` | Stop media player. |
| | | - Returns: The queued item that was playing before stopping the media player if any. |
| `previous` | `UtilityPlayPauseHandler` | Play previous queued media. |
| | | - Returns: The queued item being played if any. |
| `next` | `UtilityPlayPauseHandler` | Play next queued media. |
| | | - Returns: The queued item being played if any. |

---

Usage

###### Toggle play/pause

```tsx
"use client";

import { useRef } from "react";
import { useMediaPlayerController } from "@alessiofrittoli/react-media-player";

export const MyComponent: React.FC = () => {
const media = useRef(typeof window !== "undefined" ? new Audio() : undefined);
const { isPlaying, togglePlayPause } = useMediaPlayerController({
queue,
media,
});

return (
{!isPlaying ? "Play" : "Pause"}
);
};
```

---

###### Play previous media

```tsx
"use client";

import { useRef } from "react";
import { useMediaPlayerController } from "@alessiofrittoli/react-media-player";

export const MyComponent: React.FC = () => {
const media = useRef(typeof window !== "undefined" ? new Audio() : undefined);
const { hasPrevious, previous } = useMediaPlayerController({
queue,
media,
repeat: false,
});

return hasPrevious && Previous song;
};
```

---

###### Play next media

```tsx
"use client";

import { useRef } from "react";
import { useMediaPlayerController } from "@alessiofrittoli/react-media-player";

export const MyComponent: React.FC = () => {
const media = useRef(typeof window !== "undefined" ? new Audio() : undefined);
const { hasNext, next } = useMediaPlayerController({
queue,
media,
repeat: false,
});

return hasNext && Next song;
};
```

---

###### Play a queued media matching given UUID

```tsx
"use client";

import { useRef } from "react";
import { useMediaPlayerController } from "@alessiofrittoli/react-media-player";

export const MyComponent: React.FC = () => {
const media = useRef(typeof window !== "undefined" ? new Audio() : undefined);
const { playPause } = useMediaPlayerController({ queue, media });

return (
{
playPause({ uuid: queue.items.at(1)?.uuid });
}}
>
Play {queue.items.at(1)?.title}

);
};
```

---

###### Update queue and play a queued media matching given UUID

```tsx
"use client";

import { useRef } from "react";
import { useMediaPlayerController } from "@alessiofrittoli/react-media-player";

export const MyComponent: React.FC = () => {
const media = useRef(typeof window !== "undefined" ? new Audio() : undefined);
const { playPause } = useMediaPlayerController({ queue, media });

return (
{
playPause({ queue: queue2, uuid: queue2.items.at(1)?.uuid });
}}
>
Play {queue2.items.at(1)?.title}

);
};
```

---

##### `useMediaPlayerLoading`

Handle media loading and error states.

Parameters

| Parameter | Type | Description |
| --------------- | ------------------------------ | ------------------------------------------------ |
| `options` | `UseMediaPlayerLoadingOptions` | An object defining media player loading options. |
| `options.media` | `HTMLMediaElement` | The `HTMLMediaElement`. |

---

###### `UseMediaPlayerLoading` interface

Returns

Type: `UseMediaPlayerLoading`

An object defining loading and error states.

| Parameter | Type | Description |
| ----------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isLoading` | `boolean` | Indicates whether the current media is loading. |
| `error` | `MediaError` | The `MediaError` interface represents an error which occurred while handling |
| | | media in an HTML media element based on [`HTMLMediaElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement), |
| | | such as [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/audio) or [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/video). |
| | | - see [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/MediaError). |

---

Usage

```tsx
"use client";

import { useEffect, useState } from "react";
import { useMediaPlayerLoading } from "@alessiofrittoli/react-media-player";

export const MyComponent: React.FC = () => {
const [media, setMedia] = useState();

const { isLoading, error } = useMediaPlayerLoading({ media });

useEffect(() => {
setMedia(new Audio("/song.mp3"));
}, []);

return (
<>
{isLoading && Loading...}
{!isLoading && !error && Loaded}
{error?.code === MediaError?.MEDIA_ERR_ABORTED && (

The fetching of the associated resource was aborted by the user's
request.

)}
{error?.code === MediaError?.MEDIA_ERR_NETWORK && (

Some kind of network error occurred which prevented the media from
being successfully fetched, despite having previously been available.

)}
{error?.code === MediaError?.MEDIA_ERR_DECODE && (

Despite having previously been determined to be usable, an error
occurred while trying to decode the media resource, resulting in an
error.

)}
{error?.code === MediaError?.MEDIA_ERR_SRC_NOT_SUPPORTED && (

The associated resource or media provider object (such as a
MediaStream) has been found to be unsuitable.

)}
>
);
};
```

---

##### `useMediaPreload`

Handle media preload.

Type parameters

| Parameter | Type | Description |
| --------- | ------------------------- | ---------------------- |
| `T` | `T extends Queue = Queue` | The type of the queue. |

---

Parameters

| Parameter | Type | Default | Description |
| ------------------------- | ----------------------------- | ------- | -------------------------------------------------------------------- |
| `options` | `UseVolumeOptions` | - | Configuration options for the volume hook. |
| `options.controller` | `UseMediaPlayerController` | - | The media player controller. |
| `options.cacheEntries` | `number` | `3` | Defines the maximum cache entries. |
| `options.checkConnection` | `boolean` | `true` | Defines whether preload is enabled based on user connection quality. |

---

###### `UseMediaPreload` interface

Returns

Type: `UseMediaPreload`

An object containing preload functions.

| Property | Type | Description |
| ---------------------- | ------------------------------ | ----------------------- |
| `preloadMedia` | `PreloadMediaHandler` | Preload media. |
| `preloadPreviousMedia` | `PreloadPreviousOrNextHandler` | Preload previous media. |
| `preloadNextMedia` | `PreloadPreviousOrNextHandler` | Preload next media. |

---

Usage

###### Basic usage

```tsx
"use client";

import { useEffect, useState } from "react";
import { addItemsUUID } from "@alessiofrittoli/react-media-player/utils";
import {
useMediaPlayerController,
useMediaPreload,
type Media,
type Queue,
} from "@alessiofrittoli/react-media-player";

interface Playlist extends Queue {
name?: string;
}

const queue: Playlist = {
name: "Playlist name",
items: addItemsUUID([
{
src: "/song-1.mp3",
type: "audio",
},
{
src: "/song-2.mp3",
type: "audio",
},
]),
};

export const MyComponent: React.FC = () => {
const [media, setMedia] = useState();

const controller = useMediaPlayerController({ queue, media });
const { preloadPreviousMedia, preloadNextMedia } = useMediaPreload({
controller,
});
const { isPlaying, hasPrevious, hasNext, previous, next, togglePlayPause } =
controller;

useEffect(() => {
setMedia(new Audio());
}, []);

return (
<>
{
if (!hasPrevious) return;
preloadPreviousMedia();
}}
onClick={() => {
if (!hasPrevious) return;
previous();
}}
>
Previous

{!isPlaying ? "Play" : "Pause"}
{
if (!hasNext) return;
preloadNextMedia();
}}
onClick={() => {
if (!hasNext) return;
next();
}}
>
Next

>
);
};
```

---

###### Override `checkConnection` option

Returns 'metadata' for slow-2g or 2g connections

```ts
import { useMediaPreload } from "@alessiofrittoli/react-media-player";

const { preloadNextMedia } = useMediaPreload({
controller,
checkConnection = true, // preload strategy may use `metadata` if connection is `slow-2g` or `2g`
});

preloadNextMedia(false); // preload strategy will be `auto` ignoring previously passed `checkConnection` option
```

---

##### `useMediaSession`

Hook into MediaSession API for controlling media playback through system controls.

Manages MediaSession state and action handlers for play, pause, stop, seek, previous, and next operations.
Synchronizes the native media element's playback state with the MediaSession API and handles user interactions
through system media controls (e.g., keyboard shortcuts, media control buttons).

Parameters

| Parameter | Type | Description |
| ------------------------ | --------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `options` | `UseMediaSessionOptions` | An object defining options and callbacks. |
| `options.media` | `HTMLMediaElement` | The `HTMLMediaElement`. |
| `options.register` | `boolean` | Indicates whether to register the action handlers. |
| | | ⚠️ It is better to set `register` to `true` once and only after `media.play()` has been called. |
| `options.onPlay` | `MediaSessionActionHandler` | A custom callback executed once user requested to play the media through browser/device controls. |
| `options.onPause` | `MediaSessionActionHandler` | A custom callback executed once user requested to pause the media through browser/device controls. |
| `options.onStop` | `MediaSessionActionHandler` | A custom callback executed once user requested to stop the media through browser/device controls. |
| | | ⚠️ Stop requests always depend on browser support. |
| `options.onPrev` | `MediaSessionActionHandler` | A custom callback executed once user requested to play the previous media through browser/device controls. |
| | | ⚠️ Please note that if no `onPrev` function is given, the MediaSession functionality will not be enabled. |
| `options.onNext` | `MediaSessionActionHandler` | A custom callback executed once user requested to play the next media through browser/device controls. |
| | | ⚠️ Please note that if no `onNext` function is given, the MediaSession functionality will not be enabled. |
| `options.onSeekBackward` | `MediaSessionActionHandler` | A custom callback executed once user requested to seek backward through browser/device controls. |
| `options.onSeekForward` | `MediaSessionActionHandler` | A custom callback executed once user requested to seek forward through browser/device controls. |
| `options.onSeekTo` | `MediaSessionActionHandler` | A custom callback executed once user requested to seek to a specific time through browser/device controls. |

---

Usage

```ts
import {
PlayerState,
useMediaSession,
useMediaPlayerController,
} from "@alessiofrittoli/react-media-player";

const { state, hasNext, hasPrevious, togglePlayPause, stop, previous, next } =
useMediaPlayerController({ queue, media });

useMediaSession({
media,
register: state !== PlayerState.STOPPED,
onPlay: togglePlayPause,
onPause: togglePlayPause,
onStop: stop,
onPrev: hasPrevious ? previous : undefined,
onNext: hasNext ? next : undefined,
});
```

---

##### `useMediaSessionPiP`

Hook into MediaSession Picture-in-Picture requests.

_Useful resources_

- [Document Picture-in-Picture API](https://npmjs.com/package/@alessiofrittoli/web-utils#document-picture-in-picture)
- [Media Artwork Picture-in-Picture API](https://www.npmjs.com/package/@alessiofrittoli/media-utils#openartworkpictureinpicture)

Parameters

| Parameter | Type | Description |
| -------------------- | --------------------------- | ---------------------------------------------------------------- |
| `options` | `UseMediaSessionPiPOptions` | An object defining options and callbacks. |
| `options.register` | `boolean` | Indicates whether to register the action handler. |
| | | ⚠️ Enter PiP requests always depends on browser support. |
| `options.onEnterPiP` | `MediaSessionActionHandler` | A custom callback executed once the user requested to enter PiP. |

---

Usage

```tsx
"use client";

import { useCallback, useState, type ReactPortal } from "react";
import {
PlayerState,
useMediaSessionPiP,
useMediaPlayerController,
} from "@alessiofrittoli/react-media-player";
import {
openDocumentPictureInPicture,
isDocumentPictureInPictureSupported,
} from "@alessiofrittoli/web-utils";
import { openArtworkPictureInPicture } from "@alessiofrittoli/media-utils/picture-in-picture";

export const MyComponent: React.FC = () => {
const [portal, setPortal] = useState();
const { state } = useMediaPlayerController({ queue, media });

const open = useCallback(async () => {
if (isDocumentPictureInPictureSupported()) {
const { window } = await openDocumentPictureInPicture();

const reactNode = (



);

const portal = createPortal(reactNode, window.document.body);

return;
}

await openArtworkPictureInPicture( ... );
}, []);

useMediaSessionPiP({
register: state !== PlayerState.STOPPED,
onEnterPiP: open,
});

return portal;
};
```

---

#### React Components

##### ``

Creates a React Audio Player and exposes [`useAudioPlayer`](#useaudioplayer) API through React Context
with [``](#audioplayerprovider-) and [``](#volumeprovider-).

This allows you to easily mix-up client and server components passed to the Component children.

Component Props

- extends [`useAudioPlayer`](#useaudioplayer) options

| Property | Type | Description |
| ---------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | Any `ReactNode` which will get access to [`useAudioPlayer`](#useaudioplayer) API and volume UI states through [`useAudioPlayerStore`](#useaudioplayerstore) and [`useVolumeStore`](#usevolumestore) respectively. |

---

Usage

###### Basic usage

```tsx
import { AudioPlayer } from "@alessiofrittoli/react-media-player";

export const AppAudioPlayer: React.FC = () => (



);
```

---

###### Accessing APIs in custom UI controls

```tsx
import { useCallback } from "react";
import { useUpdateEffect } from "@alessiofrittoli/react-hooks";
import { useAudioPlayerStore } from "@alessiofrittoli/react-media-player";

export const AudioPlayerControls: React.FC = () => {
const { isPlaying, togglePlayPause } = useAudioPlayerStore();

return (
<>
{!isPlaying ? "Play" : "Pause"}
>
);
};
```

---

##### ``

Creates a React Video Player and exposes [`useVideoPlayer`](#usevideoplayer) API through React Context
with [``](#videoplayerprovider-) and [``](#volumeprovider-).

This allows you to easily mix-up client and server components passed to the Component children.

Component Props

- extends [`useVideoPlayer`](#usevideoplayer) options

| Property | Type | Description |
| ----------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | Any `ReactNode` which will get access to [`useVideoPlayer`](#usevideoplayer) API and volume UI states through [`useVideoPlayerStore`](#usevideoplayerstore) and [`useVolumeStore`](#usevolumestore) respectively. |
| `htmlProps` | `React.ComponentProps<'video'>` | Props passed to the rendered `HTMLVideoElement`. |

---

Usage

###### Basic usage

```tsx
import { VideoPlayer } from "@alessiofrittoli/react-media-player";

export const AppVideoPlayer: React.FC = () => (



);
```

---

###### Accessing APIs in custom UI controls

```tsx
"use client";

import { useCallback } from "react";
import { useUpdateEffect } from "@alessiofrittoli/react-hooks";
import { useVideoPlayerStore } from "@alessiofrittoli/react-media-player";

export const VideoPlayerControls: React.FC = () => {
const { isPlaying, togglePlayPause } = useVideoPlayerStore();

return (
<>
{!isPlaying ? "Play" : "Pause"}
>
);
};
```

---

##### ``

Exposes [`useAudioPlayer`](#useaudioplayer) API.

---

##### ``

Exposes [`useVideoPlayer`](#usevideoplayer) API.

---

##### ``

Exposes UI state updates utilities.

This may come pretty handy when volume is controlled by multiple UI controllers and saves [`useVolume`](#usevolume) hook
from dispatching state updates whenever a 0.1 volume value has been changed by the user.

This Component is already mounted when using the [``](#audioplayer-) or [``](#videoplayer-) Component,
so no extra action is required by you.

Usage

```tsx
"use client";

import { useCallback } from "react";
import { useUpdateEffect } from "@alessiofrittoli/react-hooks";
import {
AudioPlayer,
useVolumeStore,
useAudioPlayerStore,
} from "@alessiofrittoli/react-media-player";

export const AppAudioPlayer: React.FC = () => (



);

export const AudioPlayerVolumeControl: React.FC = () => {
const { volume, setVolume: commitVolume } = useVolumeStore();

const { initialVolume, setVolume, toggleMute } = useAudioPlayerStore();

const isMute = volume <= 0;

const updateVolume = useCallback(
(volume: number) => {
setVolume(volume / 100);
commitVolume(volume / 100);
},
[setVolume, commitVolume],
);

const toggleMuteHandler = useCallback(() => {
commitVolume(toggleMute());
}, [toggleMute, commitVolume]);

useUpdateEffect(() => {
updateVolume(initialVolume * 100);
}, [initialVolume, updateVolume]);

const onChangeHandler = useCallback(
(event) => {
const input = event.target as HTMLInputElement;
const value = Number(input.value);

if (isNaN(value)) return;

const percent = (value * 100) / 100;

updateVolume(percent);
},
[updateVolume],
);

return (
<>
{!isMute ? "Mute" : "Unmute"}

>
);
};
```

---

#### Utils

##### Queue Utils

This library exposes queue utility functions exported by [`@alessiofrittoli/react-hooks`](https://npmjs.com/package/@alessiofrittoli/react-hooks)
and defines others documented below.

- See [Queue Utils](https://npmjs.com/package/@alessiofrittoli/react-hooks#queue-utils).

Usage

```ts
import {
addItemUUID,
addItemsUUID,
maybeAddItemUUID,
maybeAddItemsUUID,
findIndexByUUID,
} from "@alessiofrittoli/react-media-player/utils";

...
```

---

##### `inheritDataFromQueue`

Inherit metadata fields from a queue into a queued item payload.

Please note that the given item fields takes precedence over queue fields.

Inherited properties from the given queue

| Property | Description |
| -------------- | ---------------------------- |
| `album` | The album name of the media. |
| `artist` | The artist of the media. |
| `artwork` | The media artwork. |
| `videoArtwork` | The media video artwork. |

---

Type parameters

| Parameter | Type | Description |
| --------- | ------------------------- | ---------------------- |
| `T` | `T extends Queue = Queue` | The type of the queue. |

---

Parameters

| Parameter | Type | Default | Description |
| --------- | ------------------- | ------- | ---------------------------------------------- |
| `item` | `QueuedItemType` | - | The queued item. |
| `queue` | `T\| NewQueue` | - | The queue from which the fields are inherited. |

---

Returns

Type: `QueuedItemType`

The queued item with intherited fileds from the given `queue`.

---

Usage

```ts
import { inheritDataFromQueue } from "@alessiofrittoli/react-media-player/utils";

const item = {
uuid: "random-uuid",
title: "Track 1",
} as unknown as QueuedItemType;

const queue = {
album: "Album A",
artist: "Artist A",
artwork: "artwork-a.jpg",
videoArtwork: "video-a.jpg",
} as unknown as Queue;

inheritDataFromQueue(item, queue);

/*
Returns:
{
album : 'Album A',
artist : 'Artist A',
artwork : 'artwork-a.jpg',
videoArtwork : 'video-a.jpg',
uuid : 'random-uuid',
title : 'Track 1',
}
*/
```

---

### Development

#### Install dependencies

```bash
npm install
```

or using `pnpm`

```bash
pnpm i
```

#### Build the source code

Run the following command to test and build code for distribution.

```bash
pnpm build
```

#### [ESLint](https://www.npmjs.com/package/eslint)

Run warnings and errors checks.

```bash
pnpm lint
```

#### [Jest](https://npmjs.com/package/jest)

Run all the defined test suites by running the following:

```bash
# Run tests and watch file changes.
pnpm test:watch

# Run tests in a CI environment.
pnpm test:ci
```

- See [`package.json`](./package.json) file scripts for more info.

Run tests with coverage.

An HTTP server is then started to serve coverage files from `./coverage` folder.

⚠️ You may see a blank page the first time you run this command. Simply refresh the browser to see the updates.

```bash
pnpm test:coverage:serve
```

---

### Contributing

Contributions are truly welcome!

Please refer to the [Contributing Doc](./CONTRIBUTING.md) for more information on how to start contributing to this project.

Help keep this project up to date with [GitHub Sponsor][sponsor-url].

[![GitHub Sponsor][sponsor-badge]][sponsor-url]

---

### Security

If you believe you have found a security vulnerability, we encourage you to **_responsibly disclose this and NOT open a public issue_**. We will investigate all legitimate reports. Email `security@alessiofrittoli.it` to disclose any security vulnerabilities.

### Made with ☕




avatar






Alessio Frittoli





https://alessiofrittoli.it |
info@alessiofrittoli.it