diff --git a/README.md b/README.md
index 35e5e3f..9b74663 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,12 @@
> Build custom video players effortless
-* Render props, get all video state passed down as props.
-* Bidirectional flow to render and update the video state in a declarative way.
-* No side effects out of the box, you just need to build the UI.
-* Actions handling: play, pause, mute, unmute, navigate, etc
-* Dependency free, [<2KB size](https://bundlephobia.com/result?p=react-video-renderer)
-* Cross-browser support, no more browser hacks.
+- Render props, get all video state passed down as props.
+- Bidirectional flow to render and update the video state in a declarative way.
+- No side effects out of the box, you just need to build the UI.
+- Actions handling: play, pause, mute, unmute, navigate, etc
+- Dependency free, [<2KB size](https://bundlephobia.com/result?p=react-video-renderer)
+- Cross-browser support, no more browser hacks.
## Demo 🎩
@@ -30,15 +30,18 @@ import Video from 'react-video-renderer';
{(video, state, actions) => (
{video}
-
{state.currentTime} / {state.duration} / {state.buffered}
+
+ {state.currentTime} / {state.duration} / {state.buffered}
+
+
)}
-
+;
```
@@ -64,7 +67,12 @@ interface Props {
### Render method
```typescript
-type RenderCallback = (reactElement: ReactElement
, state: VideoState, actions: VideoActions, ref: React.RefObject) => ReactNode;
+type RenderCallback = (
+ reactElement: ReactElement,
+ state: VideoState,
+ actions: VideoActions,
+ ref: React.RefObject
+) => ReactNode;
```
### State
@@ -106,18 +114,10 @@ interface VideoActions {
```
@@ -138,7 +138,7 @@ interface VideoActions {
- )
+ );
}}
```
@@ -150,16 +150,16 @@ interface VideoActions {
> subtitles can be rendered natively, or they can be rendered using `VideoState.currentActiveCues` property:
```jsx
-
```
diff --git a/example/app.tsx b/example/app.tsx
index 01326a3..f20bc8f 100644
--- a/example/app.tsx
+++ b/example/app.tsx
@@ -3,6 +3,7 @@ import { Component } from 'react';
import VidPlayIcon from '@atlaskit/icon/glyph/vid-play';
import VidPauseIcon from '@atlaskit/icon/glyph/vid-pause';
import VidFullScreenOnIcon from '@atlaskit/icon/glyph/vid-full-screen-on';
+import EditorTableDisplayOptionsIcon from '@atlaskit/icon/glyph/editor/table-display-options';
import VolumeIcon from '@atlaskit/icon/glyph/hipchat/outgoing-sound';
import Button from '@atlaskit/button';
import Select from '@atlaskit/single-select';
@@ -245,6 +246,12 @@ export default class App extends Component<{}, AppState> {
const fullScreenButton = sourceType === 'video' && (
} onClick={actions.requestFullscreen} />
);
+ const pictureInPictureButton = videoState.isPictureInPictureEnabled && sourceType === 'video' && (
+ }
+ onClick={actions.togglePictureInPicture}
+ />
+ );
const hdButton = sourceType === 'video' && ;
const playbackSpeedSelect = (
@@ -306,6 +313,7 @@ export default class App extends Component<{}, AppState> {
{playbackSpeedSelect}
{hdButton}
{fullScreenButton}
+ {pictureInPictureButton}
diff --git a/src/utils.ts b/src/utils.ts
index f70ffc9..a065917 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,6 +1,8 @@
export const requestFullScreen = (element: HTMLVideoElement) => {
const methods = ['requestFullscreen', 'webkitRequestFullscreen', 'mozRequestFullScreen', 'msRequestFullscreen'];
const methodName = (methods as any).find((name: string) => (element as any)[name]);
-
+
(element as any)[methodName]();
-}
\ No newline at end of file
+};
+
+export const getDocument = () => ('document' in window ? document : undefined);
diff --git a/src/video.tsx b/src/video.tsx
index d3381f3..606edc5 100644
--- a/src/video.tsx
+++ b/src/video.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Component, ReactElement, ReactNode, SyntheticEvent, RefObject, MediaHTMLAttributes } from 'react';
import { VideoTextTracks, VideoTextTrackKind, getVideoTextTrackId } from './text';
-import { requestFullScreen } from './utils';
+import { getDocument, requestFullScreen } from './utils';
export type VideoStatus = 'playing' | 'paused' | 'errored';
export type VideoError = MediaError | null;
@@ -16,6 +16,8 @@ export interface VideoState {
isMuted: boolean;
isLoading: boolean;
error?: VideoError;
+ isPictureInPictureActive: boolean;
+ isPictureInPictureEnabled: boolean;
}
export type NavigateFunction = (time: number) => void;
@@ -32,6 +34,9 @@ export interface VideoActions {
mute: () => void;
unmute: () => void;
toggleMute: () => void;
+ requestPictureInPicture: () => void;
+ exitPictureInPicture: () => void;
+ togglePictureInPicture: () => void;
}
export type RenderCallback = (
@@ -195,6 +200,8 @@ export class Video extends Component {
private get videoState(): VideoState {
const { currentTime, volume, status, duration, buffered, isMuted, isLoading, error } = this.state;
+ const { isPictureInPictureEnabled, isPictureInPictureActive } = this;
+
return {
currentTime,
currentActiveCues: (kind: VideoTextTrackKind, lang: string) =>
@@ -206,6 +213,8 @@ export class Video extends Component {
isMuted,
isLoading,
error,
+ isPictureInPictureActive,
+ isPictureInPictureEnabled,
};
}
@@ -249,6 +258,37 @@ export class Video extends Component {
}
};
+ private get isPictureInPictureActive(): boolean {
+ return !!getDocument()?.pictureInPictureElement;
+ }
+
+ private get isPictureInPictureEnabled(): boolean {
+ const { sourceType } = this.props;
+ return !!getDocument()?.pictureInPictureEnabled && sourceType === 'video';
+ }
+
+ private exitPictureInPicture = () => {
+ this.isPictureInPictureActive && getDocument()?.exitPictureInPicture();
+ };
+
+ private requestPictureInPicture = async () => {
+ if (!this.isPictureInPictureEnabled) {
+ return;
+ }
+ try {
+ // If the binary hasn't been loaded yet, this can throw an error
+ await this.videoRef.current?.requestPictureInPicture();
+ } catch (e) {}
+ };
+
+ private togglePictureInPicture = () => {
+ if (this.isPictureInPictureActive) {
+ this.exitPictureInPicture();
+ } else {
+ this.requestPictureInPicture();
+ }
+ };
+
private mute = () => {
const { volume } = this.state;
@@ -271,7 +311,20 @@ export class Video extends Component {
};
private get actions(): VideoActions {
- const { play, pause, navigate, setVolume, setPlaybackSpeed, requestFullscreen, mute, unmute, toggleMute } = this;
+ const {
+ play,
+ pause,
+ navigate,
+ setVolume,
+ setPlaybackSpeed,
+ requestFullscreen,
+ mute,
+ unmute,
+ toggleMute,
+ requestPictureInPicture,
+ exitPictureInPicture,
+ togglePictureInPicture,
+ } = this;
return {
play,
@@ -283,6 +336,9 @@ export class Video extends Component {
mute,
unmute,
toggleMute,
+ requestPictureInPicture,
+ exitPictureInPicture,
+ togglePictureInPicture,
};
}