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.

custom video player start

+281 -5
+103
src/components/video/VideoContext.tsx
··· 1 + import React, { 2 + createContext, 3 + MutableRefObject, 4 + useEffect, 5 + useReducer, 6 + } from "react"; 7 + 8 + interface VideoPlayerContextType { 9 + source: null | string; 10 + playerWrapper: HTMLDivElement | null; 11 + player: HTMLVideoElement | null; 12 + controlState: "paused" | "playing"; 13 + fullscreen: boolean; 14 + } 15 + const initial = ( 16 + player: HTMLVideoElement | null = null, 17 + wrapper: HTMLDivElement | null = null 18 + ): VideoPlayerContextType => ({ 19 + source: null, 20 + playerWrapper: wrapper, 21 + player, 22 + controlState: "paused", 23 + fullscreen: false, 24 + }); 25 + 26 + type VideoPlayerContextAction = 27 + | { type: "SET_SOURCE"; url: string } 28 + | { type: "CONTROL"; do: "PAUSE" | "PLAY"; soft?: boolean } 29 + | { type: "FULLSCREEN"; do: "ENTER" | "EXIT"; soft?: boolean } 30 + | { 31 + type: "UPDATE_PLAYER"; 32 + player: HTMLVideoElement | null; 33 + playerWrapper: HTMLDivElement | null; 34 + }; 35 + 36 + function videoPlayerContextReducer( 37 + original: VideoPlayerContextType, 38 + action: VideoPlayerContextAction 39 + ): VideoPlayerContextType { 40 + const video = { ...original }; 41 + if (action.type === "SET_SOURCE") { 42 + video.source = action.url; 43 + return video; 44 + } 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 + 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(); 65 + return video; 66 + } 67 + 68 + return original; 69 + } 70 + 71 + export const VideoPlayerContext = createContext<VideoPlayerContextType>( 72 + initial() 73 + ); 74 + export const VideoPlayerDispatchContext = createContext< 75 + React.Dispatch<VideoPlayerContextAction> 76 + >(null as any); 77 + 78 + export function VideoPlayerContextProvider(props: { 79 + children: React.ReactNode; 80 + player: MutableRefObject<HTMLVideoElement | null>; 81 + playerWrapper: MutableRefObject<HTMLDivElement | null>; 82 + }) { 83 + const [videoData, dispatch] = useReducer<typeof videoPlayerContextReducer>( 84 + videoPlayerContextReducer, 85 + initial() 86 + ); 87 + 88 + useEffect(() => { 89 + dispatch({ 90 + type: "UPDATE_PLAYER", 91 + player: props.player.current, 92 + playerWrapper: props.playerWrapper.current, 93 + }); 94 + }, [props.player, props.playerWrapper]); 95 + 96 + return ( 97 + <VideoPlayerContext.Provider value={videoData}> 98 + <VideoPlayerDispatchContext.Provider value={dispatch}> 99 + {props.children} 100 + </VideoPlayerDispatchContext.Provider> 101 + </VideoPlayerContext.Provider> 102 + ); 103 + }
+55
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"; 7 + 8 + interface VideoPlayerProps { 9 + children?: React.ReactNode; 10 + } 11 + 12 + const VideoPlayerInternals = forwardRef<HTMLVideoElement>((props, ref) => { 13 + 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 + 33 + return ( 34 + <video ref={ref} onPlay={onPlay} onPause={onPause} controls> 35 + {video.source ? <source src={video.source} type="video/mp4" /> : null} 36 + </video> 37 + ); 38 + }); 39 + 40 + export function VideoPlayer(props: VideoPlayerProps) { 41 + const playerRef = useRef<HTMLVideoElement | null>(null); 42 + const playerWrapperRef = useRef<HTMLDivElement | null>(null); 43 + 44 + 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> 53 + </VideoPlayerContextProvider> 54 + ); 55 + }
+26
src/components/video/controls/FullscreenControl.tsx
··· 1 + import { useCallback, useContext } from "react"; 2 + import { 3 + VideoPlayerContext, 4 + VideoPlayerDispatchContext, 5 + } from "../VideoContext"; 6 + 7 + export function FullscreenControl() { 8 + const dispatch = useContext(VideoPlayerDispatchContext); 9 + const video = useContext(VideoPlayerContext); 10 + 11 + const handleClick = useCallback(() => { 12 + dispatch({ 13 + type: "FULLSCREEN", 14 + do: video.fullscreen ? "EXIT" : "ENTER", 15 + }); 16 + }, [video, dispatch]); 17 + 18 + let text = "not fullscreen"; 19 + if (video.fullscreen) text = "in fullscreen"; 20 + 21 + return ( 22 + <button type="button" onClick={handleClick}> 23 + {text} 24 + </button> 25 + ); 26 + }
+32
src/components/video/controls/PauseControl.tsx
··· 1 + import { useCallback, useContext } from "react"; 2 + import { 3 + VideoPlayerContext, 4 + VideoPlayerDispatchContext, 5 + } from "../VideoContext"; 6 + 7 + export function PauseControl() { 8 + const dispatch = useContext(VideoPlayerDispatchContext); 9 + const video = useContext(VideoPlayerContext); 10 + 11 + 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]); 23 + 24 + let text = "paused"; 25 + if (video.controlState === "playing") text = "playing"; 26 + 27 + return ( 28 + <button type="button" onClick={handleClick}> 29 + {text} 30 + </button> 31 + ); 32 + }
+19
src/components/video/controls/SourceControl.tsx
··· 1 + import { useContext, useEffect } from "react"; 2 + import { VideoPlayerDispatchContext } from "../VideoContext"; 3 + 4 + interface SourceControlProps { 5 + source: string; 6 + } 7 + 8 + export function SourceControl(props: SourceControlProps) { 9 + const dispatch = useContext(VideoPlayerDispatchContext); 10 + 11 + useEffect(() => { 12 + dispatch({ 13 + type: "SET_SOURCE", 14 + url: props.source, 15 + }); 16 + }, [props.source, dispatch]); 17 + 18 + return null; 19 + }
+2
src/setup/App.tsx
··· 6 6 import { NotFoundPage } from "@/views/notfound/NotFoundView"; 7 7 import { MediaView } from "@/views/MediaView"; 8 8 import { SearchView } from "@/views/search/SearchView"; 9 + import { TestView } from "@/views/TestView"; 9 10 10 11 function App() { 11 12 return ( ··· 18 19 <Route exact path="/media/movie/:media" component={MediaView} /> 19 20 <Route exact path="/media/series/:media" component={MediaView} /> 20 21 <Route exact path="/search/:type/:query?" component={SearchView} /> 22 + <Route exact path="/test" component={TestView} /> 21 23 <Route path="*" component={NotFoundPage} /> 22 24 </Switch> 23 25 </BookmarkContextProvider>
+16
src/views/TestView.tsx
··· 1 + import { FullscreenControl } from "@/components/video/controls/FullscreenControl"; 2 + import { PauseControl } from "@/components/video/controls/PauseControl"; 3 + import { SourceControl } from "@/components/video/controls/SourceControl"; 4 + import { VideoPlayer } from "@/components/video/VideoPlayer"; 5 + 6 + // test videos: https://gist.github.com/jsturgis/3b19447b304616f18657 7 + 8 + export function TestView() { 9 + return ( 10 + <VideoPlayer> 11 + <PauseControl /> 12 + <FullscreenControl /> 13 + <SourceControl source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" /> 14 + </VideoPlayer> 15 + ); 16 + }
+28 -5
yarn.lock
··· 10 10 "core-js-pure" "^3.25.1" 11 11 "regenerator-runtime" "^0.13.11" 12 12 13 - "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.5", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.6": 13 + "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.5", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.6", "@babel/runtime@^7.4.5", "@babel/runtime@^7.9.2": 14 14 "integrity" "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==" 15 15 "resolved" "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz" 16 16 "version" "7.20.6" ··· 780 780 dependencies: 781 781 "ip-regex" "^4.1.0" 782 782 783 - "classnames@^2.0.0": 783 + "classnames@^2.0.0", "classnames@^2.2.6": 784 784 "integrity" "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" 785 785 "resolved" "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz" 786 786 "version" "2.3.2" ··· 2100 2100 "integrity" "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" 2101 2101 "resolved" "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" 2102 2102 "version" "4.6.2" 2103 + 2104 + "lodash.throttle@^4.1.1": 2105 + "integrity" "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" 2106 + "resolved" "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz" 2107 + "version" "4.1.1" 2103 2108 2104 2109 "lodash@^4.17.15": 2105 2110 "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" ··· 2787 2792 dependencies: 2788 2793 "read" "1" 2789 2794 2790 - "prop-types@^15.6.0", "prop-types@^15.6.2", "prop-types@^15.8.1": 2795 + "prop-types@^15.6.0", "prop-types@^15.6.2", "prop-types@^15.7.2", "prop-types@^15.8.1": 2791 2796 "integrity" "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==" 2792 2797 "resolved" "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" 2793 2798 "version" "15.8.1" ··· 2821 2826 dependencies: 2822 2827 "performance-now" "^2.1.0" 2823 2828 2824 - "react-dom@^0.14.2 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16 || ^17 || ^18", "react-dom@^17.0.2": 2829 + "react-dom@^0.14.2 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16 || ^17 || ^18", "react-dom@^17.0.2": 2825 2830 "integrity" "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==" 2826 2831 "resolved" "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" 2827 2832 "version" "17.0.2" ··· 2882 2887 "shallowequal" "^1.0.0" 2883 2888 "subscribe-ui-event" "^2.0.6" 2884 2889 2885 - "react@^0.14.2 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16 || ^17 || ^18", "react@^17.0.2", "react@>= 16.8.0", "react@>=15", "react@17.0.2": 2890 + "react@^0.14.2 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16 || ^17 || ^18", "react@^17.0.2", "react@>= 16.8.0", "react@>=15", "react@17.0.2": 2886 2891 "integrity" "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==" 2887 2892 "resolved" "https://registry.npmjs.org/react/-/react-17.0.2.tgz" 2888 2893 "version" "17.0.2" ··· 2941 2946 dependencies: 2942 2947 "picomatch" "^2.2.1" 2943 2948 2949 + "redux@^4.0.1": 2950 + "integrity" "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==" 2951 + "resolved" "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz" 2952 + "version" "4.2.0" 2953 + dependencies: 2954 + "@babel/runtime" "^7.9.2" 2955 + 2944 2956 "regenerator-runtime@^0.13.11": 2945 2957 "integrity" "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" 2946 2958 "resolved" "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" ··· 3414 3426 "integrity" "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" 3415 3427 "resolved" "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz" 3416 3428 "version" "1.0.1" 3429 + 3430 + "video-react@^0.16.0": 3431 + "integrity" "sha512-138NHPS8bmgqCYVCdbv2GVFhXntemNHWGw9AN8iJSzr3jizXMmWJd2LTBppr4hZJUbyW1A1tPZ3CQXZUaexMVA==" 3432 + "resolved" "https://registry.npmjs.org/video-react/-/video-react-0.16.0.tgz" 3433 + "version" "0.16.0" 3434 + dependencies: 3435 + "@babel/runtime" "^7.4.5" 3436 + "classnames" "^2.2.6" 3437 + "lodash.throttle" "^4.1.1" 3438 + "prop-types" "^15.7.2" 3439 + "redux" "^4.0.1" 3417 3440 3418 3441 "vite-plugin-package-version@^1.0.2": 3419 3442 "integrity" "sha512-xCJMR0KD4rqSUwINyHJlLizio2VzYzaMrRkqC9xWaVGXgw1lIrzdD+wBUf1XDM8EhL1JoQ7aykLOfKrlZd1SoQ=="