@banou/media-player 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -3
- package/dist/engine/index.d.ts +10 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/picture-in-picture.d.ts +27 -0
- package/dist/engine/playback.d.ts +45 -0
- package/dist/engine/source-buffer.d.ts +12 -0
- package/dist/engine/subtitles.d.ts +36 -0
- package/dist/engine/thumbnails.d.ts +22 -0
- package/dist/engine-Dmc7JSd9.js +470 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +1610 -0
- package/dist/react/components/chrome.d.ts +12 -0
- package/dist/react/components/control-bar.d.ts +2 -0
- package/dist/react/components/overlay.d.ts +4 -0
- package/dist/react/components/playback-slider.d.ts +2 -0
- package/dist/react/components/progress-bar.d.ts +2 -0
- package/dist/react/components/settings.d.ts +2 -0
- package/dist/react/components/sound.d.ts +7 -0
- package/{build → dist/react}/components/tooltip-display.d.ts +8 -8
- package/dist/react/components/volume-slider.d.ts +6 -0
- package/dist/react/hooks/use-drag-value.d.ts +23 -0
- package/dist/react/hooks/use-picture-in-picture.d.ts +2 -0
- package/dist/react/hooks/use-playback.d.ts +8 -0
- package/dist/react/hooks/use-thumbnails.d.ts +12 -0
- package/dist/react/media.d.ts +98 -0
- package/dist/react/player.d.ts +26 -0
- package/dist/react/source-feature.d.ts +106 -0
- package/dist/react/video-player.d.ts +83 -0
- package/dist/utils/source.d.ts +30 -0
- package/dist/utils/track-label.d.ts +31 -0
- package/dist/utils/volume-utils.d.ts +6 -0
- package/package.json +58 -37
- package/src/lib/engine/index.ts +14 -0
- package/src/lib/engine/picture-in-picture.ts +250 -0
- package/src/lib/engine/playback.ts +352 -0
- package/src/lib/engine/source-buffer.ts +61 -0
- package/src/lib/engine/subtitles.ts +210 -0
- package/src/lib/engine/thumbnails.ts +137 -0
- package/src/lib/globals.d.ts +8 -0
- package/src/lib/index.tsx +51 -0
- package/src/lib/react/components/chrome.tsx +130 -0
- package/src/{components → lib/react/components}/control-bar.tsx +74 -96
- package/src/lib/react/components/overlay.tsx +125 -0
- package/src/{components → lib/react/components}/playback-slider.tsx +30 -52
- package/src/lib/react/components/progress-bar.tsx +341 -0
- package/src/lib/react/components/settings.tsx +384 -0
- package/src/lib/react/components/sound.tsx +133 -0
- package/src/{components → lib/react/components}/tooltip-display.tsx +15 -12
- package/src/{components → lib/react/components}/volume-slider.tsx +32 -46
- package/src/lib/react/hooks/use-drag-value.ts +73 -0
- package/src/lib/react/hooks/use-picture-in-picture.ts +36 -0
- package/src/lib/react/hooks/use-playback.ts +146 -0
- package/src/lib/react/hooks/use-thumbnails.ts +78 -0
- package/src/lib/react/media.ts +124 -0
- package/src/lib/react/player.ts +20 -0
- package/src/lib/react/source-feature.ts +108 -0
- package/src/lib/react/video-player.tsx +223 -0
- package/src/lib/utils/fonts.ts +160 -0
- package/src/lib/utils/source.ts +60 -0
- package/src/lib/utils/track-label.ts +51 -0
- package/src/lib/utils/volume-utils.ts +13 -0
- package/build/components/chrome.d.ts +0 -7
- package/build/components/control-bar.d.ts +0 -6
- package/build/components/overlay.d.ts +0 -4
- package/build/components/playback-slider.d.ts +0 -2
- package/build/components/progress-bar.d.ts +0 -1
- package/build/components/settings.d.ts +0 -2
- package/build/components/sound.d.ts +0 -4
- package/build/components/volume-slider.d.ts +0 -6
- package/build/index.d.ts +0 -26
- package/build/index.js +0 -3416
- package/build/main.d.ts +0 -1
- package/build/state-machines/data-source.d.ts +0 -27
- package/build/state-machines/index.d.ts +0 -34038
- package/build/state-machines/media-properties.d.ts +0 -45
- package/build/state-machines/media-source.d.ts +0 -34
- package/build/state-machines/media.d.ts +0 -8558
- package/build/state-machines/subtitles.d.ts +0 -56
- package/build/state-machines/thumbnails.d.ts +0 -24
- package/build/state-machines/utils.d.ts +0 -26
- package/build/utils/actor-utils.d.ts +0 -2
- package/build/utils/context.d.ts +0 -14
- package/build/utils/index.d.ts +0 -4
- package/build/utils/languages.d.ts +0 -260
- package/build/utils/mp4box.d.ts +0 -61
- package/build/utils/use-local-storage.d.ts +0 -3
- package/build/utils/use-scrub.d.ts +0 -11
- package/build/utils/volume-utils.d.ts +0 -17
- package/build/utils/window-height.d.ts +0 -2
- package/src/components/chrome.tsx +0 -95
- package/src/components/overlay.tsx +0 -91
- package/src/components/progress-bar.tsx +0 -251
- package/src/components/settings.tsx +0 -322
- package/src/components/sound.tsx +0 -101
- package/src/index.tsx +0 -195
- package/src/main.tsx +0 -169
- package/src/state-machines/data-source.ts +0 -84
- package/src/state-machines/index.ts +0 -5
- package/src/state-machines/media-properties.ts +0 -82
- package/src/state-machines/media-source.ts +0 -129
- package/src/state-machines/media.ts +0 -255
- package/src/state-machines/subtitles.ts +0 -285
- package/src/state-machines/thumbnails.ts +0 -123
- package/src/state-machines/utils.ts +0 -94
- package/src/utils/actor-utils.ts +0 -19
- package/src/utils/context.ts +0 -23
- package/src/utils/fonts.ts +0 -160
- package/src/utils/index.ts +0 -229
- package/src/utils/languages.ts +0 -262
- package/src/utils/mp4box.ts +0 -74
- package/src/utils/use-local-storage.ts +0 -30
- package/src/utils/use-scrub.ts +0 -40
- package/src/utils/volume-utils.ts +0 -39
- package/src/utils/window-height.ts +0 -19
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -28
- package/tsconfig.node.json +0 -9
- /package/{build → dist}/utils/colors.d.ts +0 -0
- /package/{build → dist}/utils/fonts.d.ts +0 -0
- /package/{build → dist}/utils/time.d.ts +0 -0
- /package/src/{assets → lib/assets}/picture-in-picture.svg +0 -0
- /package/src/{utils → lib/utils}/colors.ts +0 -0
- /package/src/{utils → lib/utils}/time.ts +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode, Ref } from 'react';
|
|
2
|
+
export type ChromeProps = {
|
|
3
|
+
ref?: Ref<HTMLDivElement> | ((element: HTMLDivElement | null) => void);
|
|
4
|
+
/** Absent means render no video element: the media belongs to someone else and arrives as children. */
|
|
5
|
+
onVideoRef?: (element: HTMLVideoElement | null) => void;
|
|
6
|
+
onCanvasRef: (element: HTMLCanvasElement | null) => void;
|
|
7
|
+
/** Above the control bar and outside the click-to-pause region, unlike `children`. */
|
|
8
|
+
overlay?: ReactNode;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
export declare const Chrome: ({ ref, onVideoRef, onCanvasRef, overlay, children }: ChromeProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
12
|
+
export default Chrome;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Ref } from 'react';
|
|
2
|
+
export type SoundProps = {
|
|
3
|
+
/** Lands on the button, which is what the control bar attaches its wheel listener to. */
|
|
4
|
+
ref?: Ref<HTMLButtonElement> | ((element: HTMLButtonElement | null) => void);
|
|
5
|
+
};
|
|
6
|
+
export declare const Sound: ({ ref }: SoundProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default Sound;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
2
|
import { PlacesType } from 'react-tooltip';
|
|
3
|
+
export declare enum buttonSize {
|
|
4
|
+
sm = "sm",
|
|
5
|
+
md = "md",
|
|
6
|
+
lg = "lg"
|
|
7
|
+
}
|
|
3
8
|
interface TooltipDisplayProps {
|
|
4
9
|
id: string;
|
|
5
10
|
toolTipText: ReactNode;
|
|
@@ -11,10 +16,5 @@ interface TooltipDisplayProps {
|
|
|
11
16
|
size?: buttonSize;
|
|
12
17
|
disabled?: boolean;
|
|
13
18
|
}
|
|
14
|
-
export declare
|
|
15
|
-
|
|
16
|
-
md = "md",
|
|
17
|
-
lg = "lg"
|
|
18
|
-
}
|
|
19
|
-
export declare const TooltipDisplay: ({ id, toolTipText, text, delayShow, closeDelay, offset, tooltipPlace, disabled, size }: TooltipDisplayProps) => import("react/jsx-runtime").JSX.Element;
|
|
20
|
-
export {};
|
|
19
|
+
export declare const TooltipDisplay: ({ id, toolTipText, text, delayShow, closeDelay, offset, tooltipPlace, disabled, size }: TooltipDisplayProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
20
|
+
export default TooltipDisplay;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PointerEvent as ReactPointerEvent, RefObject } from 'react';
|
|
2
|
+
export type UseDragValueOptions = {
|
|
3
|
+
/** The element the fraction is measured across. */
|
|
4
|
+
ref: RefObject<HTMLElement | null>;
|
|
5
|
+
/** Called with a 0..1 fraction on press and on every move, and once more on release. */
|
|
6
|
+
onChange: (fraction: number) => void;
|
|
7
|
+
orientation?: 'horizontal' | 'vertical';
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
};
|
|
10
|
+
/** Press-and-drag over an element, reported as a 0..1 fraction. */
|
|
11
|
+
export declare const useDragValue: ({ ref, onChange, orientation, disabled }: UseDragValueOptions) => {
|
|
12
|
+
dragging: boolean;
|
|
13
|
+
/** Spread onto the element that owns the gesture. */
|
|
14
|
+
handlers: {
|
|
15
|
+
onPointerDown: (event: ReactPointerEvent<HTMLElement>) => void;
|
|
16
|
+
onPointerMove: (event: ReactPointerEvent<HTMLElement>) => void;
|
|
17
|
+
onPointerUp: (event: ReactPointerEvent<HTMLElement>) => void;
|
|
18
|
+
onPointerCancel: (event: ReactPointerEvent<HTMLElement>) => void;
|
|
19
|
+
style: {
|
|
20
|
+
touchAction: 'none';
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MediaPlayerLocalOptions } from '../video-player';
|
|
2
|
+
/**
|
|
3
|
+
* Owns the engine for the life of a source: start, teardown, and every piece of state the pipeline
|
|
4
|
+
* discovers. Nothing is mirrored in React state, so the store is the only place any of it lives.
|
|
5
|
+
*/
|
|
6
|
+
export declare const usePlayback: (video: HTMLVideoElement | null, canvas: HTMLCanvasElement | null,
|
|
7
|
+
/** null when the media is remote: there are no bytes, so there is no pipeline to run. */
|
|
8
|
+
options: MediaPlayerLocalOptions | null) => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ThumbnailImage } from '../../engine';
|
|
2
|
+
import type { DownloadedRange } from '../source-feature';
|
|
3
|
+
export type UseSeekThumbnailsOptions = {
|
|
4
|
+
/** Optional alongside `length`/`read`: with no bytes there is nothing to generate from. */
|
|
5
|
+
publicPath: string | undefined;
|
|
6
|
+
workerUrl: string | undefined;
|
|
7
|
+
length: number | undefined;
|
|
8
|
+
read: ((offset: number, size: number) => Promise<ArrayBuffer>) | undefined;
|
|
9
|
+
/** When omitted the whole file is treated as readable, which is the case for a local file. */
|
|
10
|
+
downloadedRanges?: DownloadedRange[];
|
|
11
|
+
};
|
|
12
|
+
export declare const useSeekThumbnails: ({ publicPath, workerUrl, length, read, downloadedRanges, }: UseSeekThumbnailsOptions) => ThumbnailImage[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ThumbnailImage } from '../engine';
|
|
2
|
+
/**
|
|
3
|
+
* A media object the player drives but does not own.
|
|
4
|
+
*
|
|
5
|
+
* video.js v10 never asks for an `HTMLVideoElement`. Its `PlayerTarget` is `{ media, container }` and
|
|
6
|
+
* every feature gates on property presence alone, with no `instanceof` anywhere, so a plain object
|
|
7
|
+
* carrying these fields lights the whole chrome up. Upstream relies on the same thing: `VimeoMedia`
|
|
8
|
+
* implements `Partial<Video>` over a cross-origin iframe.
|
|
9
|
+
*
|
|
10
|
+
* Declared here rather than imported from `@videojs/media` on purpose. That package is transitive
|
|
11
|
+
* under `@videojs/core`, its `Media` type moved out of `@videojs/core/dom`'s exports between betas,
|
|
12
|
+
* and its shape changed across them, so importing it would pin every consumer to one beta. The
|
|
13
|
+
* assertion at the bottom of this file keeps this type honest against the real one at build time,
|
|
14
|
+
* where `@videojs/media` does exist.
|
|
15
|
+
*
|
|
16
|
+
* OMITTING a property is how you say "not supported". The feature owning it returns out of its
|
|
17
|
+
* `attach` and stays at its defaults, which is why this is mostly optional.
|
|
18
|
+
*/
|
|
19
|
+
export type TimeRangesLike = {
|
|
20
|
+
readonly length: number;
|
|
21
|
+
start: (index: number) => number;
|
|
22
|
+
end: (index: number) => number;
|
|
23
|
+
};
|
|
24
|
+
export type PlayerMedia = EventTarget & {
|
|
25
|
+
play: () => Promise<void>;
|
|
26
|
+
pause: () => void;
|
|
27
|
+
paused: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Seek and source capability. Both are needed or the store goes quiet: `playbackFeature` refuses
|
|
30
|
+
* to attach without them, and `seek` returns without doing anything. A remote media that loads
|
|
31
|
+
* nothing still has to answer `src`/`currentSrc`/`readyState`/`load`, and `readyState` has to
|
|
32
|
+
* reach HAVE_FUTURE_DATA (3) or `waiting` stays true forever and the spinner never clears.
|
|
33
|
+
*/
|
|
34
|
+
currentTime: number;
|
|
35
|
+
duration: number;
|
|
36
|
+
seeking: boolean;
|
|
37
|
+
src?: string;
|
|
38
|
+
currentSrc?: string;
|
|
39
|
+
readyState: number;
|
|
40
|
+
load?: () => void;
|
|
41
|
+
volume?: number;
|
|
42
|
+
muted?: boolean;
|
|
43
|
+
playbackRate?: number;
|
|
44
|
+
ended?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Structural, not the DOM `TimeRanges`, because a remote source transports these as plain pairs and
|
|
47
|
+
* has nothing to hand back that passes an `instanceof`. Note that video.js decides "no buffer
|
|
48
|
+
* capability" by comparing against its own empty sentinel BY IDENTITY, so a hand-rolled empty
|
|
49
|
+
* `{ length: 0 }` reads as a genuinely empty buffer rather than as an absent capability. Omit the
|
|
50
|
+
* field entirely to mean absent.
|
|
51
|
+
*/
|
|
52
|
+
buffered?: TimeRangesLike;
|
|
53
|
+
seekable?: TimeRangesLike;
|
|
54
|
+
/** Structurally what video.js reads off a failed element, so a remote source can report one too. */
|
|
55
|
+
error?: {
|
|
56
|
+
readonly code: number;
|
|
57
|
+
readonly message: string;
|
|
58
|
+
} | null;
|
|
59
|
+
videoWidth?: number;
|
|
60
|
+
videoHeight?: number;
|
|
61
|
+
requestPictureInPicture?: () => Promise<unknown>;
|
|
62
|
+
requestFullscreen?: () => Promise<unknown>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Where the seekbar's hover previews come from.
|
|
66
|
+
*
|
|
67
|
+
* With bytes in hand the player generates them itself with a second libav worker. A media it does not
|
|
68
|
+
* own has no bytes, but a commercial source usually publishes its own storyboard, so it can answer
|
|
69
|
+
* for a time directly. Narrowed on `at`, the field that carries the difference, rather than on a tag
|
|
70
|
+
* repeating what the shape already says.
|
|
71
|
+
*/
|
|
72
|
+
export type ExternalThumbnails = {
|
|
73
|
+
at: (time: number) => ThumbnailImage | undefined;
|
|
74
|
+
/** Shown all at once on the seekbar if the source can enumerate them; previews work without it. */
|
|
75
|
+
all?: ThumbnailImage[];
|
|
76
|
+
};
|
|
77
|
+
export declare const isExternalThumbnails: (value: unknown) => value is ExternalThumbnails;
|
|
78
|
+
/**
|
|
79
|
+
* One choice among several the player presents but does not fulfil: the source owns the switch.
|
|
80
|
+
*
|
|
81
|
+
* This is what a subtitle or audio track becomes when the media is remote. The player draws the menu
|
|
82
|
+
* and reports the pick; whoever owns the document does the work and renders the result.
|
|
83
|
+
*/
|
|
84
|
+
export type DelegatedSelection = {
|
|
85
|
+
options: readonly {
|
|
86
|
+
id: string;
|
|
87
|
+
label: string;
|
|
88
|
+
disabled?: boolean;
|
|
89
|
+
}[];
|
|
90
|
+
selectedId: string | null;
|
|
91
|
+
select: (id: string | null) => void | Promise<void>;
|
|
92
|
+
/** Label for the entry that turns the feature off, when the source offers one. */
|
|
93
|
+
offLabel?: string;
|
|
94
|
+
};
|
|
95
|
+
export type DelegatedTracks = {
|
|
96
|
+
selection: DelegatedSelection;
|
|
97
|
+
};
|
|
98
|
+
export declare const isDelegatedTracks: (value: unknown) => value is DelegatedTracks;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const Player: import("@videojs/react").CreatePlayerResult<import("@videojs/react").PlayerStore<[import("@videojs/react").PlayerFeature<import("@videojs/media").MediaPlaybackState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaPlaybackRateState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaQualityState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaAudioTrackState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaVolumeState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaTimeState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaSourceState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaBufferState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaFullscreenState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaPictureInPictureState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaRemotePlaybackState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaControlsState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaTextTrackState>, import("@videojs/react").PlayerFeature<import("@videojs/media").MediaErrorState>, import("@videojs/react").PlayerFeature<{
|
|
2
|
+
title?: string;
|
|
3
|
+
size?: number;
|
|
4
|
+
downloadedRanges?: import("./source-feature").DownloadedRange[];
|
|
5
|
+
indexes: import("..").MediaIndex[];
|
|
6
|
+
thumbnails: import("..").ThumbnailImage[];
|
|
7
|
+
thumbnailAt?: (time: number) => import("..").ThumbnailImage | undefined;
|
|
8
|
+
subtitleTracks: import("./source-feature").TrackChoice[];
|
|
9
|
+
selectedSubtitleTrack: string | number | undefined;
|
|
10
|
+
selectSubtitleTrack: (id: string | number | undefined) => void;
|
|
11
|
+
audioTracks: import("./source-feature").TrackChoice[];
|
|
12
|
+
selectedAudioTrack: string | number | undefined;
|
|
13
|
+
selectAudioTrack: (id: string | number) => void;
|
|
14
|
+
hideUI: boolean;
|
|
15
|
+
setHideUI: (hide: boolean) => void;
|
|
16
|
+
togglePictureInPicture: () => void;
|
|
17
|
+
playbackError: unknown;
|
|
18
|
+
ready: boolean;
|
|
19
|
+
setSourceState: (partial: Partial<import("./source-feature").SourceState>) => void;
|
|
20
|
+
}>]>>;
|
|
21
|
+
/**
|
|
22
|
+
* Typed player state, to be used instead of the `usePlayer` from `@videojs/react`, whose fields come
|
|
23
|
+
* back as `unknown`. With no selector it returns the store without subscribing; with one it subscribes.
|
|
24
|
+
*/
|
|
25
|
+
export declare const usePlayer: typeof Player.usePlayer;
|
|
26
|
+
export type PlayerStore = ReturnType<typeof Player.usePlayer>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { MediaIndex, ThumbnailImage } from '../engine';
|
|
2
|
+
/**
|
|
3
|
+
* One row of a track menu, already named.
|
|
4
|
+
*
|
|
5
|
+
* Labelled by whoever writes it rather than by the menu, because the two writers know different
|
|
6
|
+
* things: the engine has a language tag and a title to disambiguate between, while a source that
|
|
7
|
+
* owns its own player hands over a label it has already decided on. The id is opaque here for the
|
|
8
|
+
* same reason, a libav stream index one way and a site's own track id the other.
|
|
9
|
+
*/
|
|
10
|
+
export type TrackChoice = {
|
|
11
|
+
id: string | number;
|
|
12
|
+
label: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A byte span of the file the consumer has in hand, mapped onto the timeline through the keyframe
|
|
16
|
+
* index, because a file's download percentage is not its playback percentage.
|
|
17
|
+
*/
|
|
18
|
+
export type DownloadedRange = {
|
|
19
|
+
startByteOffset: number;
|
|
20
|
+
endByteOffset: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Everything about the source that the chrome reads, carried on the player store next to the built-in
|
|
24
|
+
* playback state so a component never has to know which of two channels owns a field.
|
|
25
|
+
*
|
|
26
|
+
* The store is the single reader surface; the React layer is the single writer, through
|
|
27
|
+
* `setSourceState`.
|
|
28
|
+
*/
|
|
29
|
+
export type SourceState = {
|
|
30
|
+
/** Shown top left over the video. */
|
|
31
|
+
title?: string;
|
|
32
|
+
/** Total byte length of the source. */
|
|
33
|
+
size?: number;
|
|
34
|
+
downloadedRanges?: DownloadedRange[];
|
|
35
|
+
/** Keyframe index of the input, which turns a downloaded byte range into a time range. */
|
|
36
|
+
indexes: MediaIndex[];
|
|
37
|
+
thumbnails: ThumbnailImage[];
|
|
38
|
+
/**
|
|
39
|
+
* Answers for one time directly, when the source has a storyboard it can index but not enumerate.
|
|
40
|
+
* Falls back to scanning `thumbnails` when absent, which is what the engine's generator fills.
|
|
41
|
+
*/
|
|
42
|
+
thumbnailAt?: (time: number) => ThumbnailImage | undefined;
|
|
43
|
+
subtitleTracks: TrackChoice[];
|
|
44
|
+
/** undefined means subtitles are off. */
|
|
45
|
+
selectedSubtitleTrack: string | number | undefined;
|
|
46
|
+
selectSubtitleTrack: (id: string | number | undefined) => void;
|
|
47
|
+
audioTracks: TrackChoice[];
|
|
48
|
+
selectedAudioTrack: string | number | undefined;
|
|
49
|
+
selectAudioTrack: (id: string | number) => void;
|
|
50
|
+
/** Chrome auto-hide. True means the controls, title and cursor are hidden. */
|
|
51
|
+
hideUI: boolean;
|
|
52
|
+
setHideUI: (hide: boolean) => void;
|
|
53
|
+
/** Owned here, not by `pip`: that watches the media element, and the window holds a mirror. */
|
|
54
|
+
togglePictureInPicture: () => void;
|
|
55
|
+
/** Set when the pipeline fails. Cleared when it recovers. */
|
|
56
|
+
playbackError: unknown;
|
|
57
|
+
/** Whether the engine has produced its first media segment. */
|
|
58
|
+
ready: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The write seam, wired in `attach`. Only the React layer calls it.
|
|
61
|
+
*
|
|
62
|
+
* It is a no-op until the media element attaches. Every caller writes either from an engine
|
|
63
|
+
* callback or after `startPlayback` resolves, both of which are far later than attach, and the one
|
|
64
|
+
* synchronous write is a reset to the values the defaults below already hold.
|
|
65
|
+
*/
|
|
66
|
+
setSourceState: (partial: Partial<SourceState>) => void;
|
|
67
|
+
};
|
|
68
|
+
export declare const sourceFeature: import("@videojs/react").PlayerFeature<{
|
|
69
|
+
/** Shown top left over the video. */
|
|
70
|
+
title?: string;
|
|
71
|
+
/** Total byte length of the source. */
|
|
72
|
+
size?: number;
|
|
73
|
+
downloadedRanges?: DownloadedRange[];
|
|
74
|
+
/** Keyframe index of the input, which turns a downloaded byte range into a time range. */
|
|
75
|
+
indexes: MediaIndex[];
|
|
76
|
+
thumbnails: ThumbnailImage[];
|
|
77
|
+
/**
|
|
78
|
+
* Answers for one time directly, when the source has a storyboard it can index but not enumerate.
|
|
79
|
+
* Falls back to scanning `thumbnails` when absent, which is what the engine's generator fills.
|
|
80
|
+
*/
|
|
81
|
+
thumbnailAt?: (time: number) => ThumbnailImage | undefined;
|
|
82
|
+
subtitleTracks: TrackChoice[];
|
|
83
|
+
/** undefined means subtitles are off. */
|
|
84
|
+
selectedSubtitleTrack: string | number | undefined;
|
|
85
|
+
selectSubtitleTrack: (id: string | number | undefined) => void;
|
|
86
|
+
audioTracks: TrackChoice[];
|
|
87
|
+
selectedAudioTrack: string | number | undefined;
|
|
88
|
+
selectAudioTrack: (id: string | number) => void;
|
|
89
|
+
/** Chrome auto-hide. True means the controls, title and cursor are hidden. */
|
|
90
|
+
hideUI: boolean;
|
|
91
|
+
setHideUI: (hide: boolean) => void;
|
|
92
|
+
/** Owned here, not by `pip`: that watches the media element, and the window holds a mirror. */
|
|
93
|
+
togglePictureInPicture: () => void;
|
|
94
|
+
/** Set when the pipeline fails. Cleared when it recovers. */
|
|
95
|
+
playbackError: unknown;
|
|
96
|
+
/** Whether the engine has produced its first media segment. */
|
|
97
|
+
ready: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* The write seam, wired in `attach`. Only the React layer calls it.
|
|
100
|
+
*
|
|
101
|
+
* It is a no-op until the media element attaches. Every caller writes either from an engine
|
|
102
|
+
* callback or after `startPlayback` resolves, both of which are far later than attach, and the one
|
|
103
|
+
* synchronous write is a reset to the values the defaults below already hold.
|
|
104
|
+
*/
|
|
105
|
+
setSourceState: (partial: Partial<SourceState>) => void;
|
|
106
|
+
}>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { DownloadedRange } from './source-feature';
|
|
3
|
+
import type { DelegatedTracks, ExternalThumbnails, PlayerMedia } from './media';
|
|
4
|
+
/**
|
|
5
|
+
* The source, read a range at a time: the player never downloads the whole file. Both fields travel
|
|
6
|
+
* together, so passing one without the other is a type error.
|
|
7
|
+
*/
|
|
8
|
+
export type MediaPlayerSource = {
|
|
9
|
+
read: (offset: number, size: number) => Promise<ArrayBuffer>;
|
|
10
|
+
size: number;
|
|
11
|
+
} | {
|
|
12
|
+
read?: undefined;
|
|
13
|
+
size?: undefined;
|
|
14
|
+
};
|
|
15
|
+
/** Shared by both arms: nothing here depends on who owns the media. */
|
|
16
|
+
type CommonOptions = {
|
|
17
|
+
title?: string;
|
|
18
|
+
autoplay?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Drawn above the control bar and outside the click-to-pause region, for whatever the app has to
|
|
21
|
+
* say over the video. `children` land next to the media instead, below the chrome.
|
|
22
|
+
*/
|
|
23
|
+
overlay?: ReactNode;
|
|
24
|
+
onSeek?: (fraction: number) => void;
|
|
25
|
+
onPlaybackError?: (error: unknown) => void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Bytes in: the player owns the `<video>`, and libav feeds it a fragment at a time.
|
|
29
|
+
*/
|
|
30
|
+
export type MediaPlayerLocalOptions = CommonOptions & MediaPlayerSource & {
|
|
31
|
+
/**
|
|
32
|
+
* Where libav's wasm is served from. BOTH `libav.wasm` and `libav-jspi.wasm` have to be there:
|
|
33
|
+
* libav-wasm picks between them on `WebAssembly.Suspending`, so serving one fails only on the
|
|
34
|
+
* browsers that pick the other.
|
|
35
|
+
*/
|
|
36
|
+
publicPath: string;
|
|
37
|
+
libavWorkerUrl: string;
|
|
38
|
+
jassubWorkerUrl: string;
|
|
39
|
+
/** jassub's SIMD build, `jassub-worker-modern.wasm`. */
|
|
40
|
+
jassubWasmUrl: string;
|
|
41
|
+
/**
|
|
42
|
+
* jassub's non-SIMD build, `jassub-worker.wasm`, for Safari before 16.4.
|
|
43
|
+
* Without it subtitles fail outright on those browsers rather than falling back to the slower build.
|
|
44
|
+
*/
|
|
45
|
+
jassubLegacyWasmUrl?: string;
|
|
46
|
+
/** Fallback face for `liberation sans`, used when a subtitle track names a font the file does not carry. */
|
|
47
|
+
defaultFontUrl?: string;
|
|
48
|
+
bufferSize?: number;
|
|
49
|
+
/** Byte spans available, painted on the seekbar and informing the thumbnail generator. */
|
|
50
|
+
downloadedRanges?: DownloadedRange[];
|
|
51
|
+
/**
|
|
52
|
+
* Reader for the thumbnail engine, when it should differ from playback's.
|
|
53
|
+
*
|
|
54
|
+
* They are the same by default so generation shares playback's fetch order. A consumer whose
|
|
55
|
+
* reads are not free wants them apart: a torrent hands this a non-prioritising, fail-fast reader
|
|
56
|
+
* so generating previews cannot steal download order from the bytes playback is blocked on.
|
|
57
|
+
*/
|
|
58
|
+
thumbnailRead?: (offset: number, size: number) => Promise<ArrayBuffer>;
|
|
59
|
+
/** Off entirely. A second wasm worker during pipeline boot is worth avoiding on a slow source. */
|
|
60
|
+
thumbnailsEnabled?: boolean;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* A media the caller owns and the player only drives.
|
|
64
|
+
*
|
|
65
|
+
* No bytes, so no libav, no MediaSource and no thumbnail generation. For a source whose video lives
|
|
66
|
+
* in a document this one cannot reach into, which is also the only arrangement under which its DRM
|
|
67
|
+
* works: the key session belongs to whoever owns the element.
|
|
68
|
+
*/
|
|
69
|
+
export type MediaPlayerRemoteOptions = CommonOptions & {
|
|
70
|
+
media: PlayerMedia;
|
|
71
|
+
read?: never;
|
|
72
|
+
size?: never;
|
|
73
|
+
/** The source's own storyboard, since there are no bytes to generate previews from. */
|
|
74
|
+
thumbnails?: ExternalThumbnails;
|
|
75
|
+
/** The source renders these; the player draws the menu and reports the pick. */
|
|
76
|
+
subtitles?: DelegatedTracks;
|
|
77
|
+
audioTracks?: DelegatedTracks;
|
|
78
|
+
};
|
|
79
|
+
export type MediaPlayerOptions = MediaPlayerLocalOptions | MediaPlayerRemoteOptions;
|
|
80
|
+
export declare const MediaPlayer: (options: MediaPlayerOptions & {
|
|
81
|
+
children?: ReactNode;
|
|
82
|
+
}) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
83
|
+
export default MediaPlayer;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
declare const fromBlob: ({ blob, name }: {
|
|
2
|
+
blob: Blob;
|
|
3
|
+
name?: string;
|
|
4
|
+
}) => {
|
|
5
|
+
length: number;
|
|
6
|
+
name: string | undefined;
|
|
7
|
+
read: (offset: number, size: number) => Promise<ArrayBuffer>;
|
|
8
|
+
};
|
|
9
|
+
declare const fromUrl: ({ url, length: _length, name, credentials }: {
|
|
10
|
+
url: string;
|
|
11
|
+
length?: number;
|
|
12
|
+
name?: string;
|
|
13
|
+
credentials?: RequestCredentials;
|
|
14
|
+
}) => Promise<{
|
|
15
|
+
length: number;
|
|
16
|
+
name: string | undefined;
|
|
17
|
+
read: (offset: number, size: number) => Promise<ArrayBuffer>;
|
|
18
|
+
}>;
|
|
19
|
+
/** A source resolved down to what the remuxer needs: a total length and a byte-range reader. */
|
|
20
|
+
export type RemuxerInput = {
|
|
21
|
+
length: number;
|
|
22
|
+
name?: string;
|
|
23
|
+
read: (offset: number, size: number) => Promise<ArrayBuffer>;
|
|
24
|
+
};
|
|
25
|
+
export declare const inputToRemuxerInput: (params: Parameters<typeof fromBlob | typeof fromUrl>[0] | {
|
|
26
|
+
length: number;
|
|
27
|
+
name?: string;
|
|
28
|
+
read: (offset: number, size: number) => Promise<ArrayBuffer>;
|
|
29
|
+
}) => Promise<RemuxerInput>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type LabelledTrack = {
|
|
2
|
+
streamIndex: number;
|
|
3
|
+
title: string;
|
|
4
|
+
language: string;
|
|
5
|
+
};
|
|
6
|
+
/** One row of a track menu, already named. Mirrors `TrackChoice` without importing the store. */
|
|
7
|
+
export type NamedTrack = {
|
|
8
|
+
id: string | number;
|
|
9
|
+
label: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Human name for a track's language tag. `fallback: 'none'` is what makes an unknown tag come back
|
|
13
|
+
* undefined instead of echoing itself, so the caller can fall through to the title.
|
|
14
|
+
*/
|
|
15
|
+
export declare const displayLanguage: (language: string | undefined) => string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Names a set of tracks for a menu, by language, falling back to the title then the stream index.
|
|
18
|
+
* The title is appended only to break a tie, because on a MultiSub release every title is the same
|
|
19
|
+
* source tag and titling every row gives nine identical entries.
|
|
20
|
+
*/
|
|
21
|
+
export declare const labelTracks: <T extends LabelledTrack>(tracks: T[]) => {
|
|
22
|
+
track: T;
|
|
23
|
+
label: string;
|
|
24
|
+
}[];
|
|
25
|
+
/**
|
|
26
|
+
* Names the engine's streams once, on the way into the store, so the menu only ever reads rows.
|
|
27
|
+
*
|
|
28
|
+
* The store's ids are opaque so a source that owns its own player can use its own track ids; the id
|
|
29
|
+
* here is the libav stream index, which is what this half of the code has to say.
|
|
30
|
+
*/
|
|
31
|
+
export declare const toNamedTracks: <T extends LabelledTrack>(tracks: T[]) => NamedTrack[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Higher values put more of the slider's travel at the quiet end. */
|
|
2
|
+
export declare const VOLUME_EXPONENT = 2;
|
|
3
|
+
/** Slider position (0-1) to gain, on a curve that matches how loudness is perceived. */
|
|
4
|
+
export declare const linearToLogVolume: (linearValue: number) => number;
|
|
5
|
+
/** Gain (0-1) back to slider position. */
|
|
6
|
+
export declare const logToLinearVolume: (logVolume: number) => number;
|
package/package.json
CHANGED
|
@@ -1,51 +1,72 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@banou/media-player",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "A video player for containers and codecs the browser cannot play natively, remuxed on the fly",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./engine": {
|
|
14
|
+
"types": "./dist/engine/index.d.ts",
|
|
15
|
+
"import": "./dist/engine/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
7
19
|
"files": [
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"src"
|
|
20
|
+
"dist",
|
|
21
|
+
"src/lib",
|
|
22
|
+
"!src/lib/**/*.test.ts",
|
|
23
|
+
"!src/lib/**/*.test.tsx",
|
|
24
|
+
"!src/lib/**/*.fixture.ts"
|
|
12
25
|
],
|
|
13
26
|
"scripts": {
|
|
14
|
-
"dev": "
|
|
15
|
-
"build
|
|
16
|
-
"
|
|
17
|
-
"build-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"copy-
|
|
21
|
-
"copy-
|
|
22
|
-
"
|
|
27
|
+
"dev": "npm run copy-assets && vp dev --port 4560",
|
|
28
|
+
"build": "shx rm -rf build/ && npm run copy-assets && vp build",
|
|
29
|
+
"preview": "vp preview",
|
|
30
|
+
"build-lib": "shx rm -rf dist/ && vp build -c vite.lib.config.ts && npm run types",
|
|
31
|
+
"prepublishOnly": "npm run build-lib",
|
|
32
|
+
"types": "tsc -p tsconfig.lib.json",
|
|
33
|
+
"copy-assets": "shx mkdir -p public && npm run copy-libav && npm run copy-libass",
|
|
34
|
+
"copy-libav": "shx cp node_modules/libav-wasm/build/worker.js public/libav-worker.js && shx cp node_modules/libav-wasm/build/libav.wasm public/libav.wasm && shx cp node_modules/libav-wasm/build/libav-jspi.wasm public/libav-jspi.wasm",
|
|
35
|
+
"copy-libass": "shx cp node_modules/jassub/dist/jassub-worker.js public/jassub-worker.js && shx cp node_modules/jassub/dist/jassub-worker.wasm public/jassub-worker.wasm && shx cp node_modules/jassub/dist/jassub-worker-modern.wasm public/jassub-worker-modern.wasm && shx cp node_modules/jassub/dist/default.woff2 public/default.woff2",
|
|
36
|
+
"test": "npm run fixtures && vp test run",
|
|
37
|
+
"fixtures": "node scripts/fixture.mjs"
|
|
23
38
|
},
|
|
24
39
|
"author": "Banou26",
|
|
25
|
-
"devDependencies": {
|
|
26
|
-
"@types/node": "^18.11.18",
|
|
27
|
-
"@types/react": "^18.0.27",
|
|
28
|
-
"@types/react-dom": "^18.0.10",
|
|
29
|
-
"@vitejs/plugin-react": "^4.3.4",
|
|
30
|
-
"concurrently": "^7.6.0",
|
|
31
|
-
"copyfiles": "^2.4.1",
|
|
32
|
-
"mime": "^3.0.0",
|
|
33
|
-
"shx": "^0.3.4",
|
|
34
|
-
"typescript": "^5.8.2",
|
|
35
|
-
"vite": "^6.2.1"
|
|
36
|
-
},
|
|
37
40
|
"dependencies": {
|
|
41
|
+
"@videojs/core": "10.0.0-beta.26",
|
|
42
|
+
"@videojs/react": "10.0.0-beta.26",
|
|
43
|
+
"ass-compiler": "^0.1.16",
|
|
44
|
+
"jassub": "^1.8.6",
|
|
45
|
+
"libav-wasm": "^0.8.4",
|
|
46
|
+
"react-feather": "^2.0.10",
|
|
47
|
+
"react-tooltip": "^5.28.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
38
50
|
"@emotion/react": "^11.14.0",
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
51
|
+
"react": "^19.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@emotion/react": "^11.14.0",
|
|
55
|
+
"@types/node": "^22.20.1",
|
|
56
|
+
"@types/react": "^19.0.0",
|
|
57
|
+
"@types/react-dom": "^19.0.0",
|
|
58
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
59
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
60
|
+
"playwright": "^1.62.1",
|
|
45
61
|
"react": "^19.0.0",
|
|
46
62
|
"react-dom": "^19.0.0",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
63
|
+
"shx": "^0.3.4",
|
|
64
|
+
"typescript": "^7.0.2",
|
|
65
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.4",
|
|
66
|
+
"vite-plus": "0.2.4",
|
|
67
|
+
"vitest-browser-react": "^2.2.0"
|
|
68
|
+
},
|
|
69
|
+
"overrides": {
|
|
70
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.4"
|
|
50
71
|
}
|
|
51
72
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { startPlayback, terminateRemuxer, DEFAULT_BUFFER_SIZE } from './playback'
|
|
2
|
+
export type { PlaybackOptions, PlaybackController, MediaIndex, AudioStream } from './playback'
|
|
3
|
+
|
|
4
|
+
export { createSubtitleRenderer, SUBTITLES_OFF } from './subtitles'
|
|
5
|
+
export type { SubtitleRenderer, SubtitleRendererOptions, SubtitleStream } from './subtitles'
|
|
6
|
+
|
|
7
|
+
export { createThumbnailGenerator } from './thumbnails'
|
|
8
|
+
export type { ThumbnailGenerator, ThumbnailGeneratorOptions, ThumbnailImage } from './thumbnails'
|
|
9
|
+
|
|
10
|
+
export { getTimeRanges, updateSourceBuffer } from './source-buffer'
|
|
11
|
+
export type { TimeRange } from './source-buffer'
|
|
12
|
+
|
|
13
|
+
export { createPictureInPicture } from './picture-in-picture'
|
|
14
|
+
export type { PictureInPictureController, PictureInPictureOptions } from './picture-in-picture'
|