@alessiofrittoli/media-utils
v1.3.0
Published
TypeScript media utility library
Maintainers
Readme
Table of Contents
Getting started
Run the following command to start using media-utils in your projects:
npm i @alessiofrittoli/media-utilsor using pnpm
pnpm i @alessiofrittoli/media-utilsAPI 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.
undefinedotherwise.
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
AudioEngineinstantiation.
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.
trueif the element was successfully removed.falseif 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.
- Extends
RenderOptionsinterface.
| 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.
- Extends
RenderResultinterface.
| 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.
- Extends
OpenPictureInPictureCommonOptionsinterface. - Extends
CreateImageVideoStreamOptionsinterface. - Partially extends
CreateImageVideoStreaminterface.
| 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.
- Alias for
CreateImageVideoStreaminterface
OpenVideoArtworkPictureInPictureOptions interface
Defines configuration options for opening the video in Picture-in-Picture window.
- Extends
OpenPictureInPictureCommonOptionsinterface. - Partially extends
OpenVideoArtworkPictureInPictureinterface.
| 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.
- Extends
OpenImagePictureInPictureOptionsinterface. - Extends
OpenVideoArtworkPictureInPictureOptionsinterface.
| 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
Exceptionwith codeErrorCode.PIP_NOT_SUPPORTEDif 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.
- See
OpenImagePictureInPicturefor more info.
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.
- See
OpenVideoArtworkPictureInPicturefor more info.
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 installor using pnpm
pnpm iBuild the source code
Run the following command to test and build code for distribution.
pnpm buildESLint
warnings / errors check.
pnpm lintJest
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- See
package.jsonfile 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.
test:coverage:serveContributing
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.
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.
