49 changed files with 223 additions and 42 deletions
@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
|
||||
import { useGoBack } from "@/hooks/useGoBack"; |
||||
import { useVolumeControl } from "@/hooks/useVolumeToggle"; |
||||
import { forwardRef, useContext, useEffect, useRef } from "react"; |
||||
import { VideoErrorBoundary } from "../../components/video/parts/VideoErrorBoundary"; |
||||
import { VideoErrorBoundary } from "./parts/VideoErrorBoundary"; |
||||
import { |
||||
useVideoPlayerState, |
||||
VideoPlayerContext, |
||||
VideoPlayerContextProvider, |
||||
} from "../../video/components./../components/video/VideoContext"; |
||||
} from "./VideoContext"; |
||||
|
||||
export interface VideoPlayerProps { |
||||
autoPlay?: boolean; |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
import { Icons } from "@/components/Icon"; |
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks"; |
||||
import { useControls } from "@/video/state/logic/controls"; |
||||
import { useMediaPlaying } from "@/video/state/logic/mediaplaying"; |
||||
import { useCallback } from "react"; |
||||
import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton"; |
||||
|
||||
interface Props { |
||||
className?: string; |
||||
iconSize?: string; |
||||
} |
||||
|
||||
export function PauseAction(props: Props) { |
||||
const descriptor = useVideoPlayerDescriptor(); |
||||
const mediaPlaying = useMediaPlaying(descriptor); |
||||
const controls = useControls(descriptor); |
||||
|
||||
const handleClick = useCallback(() => { |
||||
if (mediaPlaying.isPlaying) controls.pause(); |
||||
else controls.play(); |
||||
}, [mediaPlaying, controls]); |
||||
|
||||
// TODO add seeking back
|
||||
const icon = mediaPlaying.isPlaying ? Icons.PAUSE : Icons.PLAY; |
||||
|
||||
return ( |
||||
<VideoPlayerIconButton |
||||
iconSize={props.iconSize} |
||||
className={props.className} |
||||
icon={icon} |
||||
onClick={handleClick} |
||||
/> |
||||
); |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks"; |
||||
import { setProvider, unsetStateProvider } from "@/video/state/providers/utils"; |
||||
import { createVideoStateProvider } from "@/video/state/providers/videoStateProvider"; |
||||
import { useEffect, useRef } from "react"; |
||||
|
||||
export function VideoElementInternal() { |
||||
const descriptor = useVideoPlayerDescriptor(); |
||||
const ref = useRef<HTMLVideoElement>(null); |
||||
|
||||
useEffect(() => { |
||||
if (!ref.current) return; |
||||
const provider = createVideoStateProvider(descriptor, ref.current); |
||||
setProvider(descriptor, provider); |
||||
const { destroy } = provider.providerStart(); |
||||
return () => { |
||||
unsetStateProvider(descriptor); |
||||
destroy(); |
||||
}; |
||||
}, [descriptor]); |
||||
|
||||
// TODO autoplay and muted
|
||||
return ( |
||||
<video |
||||
ref={ref} |
||||
playsInline |
||||
className="h-full w-full" |
||||
src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4" |
||||
/> |
||||
); |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
import { Icon, Icons } from "@/components/Icon"; |
||||
import React from "react"; |
||||
|
||||
export interface VideoPlayerIconButtonProps { |
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void; |
||||
icon: Icons; |
||||
text?: string; |
||||
className?: string; |
||||
iconSize?: string; |
||||
} |
||||
|
||||
export function VideoPlayerIconButton(props: VideoPlayerIconButtonProps) { |
||||
return ( |
||||
<div className={props.className}> |
||||
<button |
||||
type="button" |
||||
onClick={props.onClick} |
||||
className="group pointer-events-auto p-2 text-white transition-transform duration-100 active:scale-110" |
||||
> |
||||
<div className="flex items-center justify-center rounded-full bg-white bg-opacity-0 p-2 transition-colors duration-100 group-hover:bg-opacity-20"> |
||||
<Icon icon={props.icon} className={props.iconSize ?? "text-2xl"} /> |
||||
{props.text ? <span className="ml-2">{props.text}</span> : null} |
||||
</div> |
||||
</button> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
import { getPlayerState } from "../cache"; |
||||
import { VideoPlayerStateController } from "../providers/providerTypes"; |
||||
|
||||
export function useControls(descriptor: string): VideoPlayerStateController { |
||||
const state = getPlayerState(descriptor); |
||||
|
||||
return { |
||||
pause() { |
||||
state.stateProvider?.pause(); |
||||
}, |
||||
play() { |
||||
state.stateProvider?.play(); |
||||
}, |
||||
}; |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
import { useEffect, useState } from "react"; |
||||
import { getPlayerState } from "../cache"; |
||||
import { listenEvent, sendEvent, unlistenEvent } from "../events"; |
||||
import { VideoPlayerState } from "../types"; |
||||
|
||||
export type VideoMediaPlayingEvent = { |
||||
isPlaying: boolean; |
||||
isPaused: boolean; |
||||
isLoading: boolean; |
||||
hasPlayedOnce: boolean; |
||||
}; |
||||
|
||||
function getMediaPlayingFromState( |
||||
state: VideoPlayerState |
||||
): VideoMediaPlayingEvent { |
||||
return { |
||||
hasPlayedOnce: state.hasPlayedOnce, |
||||
isLoading: state.isLoading, |
||||
isPaused: state.isPaused, |
||||
isPlaying: state.isPlaying, |
||||
}; |
||||
} |
||||
|
||||
export function updateMediaPlaying( |
||||
descriptor: string, |
||||
state: VideoPlayerState |
||||
) { |
||||
sendEvent<VideoMediaPlayingEvent>( |
||||
descriptor, |
||||
"mediaplaying", |
||||
getMediaPlayingFromState(state) |
||||
); |
||||
} |
||||
|
||||
export function useMediaPlaying(descriptor: string): VideoMediaPlayingEvent { |
||||
const state = getPlayerState(descriptor); |
||||
const [data, setData] = useState<VideoMediaPlayingEvent>( |
||||
getMediaPlayingFromState(state) |
||||
); |
||||
|
||||
useEffect(() => { |
||||
function update(payload: CustomEvent<VideoMediaPlayingEvent>) { |
||||
setData(payload.detail); |
||||
} |
||||
listenEvent(descriptor, "mediaplaying", update); |
||||
return () => { |
||||
unlistenEvent(descriptor, "mediaplaying", update); |
||||
}; |
||||
}, [descriptor]); |
||||
|
||||
return data; |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
import { getPlayerState } from "../cache"; |
||||
import { VideoPlayerStateProvider } from "./providerTypes"; |
||||
|
||||
export function setProvider( |
||||
descriptor: string, |
||||
provider: VideoPlayerStateProvider |
||||
) { |
||||
const state = getPlayerState(descriptor); |
||||
state.stateProvider = provider; |
||||
} |
||||
|
||||
/** |
||||
* Note: This only sets the state provider to null. it does not destroy the listener |
||||
*/ |
||||
export function unsetStateProvider(descriptor: string) { |
||||
const state = getPlayerState(descriptor); |
||||
state.stateProvider = null; |
||||
} |
@ -1,35 +1,30 @@
@@ -1,35 +1,30 @@
|
||||
import { |
||||
useChromecast, |
||||
useChromecastAvailable, |
||||
} from "@/hooks/useChromecastAvailable"; |
||||
import { useEffect, useRef } from "react"; |
||||
// import {
|
||||
// useChromecast,
|
||||
// useChromecastAvailable,
|
||||
// } from "@/hooks/useChromecastAvailable";
|
||||
// import { useEffect, useRef } from "react";
|
||||
|
||||
function ChromeCastButton() { |
||||
const ref = useRef<HTMLDivElement>(null); |
||||
const available = useChromecastAvailable(); |
||||
import { PauseAction } from "@/video/components/actions/PauseAction"; |
||||
import { VideoPlayerBase } from "@/video/components/VideoPlayerBase"; |
||||
|
||||
useEffect(() => { |
||||
if (!available) return; |
||||
const tag = document.createElement("google-cast-launcher"); |
||||
tag.setAttribute("id", "castbutton"); |
||||
ref.current?.appendChild(tag); |
||||
}, [available]); |
||||
// function ChromeCastButton() {
|
||||
// const ref = useRef<HTMLDivElement>(null);
|
||||
// const available = useChromecastAvailable();
|
||||
|
||||
return <div ref={ref} />; |
||||
} |
||||
// useEffect(() => {
|
||||
// if (!available) return;
|
||||
// const tag = document.createElement("google-cast-launcher");
|
||||
// tag.setAttribute("id", "castbutton");
|
||||
// ref.current?.appendChild(tag);
|
||||
// }, [available]);
|
||||
|
||||
export function TestView() { |
||||
const { startCast, stopCast } = useChromecast(); |
||||
// return <div ref={ref} />;
|
||||
// }
|
||||
|
||||
export function TestView() { |
||||
return ( |
||||
<div> |
||||
<ChromeCastButton /> |
||||
<button type="button" onClick={startCast}> |
||||
Start casting |
||||
</button> |
||||
<button type="button" onClick={stopCast}> |
||||
StopCasting |
||||
</button> |
||||
</div> |
||||
<VideoPlayerBase> |
||||
<PauseAction /> |
||||
</VideoPlayerBase> |
||||
); |
||||
} |
||||
|
Loading…
Reference in new issue