Skip to content

Commit 76f259e

Browse files
committed
[IMPL] usePIP hook
1 parent a22b433 commit 76f259e

13 files changed

Lines changed: 420 additions & 16 deletions

File tree

apps/react-tools-demo/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"react-syntax-highlighter": "^15.5.0",
3232
"remark-gfm": "^4.0.0",
3333
"typescript": "^5.0.2",
34-
"vite": "^4.4.5"
34+
"vite": "^4.4.5",
35+
"vite-plugin-mkcert": "^1.17.2"
3536
}
3637
}
770 KB
Binary file not shown.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useRef, useState } from "react"
2+
import video from './../../../assets/mov_bbb.mp4';
3+
import { usePIP } from "../../../../../../packages/react-tools/src";
4+
5+
/**
6+
The component uses _usePIP_ hook to show a video and a button to enable PIP.
7+
*/
8+
export const UsePIP = () => {
9+
const videoRef = useRef<HTMLVideoElement>(null);
10+
const [show, setShow] = useState(true);
11+
const { isSupported, openPIP } = usePIP({
12+
target: videoRef,
13+
onOpened: () => setShow(false),
14+
onClosed: () => setShow(true)
15+
});
16+
17+
return <div>
18+
<p>Supported: {isSupported ? 'Yes' : 'No'}</p>
19+
{
20+
show &&
21+
<>
22+
<video ref={videoRef} width="400" controls>
23+
<source src={video} type="video/mp4" />
24+
Your browser does not support HTML video.
25+
</video>
26+
<div>
27+
<button onClick={openPIP}>Open PIP</button>
28+
</div>
29+
</>
30+
}
31+
</div>
32+
}

apps/react-tools-demo/src/constants/components.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ export const COMPONENTS = [
101101
"useScreenWakeLock",
102102
"useSpeechRecognition",
103103
"useSpeechSynthesis",
104-
"useFPS"
104+
"useFPS",
105+
"usePIP"
105106
]
106107
],
107108
//UTILS
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#
2+
Hook to use PIP [(Picture-in-Picture API)](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API)
3+
4+
## Usage
5+
6+
```tsx
7+
export const UsePIP = () => {
8+
const videoRef = useRef<HTMLVideoElement>(null);
9+
const [show, setShow] = useState(true);
10+
const { openPIP } = usePIP({target: videoRef})
11+
12+
return <div>
13+
<video ref={videoRef} width="400" controls>
14+
<source src={video} type="video/mp4"/>
15+
Your browser does not support HTML video.
16+
</video>
17+
<div>
18+
<button onClick={openPIP}>Open PIP</button>
19+
</div>
20+
</div>
21+
}
22+
```
23+
24+
> The component uses _usePIP_ hook to show a video and a button to enable PIP.
25+
26+
27+
## API
28+
29+
```tsx
30+
usePIP = ({ onOpen, onOpened, onClose, onError, target }: UsePIPProps): UsePIPResult
31+
```
32+
33+
> ### Params
34+
>
35+
> - __param__: _UsePIPProps_
36+
object
37+
> - __param.target__: _RefObject<HTMLVideoElement>|HTMLVideoElement_
38+
element to PIP.
39+
> - __param.onOpen?__: _()=>void_
40+
function that will be executed before open PIP.
41+
> - __param.onOpened?__: _(pip: PictureInPictureWindow)=>void_
42+
function that will be executed when PIP is opened.
43+
> - __param.onClose?__: _()=>void_
44+
function that will be executed when PIP is closed.
45+
> - __param.onError?__: _(err: unknown)=>void_
46+
function that will be executed when error is throwing.
47+
>
48+
49+
> ### Returns
50+
>
51+
> __result__: _UsePIPResult_
52+
> Object with three properties:
53+
> - __isSupported__: boolean that indicates if PIP is supported or not.
54+
> - __openPIP__: function to open PIP.
55+
> - __closePIP__: function to close PIP.
56+
>
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { defineConfig } from 'vite'
22
import react from '@vitejs/plugin-react'
3+
import mkcert from 'vite-plugin-mkcert'
34

45
// https://vitejs.dev/config/
56
export default defineConfig({
6-
plugins: [react()],
77
server: {
8-
host: true
9-
}
8+
host: true,
9+
https: true
10+
},
11+
plugins: [react(), mkcert()],
1012
})

packages/react-tools/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@
104104
- [x] useSpeechRecognition
105105
- [x] useSpeechSynthesis
106106
- [x] useFPS
107-
- [ ] usePIP (https://developer.mozilla.org/en-US/docs/Web/API/Document_Picture-in-Picture_API https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API)
107+
- [ ] useDocumentPIP (https://developer.mozilla.org/en-US/docs/Web/API/Document_Picture-in-Picture_API)
108+
- [x] usePIP
108109
- [ ] useIdleDetection (https://developer.mozilla.org/en-US/docs/Web/API/Idle_Detection_API)
109110
- [ ] usePopover (https://developer.mozilla.org/en-US/docs/Web/API/Popover_API)
110111
- [ ] useRemotePlayback (https://developer.mozilla.org/en-US/docs/Web/API/Remote_Playback_API)

packages/react-tools/src/hooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,5 @@ export { useScreenWakeLock } from './useScreenWakeLock';
8181
export { useSpeechRecognition } from './useSpeechRecognition';
8282
export { useSpeechSynthesis } from './useSpeechSynthesis';
8383
export { useFPS } from './useFPS';
84-
export { usePointerLock } from './usePointerLock';
84+
export { usePointerLock } from './usePointerLock';
85+
export { usePIP } from './usePIP';
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { RefObject, useCallback } from "react";
2+
import { UsePIPProps, UsePIPResult } from "../models";
3+
4+
/**
5+
* **``**: Hook to use PIP [(Picture-in-Picture API)](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API).
6+
* @param {UsePIPProps} param - object
7+
* @param {RefObject<HTMLVideoElement>|HTMLVideoElement} param.target - element to PIP.
8+
* @param {()=>void} [param.onOpen] - function that will be executed before open PIP.
9+
* @param {(pip: PictureInPictureWindow)=>void} [param.onOpened] - function that will be executed when PIP is opened.
10+
* @param {()=>void} [param.onClosed] - function that will be executed when PIP is closed.
11+
* @param {(err: unknown)=>void} [param.onError] - function that will be executed when error is throwing.
12+
* @returns {UsePIPResult} result
13+
* Object with three properties:
14+
* - __isSupported__: boolean that indicates if PIP is supported or not.
15+
* - __openPIP__: function to open PIP.
16+
* - __closePIP__: function to close PIP.
17+
*/
18+
export const usePIP = ({ onOpen, onOpened, onClosed, onError, target }: UsePIPProps): UsePIPResult => {
19+
const isSupported = "pictureInPictureElement" in document;
20+
21+
const openPIP = useCallback(() => {
22+
const element = (target as RefObject<HTMLVideoElement>)?.current
23+
? (target as RefObject<HTMLVideoElement>).current
24+
: target as HTMLVideoElement;
25+
!!onOpen && onOpen();
26+
if (!("pictureInPictureElement" in document)) {
27+
return Promise.resolve();
28+
}
29+
return element!.requestPictureInPicture()
30+
.then(pip => {
31+
!!onOpened && onOpened(pip);
32+
element!.addEventListener("leavepictureinpicture", () => {
33+
!!onClosed && onClosed();
34+
}, false);
35+
})
36+
.catch(err => {
37+
if (onError) {
38+
onError(err);
39+
} else {
40+
throw err;
41+
}
42+
});
43+
}, []);
44+
45+
const closePIP = useCallback(() => {
46+
if (!("pictureInPictureElement" in document)) {
47+
return Promise.resolve();
48+
}
49+
return document.exitPictureInPicture()
50+
.catch(err => {
51+
if (onError) {
52+
onError(err);
53+
} else {
54+
throw err;
55+
}
56+
});
57+
}, []);
58+
59+
return { isSupported, openPIP, closePIP };
60+
}

packages/react-tools/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ export type {
4444
UseFPSProps,
4545
UseFPSResult,
4646
UsePointerLockProps,
47-
UsePointerLockResult
47+
UsePointerLockResult,
48+
UsePIPProps,
49+
UsePIPResult
4850
} from './models'
4951

5052
export {
@@ -131,7 +133,8 @@ export {
131133
useSpeechRecognition,
132134
useSpeechSynthesis,
133135
useFPS,
134-
usePointerLock
136+
usePointerLock,
137+
usePIP
135138
} from './hooks'
136139

137140
export {

0 commit comments

Comments
 (0)