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

@alessiofrittoli/media-utils

v1.3.0

Published

TypeScript media utility library

Readme

Table of Contents


Getting started

Run the following command to start using media-utils in your projects:

npm i @alessiofrittoli/media-utils

or using pnpm

pnpm i @alessiofrittoli/media-utils

API Reference

MIME types

getMimeType()

Get the MIME type from the given input string.

| Parameter | Type | Description | | --------- | ----------- | --------------------------------------------------------------------------------------------------------------------------- | | input | string | The input string where MIME type is extracted from. | | type | MediaType | (Optional) Accepted media type. This may be usefull when subtype matches other media types (eg. audio/mp4 - video/mp4). |


Type: MIMEType

  • The MIME type if found.
  • undefined otherwise.

import { getMimeType } from "@alessiofrittoli/media-utils";

console.log(getMimeType("/path/to/video-file.mp4")); // Outputs: 'video/mp4'
console.log(getMimeType("/path/to/audio-file.mp4", "audio")); // Outputs: 'audio/mp4'
console.log(getMimeType("/path/to/audio-file.mp3")); // Outputs: 'audio/mpeg'

getAllowedMimeTypes()

Get allowed MIME types.

| Parameter | Type | Description | | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | accept | string | (Optioanl) A string describing allowed MIME types. | | | | The format is the same accepted by HTMLInputElement.accept attribute. | | | | It could be one of the following examples: | | | | - * | | | | - image (type) | | | | - image/* ({type}/{subtype}) | | | | - image/png ({type}/{subtype}) - specific | | | | - .png (extension) | | | | - image,audio (multiple types) | | | | - image/*,audio/* (multiple {type}/{subtype}) | | | | - .png, .mp3 (multiple extensions) | | | | - .docx, audio, video/*, text/html (mixed) |


Type: (MIMEType | '*')[]

An array of MIME types based on the accept value. [ '*' ] if a wildcard or no accept is given.


import { getAllowedMimeTypes } from "@alessiofrittoli/media-utils";

console.log(getAllowedMimeTypes());
console.log(getAllowedMimeTypes("*"));
console.log(getAllowedMimeTypes("image"));
console.log(getAllowedMimeTypes("audio/*"));
console.log(getAllowedMimeTypes("image,video"));
console.log(getAllowedMimeTypes("image/*,video/*"));
console.log(getAllowedMimeTypes(".mp3,mp4"));
console.log(getAllowedMimeTypes(".jpg, .png"));
console.log(getAllowedMimeTypes("image/jpeg,.jpg"));

MediaSession

Types
MediaArtWork

The Media Artwork.

Compatible type with the global MediaImage interface.

| Property | Type | Description | | -------- | ---------- | --------------------------------------------------------------------------------------------------- | | src | UrlInput | The Media Artwork image URL. | | | | See UrlInput type for more info. | | size | number | The Media Artwork image size. | | | | Common values are: 96, 128, 192, 256, 384, 512. | | type | MIMEType | The Media Artwork image MIME type. |


Media

Defines the media.

Extends the global MediaMetadata interface.

| Property | Type | Description | | --------- | ---------------- | --------------------------------------------------------------------------------------------------- | | src | UrlInput | The media URL. | | | | See UrlInput type for more info. | | type | MIMEType | The media MIME type. | | title | string | The title of the media. | | artwork | MediaArtWork[] | The media artwork. | | | | See MediaArtWork type for more info. | | artist | string | The artist of the media. | | album | string | The album of the media. |


UpdateMediaMetadataAndPositionOptions

Defines the MediaSession update options.

| Property | Type | Description | | -------- | -------------------- | ----------------------- | | media | HTMLMediaElement | The HTMLMediaElement. | | data | Omit<Media, 'src'> | The playing media data. |


updatePositionState()

Update MediaSession position state.

| Parameter | Type | Description | | --------- | ------------------ | --------------------- | | media | HTMLMediaElement | The HTMLMediaElement. |


import { updatePositionState } from "@alessiofrittoli/media-utils";

navigator.mediaSession.setActionHandler("seekto", (details) => {
  if (typeof details.seekTime === "undefined") return;

  media.currentTime = details.seekTime;

  updatePositionState(media);
});

updateMediaMetadata()

Update MediaSession metadata.

| Parameter | Type | Description | | --------- | -------------------- | ----------------------- | | data | Omit<Media, 'src'> | The playing media data. |


import { updateMediaMetadata } from "@alessiofrittoli/media-utils";

media.play().then(() => {
  updateMediaMetadata({
    type: "audio/mpeg",
    title: "Song name",
    album: "Album name",
    artist: "Artist name",
    artwork: [
      {
        src: { pathname: "/path-to-image-96.png" },
        size: 96,
        type: "image/png",
      },
      {
        src: { pathname: "/path-to-image-128.png" },
        size: 128,
        type: "image/png",
      },
      {
        src: { pathname: "/path-to-image-192.png" },
        size: 192,
        type: "image/png",
      },
      {
        src: { pathname: "/path-to-image-256.png" },
        size: 256,
        type: "image/png",
      },
      {
        src: { pathname: "/path-to-image-384.png" },
        size: 384,
        type: "image/png",
      },
      {
        src: { pathname: "/path-to-image-512.png" },
        size: 512,
        type: "image/png",
      },
    ],
  });
});

updateMediaMetadataAndPosition()

Update MediaSession metadata and position state.

| Parameter | Type | Description | | --------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------- | | options | UpdateMediaMetadataAndPositionOptions | An object defining media HTMLMediaElement and associated data. | | | | See UpdateMediaMetadataAndPositionOptions for more info. |


import { updateMediaMetadataAndPosition } from "@alessiofrittoli/media-utils";

media.play().then(() => {
  updateMediaMetadataAndPosition({
    media,
    data: {
      type: "audio/mpeg",
      title: "Song name",
      album: "Album name",
      artist: "Artist name",
      artwork: [
        {
          src: { pathname: "/path-to-image-96.png" },
          size: 96,
          type: "image/png",
        },
        {
          src: { pathname: "/path-to-image-128.png" },
          size: 128,
          type: "image/png",
        },
        {
          src: { pathname: "/path-to-image-192.png" },
          size: 192,
          type: "image/png",
        },
        {
          src: { pathname: "/path-to-image-256.png" },
          size: 256,
          type: "image/png",
        },
        {
          src: { pathname: "/path-to-image-384.png" },
          size: 384,
          type: "image/png",
        },
        {
          src: { pathname: "/path-to-image-512.png" },
          size: 512,
          type: "image/png",
        },
      ],
    },
  });
});

AudioEngine

Manages volume manipulation for HTML media elements.

Provides methods for fading volume with customizable tweening, and utilities for converting between normalized volume values (0.0-1.0) and linear gain values using decibel-based calculations that better match human perception of audio loudness.

  • Refer to the Audio Engine Manager section to understand how instance caching prevents redundant AudioEngine instantiation.
AudioEngine Types
TickHandler

Callback executed on each interpolation tick.

| Parameter | Type | Description | | --------- | -------- | ------------------------------- | | value | number | The current interpolated value. |


FadeVolumeOptions

| Property | Type | Default | Description | | ---------- | ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------- | | to | number | - | Defines the final volume to set [0-1]. | | duration | number | 200 | (Optional) Duration of the tween in milliseconds. | | onTick | TickHandler | - | Callback executed on each interpolation tick. | | | | | See TickHandler types for more info. | | onEnd | TickHandler | - | (Optional) Callback executed when the interpolation completes. | | | | | See TickHandler types for more info. | | easing | EasingFn | Easing.linear | (Optional) Easing function used to transform the linear time progression. | | | | | See EasingFn types for more info. | | Hz | number | 120 | (Optional) Custom tick rate in Hz. |


Methods
AudioEngine.fade()

Fade media volume.

| Parameter | Type | Description | | --------- | ------------------- | ------------------------------------------------------------ | | options | FadeVolumeOptions | An object defining customization and callbacks. | | | | See FadeVolumeOptions for more info. |


import { AudioEngine } from '@alessiofrittoli/media-utils/audio'

const audio  = new Audio( ... )
const engine = new AudioEngine( audio )

audio.volume = 0
audio.play()
 .then( () => {
   // Fade volume to 0.5 over 2 seconds
   engine.fade( {
     to         : 1,
     duration   : 2000,
   } )
 } )

Static methods
AudioEngine.VolumeToGain()

Converts a volume value (0.0 to 1.0) to a linear gain value.

The returned value will better match the human perceived volume.

| Parameter | Type | Description | | --------- | -------- | ----------------------------------------------------------------------------- | | volume | number | The volume value, where 0 represents silence and 1 represents maximum volume. |


Type: number.

The corresponding linear gain value, or 0 if the gain is effectively inaudible.


import { AudioEngine } from '@alessiofrittoli/media-utils/audio'

const audio  = new Audio( ... )
const engine = new AudioEngine( audio )

audio.volume = 0
audio.play()
  .then( () => {
    engine.fade( {
      to: AudioEngine.VolumeToGain( 0.5 ),
      onEnd() {
        console.log( audio.volume ) // Outputs: `0.03162277660168379`
      },
    } )
  } )

AudioEngine.GainToVolume()

Converts a linear gain value to a normalized volume value between 0 and 1.

| Parameter | Type | Description | | --------- | -------- | --------------------------------- | | gain | number | The linear gain value to convert. |


Type: number.

The normalized volume value in the range [0, 1].


import { AudioEngine } from '@alessiofrittoli/media-utils/audio'

const audio  = new Audio( ... )
const engine = new AudioEngine( audio )

audio.volume = 0
audio.play()
  .then( () => {
    engine.fade( {
      to: AudioEngine.VolumeToGain( 0.5 ),
      onEnd() {
        console.log( audio.volume ) // Outputs: `0.03162277660168379`
        console.log( AudioEngine.GainToVolume( audio.volume ) ) // Outputs: `0.5`
      },
    } )
  } )

AudioEngine.normalize()

Conditionally normalize volume using AudioEngine.VolumeToGain.

This method acts as a wrapper and has the same API of AudioEngine.VolumeToGain() but it accepts a boolean value as second argument that allows you to quickly opt-in/opt-out based on an incoming variable, thus there is no need to call AudioEngine.VolumeToGain() conditionally.

import { AudioEngine } from '@alessiofrittoli/media-utils/audio'

// `normalizeAudio` may come from global settings
const audio  = new Audio( ... )
const engine = new AudioEngine( audio )

audio.volume = 0
audio.play()
  .then( () => {
    engine.fade( {
      to: AudioEngine.normalize( 0.5, normalizeAudio ),
      onEnd() {
        console.log( audio.volume ) // Outputs: `0.03162277660168379` if `normalizeAudio` is set to `true`, 0.5 otherwise.
      },
    } )
  } )

AudioEngine.denormalize()

Conditionally denormalize volume using AudioEngine.GainToVolume.

This method acts as a wrapper and has the same API of AudioEngine.GainToVolume() but it accepts a boolean value as second argument that allows you to quickly opt-in/opt-out based on an incoming variable, thus there is no need to call AudioEngine.GainToVolume() conditionally.

import { AudioEngine } from '@alessiofrittoli/media-utils/audio'

// `normalizeAudio` may come from global settings
const audio  = new Audio( ... )
const engine = new AudioEngine( audio )

audio.volume = 0
audio.play()
  .then( () => {
    engine.fade( {
      to: AudioEngine.normalize( 0.5, normalizeAudio ),
      onEnd() {
        console.log(
          AudioEngine.denormalize( audio.volume, normalizeAudio )
        )
      },
    } )
  } )

Audio Engine Manager

The Audio Engine Manager is responsible for maintaining a one-to-one association between an HTMLMediaElement and its corresponding AudioEngine instance. Its primary goal is to prevent unnecessary re-creation of AudioEngine objects when interacting with the same media element.

When working with audio processing APIs, repeatedly instantiating AudioEngine for the same HTMLMediaElement would:

  • Introduce unnecessary computational overhead.
  • Potentially duplicate internal state.
  • Increase memory usage.
  • Lead to inconsistent behavior.

The manager ensures that each media element has exactly one AudioEngine instance.

getEngine()

Get the AudioEngine associated with the given media. If none exists, it lazily creates a new instance.

| Parameter | type | Decription | | --------- | ------------------ | --------------------- | | media | HTMLMediaElement | The HTMLMediaElement. |


Type: AudioEngine.

The AudioEngine associated with the given media or a new AudioEngine instance if none exists.


import { getEngine } from "@alessiofrittoli/media-utils/audio";

const mute = (media: HTMLMediaElement) => {
  getEngine(media).fade({ to: 0 });
};

const unmute = (media: HTMLMediaElement) => {
  getEngine(media).fade({ to: 1 });
};

destroyEngine()

Proactively delete the AudioEngine associated with the given media to free resources.

| Parameter | type | Decription | | --------- | ------------------ | --------------------- | | media | HTMLMediaElement | The HTMLMediaElement. |


Type: boolean.

  • true if the element was successfully removed.
  • false if no engine was registered.

import { getEngine, destroyEngine } from "@alessiofrittoli/media-utils/audio";

const stop = (media: HTMLMediaElement) => {
  getEngine(media).fade({
    to: 0,
    onEnd() {
      media.pause();
      destroyEngine(media);
    },
  });
};

Audio Utilities
fadeVolume()

Fade media volume.

This utility function acts as a wrapper handling AudioEngine instances for you using the Audio Engine Manager.

| Parameter | type | Decription | | --------- | ------------------- | ------------------------------------------------------------ | | media | HTMLMediaElement | The HTMLMediaElement. | | options | FadeVolumeOptions | An object defining customization and callbacks. | | | | See FadeVolumeOptions for more info. |


import { fadeVolume } from "@alessiofrittoli/media-utils/audio";

const play = (media: HTMLMediaElement) => {
  media.volume = 0;
  media.play().then(() => {
    fadeVolume(media, { to: 1 });
  });
};

Media Playback

Media Playback types

PlayMediaOptions

An object defining play options and callbacks.

It extends UpdateMediaMetadataAndPositionOptions and FadeVolumeOptions interfaces and adds/overrides the following properties.

| Property | Type | Default | Description | | --------- | ------------------------------- | ------- | -------------------------------------------------------------------------------- | | volume | number | - | Defines the final volume to set [0-1]. | | | | | - overrides FadeVolumeOptions['to'] | | fade | number | 200 | (Optional) A custom volume fade duration in milliseconds. | | | | | - overrides FadeVolumeOptions['duration'] | | onError | ( error: MediaError ) => void | - | (Optional) A custom callback executed when an error occurs when playing a media. |


PauseMediaOptions

An object defining pause options and callbacks.

It extends PlayMediaOptions and omits unnecessary properties.


playMedia()

Play the given media.

| Parameter | Type | Description | | --------- | ------------------ | ---------------------------------------------------------- | | options | PlayMediaOptions | An object defining play options and callbacks. | | | | See PlayMediaOptions for more info. |


Type: Promise<void>

A new Promise which resolves once media.play() promise is resolved.


import { playMedia } from "@alessiofrittoli/media-utils";

const media = new Audio( ... )

play.addEventListener("click", () => {

  playMedia( {
    media,
    volume: 1,
    fade: 800,
    data: {
      type: 'audio/mpeg',
      title: 'Media title',
    },
    onError( error ) {
      switch ( error.code ) {
        case MediaError.MEDIA_ERR_ABORTED:
          console.error( 'The fetching of the associated resource was aborted by the user\'s request.' )
          break
        case MediaError.MEDIA_ERR_NETWORK:
          console.error( 'Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.' )
          break
        case MediaError.MEDIA_ERR_DECODE:
          console.error( 'Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error.' )
          break
        case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
          console.error( 'The associated resource or media provider object (such as a MediaStream) has been found to be unsuitable.' )
          break
        default:
          console.error( 'Unknow error.' )
      }
    },
  } )

});

pauseMedia()

Pause the given media.

| Parameter | Type | Description | | --------- | ------------------- | --------------------------------------------------------------------------- | | options | PauseMediaOptions | An object defining pause options and callbacks. | | | | See PauseMediaOptions for more info. for more info. |


import { pauseMedia } from "@alessiofrittoli/media-utils";

const media = new Audio( ... )

pause.addEventListener("click", () => {

  pauseMedia( { media, fade: 800 } )

});

Image Video Stream

Types
CreateImageVideoStreamOptions interface

Options for rendering an image into a video stream.

It optionally accepts a previously created HTMLVideoElement where image will get streamed into.

| Property | Type | Default | Description | | -------- | ------------------------ | ------- | ---------------------------------------------------------------------------------------- | | media | Blob\|HTMLImageElement | - | The image source to render. It can be either a Blob or a preloaded HTMLImageElement. |


RenderOptions interface

Rendering options.

| Property | Type | Default | Description | | ------------- | ------------------------ | --------- | --------------------------------------------------------------------------------------------------- | | media | Blob\|HTMLImageElement | - | (Optional) The image source to render. It can be either a Blob or a preloaded HTMLImageElement. | | video | HTMLVideoElement | - | (Optional) A previously created HTMLVideoElement where image get streamed into. | | aspectRatio | number | - | (Optional) Target aspect ratio (width / height) of the rendering area. | | | | | If omitted, the original media aspect ratio is preserved. | | fit | 'contain'\|'cover' | contain | (Optional) Defines how the media should fit inside the rendering area. | | | | | - contain (default) behaves like object-fit: contain | | | | | - cover behaves like object-fit: cover | | | | | Only applies when aspectRatio is specified. |


RenderResult interface

The rendering result.

| Property | Type | Description | | -------- | ------------------ | ----------------------------------------------------- | | video | HTMLVideoElement | The HTMLVideoElement where image get streamed into. |


RenderHandler type

Render a new image to the video stream.

type RenderHandler = (options?: RenderOptions) => Promise<RenderResult>;

DestroyHandler type

Stop stream tracks and release allocated resources.

type DestroyHandler = () => void;

CreateImageVideoStream interface

Defines the returned result of rendering an image into a video.

| Property | Type | Description | | --------- | ---------------- | ---------------------------------------------------- | | render | RenderHandler | Render a new image to the video stream. | | | | - See RenderHandler type. | | destroy | DestroyHandler | Stop stream tracks and release allocated resources. | | | | - See DestroyHandler type. |


createImageVideoStream

Render an image into a video stream.

If the provided media (HTMLImageElement) fails to load, a 1x1 black frame is rendered instead and the promise does not reject.

| Parameter | Type | Description | | --------- | ------------------------------- | -------------------------------------------------------------------------------------------- | | options | CreateImageVideoStreamOptions | Rendering options. | | | | - See CreateImageVideoStreamOptions interface. |


Type: Promise<CreateImageVideoStream>

A new Promise that resolves the allocated rendering resources.


Fetch and render image Blob
import { fetch } from "@alessiofrittoli/fetcher";
import {
  getFallbackImage,
  createImageVideoStream,
} from "@alessiofrittoli/media-utils";

const createImageStream = async () => {
  const { data: media, error } = await fetch<Blob>("/path-to/image.png", {
    responseType: "blob",
  });

  if (error) {
    return createImageVideoStream({ media: getFallbackImage() });
  }

  return createImageVideoStream({ media });
};

Custom aspect ratio
import { createImageVideoStream } from "@alessiofrittoli/media-utils";

const createImageStream = () => {
  const media = document.createElement("img");
  media.src = "/path-to/image.png";

  return createImageVideoStream({
    media,
    aspectRatio: 16 / 9,
    fit: "cover", // or 'contain'
  });
};

Update streamed content
import { createImageVideoStream } from "@alessiofrittoli/media-utils";

const createImageStream = async () => {
  const media = document.createElement("img");
  media.src = "/path-to/image.png";

  const { video, render } = await createImageVideoStream({
    media,
    aspectRatio: 16 / 9,
    fit: "cover", // or 'contain'
  });

  // update streamed content after 5 seconds
  setTimeout(() => {
    media.src = "/path-to/image-2.png";

    await render({ aspectRatio: 1 });
  }, 5000);

  return video;
};

Using a previously created video
import { createImageVideoStream } from "@alessiofrittoli/media-utils";

const createImageStream = async () => {
  const video = document.createElement("video");
  video.src = "/path-to/video.mp4";

  const media = document.createElement("img");
  media.src = "/path-to/image.png";

  // replace original video content with streamed image
  await createImageVideoStream({
    media,
    video,
    aspectRatio: 16 / 9,
    fit: "cover", // or 'contain'
  });

  return video;
};

Free allocated resources
import { createImageVideoStream } from "@alessiofrittoli/media-utils";

const createImageStream = async () => {
  const media = document.createElement("img");
  media.src = "/path-to/image.png";

  // replace original video content with streamed image
  const { video, destroy } = await createImageVideoStream({
    media,
    aspectRatio: 16 / 9,
    fit: "cover", // or 'contain'
  });

  // free allocated resources after 5 seconds
  setTimeout(() => {
    destroy();
  }, 5000);

  return video;
};

Media Artwork Picture-in-Picture

Types
OpenPictureInPictureCommonOptions interface

Defines common configuration options for opening the media in Picture-in-Picture mode.

| Parameter | Type | Description | | --------- | ------------ | -------------------------------------------------------------------------- | | onQuit | () => void | (Optional) A callback to execute when Picture-in-Picture window is closed. |


OpenImagePictureInPictureOptions interface

Defines configuration options for opening the image in Picture-in-Picture mode.

| Parameter | Type | Description | | --------- | ---------------------------------------------------- | --------------------------------------------------- | | media | CreateImageVideoStreamOptions[ 'media' ]\|UrlInput | The media to render in a Picture-in-Picture window. |


OpenImagePictureInPicture type

Defines the returned result of opening a rendered image into a video Picture-in-Picture window.


OpenVideoArtworkPictureInPictureOptions interface

Defines configuration options for opening the video in Picture-in-Picture window.

| Parameter | Type | Description | | --------- | ---------------------------- | ------------------------------------------------- | | media | HTMLVideoElement\|UrlInput | The media to open in a Picture-in-Picture window. |


OpenVideoArtworkPictureInPicture interface

Defines the returned result of opening a video into a Picture-in-Picture window.

| Property | Type | Description | | -------- | ------------------ | --------------------------------------------- | | video | HTMLVideoElement | The HTMLVideoElement in Picture-in-Picture. |


OpenArtworkPictureInPictureOptions interface

Defines configuration options for opening media artwork in Picture-in-Picture window.

| Parameter | Type | Description | | --------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | media | CreateImageVideoStreamOptions['media']\| HTMLVideoElement\|MediaArtWork | The media to open in a Picture-in-Picture window. | | | | - See CreateImageVideoStreamOptions interface. | | | | - See MediaArtWork interface. |


OpenArtworkPictureInPicture interface

Defines the returned result of opening a media artwork into a Picture-in-Picture window.

It could be OpenImagePictureInPicture or OpenVideoArtworkPictureInPicture.


isPictureInPictureSupported

Checks if the Picture-in-Picture API is supported by the current browser.


requiresPictureInPictureAPI

Validates that the Picture-in-Picture API is supported by the current browser.

  • Throws a new Exception with code ErrorCode.PIP_NOT_SUPPORTED if the Picture-in-Picture API is not supported.

openImagePictureInPicture

Opens an image in Picture-in-Picture window.

Once Picture-in-Picture get closed by the user, allocated resources are automatically freed.

| Parameter | Type | Description | | --------- | ---------------------------------- | -------------------------------------------------------------------------------------------------- | | options | OpenImagePictureInPictureOptions | Configuration options for opening the image in PiP window. | | | | - See OpenImagePictureInPictureOptions interface. |


Type: Promise<OpenImagePictureInPicture>

A new Promise that resolves to the Picture-in-Picture result containing the video element and destroy function.


Simple usage
import {
  ErrorCode,
  openImagePictureInPicture,
} from "@alessiofrittoli/media-utils";

try {
  navigator.mediaSession.setActionHandler("enterpictureinpicture", async () => {
    try {
      await openImagePictureInPicture({
        media: { pathname: "path/to/image.png" },
        aspectRatio: 1,
        fit: "cover",
        onQuit: () => console.log("Picture-in-Picture closed"),
      });
    } catch (_err) {
      const err = _err as Error;

      const error = Exception.isException<string, ErrorCode>(err)
        ? err
        : new Exception(err.message, {
            code: ErrorCode.UNKNOWN,
            name: err.name,
            cause: err,
          });
      switch (error.code) {
        case ErrorCode.PIP_NOT_SUPPORTED:
          alert("Picture-In-Picture is not supported by the current browser.");
          break;
        case ErrorCode.RENDERING_CONTEXT_UNAVAILABLE:
          alert("Couldn't render image in Picture-in-Picture.");
          break;
        default:
          console.error(error);
      }
    }
  });
} catch (error) {
  console.warn(
    'Warning! The "enterpictureinpicture" media session action is not supported.',
    error,
  );
}

Update image Picture-in-Picture content
import {
  openImagePictureInPicture,
  type OpenImagePictureInPicture,
} from "@alessiofrittoli/media-utils";

let resources: OpenImagePictureInPicture;

navigator.mediaSession.setActionHandler("enterpictureinpicture", async () => {
  resources = await openImagePictureInPicture({
    media: { pathname: "/path/to/image.png" },
  });
});

navigator.mediaSession.setActionHandler("nexttrack", async () => {
  resources = await openImagePictureInPicture({
    media: { pathname: "/path/to/image.png" },
    ...resources,
  });
});

openVideoArtworkPictureInPicture

Opens a video artwork element in Picture-in-Picture window.

This function is intended for rendering a short song artwork video. This is not suitable if your're looking for a proper video Picture-in-Picture since it simple as calling video.requestPictureInPicture().

Supports both HTMLVideoElement instances and URL inputs. When a URL is provided, a video element is created and the URL is set as its source.

| Parameter | Type | Description | | --------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | options | OpenVideoArtworkPictureInPictureOptions | Configuration options for opening the video in Picture-in-Picture. | | | | - See OpenVideoArtworkPictureInPictureOptions interface. |


Type: Promise<OpenVideoArtworkPictureInPicture>

A new Promise that resolves with an object containing the video element displayed in Picture-in-Picture mode.


import { openVideoArtworkPictureInPicture } from "@alessiofrittoli/media-utils";

// With a URL
const { video } = await openVideoArtworkPictureInPicture({
  media: "/song-artwork-video.mp4",
  onQuit: () => console.log("Picture-in-Picture closed"),
});

// With an HTMLVideoElement
const media = document.querySelector("video");
media.src = "/song-artwork-video.mp4";

const { video } = await openVideoArtworkPictureInPicture({ media });

openArtworkPictureInPicture

Opens the given media in Picture-in-Picture window.

It easly handles images and videos rendering using openImagePictureInPicture or openVideoArtworkPictureInPicture.

Once Picture-in-Picture get closed by the user, allocated resources are automatically freed.

| Parameter | Type | Description | | --------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------ | | options | OpenArtworkPictureInPictureOptions | Configuration options for opening the media in Picture-in-Picture window. | | | | - See OpenArtworkPictureInPictureOptions interface. |


Type: Promise<OpenArtworkPictureInPicture>

A new Promise that resolves to the Picture-in-Picture result.


Simple usage
import { openArtworkPictureInPicture } from "@alessiofrittoli/media-utils";

const result = await openArtworkPictureInPicture({
  media: {
    type: "image/png",
    src: "/path/to/image.png",
  },
  aspectRatio: 1,
  fit: "cover",
});

// update
await openArtworkPictureInPicture({
  media: {
    type: "video/mp4",
    src: "/path/to/video.mp4",
  },
  ...result,
});

Using media session image
import {
  openArtworkPictureInPicture,
  updateMediaMetadata,
  type MediaSessionMetadata,
} from "@alessiofrittoli/media-utils";

const data: MediaSessionMetadata = {
  title: "Test Song",
  artist: "Test Artist",
  album: "Test Album",
  artwork: [
    { src: "/path/to/song-artwork.png", sizes: 128, type: "image/png" },
  ],
};

updateMediaMetadata(data);

await openArtworkPictureInPicture();

Handling errors
import {
  ErrorCode,
  openArtworkPictureInPicture,
} from "@alessiofrittoli/media-utils";

try {
  await openArtworkPictureInPicture({ ... });
} catch (_err) {
  const err = _err as Error;

  const error = Exception.isException<string, ErrorCode>(err)
    ? err
    : new Exception(err.message, {
        code: ErrorCode.UNKNOWN,
        name: err.name,
        cause: err,
      });
  switch (error.code) {
    case ErrorCode.PIP_NOT_SUPPORTED:
      alert("Picture-In-Picture is not supported by the current browser.");
      break;
    case ErrorCode.RENDERING_CONTEXT_UNAVAILABLE:
      alert("Couldn't render image in Picture-in-Picture.");
      break;
    default:
      console.error(error);
  }
}

Utilities

formatMediaTiming()

Formats a time value in seconds into a human-readable media timing string (M:SS or H:MM:SS).

| Parameter | Type | Default | Description | | ----------- | --------- | ------- | ------------------------------------------------------------ | | time | number | - | The time value in seconds to format. | | showHours | boolean | false | (Optional) Whether to include hours in the formatted output. |


Type: string

A formatted time string in the format "M:SS" or "H:MM:SS" if showHours is true. If the time is 0 or invalid, returns "0:00".


import { formatMediaTiming } from "@alessiofrittoli/media-utils";

console.log(formatMediaTiming(45)); // Outputs: "0:45"
console.log(formatMediaTiming(90)); // Outputs: "1:30"
console.log(formatMediaTiming(125)); // Outputs: "2:05"

console.log(formatMediaTiming(3661, true)); // Outputs: "1:01:01"

console.log(formatMediaTiming(0)); // Outputs: "0:00"
console.log(formatMediaTiming(-NaN)); // Outputs: "0:00"
console.log(formatMediaTiming(NaN)); // Outputs: "0:00"
console.log(formatMediaTiming(-Infinity)); // Outputs: "0:00"
console.log(formatMediaTiming(Infinity)); // Outputs: "0:00"

console.log(formatMediaTiming(0, true)); // Outputs: "0:00:00"
console.log(formatMediaTiming(-NaN, true)); // Outputs: "0:00:00"
console.log(formatMediaTiming(NaN, true)); // Outputs: "0:00:00"
console.log(formatMediaTiming(-Infinity, true)); // Outputs: "0:00:00"
console.log(formatMediaTiming(Infinity, true)); // Outputs: "0:00:00"

getPreloadStrategy

Determines the optimal preload strategy for media elements based on network conditions.

It returns a value that corresponds to the preload attribute used in HTML media elements like <video> and <audio>.

Type: HTMLMediaElement[ 'preload' ]

The recommended preload attribute value ('none', 'metadata', or 'auto').

  • Returns 'auto' if network connection information is unavailable
  • Returns 'none' if data saver mode is enabled
  • Returns 'metadata' for slow-2g or 2g connections
  • Returns 'auto' for faster connections (3g, 4g, etc.)


Development

Install depenendencies

npm install

or using pnpm

pnpm i

Build the source code

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

pnpm build

ESLint

warnings / errors check.

pnpm lint

Jest

Run all the defined test suites by running the following:

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

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

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.

test:coverage:serve

Contributing

Contributions are truly welcome!

Please refer to the Contributing Doc for more information on how to start contributing to this project.

Help keep this project up to date with GitHub Sponsor.

GitHub Sponsor


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 [email protected] to disclose any security vulnerabilities.

Made with ☕