@banou/media-player 0.10.6 → 0.10.7
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 +24 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +532 -399
- package/dist/react/hooks/use-pass-through-picture-in-picture.d.ts +10 -0
- package/dist/react/media.d.ts +48 -0
- package/dist/react/player.d.ts +1 -0
- package/dist/react/source-feature.d.ts +53 -8
- package/dist/react/video-player.d.ts +7 -1
- package/package.json +1 -1
- package/src/lib/index.tsx +4 -1
- package/src/lib/react/components/chrome.tsx +31 -0
- package/src/lib/react/components/control-bar.tsx +79 -4
- package/src/lib/react/hooks/use-pass-through-picture-in-picture.ts +113 -0
- package/src/lib/react/media.ts +49 -0
- package/src/lib/react/source-feature.ts +47 -4
- package/src/lib/react/video-player.tsx +17 -1
|
@@ -61,6 +61,43 @@ export type DownloadedRange = {
|
|
|
61
61
|
endByteOffset: number
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/** A box in viewport coordinates, as `getBoundingClientRect` reports it. */
|
|
65
|
+
export type ViewportBox = {
|
|
66
|
+
left: number
|
|
67
|
+
top: number
|
|
68
|
+
width: number
|
|
69
|
+
height: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The picture in picture control of a media the player does not own, which the host enters from its
|
|
74
|
+
* own document (see `PassThroughPictureInPicture`).
|
|
75
|
+
*
|
|
76
|
+
* Armed means a click on the control goes through it to whatever the host renders below, so the
|
|
77
|
+
* chrome lays a layer over everything else with a hole at `armed`: inside the hole every pointer event
|
|
78
|
+
* belongs to the host's document, and the layer is how this one learns the pointer has left it.
|
|
79
|
+
*/
|
|
80
|
+
export type PassThroughControl = {
|
|
81
|
+
/** Between the media's `enterpictureinpicture` and `leavepictureinpicture`. */
|
|
82
|
+
active: boolean
|
|
83
|
+
/** The control's box while a click on it is let through, undefined otherwise. */
|
|
84
|
+
armed?: ViewportBox
|
|
85
|
+
/**
|
|
86
|
+
* Lets a click at `box` through to the host, and tells the host it is armed. For the pointer
|
|
87
|
+
* arriving on the control. Does nothing while the media has no metadata yet (`readyState` below 1)
|
|
88
|
+
* or has an `error`, since the host's request would be refused and the click lost with it.
|
|
89
|
+
*/
|
|
90
|
+
arm: (box: ViewportBox) => void
|
|
91
|
+
/**
|
|
92
|
+
* Takes the click back, and tells the host it is disarmed if it had been told it was armed. For the
|
|
93
|
+
* pointer leaving the control; also done here on entering picture in picture, on a touch, and when
|
|
94
|
+
* the control stops being offered.
|
|
95
|
+
*/
|
|
96
|
+
disarm: () => void
|
|
97
|
+
/** Leaves picture in picture through the media. */
|
|
98
|
+
exit: () => void
|
|
99
|
+
}
|
|
100
|
+
|
|
64
101
|
/**
|
|
65
102
|
* Everything about the source that the chrome reads, carried on the player store next to the built-in
|
|
66
103
|
* playback state so a component never has to know which of two channels owns a field.
|
|
@@ -145,12 +182,17 @@ export type SourceState = {
|
|
|
145
182
|
* Owned here, not by `pip`: that watches the media element, and the window holds a mirror.
|
|
146
183
|
*
|
|
147
184
|
* null means the control is not offered at all. Locally that is "no element yet". For a media the
|
|
148
|
-
* player does not own it
|
|
149
|
-
* this does needs a local element to draw, and
|
|
150
|
-
*
|
|
151
|
-
* button that toggles nothing and never lights up is worse than no button.
|
|
185
|
+
* player does not own it is always null, because a request made from this document is refused: the
|
|
186
|
+
* compositing this does needs a local element to draw, and the browser wants a gesture in the
|
|
187
|
+
* media's own document. `passThroughPictureInPicture` is that arm's control instead.
|
|
152
188
|
*/
|
|
153
189
|
togglePictureInPicture: (() => void) | null
|
|
190
|
+
/**
|
|
191
|
+
* The picture in picture control for a media the player does not own, when the host opted in with
|
|
192
|
+
* `pictureInPicture` and the viewer points with something that hovers. null otherwise, which is
|
|
193
|
+
* always the case for a local source.
|
|
194
|
+
*/
|
|
195
|
+
passThroughPictureInPicture: PassThroughControl | null
|
|
154
196
|
|
|
155
197
|
/**
|
|
156
198
|
* Which shape the control takes. `window` opens one. `burn-in` cannot, and instead paints the
|
|
@@ -226,6 +268,7 @@ const initialState: SourceState = {
|
|
|
226
268
|
setHideUI: () => {},
|
|
227
269
|
hasMedia: false,
|
|
228
270
|
togglePictureInPicture: null,
|
|
271
|
+
passThroughPictureInPicture: null,
|
|
229
272
|
pictureInPictureMode: null,
|
|
230
273
|
burnedInSubtitles: false,
|
|
231
274
|
playbackError: null,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { ReactNode } from 'react'
|
|
3
3
|
import type { MediaChapter } from '../engine'
|
|
4
4
|
import type { DownloadedRange } from './source-feature'
|
|
5
|
-
import type { DelegatedTracks, ExternalThumbnails, PlayerMedia } from './media'
|
|
5
|
+
import type { DelegatedTracks, ExternalThumbnails, PassThroughPictureInPicture, PlayerMedia } from './media'
|
|
6
6
|
import type { ExposePlayerOptions } from '../remote'
|
|
7
7
|
|
|
8
8
|
import { useEffect, useRef, useState } from 'react'
|
|
@@ -13,6 +13,7 @@ import { Player, usePlayer } from './player'
|
|
|
13
13
|
import { usePlayback } from './hooks/use-playback'
|
|
14
14
|
import { useSeekThumbnails } from './hooks/use-thumbnails'
|
|
15
15
|
import { usePictureInPicture } from './hooks/use-picture-in-picture'
|
|
16
|
+
import { usePassThroughPictureInPicture } from './hooks/use-pass-through-picture-in-picture'
|
|
16
17
|
import Chrome from './components/chrome'
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -185,6 +186,12 @@ export type MediaPlayerRemoteOptions =
|
|
|
185
186
|
/** The source renders these; the player draws the menu and reports the pick. */
|
|
186
187
|
subtitles?: DelegatedTracks
|
|
187
188
|
audioTracks?: DelegatedTracks
|
|
189
|
+
/**
|
|
190
|
+
* Offer picture in picture, entered by the host from inside the media's own document. Off by
|
|
191
|
+
* default, and then there is no control at all. See `PassThroughPictureInPicture` for what the
|
|
192
|
+
* host takes on by passing it. Read by value, so it may be written inline.
|
|
193
|
+
*/
|
|
194
|
+
pictureInPicture?: PassThroughPictureInPicture
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
export type MediaPlayerOptions = MediaPlayerLocalOptions | MediaPlayerRemoteOptions
|
|
@@ -264,6 +271,10 @@ const PlayerRoot = ({ options, children }: { options: MediaPlayerOptions, childr
|
|
|
264
271
|
mode: pictureInPictureMode,
|
|
265
272
|
burnedIn: burnedInSubtitles,
|
|
266
273
|
} = usePictureInPicture(video, subtitleLayer)
|
|
274
|
+
const passThroughPictureInPicture = usePassThroughPictureInPicture(
|
|
275
|
+
remote?.media ?? null,
|
|
276
|
+
remote?.pictureInPicture,
|
|
277
|
+
)
|
|
267
278
|
|
|
268
279
|
// Subscribed rather than read off the store, because it is a no-op until the media element
|
|
269
280
|
// attaches: when attach swaps in the real setter the identity changes and these publish again.
|
|
@@ -295,6 +306,11 @@ const PlayerRoot = ({ options, children }: { options: MediaPlayerOptions, childr
|
|
|
295
306
|
burnedInSubtitles,
|
|
296
307
|
])
|
|
297
308
|
|
|
309
|
+
// its own effect: it changes on every hover of the control, and nothing else here does
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
setSourceState({ passThroughPictureInPicture })
|
|
312
|
+
}, [setSourceState, passThroughPictureInPicture])
|
|
313
|
+
|
|
298
314
|
// A delegated track list writes the same store fields the engine writes, so the menus never learn
|
|
299
315
|
// which arm they are showing. Only the writer differs: here the pick is forwarded to whoever owns
|
|
300
316
|
// the document, and it renders the result itself.
|