Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add no video image as canvas element #37

Open
wants to merge 24 commits into
base: feature/add-grid-layout
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ import { AppProps } from 'next/app'
const App = ({ Component, pageProps }: AppProps) => <Component {...pageProps} />

export default App

declare global {
interface HTMLCanvasElement {
captureStream(frameRate?: number): MediaStream;
}
}
1 change: 1 addition & 0 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const Home = () => (
<Time />
<JoinForm />
</div>
<br></br>
</>
)

Expand Down
18 changes: 4 additions & 14 deletions pages/room/[roomId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const Room = ({
}
}, [roomName])

const [userid, peer, peerError] = usePeerState({ userId: undefined, stunUrl: stunUrl })
const [peer, userid, peerError] = usePeerState({ userId: undefined, stunUrl: stunUrl })
const [calls] = useConnectionState(peer, socketRef.current, stream)
const [callStatus, setCallStatus] = useState<boolean>(false)
const attendees = <Attendees peerCalls={calls} />
Expand Down Expand Up @@ -100,26 +100,16 @@ const Room = ({
)
}

if (callStatus && calls.length === 0) {
roomHeader = (
<>
<h1>Joined | {roomName}</h1>
<h2>You are the 1st in the room</h2>
</>
)
} else if (callStatus) {
if (callStatus) {
joinButton = <></>
roomHeader = (
<>
<h1>Joined | {roomName}</h1>
<h2>{toCardinal(calls.length)} in the room</h2>
<h2>{toCardinal(calls.length + 1)} in the room</h2>
</>
)
}

if (callStatus) {
joinButton = <></>
}

return (
<>
<Head>
Expand Down
15 changes: 15 additions & 0 deletions public/no-video.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion types/join-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const JoinForm = () => {

return (
<>
<h2>Type name or press open to generate a room name{name}</h2>
<h2>Type name or press open to generate a room name</h2>
<form id={`join-${name}`}>
<input
type='text'
Expand Down
72 changes: 72 additions & 0 deletions types/photo-uploader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useEffect, useRef, useState } from 'react'
import Video from '../types/video'

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, prettier/prettier
const PhotoUploader = () => {
const defaultPhoto = '/no-video.svg'
const canvasRef = useRef<HTMLCanvasElement>(null)
const [photo, setPhoto] = useState(defaultPhoto)
const [stream, setStream] = useState<MediaStream | null>(null)

const draw = (context: CanvasRenderingContext2D) => {
const image: HTMLImageElement = new Image()
let imageWidth: number
let imageHeight: number
image.src = photo
image.onload = () => {
const ratio = Math.min(288 / image.width, 288 / image.height)
if (image.width > 288 || image.height > 288) {
imageWidth = image.width * ratio
imageHeight = image.height * ratio
} else {
imageWidth = image.width
imageHeight = image.height
}
const x = context.canvas.width / 2 - imageWidth / 2
const y = context.canvas.height / 2 - imageHeight / 2
context.clearRect(0, 0, context.canvas.width, context.canvas.height)
context.drawImage(image, x, y, imageWidth, imageHeight)
}
}

const handleChange = (event: any) => {
if (event.target.files[0]) {
setPhoto(URL.createObjectURL(event.target.files[0]))
}
}

const removePhoto = () => {
setPhoto(defaultPhoto)
}

useEffect(() => {
if (!canvasRef.current) {
throw Error('Missing canvas element reference')
}
const canvas = canvasRef.current
const context = canvas.getContext('2d')
if (context) {
draw(context)
setStream(canvas.captureStream())
}
}, [photo])

return (
<div className='flex flex-col items-center'>
<form encType='multipart/form-data'>
<input
type='file'
accept='image/*'
id="profile_photo"
onChange={handleChange}
/>
<button onClick={removePhoto}>Remove photo</button>
<canvas ref={canvasRef} height='288px'/>
{/* @ts-ignore */}
<Video stream={stream} muted={true} />
</form>
</div>
)
}

export default PhotoUploader
29 changes: 29 additions & 0 deletions types/photo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect, useRef } from 'react'

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, prettier/prettier
const Photo = () => {
const canvasRef = useRef<HTMLCanvasElement>(null)

const draw = (context: CanvasRenderingContext2D) => {
const image: HTMLImageElement = new Image()
image.src = '/no-video.svg'
image.onload = () => {
context.drawImage(image, 0, 0)
}
}

useEffect(() => {
const canvas = canvasRef.current
const context = canvas?.getContext('2d')
if (context) {
draw(context)
}
})
// w-40 h-40 md:w-56 md:h-56 xl:w-72 xl:h-72

return (
<canvas ref={canvasRef} height='300px'/>
)
}

export default Photo
29 changes: 16 additions & 13 deletions types/presenter.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { useState } from 'react'
import Disconnect from '../public/disconnect'
import Mute from '../public/mute'
import Share from '../public/share'
import UnShare from '../public/un-share'
import UnMute from '../public/unmute'
import Video from '../types/video'

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const Presenter = (props: { stream: MediaStream; disconnect: () => void }) => {
const Presenter = (props: { stream: MediaStream | null, disconnect: () => void }) => {
const [micActivated, setMicActivated] = useState<boolean>(true)
const [audioTrack, setAudioTrack] = useState<MediaStreamTrack | undefined>(undefined)
const [videoActive, setVideoActive] = useState<boolean>(true)
const [screenCaptureActivated, setScreenCapture] = useState<boolean>(false)

Expand All @@ -21,18 +19,23 @@ const Presenter = (props: { stream: MediaStream; disconnect: () => void }) => {

const activateMicrophone = () => {
setMicActivated(true)
const audioTracks = props.stream.getAudioTracks()
audioTracks.forEach((track) => {
track.enabled = true
})
if (audioTrack) {
props.stream?.addTrack(audioTrack)
}
}

const deactivateMicrophone = () => {
if (!props.stream) {
return
}
setMicActivated(false)
const audioTracks = props.stream.getAudioTracks()
audioTracks.forEach((track) => {
track.enabled = false
})
if (!audioTrack) {
const track = props.stream.getAudioTracks()[0]
setAudioTrack(track)
props.stream.removeTrack(track)
} else {
props.stream.removeTrack(audioTrack)
}
}

const startVideo = () => {
Expand Down Expand Up @@ -85,7 +88,7 @@ const Presenter = (props: { stream: MediaStream; disconnect: () => void }) => {
{videoButton}
<button className='bg-red-800' onClick={props.disconnect}>End</button>
</div>
<Video stream={props.stream} muted={true} />
<Video stream={props.stream} muted={false} />
</div>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion types/video.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useRef } from 'react'

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, prettier/prettier
const Video = (props: { stream: MediaStream, muted: boolean }) => {
const Video = (props: { stream: MediaStream | null, muted: boolean }) => {
const videoRef = useRef<HTMLVideoElement>(null)

const handleCanPlay = () => {
Expand Down
8 changes: 4 additions & 4 deletions useConnectionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { Socket } from 'socket.io-client'
import PeerCall from './types/peer-call'

const useConnectionState = (
peer: Peer,
socket: Socket,
stream: MediaStream
peer: Peer | null,
socket: Socket | null,
stream: MediaStream | null
): [PeerCall[]] => {
const [calls, setCalls] = useState<PeerCall[]>([])

Expand All @@ -33,7 +33,7 @@ const useConnectionState = (
socket.on('user-disconnected', (peerId: string) => {
removeCallFromPeersByUserId(peerId)
})
}, [peer, stream])
}, [peer, socket, stream])

const addCallToPeers = (peerId: string, call: MediaConnection) => {
call.on('stream', (peerVideoStream: MediaStream) => {
Expand Down
21 changes: 8 additions & 13 deletions usePeerState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import PeerError from './types/peer-error'
// copied partially from https://github.com/madou/react-peer/blob/master/src/use-peer-state.tsx
const usePeerState = (
opts: { userId: string | undefined, stunUrl: string } = { userId: undefined, stunUrl: '' }
): [string | undefined, Peer, PeerError | undefined] => {
const [error, setError] = useState<PeerError | undefined>(undefined)
): [Peer | null, string | undefined, PeerError | undefined] => {
const [peer, setPeer] = useState<Peer | null>(null)
const [userId, setUserId] = useState(opts.userId)
const [error, setError] = useState<PeerError | undefined>(undefined)

useEffect(() => {
import('peerjs').then(({ default: Peer }) => {
Expand Down Expand Up @@ -38,27 +38,22 @@ const usePeerState = (
}
})

localPeer?.on('open', () => {
if (userId !== localPeer?.id) {
setUserId(localPeer?.id)
}
localPeer?.on('open', (id) => {
console.dir(id)
setUserId(id)
})

localPeer?.on('error', (err) => setError(err))
})

return function cleanup() {
if (peer) {
peer.destroy()
}
peer?.destroy()
}
}, [opts.userId])
}, [userId, error])

return [
userId,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
peer,
userId,
error
]
}
Expand Down
4 changes: 1 addition & 3 deletions useUserMedia.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'

const useUserMedia = (): MediaStream => {
const useUserMedia = (): MediaStream | null => {
const [stream, setStream] = useState<MediaStream | null>(null)

useEffect(() => {
Expand All @@ -25,8 +25,6 @@ const useUserMedia = (): MediaStream => {
}
}
}, [stream])
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return stream
}

Expand Down