diff --git a/src/App.tsx b/src/App.tsx
index 1370f32..a53c111 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -248,36 +248,21 @@ export default function App({
downloadRecording,
} = useWhiteboardRecording()
- const handleStartRecording = useCallback(async () => {
+ const handleStartRecording = useCallback(() => {
if (!excalidrawAPIRef.current) {
showError(t('whiteboard', 'Could not access whiteboard'))
return
}
- // Wait for a short moment to ensure canvas is ready
- await new Promise(resolve => setTimeout(resolve, 100))
+ const staticCanvas = document.querySelector('.excalidraw__canvas.static') as HTMLCanvasElement
+ const interactiveCanvas = document.querySelector('.excalidraw__canvas.interactive') as HTMLCanvasElement
- // Get all canvases and find the main one
- const allCanvases = document.querySelectorAll('.excalidraw canvas')
- console.log('Found canvases:', allCanvases)
-
- // Convert NodeList to array for easier filtering
- const canvasArray = Array.from(allCanvases)
-
- // Find the main canvas - it should be visible and have dimensions
- const mainCanvas = canvasArray.find(canvas => {
- const rect = canvas.getBoundingClientRect()
- const style = window.getComputedStyle(canvas)
- return rect.width > 0 && rect.height > 0 && style.display !== 'none' && !canvas.classList.contains('reset-zoom')
- }) as HTMLCanvasElement
-
- if (!mainCanvas) {
- showError(t('whiteboard', 'Could not find main canvas element'))
+ if (!staticCanvas || !interactiveCanvas) {
+ showError(t('whiteboard', 'Could not find canvases to record'))
return
}
- console.log('Using canvas for recording:', mainCanvas)
- startRecording(mainCanvas)
+ startRecording({ staticCanvas, interactiveCanvas })
}, [startRecording])
return (
diff --git a/src/components/RecordingControls.tsx b/src/components/RecordingControls.tsx
index d03b1e5..39d22ed 100644
--- a/src/components/RecordingControls.tsx
+++ b/src/components/RecordingControls.tsx
@@ -4,8 +4,8 @@
*/
import { useCallback } from 'react'
+import type { RecordingState } from '../hooks/useWhiteboardRecording'
import { translate as t } from '@nextcloud/l10n'
-import { RecordingState } from '../hooks/useWhiteboardRecording'
interface RecordingControlsProps {
recordingState: RecordingState
@@ -15,47 +15,49 @@ interface RecordingControlsProps {
}
export function RecordingControls({
- recordingState,
- onStartRecording,
- onStopRecording,
- onDownloadRecording,
+ recordingState,
+ onStartRecording,
+ onStopRecording,
+ onDownloadRecording,
}: RecordingControlsProps) {
- const formatDuration = useCallback((seconds: number) => {
- const minutes = Math.floor(seconds / 60)
- const remainingSeconds = seconds % 60
- return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
- }, [])
+ const formatDuration = useCallback((seconds: number) => {
+ const minutes = Math.floor(seconds / 60)
+ const remainingSeconds = seconds % 60
+ return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
+ }, [])
- return (
-
- {!recordingState.isRecording ? (
-
- ) : (
- <>
-
- >
- )}
- {!recordingState.isRecording && recordingState.frames.length > 0 && (
-
- )}
-
- )
+ return (
+
+ {!recordingState.isRecording
+ ? (
+
+ )
+ : (
+ <>
+
+ >
+ )}
+ {!recordingState.isRecording && recordingState.frames.length > 0 && (
+
+ )}
+
+ )
}
diff --git a/src/hooks/useWhiteboardRecording.ts b/src/hooks/useWhiteboardRecording.ts
index 210dd6f..26ac7c7 100644
--- a/src/hooks/useWhiteboardRecording.ts
+++ b/src/hooks/useWhiteboardRecording.ts
@@ -7,12 +7,21 @@ import { useState, useRef, useCallback } from 'react'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
+interface ExtendedMediaRecorder extends MediaRecorder {
+ animationFrameId?: number
+}
+
export interface RecordingState {
isRecording: boolean
duration: number
frames: Blob[]
}
+interface CanvasParams {
+ staticCanvas: HTMLCanvasElement
+ interactiveCanvas: HTMLCanvasElement
+}
+
export function useWhiteboardRecording() {
const [recordingState, setRecordingState] = useState({
isRecording: false,
@@ -20,23 +29,45 @@ export function useWhiteboardRecording() {
frames: [],
})
- const mediaRecorderRef = useRef(null)
+ const mediaRecorderRef = useRef(null)
const streamRef = useRef(null)
const durationIntervalRef = useRef(null)
+ const animationFrameIdRef = useRef(null)
- const startRecording = useCallback((canvas: HTMLCanvasElement) => {
+ const startRecording = useCallback(({ staticCanvas, interactiveCanvas }: CanvasParams) => {
try {
- // Get canvas stream at 60 FPS for smoother recording
- const stream = canvas.captureStream(60)
+ const combinedCanvas = document.createElement('canvas')
+ const ctx = combinedCanvas.getContext('2d')
+ if (!ctx) {
+ throw new Error('Failed to get canvas context')
+ }
+
+ combinedCanvas.width = staticCanvas.width
+ combinedCanvas.height = staticCanvas.height
+
+ const drawFrame = () => {
+
+ ctx.clearRect(0, 0, combinedCanvas.width, combinedCanvas.height)
+
+ ctx.drawImage(staticCanvas, 0, 0)
+
+ ctx.drawImage(interactiveCanvas, 0, 0)
+ }
+
+ const stream = combinedCanvas.captureStream(60)
streamRef.current = stream
- // Use WebM with VP8 for recording (better browser support)
+ const updateCanvas = () => {
+ drawFrame()
+ animationFrameIdRef.current = requestAnimationFrame(updateCanvas)
+ }
+ updateCanvas()
+
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp8',
- videoBitsPerSecond: 8000000, // 8 Mbps for better quality
- })
+ videoBitsPerSecond: 8000000,
+ }) as ExtendedMediaRecorder
- // Handle data available event
mediaRecorder.ondataavailable = (event) => {
setRecordingState((prev) => ({
...prev,
@@ -44,11 +75,9 @@ export function useWhiteboardRecording() {
}))
}
- // Start recording with smaller chunks for better handling
- mediaRecorder.start(500) // Capture chunks every 500ms
+ mediaRecorder.start(500)
mediaRecorderRef.current = mediaRecorder
- // Start duration counter
durationIntervalRef.current = window.setInterval(() => {
setRecordingState((prev) => ({
...prev,
@@ -76,13 +105,15 @@ export function useWhiteboardRecording() {
return
}
- // Stop media recorder
+ if (animationFrameIdRef.current !== null) {
+ cancelAnimationFrame(animationFrameIdRef.current)
+ animationFrameIdRef.current = null
+ }
+
mediaRecorderRef.current.stop()
- // Stop all tracks
streamRef.current.getTracks().forEach((track) => track.stop())
- // Clear duration interval
if (durationIntervalRef.current) {
clearInterval(durationIntervalRef.current)
}
@@ -101,11 +132,9 @@ export function useWhiteboardRecording() {
}
try {
- // Create WebM blob
const blob = new Blob(recordingState.frames, { type: 'video/webm' })
const url = URL.createObjectURL(blob)
- // Create download link
const a = document.createElement('a')
a.href = url
a.download = `whiteboard-recording-${Date.now()}.webm`