Compare commits
3 Commits
7a7e99337f
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
370e056a2b | |
|
|
55e5136e88 | |
|
|
2586e60f0c |
|
|
@ -0,0 +1,23 @@
|
||||||
|
# watchtogether
|
||||||
|
|
||||||
|
This was a project that I created over 7 days of working while having covid :)
|
||||||
|
|
||||||
|
I probably won't do much with the codebase, but enjoy how I basically figured out how to sync video streams with multiple clients
|
||||||
|
|
||||||
|
## The Stack
|
||||||
|
|
||||||
|
- frontend
|
||||||
|
|
||||||
|
- next js
|
||||||
|
- websockets
|
||||||
|
- chakra-ui
|
||||||
|
- react-player
|
||||||
|
|
||||||
|
- backend
|
||||||
|
|
||||||
|
- go
|
||||||
|
- gorilla/websockets
|
||||||
|
|
||||||
|
## how 2 deploy?
|
||||||
|
|
||||||
|
you don't
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="8a64704d-5500-41a6-aa4c-e275933fc58c" name="Changes" comment="">
|
<list default="true" id="8a64704d-5500-41a6-aa4c-e275933fc58c" name="Changes" comment="">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../frontend/src/components/Player.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/components/Player.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# backend
|
||||||
|
|
@ -1,39 +1 @@
|
||||||
# Example app with [chakra-ui](https://github.com/chakra-ui/chakra-ui) and TypeScript
|
# frontend
|
||||||
|
|
||||||
This example features how to use [chakra-ui](https://github.com/chakra-ui/chakra-ui) as the component library within a Next.js app with TypeScript.
|
|
||||||
|
|
||||||
Next.js and chakra-ui have built-in TypeScript declarations, so we'll get autocompletion for their modules straight away.
|
|
||||||
|
|
||||||
We are connecting the Next.js `_app.js` with `chakra-ui`'s Provider and theme so the pages can have app-wide dark/light mode. We are also creating some components which shows the usage of `chakra-ui`'s style props.
|
|
||||||
|
|
||||||
## Preview
|
|
||||||
|
|
||||||
Preview the example live on [StackBlitz](http://stackblitz.com/):
|
|
||||||
|
|
||||||
[](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-chakra-ui-typescript)
|
|
||||||
|
|
||||||
## Deploy your own
|
|
||||||
|
|
||||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
|
||||||
|
|
||||||
[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-chakra-ui-typescript&project-name=with-chakra-ui-typescript&repository-name=with-chakra-ui-typescript)
|
|
||||||
|
|
||||||
## How to use
|
|
||||||
|
|
||||||
### Using `create-next-app`
|
|
||||||
|
|
||||||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx create-next-app --example with-chakra-ui-typescript with-chakra-ui-typescript-app
|
|
||||||
# or
|
|
||||||
yarn create next-app --example with-chakra-ui-typescript with-chakra-ui-typescript-app
|
|
||||||
```
|
|
||||||
|
|
||||||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
Chakra has supported Gradients and RTL in `v1.1`. To utilize RTL, [add RTL direction and swap](https://chakra-ui.com/docs/features/rtl-support).
|
|
||||||
|
|
||||||
If you don't have multi-direction app, you should make `<Html lang="ar" dir="rtl">` inside `_document.ts`.
|
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,7 @@ const Player = forwardRef<ReactPlayer, ReactPlayerProps>((props, ref) => {
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
config={config}
|
config={config}
|
||||||
controls
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
playing={false}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ const useWS = ({ user }: useWSProps): PlayerSocket | null => {
|
||||||
// todo checkout usecallback
|
// todo checkout usecallback
|
||||||
const [socket, setSocket] = useState<PlayerSocket>(null);
|
const [socket, setSocket] = useState<PlayerSocket>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (socket !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let internalSocket = new PlayerSocket(user);
|
let internalSocket = new PlayerSocket(user);
|
||||||
setSocket(internalSocket);
|
setSocket(internalSocket);
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import IUser from "./IUser";
|
||||||
|
|
||||||
interface IdentityData {
|
interface IdentityData {
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
controller?: boolean;
|
hasController?: boolean;
|
||||||
clientID?: string;
|
clientID?: string;
|
||||||
playlist?: string;
|
playlist?: string;
|
||||||
playhead?: number;
|
playhead?: number;
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,12 @@ import { Container } from "../components/Container";
|
||||||
import Player from "../components/Player";
|
import Player from "../components/Player";
|
||||||
import useWS from "../hooks/useWS";
|
import useWS from "../hooks/useWS";
|
||||||
import IdentityData from "../interfaces/Identity";
|
import IdentityData from "../interfaces/Identity";
|
||||||
|
import { MessageTypes } from "../interfaces/IMessage";
|
||||||
import SetPlayheadEvent from "../interfaces/Playhead";
|
import SetPlayheadEvent from "../interfaces/Playhead";
|
||||||
import SocketEvents from "../interfaces/SocketEvents";
|
import SocketEvents from "../interfaces/SocketEvents";
|
||||||
import { isBrowser } from "../util";
|
import { isBrowser } from "../util";
|
||||||
import PlayerSocket from "../ws/websocket";
|
import Message from "../util/Message";
|
||||||
|
import MessageUtil from "../util/MessageUtil";
|
||||||
|
|
||||||
interface PlayerPageProps {
|
interface PlayerPageProps {
|
||||||
user: User;
|
user: User;
|
||||||
|
|
@ -23,25 +25,74 @@ const PlayerPage: NextPage<PlayerPageProps> = ({ user }) => {
|
||||||
const playerRef = useRef<ReactPlayer>();
|
const playerRef = useRef<ReactPlayer>();
|
||||||
const [id, setID] = useState<string>("");
|
const [id, setID] = useState<string>("");
|
||||||
const [identity, setIdentity] = useState<IdentityData>();
|
const [identity, setIdentity] = useState<IdentityData>();
|
||||||
|
const [paused, setPaused] = useState<boolean>(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isBrowser() && typeof socket !== "undefined") {
|
if (isBrowser() && typeof socket !== "undefined") {
|
||||||
socket?.emitter.once(SocketEvents.Identify, (e: IdentityData) => {
|
socket?.emitter.once(SocketEvents.Identify, (e: IdentityData) => {
|
||||||
setID(e.playlist);
|
setID(e.playlist);
|
||||||
setIdentity(e);
|
setIdentity(e);
|
||||||
|
playerRef?.current.seekTo(e.playhead);
|
||||||
|
setPaused(e.paused);
|
||||||
});
|
});
|
||||||
socket?.emitter.on(SocketEvents.SetPlayhead, (e: SetPlayheadEvent) => {
|
socket?.emitter.on(SocketEvents.SetPlayhead, (e: SetPlayheadEvent) => {
|
||||||
console.log(e);
|
console.log(e.paused);
|
||||||
|
setPaused(e.paused);
|
||||||
|
playerRef.current.seekTo(e.playhead);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [socket]);
|
}, [socket]);
|
||||||
const onSeek = () => {};
|
const onPlay = () => {
|
||||||
|
if (!identity.admin) return;
|
||||||
|
setPaused(false);
|
||||||
|
socket?.send(
|
||||||
|
MessageUtil.encode(
|
||||||
|
new Message(MessageTypes.SetPlayhead, {
|
||||||
|
playhead: playerRef.current.getCurrentTime(),
|
||||||
|
paused: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const onSeek = (playedSeconds: number) => {
|
||||||
|
if (!identity.admin) return;
|
||||||
|
socket.send(
|
||||||
|
MessageUtil.encode(
|
||||||
|
new Message(MessageTypes.SetPlayhead, {
|
||||||
|
playhead: playedSeconds,
|
||||||
|
paused: paused,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const onPause = () => {
|
||||||
|
console.log("running now");
|
||||||
|
if (!identity.admin) return;
|
||||||
|
setPaused(true);
|
||||||
|
socket?.send(
|
||||||
|
MessageUtil.encode(
|
||||||
|
new Message(MessageTypes.SetPlayhead, {
|
||||||
|
playhead: playerRef.current.getCurrentTime(),
|
||||||
|
paused: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Watch Together</title>
|
<title>Watch Together</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Container height="100vh" background={"#000"}>
|
<Container height="100vh" background={"#000"}>
|
||||||
<Player ref={playerRef} />
|
<Player
|
||||||
|
url={id}
|
||||||
|
onPlay={onPlay}
|
||||||
|
onPause={onPause}
|
||||||
|
onSeek={onSeek}
|
||||||
|
controls={identity?.hasController}
|
||||||
|
playing={!paused}
|
||||||
|
ref={playerRef}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue