From 1217cf4e99d7861cfa587708efc5673d78efbdb9 Mon Sep 17 00:00:00 2001 From: Jose Nunez Date: Mon, 29 Aug 2022 17:50:02 +1000 Subject: [PATCH 1/2] adds support for picture in picture --- src/utils.ts | 6 ++++-- src/video.tsx | 60 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) 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, }; } From 0b56a1549e8dca8d24c2c11626f3716e73295ad4 Mon Sep 17 00:00:00 2001 From: Jose Nunez Date: Tue, 30 Aug 2022 07:36:22 +1000 Subject: [PATCH 2/2] adds picture in picture to examples --- README.md | 54 +++++++++++++++++++++++++------------------------ example/app.tsx | 8 ++++++++ 2 files changed, 36 insertions(+), 26 deletions(-) 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' && ( ; const playbackSpeedSelect = ( @@ -306,6 +313,7 @@ export default class App extends Component<{}, AppState> { {playbackSpeedSelect} {hdButton} {fullScreenButton} + {pictureInPictureButton}