videochat working

This commit is contained in:
Jeff Emmett 2024-12-09 03:42:44 -05:00
parent baf1efce43
commit 2d763c669a
5 changed files with 96 additions and 203 deletions

View File

@ -15,5 +15,5 @@ R2_BUCKET_NAME='your_bucket_name'
R2_PREVIEW_BUCKET_NAME='your_preview_bucket_name' R2_PREVIEW_BUCKET_NAME='your_preview_bucket_name'
# Daily.co Configuration # Daily.co Configuration
DAILY_API_KEY='your_daily_api_key' VITE_DAILY_API_KEY=your_daily_api_key_here
DAILY_DOMAIN='your_daily_domain' VITE_DAILY_DOMAIN='your_daily_domain'

2
.gitignore vendored
View File

@ -175,7 +175,5 @@ dist
.env.*.local .env.*.local
.dev.vars .dev.vars
# Keep example file
!.env.example
package-lock.json package-lock.json

View File

@ -19,6 +19,8 @@ import { MarkdownShape } from "./shapes/MarkdownShapeUtil"
import { MarkdownTool } from "./tools/MarkdownTool" import { MarkdownTool } from "./tools/MarkdownTool"
import { createRoot } from "react-dom/client" import { createRoot } from "react-dom/client"
import { handleInitialPageLoad } from "./utils/handleInitialPageLoad" import { handleInitialPageLoad } from "./utils/handleInitialPageLoad"
import { DailyProvider } from "@daily-co/daily-react"
import Daily from "@daily-co/daily-js"
inject() inject()
@ -30,6 +32,8 @@ const customShapeUtils = [
] ]
const customTools = [ChatBoxTool, VideoChatTool, EmbedTool, MarkdownTool] const customTools = [ChatBoxTool, VideoChatTool, EmbedTool, MarkdownTool]
const callObject = Daily.createCallObject()
export default function InteractiveShapeExample() { export default function InteractiveShapeExample() {
return ( return (
<div className="tldraw__editor"> <div className="tldraw__editor">
@ -64,14 +68,16 @@ function App() {
console.log("App initialized, NODE_ENV:", process.env.NODE_ENV) console.log("App initialized, NODE_ENV:", process.env.NODE_ENV)
return ( return (
<BrowserRouter> <DailyProvider callObject={callObject}>
<Routes> <BrowserRouter>
<Route path="/" element={<Default />} /> <Routes>
<Route path="/contact" element={<Contact />} /> <Route path="/" element={<Default />} />
<Route path="/board/:slug" element={<Board />} /> <Route path="/contact" element={<Contact />} />
<Route path="/inbox" element={<Inbox />} /> <Route path="/board/:slug" element={<Board />} />
</Routes> <Route path="/inbox" element={<Inbox />} />
</BrowserRouter> </Routes>
</BrowserRouter>
</DailyProvider>
) )
} }

View File

@ -1,13 +1,5 @@
import { BaseBoxShapeUtil, TLBaseShape } from "tldraw" import { BaseBoxShapeUtil, TLBaseShape } from "tldraw"
import { useEffect, useState, useRef } from "react" import { useEffect, useState } from "react"
import { WORKER_URL } from "../routes/Board"
import DailyIframe from "@daily-co/daily-js"
interface Window {
DailyIframe: {
setLogLevel(level: "error" | "warn" | "info" | "debug"): void
}
}
export type IVideoChatShape = TLBaseShape< export type IVideoChatShape = TLBaseShape<
"VideoChat", "VideoChat",
@ -15,63 +7,11 @@ export type IVideoChatShape = TLBaseShape<
w: number w: number
h: number h: number
roomUrl: string | null roomUrl: string | null
userName: string allowCamera: boolean
allowMicrophone: boolean
} }
> >
interface DailyApiError {
error: string
info?: string
message?: string
}
// Simplified component using Daily Prebuilt
const VideoChatComponent = ({ roomUrl }: { roomUrl: string }) => {
const wrapperRef = useRef<HTMLDivElement>(null)
const callFrameRef = useRef<ReturnType<
typeof DailyIframe.createFrame
> | null>(null)
useEffect(() => {
if (!wrapperRef.current || !roomUrl) return
// Create and configure the Daily call frame
callFrameRef.current = DailyIframe.createFrame(wrapperRef.current, {
iframeStyle: {
width: "100%",
height: "100%",
border: "0",
borderRadius: "4px",
},
showLeaveButton: true,
showFullscreenButton: true,
})
// Join the room
callFrameRef.current.join({ url: roomUrl })
// Cleanup
return () => {
if (callFrameRef.current) {
callFrameRef.current.destroy()
}
}
}, [roomUrl])
return (
<div
ref={wrapperRef}
style={{
width: "100%",
height: "100%",
backgroundColor: "#f0f0f0",
borderRadius: "4px",
overflow: "hidden",
}}
/>
)
}
export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> { export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
static override type = "VideoChat" static override type = "VideoChat"
@ -84,86 +24,45 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
roomUrl: null, roomUrl: null,
w: 640, w: 640,
h: 480, h: 480,
userName: "", allowCamera: false,
allowMicrophone: false,
} }
} }
async ensureRoomExists(shape: IVideoChatShape) { async ensureRoomExists(shape: IVideoChatShape) {
console.log("Environment variables:", { if (shape.props.roomUrl !== null) return
NODE_ENV: process.env.NODE_ENV,
allEnvVars: import.meta.env,
dailyApiKey: import.meta.env.VITE_DAILY_API_KEY,
})
if (shape.props.roomUrl !== null) {
return
}
try { try {
// Ensure API key exists and is properly formatted const apiKey = import.meta.env["VITE_DAILY_API_KEY"]
const apiKey = import.meta.env.VITE_DAILY_API_KEY?.trim() if (!apiKey) throw new Error("Daily API key is missing")
console.log("API Key exists:", !!apiKey)
console.log(
"API Key format:",
apiKey?.substring(0, 4) === "key_" ? "correct" : "incorrect",
)
console.log("Available env vars:", import.meta.env)
if (!apiKey) { const response = await fetch("https://api.daily.co/v1/rooms", {
throw new Error("Daily API key is missing")
}
// Create room using Daily.co API directly
const response = await fetch(`https://api.daily.co/v1/rooms`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
// Ensure no extra spaces in the Authorization header Authorization: `Bearer ${apiKey.trim()}`,
Authorization: `Bearer ${apiKey}`.trim(),
}, },
body: JSON.stringify({ body: JSON.stringify({
properties: { properties: {
enable_chat: true, enable_chat: true,
start_audio_off: true, start_audio_off: true,
start_video_off: true, start_video_off: true,
enable_screenshare: true,
enable_recording: true,
max_participants: 8,
enable_network_ui: true,
enable_prejoin_ui: true,
enable_people_ui: true,
enable_pip_ui: true,
enable_emoji_reactions: true,
enable_hand_raising: true,
enable_noise_cancellation_ui: true,
}, },
}), }),
}) })
if (!response.ok) { if (!response.ok)
const errorData = (await response.json()) as DailyApiError throw new Error(`Failed to create room (${response.status})`)
console.error("Daily API Error:", {
status: response.status,
statusText: response.statusText,
errorData,
authHeader: `Bearer ${apiKey.substring(0, 5)}...`, // Log first 5 chars for debugging
})
throw new Error(
errorData.message ||
errorData.info ||
`Failed to create room (${response.status})`,
)
}
const { url } = (await response.json()) as { url: string } const responseData = (await response.json()) as { url: string }
const url = responseData.url
if (!url) throw new Error("Room URL is missing")
this.editor.updateShape<IVideoChatShape>({ this.editor.updateShape<IVideoChatShape>({
id: shape.id, id: shape.id,
type: "VideoChat", type: "VideoChat",
props: { props: { ...shape.props, roomUrl: url },
...shape.props,
roomUrl: url,
},
}) })
} catch (error) { } catch (error) {
console.error("Failed to create Daily room:", error) console.error("Failed to create Daily room:", error)
@ -172,48 +71,39 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
} }
component(shape: IVideoChatShape) { component(shape: IVideoChatShape) {
const [isInRoom, setIsInRoom] = useState(false) const [hasPermissions, setHasPermissions] = useState(false)
const [error, setError] = useState("")
const [isLoading, setIsLoading] = useState(false)
useEffect(() => { useEffect(() => {
setIsLoading(true) this.ensureRoomExists(shape).catch(console.error)
this.ensureRoomExists(shape)
.catch((err) => setError(err.message))
.finally(() => setIsLoading(false))
}, []) }, [])
// useEffect(() => { useEffect(() => {
// // Disable Daily.co debug logs // Request permissions when needed
// if (window.DailyIframe) { const requestPermissions = async () => {
// window.DailyIframe.setLogLevel("error") try {
// } if (shape.props.allowCamera || shape.props.allowMicrophone) {
// }, []) const constraints = {
video: shape.props.allowCamera,
audio: shape.props.allowMicrophone,
}
await navigator.mediaDevices.getUserMedia(constraints)
setHasPermissions(true)
}
} catch (error) {
console.error("Permission request failed:", error)
setHasPermissions(false)
}
}
if (isLoading) { requestPermissions()
return ( }, [shape.props.allowCamera, shape.props.allowMicrophone])
<div
style={{
width: `${shape.props.w}px`,
height: `${shape.props.h}px`,
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#f0f0f0",
borderRadius: "4px",
}}
>
<div>Initializing video chat...</div>
</div>
)
}
if (!shape.props.roomUrl) { if (!shape.props.roomUrl) {
return ( return (
<div <div
style={{ style={{
width: `${shape.props.w}px`, width: shape.props.w,
height: `${shape.props.h}px`, height: shape.props.h,
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
@ -226,49 +116,54 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
) )
} }
// Construct URL with permission parameters
const roomUrlWithParams = new URL(shape.props.roomUrl)
roomUrlWithParams.searchParams.set(
"allow_camera",
String(shape.props.allowCamera),
)
roomUrlWithParams.searchParams.set(
"allow_mic",
String(shape.props.allowMicrophone),
)
console.log(shape.props.roomUrl)
return ( return (
<div <div
style={{ style={{
width: `${shape.props.w}px`, width: `${shape.props.w}px`,
height: `${shape.props.h}px`, height: `${shape.props.h}px`,
position: "relative", position: "relative",
pointerEvents: "all",
}} }}
> >
{!isInRoom ? ( <iframe
<button src={roomUrlWithParams.toString()}
onClick={() => setIsInRoom(true)} width="100%"
style={{ height="100%"
position: "absolute", style={{ border: "none" }}
top: "50%", allow={`camera ${shape.props.allowCamera ? "self" : ""}; microphone ${
left: "50%", shape.props.allowMicrophone ? "self" : ""
transform: "translate(-50%, -50%)", }`}
padding: "12px 24px", ></iframe>
backgroundColor: "#2563eb", <p
color: "white", style={{
border: "none", position: "absolute",
borderRadius: "4px", bottom: 0,
cursor: "pointer", left: 0,
}} margin: "8px",
> padding: "4px 8px",
Join Video Chat background: "rgba(255, 255, 255, 0.9)",
</button> borderRadius: "4px",
) : ( fontSize: "12px",
<VideoChatComponent roomUrl={shape.props.roomUrl} /> pointerEvents: "all",
)} cursor: "text",
{error && ( userSelect: "text",
<div }}
style={{ >
position: "absolute", url: {shape.props.roomUrl}
bottom: 10, </p>
left: 10,
right: 10,
color: "red",
textAlign: "center",
}}
>
{error}
</div>
)}
</div> </div>
) )
} }

View File

@ -22,11 +22,5 @@ export default defineConfig({
"import.meta.env.VITE_WORKER_URL": JSON.stringify( "import.meta.env.VITE_WORKER_URL": JSON.stringify(
process.env.VITE_WORKER_URL, process.env.VITE_WORKER_URL,
), ),
"import.meta.env.VITE_DAILY_DOMAIN": JSON.stringify(
"https://mycopunks.daily.co",
),
"import.meta.env.VITE_DAILY_API_KEY": JSON.stringify(
process.env.VITE_DAILY_API_KEY,
),
}, },
}) })