pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

video player starter

+137 -119
+27 -45
src/components/video/VideoContext.tsx
··· 1 1 import React, { 2 2 createContext, 3 3 MutableRefObject, 4 + useContext, 4 5 useEffect, 5 6 useReducer, 6 7 } from "react"; 8 + import { 9 + initialPlayerState, 10 + PlayerState, 11 + useVideoPlayer, 12 + } from "./hooks/useVideoPlayer"; 7 13 8 14 interface VideoPlayerContextType { 9 - source: null | string; 10 - playerWrapper: HTMLDivElement | null; 11 - player: HTMLVideoElement | null; 12 - controlState: "paused" | "playing"; 13 - fullscreen: boolean; 15 + source: string | null; 16 + state: PlayerState; 14 17 } 15 - const initial = ( 16 - player: HTMLVideoElement | null = null, 17 - wrapper: HTMLDivElement | null = null 18 - ): VideoPlayerContextType => ({ 18 + const initial: VideoPlayerContextType = { 19 19 source: null, 20 - playerWrapper: wrapper, 21 - player, 22 - controlState: "paused", 23 - fullscreen: false, 24 - }); 20 + state: initialPlayerState, 21 + }; 25 22 26 23 type VideoPlayerContextAction = 27 24 | { type: "SET_SOURCE"; url: string } 28 - | { type: "CONTROL"; do: "PAUSE" | "PLAY"; soft?: boolean } 29 - | { type: "FULLSCREEN"; do: "ENTER" | "EXIT"; soft?: boolean } 30 25 | { 31 26 type: "UPDATE_PLAYER"; 32 - player: HTMLVideoElement | null; 33 - playerWrapper: HTMLDivElement | null; 27 + state: PlayerState; 34 28 }; 35 29 36 30 function videoPlayerContextReducer( ··· 42 36 video.source = action.url; 43 37 return video; 44 38 } 45 - if (action.type === "CONTROL") { 46 - if (action.do === "PAUSE") video.controlState = "paused"; 47 - else if (action.do === "PLAY") video.controlState = "playing"; 48 - if (action.soft) return video; 49 - 50 - if (action.do === "PAUSE") video.player?.pause(); 51 - else if (action.do === "PLAY") video.player?.play(); 52 - return video; 53 - } 54 39 if (action.type === "UPDATE_PLAYER") { 55 - video.player = action.player; 56 - video.playerWrapper = action.playerWrapper; 57 - return video; 58 - } 59 - if (action.type === "FULLSCREEN") { 60 - video.fullscreen = action.do === "ENTER"; 61 - if (action.soft) return video; 62 - 63 - if (action.do === "ENTER") video.playerWrapper?.requestFullscreen(); 64 - else document.exitFullscreen(); 40 + video.state = action.state; 65 41 return video; 66 42 } 67 43 68 44 return original; 69 45 } 70 46 71 - export const VideoPlayerContext = createContext<VideoPlayerContextType>( 72 - initial() 73 - ); 47 + export const VideoPlayerContext = 48 + createContext<VideoPlayerContextType>(initial); 74 49 export const VideoPlayerDispatchContext = createContext< 75 50 React.Dispatch<VideoPlayerContextAction> 76 51 >(null as any); ··· 78 53 export function VideoPlayerContextProvider(props: { 79 54 children: React.ReactNode; 80 55 player: MutableRefObject<HTMLVideoElement | null>; 81 - playerWrapper: MutableRefObject<HTMLDivElement | null>; 82 56 }) { 57 + const { playerState } = useVideoPlayer(props.player); 83 58 const [videoData, dispatch] = useReducer<typeof videoPlayerContextReducer>( 84 59 videoPlayerContextReducer, 85 - initial() 60 + initial 86 61 ); 87 62 88 63 useEffect(() => { 89 64 dispatch({ 90 65 type: "UPDATE_PLAYER", 91 - player: props.player.current, 92 - playerWrapper: props.playerWrapper.current, 66 + state: playerState, 93 67 }); 94 - }, [props.player, props.playerWrapper]); 68 + }, [playerState]); 95 69 96 70 return ( 97 71 <VideoPlayerContext.Provider value={videoData}> ··· 101 75 </VideoPlayerContext.Provider> 102 76 ); 103 77 } 78 + 79 + export function useVideoPlayerState() { 80 + const { state } = useContext(VideoPlayerContext); 81 + 82 + return { 83 + videoState: state, 84 + }; 85 + }
+7 -35
src/components/video/VideoPlayer.tsx
··· 1 - import { forwardRef, useCallback, useContext, useEffect, useRef } from "react"; 2 - import { 3 - VideoPlayerContext, 4 - VideoPlayerContextProvider, 5 - VideoPlayerDispatchContext, 6 - } from "./VideoContext"; 1 + import { forwardRef, useContext, useRef } from "react"; 2 + import { VideoPlayerContext, VideoPlayerContextProvider } from "./VideoContext"; 7 3 8 4 interface VideoPlayerProps { 9 5 children?: React.ReactNode; 10 6 } 11 7 12 - const VideoPlayerInternals = forwardRef<HTMLVideoElement>((props, ref) => { 8 + const VideoPlayerInternals = forwardRef<HTMLVideoElement>((_, ref) => { 13 9 const video = useContext(VideoPlayerContext); 14 - const dispatch = useContext(VideoPlayerDispatchContext); 15 - 16 - const onPlay = useCallback(() => { 17 - dispatch({ 18 - type: "CONTROL", 19 - do: "PLAY", 20 - soft: true, 21 - }); 22 - }, [dispatch]); 23 - const onPause = useCallback(() => { 24 - dispatch({ 25 - type: "CONTROL", 26 - do: "PAUSE", 27 - soft: true, 28 - }); 29 - }, [dispatch]); 30 - 31 - useEffect(() => {}, []); 32 10 33 11 return ( 34 - <video ref={ref} onPlay={onPlay} onPause={onPause} controls> 12 + <video controls ref={ref}> 35 13 {video.source ? <source src={video.source} type="video/mp4" /> : null} 36 14 </video> 37 15 ); ··· 39 17 40 18 export function VideoPlayer(props: VideoPlayerProps) { 41 19 const playerRef = useRef<HTMLVideoElement | null>(null); 42 - const playerWrapperRef = useRef<HTMLDivElement | null>(null); 43 20 44 21 return ( 45 - <VideoPlayerContextProvider 46 - player={playerRef} 47 - playerWrapper={playerWrapperRef} 48 - > 49 - <div ref={playerWrapperRef} className="bg-blue-900"> 50 - <VideoPlayerInternals ref={playerRef} /> 51 - {props.children} 52 - </div> 22 + <VideoPlayerContextProvider player={playerRef}> 23 + <VideoPlayerInternals ref={playerRef} /> 24 + {props.children} 53 25 </VideoPlayerContextProvider> 54 26 ); 55 27 }
+21 -20
src/components/video/controls/FullscreenControl.tsx
··· 1 - import { useCallback, useContext } from "react"; 2 - import { 3 - VideoPlayerContext, 4 - VideoPlayerDispatchContext, 5 - } from "../VideoContext"; 1 + // import { useCallback, useContext } from "react"; 2 + // import { 3 + // VideoPlayerContext, 4 + // VideoPlayerDispatchContext, 5 + // } from "../VideoContext"; 6 6 7 7 export function FullscreenControl() { 8 - const dispatch = useContext(VideoPlayerDispatchContext); 9 - const video = useContext(VideoPlayerContext); 8 + return <p>Hello world</p>; 9 + // const dispatch = useContext(VideoPlayerDispatchContext); 10 + // const video = useContext(VideoPlayerContext); 10 11 11 - const handleClick = useCallback(() => { 12 - dispatch({ 13 - type: "FULLSCREEN", 14 - do: video.fullscreen ? "EXIT" : "ENTER", 15 - }); 16 - }, [video, dispatch]); 12 + // const handleClick = useCallback(() => { 13 + // dispatch({ 14 + // type: "FULLSCREEN", 15 + // do: video.fullscreen ? "EXIT" : "ENTER", 16 + // }); 17 + // }, [video, dispatch]); 17 18 18 - let text = "not fullscreen"; 19 - if (video.fullscreen) text = "in fullscreen"; 19 + // let text = "not fullscreen"; 20 + // if (video.fullscreen) text = "in fullscreen"; 20 21 21 - return ( 22 - <button type="button" onClick={handleClick}> 23 - {text} 24 - </button> 25 - ); 22 + // return ( 23 + // <button type="button" onClick={handleClick}> 24 + // {text} 25 + // </button> 26 + // ); 26 27 }
+7 -19
src/components/video/controls/PauseControl.tsx
··· 1 - import { useCallback, useContext } from "react"; 2 - import { 3 - VideoPlayerContext, 4 - VideoPlayerDispatchContext, 5 - } from "../VideoContext"; 1 + import { useCallback } from "react"; 2 + import { useVideoPlayerState } from "../VideoContext"; 6 3 7 4 export function PauseControl() { 8 - const dispatch = useContext(VideoPlayerDispatchContext); 9 - const video = useContext(VideoPlayerContext); 5 + const { videoState } = useVideoPlayerState(); 10 6 11 7 const handleClick = useCallback(() => { 12 - if (video.controlState === "playing") 13 - dispatch({ 14 - type: "CONTROL", 15 - do: "PAUSE", 16 - }); 17 - else if (video.controlState === "paused") 18 - dispatch({ 19 - type: "CONTROL", 20 - do: "PLAY", 21 - }); 22 - }, [video, dispatch]); 8 + if (videoState?.isPlaying) videoState.pause(); 9 + else videoState.play(); 10 + }, [videoState]); 23 11 24 12 let text = "paused"; 25 - if (video.controlState === "playing") text = "playing"; 13 + if (videoState?.isPlaying) text = "playing"; 26 14 27 15 return ( 28 16 <button type="button" onClick={handleClick}>
+20
src/components/video/hooks/controlVideo.ts
··· 1 + export interface PlayerControls { 2 + play(): void; 3 + pause(): void; 4 + } 5 + 6 + export const initialControls: PlayerControls = { 7 + play: () => null, 8 + pause: () => null, 9 + }; 10 + 11 + export function populateControls(player: HTMLVideoElement): PlayerControls { 12 + return { 13 + play() { 14 + player.play(); 15 + }, 16 + pause() { 17 + player.pause(); 18 + }, 19 + }; 20 + }
+55
src/components/video/hooks/useVideoPlayer.ts
··· 1 + import React, { MutableRefObject, useEffect, useState } from "react"; 2 + import { 3 + initialControls, 4 + PlayerControls, 5 + populateControls, 6 + } from "./controlVideo"; 7 + 8 + export type PlayerState = { 9 + isPlaying: boolean; 10 + isPaused: boolean; 11 + } & PlayerControls; 12 + 13 + export const initialPlayerState = { 14 + isPlaying: false, 15 + isPaused: true, 16 + ...initialControls, 17 + }; 18 + 19 + type SetPlayer = (s: React.SetStateAction<PlayerState>) => void; 20 + 21 + function readState(player: HTMLVideoElement, update: SetPlayer) { 22 + const state = { 23 + ...initialPlayerState, 24 + }; 25 + state.isPaused = player.paused; 26 + state.isPlaying = !player.paused; 27 + 28 + update(state); 29 + } 30 + 31 + function registerListeners(player: HTMLVideoElement, update: SetPlayer) { 32 + player.addEventListener("pause", () => { 33 + update((s) => ({ ...s, isPaused: true, isPlaying: false })); 34 + }); 35 + player.addEventListener("play", () => { 36 + update((s) => ({ ...s, isPaused: false, isPlaying: true })); 37 + }); 38 + } 39 + 40 + export function useVideoPlayer(ref: MutableRefObject<HTMLVideoElement | null>) { 41 + const [state, setState] = useState(initialPlayerState); 42 + 43 + useEffect(() => { 44 + const player = ref.current; 45 + if (player) { 46 + readState(player, setState); 47 + registerListeners(player, setState); 48 + setState((s) => ({ ...s, ...populateControls(player) })); 49 + } 50 + }, [ref]); 51 + 52 + return { 53 + playerState: state, 54 + }; 55 + }