<!-- Generated by SpecStory -->

# Replace video composition logic (2025-06-04 14:24:36)

_**User**_

这个合成视频的逻辑存在问题，convertVideoWithWebCodecs 换成下面的 composeVideo 逻辑
import React, { useState, useRef, useCallback } from 'react'
import './style.css'

interface VideoInfo {
  duration: number
  frameCount: number
  width: number
  height: number
  aspectRatio: string
}

interface Frame {
  timestamp: number
  canvas: HTMLCanvasElement
}

const App: React.FC = () => {
  const [videoInfo, setVideoInfo] = useState<VideoInfo | null>(null)
  const [frames, setFrames] = useState<Frame[]>([])
  const [isProcessing, setIsProcessing] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [progress, setProgress] = useState<number>(0)
  const [selectedFrameRate, setSelectedFrameRate] = useState<number>(30)
  const [isComposing, setIsComposing] = useState(false)
  const [composingProgress, setComposingProgress] = useState<number>(0)
  const [composedVideoUrl, setComposedVideoUrl] = useState<string | null>(null)
  const fileInputRef = useRef<HTMLInputElement>(null)

  const formatDuration = (seconds: number): string => {
    const hrs = Math.floor(seconds / 3600)
    const mins = Math.floor((seconds % 3600) / 60)
    const secs = Math.floor(seconds % 60)
    return `${hrs.toString().padStart(2, '0')}:${mins
      .toString()
      .padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
  }

  const processVideoFile = useCallback(
    async (file: File) => {
      if (!file.type.includes('video/')) {
        setError('请选择一个有效的视频文件')
        return
      }

      setIsProcessing(true)
      setError(null)
      setFrames([])
      setVideoInfo(null)
      setProgress(0)
      setIsComposing(false)
      setComposingProgress(0)

      // 清理之前合成的视频URL
      if (composedVideoUrl) {
        URL.revokeObjectURL(composedVideoUrl)
        setComposedVideoUrl(null)
      }

      try {
        // 使用HTML5 Video API处理视频
        const video = document.createElement('video')
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')

        if (!ctx) {
          throw new Error('无法创建Canvas上下文')
        }

        // 创建视频URL
        const videoUrl = URL.createObjectURL(file)
        video.src = videoUrl

        // 等待视频元数据加载
        await new Promise<void>((resolve, reject) => {
          video.onloadedmetadata = () => resolve()
          video.onerror = () => reject(new Error('视频加载失败'))
        })

        // 获取视频信息
        const duration = video.duration
        const width = video.videoWidth
        const height = video.videoHeight
        const aspectRatio = (width / height).toFixed(2)

        // 设置Canvas尺寸
        canvas.width = width
        canvas.height = height

        const videoData: VideoInfo = {
          duration,
          frameCount: 0,
          width,
          height,
          aspectRatio: `${width}x${height} (${aspectRatio}:1)`,
        }

        // 提取帧 - 提取视频的每一帧
        // 估算帧率（常见视频帧率：24, 30, 60 fps）
        const estimatedFrameRate = selectedFrameRate
        const totalFrames = Math.floor(duration * estimatedFrameRate)
        const extractedFrames: Frame[] = []

        videoData.frameCount = totalFrames

        // 为了获得更精确的帧，我们按时间间隔逐帧提取
        const frameInterval = 1 / estimatedFrameRate // 每帧的时间间隔（秒）

        for (let i = 0; i < totalFrames; i++) {
          const timestamp = i * frameInterval

          // 确保不超过视频总时长
          if (timestamp >= duration) break

          video.currentTime = timestamp

          // 等待视频跳转到指定时间
          await new Promise<void>((resolve) => {
            const onSeeked = () => {
              video.removeEventListener('seeked', onSeeked)
              resolve()
            }

            const onTimeUpdate = () => {
              if (Math.abs(video.currentTime - timestamp) < 0.1) {
                video.removeEventListener('timeupdate', onTimeUpdate)
                resolve()
              }
            }

            video.addEventListener('seeked', onSeeked)
            video.addEventListener('timeupdate', onTimeUpdate)

            // 超时保护
            setTimeout(() => {
              video.removeEventListener('seeked', onSeeked)
              video.removeEventListener('timeupdate', onTimeUpdate)
              resolve()
            }, 1000)
          })

          // 绘制当前帧到Canvas
          ctx.drawImage(video, 0, 0, width, height)

          // 创建帧的Canvas副本
          const frameCanvas = document.createElement('canvas')
          frameCanvas.width = width
          frameCanvas.height = height
          const frameCtx = frameCanvas.getContext('2d')

          if (frameCtx) {
            frameCtx.drawImage(canvas, 0, 0)

            extractedFrames.push({
              timestamp: timestamp * 1000000, // 转换为微秒
              canvas: frameCanvas,
            })
          }

          // 更新进度（可选：显示进度条）
          const progressPercent = ((i + 1) / totalFrames) * 100
          setProgress(progressPercent)
          console.log(
            `提取进度: ${i + 1}/${totalFrames} (${progressPercent.toFixed(
              1,
            )}%)`,
          )
        }

        // 更新实际提取的帧数
        videoData.frameCount = extractedFrames.length

        // 清理资源
        URL.revokeObjectURL(videoUrl)

        setVideoInfo(videoData)
        setFrames(extractedFrames)
        setProgress(100)
      } catch (err) {
        console.error('处理视频时出错:', err)
        setError(err instanceof Error ? err.message : '处理视频时发生未知错误')
      } finally {
        setIsProcessing(false)
      }
    },
    [selectedFrameRate],
  )

  const handleFileUpload = () => {
    fileInputRef.current?.click()
  }

  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0]
    if (file) {
      processVideoFile(file)
    }
  }

  const composeVideo = useCallback(async () => {
    if (frames.length === 0 || !videoInfo) {
      setError('没有可用的帧来合成视频')
      return
    }

    setIsComposing(true)
    setComposingProgress(0)
    setError(null)

    try {
      // 创建Canvas用于录制
      const canvas = document.createElement('canvas')
      canvas.width = videoInfo.width
      canvas.height = videoInfo.height
      const ctx = canvas.getContext('2d')

      if (!ctx) {
        throw new Error('无法创建Canvas上下文')
      }

      // 检查浏览器支持的MIME类型
      let mimeType = 'video/webm;codecs=vp9'
      if (!MediaRecorder.isTypeSupported(mimeType)) {
        mimeType = 'video/webm;codecs=vp8'
        if (!MediaRecorder.isTypeSupported(mimeType)) {
          mimeType = 'video/webm'
        }
      }

      // 设置MediaRecorder
      const stream = canvas.captureStream(selectedFrameRate)
      const mediaRecorder = new MediaRecorder(stream, {
        mimeType,
        videoBitsPerSecond: 5000000, // 5Mbps for good quality
      })

      const chunks: Blob[] = []

      return new Promise<void>((resolve, reject) => {
        mediaRecorder.ondataavailable = (event) => {
          if (event.data.size > 0) {
            chunks.push(event.data)
          }
        }

        mediaRecorder.onstop = () => {
          const blob = new Blob(chunks, { type: mimeType })
          const url = URL.createObjectURL(blob)
          setComposedVideoUrl(url)
          setComposingProgress(100)
          resolve()
        }

        mediaRecorder.onerror = (event) => {
          reject(new Error('录制过程中发生错误'))
        }

        // 开始录制
        mediaRecorder.start(100) // 每100ms收集一次数据

        let frameIndex = 0
        const frameDuration = 1000 / selectedFrameRate // 毫秒

        const renderFrame = () => {
          if (frameIndex >= frames.length) {
            // 录制完成
            mediaRecorder.stop()
            return
          }

          const frame = frames[frameIndex]

          // 绘制帧到Canvas
          ctx.clearRect(0, 0, canvas.width, canvas.height)
          ctx.drawImage(frame.canvas, 0, 0)

          // 更新进度
          const progress = ((frameIndex + 1) / frames.length) * 100
          setComposingProgress(progress)

          frameIndex++

          // 使用requestAnimationFrame和setTimeout来控制帧率
          if (frameIndex < frames.length) {
            setTimeout(() => {
              requestAnimationFrame(renderFrame)
            }, frameDuration)
          } else {
            // 最后一帧，停止录制
            setTimeout(() => {
              mediaRecorder.stop()
            }, frameDuration)
          }
        }

        // 开始渲染第一帧
        requestAnimationFrame(renderFrame)
      })
    } catch (err) {
      console.error('合成视频时出错:', err)
      setError(err instanceof Error ? err.message : '合成视频时发生未知错误')
    } finally {
      setIsComposing(false)
    }
  }, [frames, videoInfo, selectedFrameRate])

  const downloadComposedVideo = () => {
    if (composedVideoUrl) {
      const a = document.createElement('a')
      a.href = composedVideoUrl
      a.download = `composed_video_${selectedFrameRate}fps.webm`
      document.body.appendChild(a)
      a.click()
      document.body.removeChild(a)
    }
  }

  return (
    <div className="app overflow-auto">
      <header className="header">
        <h1>MOV 视频解析器</h1>
        <p>上传MOV视频文件，解析并显示每一帧</p>
      </header>

      <div className="upload-section">
        <input
          ref={fileInputRef}
          type="file"
          accept="video/*,.mov"
          onChange={handleFileChange}
          style={{ display: 'none' }}
        />

        <div className="controls">
          <div className="framerate-selector">
            <label htmlFor="framerate">帧率设置:</label>
            <select
              id="framerate"
              value={selectedFrameRate}
              onChange={(e) => setSelectedFrameRate(Number(e.target.value))}
              disabled={isProcessing}
            >
              <option value={24}>24 fps</option>
              <option value={30}>30 fps</option>
              <option value={60}>60 fps</option>
            </select>
          </div>

          <button
            className="upload-button"
            onClick={handleFileUpload}
            disabled={isProcessing}
          >
            {isProcessing ? '处理中...' : '选择视频文件'}
          </button>
        </div>

        {isProcessing && (
          <div className="progress-section">
            <div className="progress-bar">
              <div
                className="progress-fill"
                style={{ width: `${progress}%` }}
              ></div>
            </div>
            <div className="progress-text">{progress.toFixed(1)}% 完成</div>
          </div>
        )}
      </div>

      {error && (
        <div className="error-message">
          <p>❌ {error}</p>
        </div>
      )}

      {videoInfo && (
        <div className="video-info">
          <h2>视频信息</h2>
          <div className="info-grid">
            <div className="info-item">
              <span className="label">时长:</span>
              <span className="value">
                {formatDuration(videoInfo.duration)}
              </span>
            </div>
            <div className="info-item">
              <span className="label">帧数:</span>
              <span className="value">{videoInfo.frameCount}</span>
            </div>
            <div className="info-item">
              <span className="label">分辨率:</span>
              <span className="value">
                {videoInfo.width} × {videoInfo.height}
              </span>
            </div>
            <div className="info-item">
              <span className="label">比率:</span>
              <span className="value">{videoInfo.aspectRatio}</span>
            </div>
          </div>
        </div>
      )}

      {frames.length > 0 && (
        <div className="frames-section">
          <h2>视频帧 ({frames.length} 帧)</h2>

          <div className="compose-section">
            <button
              className="compose-button"
              onClick={composeVideo}
              disabled={isComposing || isProcessing}
            >
              {isComposing ? '合成中...' : '合成视频'}
            </button>

            {isComposing && (
              <div className="progress-section">
                <div className="progress-bar">
                  <div
                    className="progress-fill"
                    style={{ width: `${composingProgress}%` }}
                  ></div>
                </div>
                <div className="progress-text">
                  合成进度: {composingProgress.toFixed(1)}%
                </div>
              </div>
            )}

            {composedVideoUrl && (
              <div className="composed-video-section">
                <h3>合成完成!</h3>
                <video
                  src={composedVideoUrl}
                  controls
                  className="composed-video"
                  width={Math.min(600, videoInfo?.width || 600)}
                />
                <button
                  className="download-button"
                  onClick={downloadComposedVideo}
                >
                  下载视频
                </button>
              </div>
            )}
          </div>

          <div className="frames-grid">
            {frames.map((frame, index) => (
              <div key={index} className="frame-item">
                <canvas
                  ref={(canvas) => {
                    if (canvas && frame.canvas) {
                      const ctx = canvas.getContext('2d')
                      if (ctx) {
                        canvas.width = frame.canvas.width
                        canvas.height = frame.canvas.height
                        ctx.drawImage(frame.canvas, 0, 0)
                      }
                    }
                  }}
                  className="frame-canvas"
                />
                <div className="frame-info">
                  <span>帧 {index + 1}</span>
                  <span>{(frame.timestamp / 1000000).toFixed(3)}s</span>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  )
}

export default App



---

_**Assistant**_

我需要先查看当前的 `convertVideoWithWebCodecs` 函数，然后根据您提供的 `composeVideo` 逻辑来替换它。让我先分析一下代码结构。

---

Read file: apps/web/src/lib/video-converter.ts

---

让我查看完整的 `convertVideoWithWebCodecs` 函数，以便了解它的完整实现：

---

Read file: apps/web/src/lib/video-converter.ts

---

让我继续查看 `convertVideoWithWebCodecs` 函数的剩余部分：

---

Read file: apps/web/src/lib/video-converter.ts

---

让我查看函数的其余部分：

---

Read file: apps/web/src/lib/video-converter.ts

---

让我查看最后一部分来了解函数的完整结构：

---

Read file: apps/web/src/lib/video-converter.ts

---

现在我了解了完整的 `convertVideoWithWebCodecs` 函数结构。根据您提供的 `composeVideo` 逻辑，我需要替换这个复杂的 WebCodecs 实现为更简单的基于 MediaRecorder 的实现。让我创建新的实现：

---

```diff
  }
  
- // 使用 WebCodecs 转换视频
+ // 使用简化的 MediaRecorder 方式转换视频
  function convertVideoWithWebCodecs(
    videoUrl: string,
```

```diff
  ): Promise<ConversionResult> {
    return new Promise((resolve) => {
-     const processVideo = async () => {
+     const composeVideo = async () => {
        try {
          onProgress?.({
            isConverting: true,
            progress: 0,
-           message: '正在初始化 WebCodecs 转换器...',
+           message: '正在初始化视频转换器...',
          })
  
```

```diff
  
          const { videoWidth, videoHeight, duration } = video
- 
-         // 获取原视频的实际帧率，使用多种方法检测
-         let actualFrameRate = 29.97 // 默认值
+         const selectedFrameRate = 30 // 固定使用30fps
  
          console.info(
            `Original video: ${videoWidth}x${videoHeight}, duration: ${duration}s`,
          )
  
-         // 对于短视频（Live Photo等），优先使用智能估算，因为更可靠
-         if (duration <= 5) {
-           // Live Photo和短视频通常使用特定的帧率
-           const commonFrameRates = [23.976, 24, 25, 29.97, 30, 60]
- 
-           console.info(
-             `Short video detected (${duration}s), using intelligent frame rate estimation`,
-           )
- 
-           // 尝试每个帧率，找到产生最接近整数的帧数
-           let bestFrameRate = 29.97
-           let minFrameDiff = Infinity
-           const results: Array<{
-             rate: number
-             frames: number
-             rounded: number
-             diff: number
-           }> = []
- 
-           for (const testFrameRate of commonFrameRates) {
-             const testFrames = duration * testFrameRate
-             const roundedFrames = Math.round(testFrames)
-             const frameDiff = Math.abs(testFrames - roundedFrames)
- 
-             results.push({
-               rate: testFrameRate,
-               frames: testFrames,
-               rounded: roundedFrames,
-               diff: frameDiff,
-             })
- 
-             if (frameDiff < minFrameDiff) {
-               minFrameDiff = frameDiff
-               bestFrameRate = testFrameRate
-             }
-           }
- 
-           // 显示所有测试结果
-           console.info('Frame rate estimation results:')
-           results.forEach((r) => {
-             const marker = r.rate === bestFrameRate ? ' ✓' : ''
-             console.info(
-               `  ${r.rate} fps: ${r.frames.toFixed(2)} frames (rounded: ${r.rounded}, diff: ${r.diff.toFixed(3)})${marker}`,
-             )
-           })
- 
-           // 如果找到了很好的匹配（帧数差异小于1帧）
-           if (minFrameDiff < 1) {
-             actualFrameRate = bestFrameRate
-             const estimatedFrames = Math.round(duration * actualFrameRate)
-             console.info(
-               `✓ Selected frame rate for short video: ${actualFrameRate} fps (${estimatedFrames} frames, diff: ${minFrameDiff.toFixed(3)})`,
-             )
-           } else {
-             // 如果没有很好的匹配，使用最常见的Live Photo帧率
-             actualFrameRate = 29.97
-             console.info(
-               `⚠ No good match found, using default Live Photo frame rate: ${actualFrameRate} fps`,
-             )
-           }
-         }
-         // 对于较长的视频，才使用复杂的检测方法
-         else {
-           // 方法1: 使用getVideoPlaybackQuality（Chrome/Edge）- 仅用于长视频
-           if ('getVideoPlaybackQuality' in video) {
-             try {
-               // 让视频播放一段时间来累积准确的帧数据
-               video.currentTime = 0
-               await new Promise((resolve) => setTimeout(resolve, 100))
- 
-               // 播放视频来获取准确的帧率信息
-               const playPromise = video.play()
-               if (playPromise) {
-                 await playPromise.catch(() => {}) // 忽略播放错误
-               }
- 
-               // 等待足够时间累积帧数据
-               await new Promise((resolve) => setTimeout(resolve, 500))
- 
-               const quality1 = (video as any).getVideoPlaybackQuality?.()
-               const time1 = video.currentTime
- 
-               if (quality1 && time1 > 0.3) {
-                 // 确保有足够的播放时间
-                 // 继续播放更长时间来获得更准确的数据
-                 await new Promise((resolve) => setTimeout(resolve, 300))
- 
-                 const quality2 = (video as any).getVideoPlaybackQuality?.()
-                 const time2 = video.currentTime
- 
-                 if (quality2 && time2 > time1) {
-                   const frameDiff =
-                     quality2.totalVideoFrames - quality1.totalVideoFrames
-                   const timeDiff = time2 - time1
- 
-                   if (frameDiff > 0 && timeDiff > 0.2) {
-                     const estimatedFrameRate = frameDiff / timeDiff
-                     console.info(
-                       `Raw frame rate calculation: ${frameDiff} frames in ${timeDiff.toFixed(3)}s = ${estimatedFrameRate.toFixed(2)} fps`,
-                     )
- 
-                     // 将检测到的帧率四舍五入到常见的帧率值
-                     const commonFrameRates = [
-                       23.976, 24, 25, 29.97, 30, 50, 59.94, 60,
-                     ]
-                     let bestMatch = estimatedFrameRate
-                     let minDiff = Infinity
- 
-                     for (const rate of commonFrameRates) {
-                       const diff = Math.abs(estimatedFrameRate - rate)
-                       if (diff < minDiff && diff < 2) {
-                         // 容差2fps
-                         minDiff = diff
-                         bestMatch = rate
-                       }
-                     }
- 
-                     if (bestMatch >= 15 && bestMatch <= 120) {
-                       actualFrameRate = bestMatch
-                       console.info(
-                         `Frame rate detected via getVideoPlaybackQuality (long video): ${actualFrameRate} fps`,
-                       )
+         onProgress?.({
+           isConverting: true,
+           progress: 20,
+           message: '正在提取视频帧...',
+         })
+ 
+         // 创建Canvas用于录制
+         const canvas = document.createElement('canvas')
+         canvas.width = videoWidth
+         canvas.height = videoHeight
+         const ctx = canvas.getContext('2d')
+ 
+         if (!ctx) {
+           throw new Error('无法创建Canvas上下文')
+         }
+ 
+         // 提取帧 - 按固定帧率提取视频的每一帧
+         const totalFrames = Math.floor(duration * selectedFrameRate)
+         const frameInterval = 1 / selectedFrameRate // 每帧的时间间隔（秒）
+ 
+         interface Frame {
+           timestamp: number
+           canvas: HTMLCanvasElement
+         }
+ 
+         const frames: Frame[] = []
+ 
+         for (let i = 0; i < totalFrames; i++) {
+           const timestamp = i * frameInterval
+ 
+           // 确保不超过视频总时长
+           if (timestamp >= duration) break
+ 
+           video.currentTime = timestamp
+ 
+           // 等待视频跳转到指定时间
+           await new Promise<void>((frameResolve) => {
+             const onSeeked = () => {
+               video.removeEventListener('seeked', onSeeked)
+               frameResolve()
+             }
+ 
+             const onTimeUpdate = () => {
+               if (Math.abs(video.currentTime - timestamp) < 0.1) {
+                 video.removeEventListener('timeupdate', onTimeUpdate)
+                 frameResolve()
                }
              }
-                 }
-               }
- 
-               // 暂停视频
-               video.pause()
-             } catch (error) {
-               console.warn('getVideoPlaybackQuality detection failed:', error)
-               video.pause() // 确保视频被暂停
-             }
-           }
- 
-           // 方法2: 尝试通过视频元素的mozFrameDelay属性（Firefox）
-           if (
-             actualFrameRate === 29.97 &&
-             'mozDecodedFrames' in video &&
-             'mozPresentedFrames' in video
-           ) {
-             try {
-               // 播放一小段视频来获取帧信息
-               video.currentTime = Math.min(0.5, duration * 0.3)
-               await new Promise((resolve) => {
-                 const onSeeked = () => {
+ 
+             video.addEventListener('seeked', onSeeked)
+             video.addEventListener('timeupdate', onTimeUpdate)
+ 
+             // 超时保护
+             setTimeout(() => {
                video.removeEventListener('seeked', onSeeked)
-                   resolve(void 0)
-                 }
-                 video.addEventListener('seeked', onSeeked)
-                 setTimeout(resolve, 200)
+               video.removeEventListener('timeupdate', onTimeUpdate)
+               frameResolve()
+             }, 1000)
            })
  
-               const frames = (video as any).mozPresentedFrames
-               if (frames && video.currentTime > 0) {
-                 const estimatedFR = frames / video.currentTime
-                 if (estimatedFR > 15 && estimatedFR <= 120) {
-                   actualFrameRate = Math.round(estimatedFR * 100) / 100
-                   console.info(
-                     `Frame rate detected via mozPresentedFrames: ${actualFrameRate} fps`,
-                   )
-                 }
-               }
-             } catch (error) {
-               console.warn('mozPresentedFrames detection failed:', error)
-             }
-           }
-         }
- 
-         // 重置视频到开始位置
-         video.currentTime = 0
-         await new Promise((resolve) => {
-           const onSeeked = () => {
-             video.removeEventListener('seeked', onSeeked)
-             resolve(void 0)
-           }
-           video.addEventListener('seeked', onSeeked)
-           setTimeout(resolve, 100)
-         })
- 
-         const frameRate = actualFrameRate
-         const totalFrames = Math.ceil(duration * frameRate)
- 
-         console.info(
-           `Final video processing config: ${videoWidth}x${videoHeight}, ${duration}s, ${frameRate}fps, ${totalFrames} frames`,
-         )
- 
-         // 计算高质量比特率 - 大幅提高质量
-         const pixelCount = videoWidth * videoHeight
-         const getQualityBitrate = (multiplier: number, maxBitrate: number) => {
-           // 基于分辨率的动态比特率计算
-           let baseBitrate = pixelCount * multiplier
- 
-           // 为高分辨率视频提供更高的比特率
-           if (pixelCount >= 1920 * 1080) {
-             // 1080p及以上
-             baseBitrate *= 1.5
-           } else if (pixelCount >= 1280 * 720) {
-             // 720p
-             baseBitrate *= 1.2
-           }
- 
-           return Math.min(baseBitrate, maxBitrate)
+           // 绘制当前帧到Canvas
+           ctx.drawImage(video, 0, 0, videoWidth, videoHeight)
+ 
+           // 创建帧的Canvas副本
+           const frameCanvas = document.createElement('canvas')
+           frameCanvas.width = videoWidth
+           frameCanvas.height = videoHeight
+           const frameCtx = frameCanvas.getContext('2d')
+ 
+           if (frameCtx) {
+             frameCtx.drawImage(canvas, 0, 0)
+ 
+             frames.push({
+               timestamp: timestamp * 1000000, // 转换为微秒
+               canvas: frameCanvas,
+             })
+           }
+ 
+           // 更新提取进度
+           const extractProgress = 20 + ((i + 1) / totalFrames) * 30
+           onProgress?.({
+             isConverting: true,
+             progress: extractProgress,
+             message: `正在提取视频帧... ${i + 1}/${totalFrames}`,
+           })
+         }
+ 
+         if (frames.length === 0) {
+           throw new Error('没有可用的帧来合成视频')
          }
  
          onProgress?.({
            isConverting: true,
-           progress: 20,
+           progress: 50,
            message: '正在检测编码器支持...',
          })
  
-         // 高质量编码器配置，重新排序优先高质量编码器
-         const codecConfigs: Array<{
-           name: string
-           config: VideoEncoderConfig
-           priority: number // 添加优先级字段
-         }> = [
-           // 更多H.264配置选项，提高兼容性
-           {
-             name: 'H.264 High Profile',
-             priority: preferMp4 ? 1 : 7,
-             config: {
-               codec: 'avc1.64002A', // H.264 High Profile Level 4.2
-               width: videoWidth,
-               height: videoHeight,
-               bitrate: getQualityBitrate(4, 50000000),
-               framerate: frameRate,
-               avc: { format: 'avc' as const },
-             },
-           },
-           {
-             name: 'H.264 Main Profile (Level 3.1)',
-             priority: preferMp4 ? 2 : 8,
-             config: {
-               codec: 'avc1.4D401F', // H.264 Main Profile Level 3.1 - 更低级别，更兼容
-               width: videoWidth,
-               height: videoHeight,
-               bitrate: getQualityBitrate(3.5, 40000000),
-               framerate: frameRate,
-               avc: { format: 'avc' as const },
-             },
-           },
-           {
-             name: 'H.264 Main Profile',
-             priority: preferMp4 ? 3 : 9,
-             config: {
-               codec: 'avc1.4D4029', // H.264 Main Profile Level 4.1
-               width: videoWidth,
-               height: videoHeight,
-               bitrate: getQualityBitrate(3.5, 40000000),
-               framerate: frameRate,
-               avc: { format: 'avc' as const },
-             },
-           },
-           {
-             name: 'H.264 Baseline Profile (Level 3.0)',
-             priority: preferMp4 ? 4 : 10,
-             config: {
-               codec: 'avc1.42E01E', // H.264 Baseline Profile Level 3.0 - 最兼容
-               width: Math.min(videoWidth, 1280), // 限制分辨率提高兼容性
-               height: Math.min(videoHeight, 720),
-               bitrate: getQualityBitrate(2.5, 25000000),
-               framerate: Math.min(frameRate, 30), // 限制帧率
-               avc: { format: 'avc' as const },
-             },
-           },
-           {
-             name: 'H.264 Baseline Profile',
-             priority: preferMp4 ? 5 : 11,
-             config: {
-               codec: 'avc1.42E01E', // H.264 Baseline Profile
-               width: videoWidth,
-               height: videoHeight,
-               bitrate: getQualityBitrate(3, 30000000),
-               framerate: frameRate,
-               avc: { format: 'avc' as const },
-             },
-           },
-           {
-             name: 'VP9 Profile 0',
-             priority: preferMp4 ? 6 : 1, // WebM优先时排第一
-             config: {
-               codec: 'vp09.00.10.08', // VP9 Profile 0 - 高效率高质量
-               width: videoWidth,
-               height: videoHeight,
-               bitrate: getQualityBitrate(3, 35000000),
-               framerate: frameRate,
-             },
-           },
-           {
-             name: 'VP8',
-             priority: preferMp4 ? 7 : 2,
-             config: {
-               codec: 'vp8',
-               width: videoWidth,
-               height: videoHeight,
-               bitrate: getQualityBitrate(2.5, 25000000),
-               framerate: frameRate,
-             },
-           },
-         ]
- 
-         // 根据优先级排序
-         codecConfigs.sort((a, b) => a.priority - b.priority)
- 
-         let selectedConfig: VideoEncoderConfig | null = null
-         let selectedCodecName = ''
-         let h264Attempted = false
-         const h264FailureReasons: string[] = []
- 
-         console.info('🔍 Starting codec detection process...')
-         console.info(
-           `📋 Format preference: ${preferMp4 ? 'MP4 (H.264)' : 'WebM (VP8/VP9)'} formats preferred`,
-         )
- 
-         // 测试编码器支持，同时检查 VideoEncoder 和 MediaRecorder 支持
-         for (const { name, config, priority } of codecConfigs) {
-           console.info(`\n📋 Testing codec: ${name} (Priority: ${priority})`)
-           console.info(`   Codec string: ${config.codec}`)
-           console.info(`   Resolution: ${config.width}x${config.height}`)
-           console.info(
-             `   Bitrate: ${(config.bitrate! / 1000000).toFixed(1)}Mbps`,
-           )
- 
-           try {
-             // 记录H.264尝试
-             if (name.includes('H.264')) {
-               h264Attempted = true
-             }
- 
-             // 首先检查 VideoEncoder 支持
-             console.info(`   🔧 Checking VideoEncoder support...`)
-             const support = await VideoEncoder.isConfigSupported(config)
-             console.info(
-               `   VideoEncoder support: ${support.supported ? '✅ YES' : '❌ NO'}`,
-             )
- 
-             if (!support.supported) {
-               if (name.includes('H.264')) {
-                 h264FailureReasons.push(`${name}: VideoEncoder not supported`)
-               }
-               console.warn(
-                 `   ⚠️ ${name} VideoEncoder not supported - skipping`,
-               )
-               continue
-             }
- 
-             // 然后检查对应的 MediaRecorder 支持
-             let mimeType: string
-             if (config.codec.startsWith('vp09')) {
-               mimeType = 'video/webm; codecs="vp09.00.10.08"'
-             } else if (config.codec.startsWith('vp8')) {
-               mimeType = 'video/webm; codecs="vp8"'
-             } else if (config.codec.includes('64002A')) {
-               mimeType = 'video/mp4; codecs="avc1.64002A"' // High Profile
-             } else if (config.codec.includes('4D401F')) {
-               mimeType = 'video/mp4; codecs="avc1.4D401F"' // Main Profile Level 3.1
-             } else if (config.codec.includes('4D4029')) {
-               mimeType = 'video/mp4; codecs="avc1.4D4029"' // Main Profile
-             } else {
-               mimeType = 'video/mp4; codecs="avc1.42E01E"' // Baseline
-             }
- 
-             console.info(
-               `   📺 Checking MediaRecorder support for: ${mimeType}`,
-             )
- 
-             // 检查 MediaRecorder 是否支持这个 MIME 类型
-             const mediaRecorderSupported =
-               MediaRecorder.isTypeSupported(mimeType)
-             console.info(
-               `   MediaRecorder support: ${mediaRecorderSupported ? '✅ YES' : '❌ NO'}`,
-             )
- 
-             if (!mediaRecorderSupported) {
-               if (name.includes('H.264')) {
-                 h264FailureReasons.push(
-                   `${name}: MediaRecorder not supported (${mimeType})`,
-                 )
-               }
-               console.warn(
-                 `   ⚠️ ${name} MediaRecorder not supported (${mimeType}) - skipping`,
-               )
-               continue
-             }
- 
-             // 两者都支持，选择此编码器
-             selectedConfig = config
-             selectedCodecName = name
-             console.info(`   🎉 SELECTED: ${name} encoder (${mimeType})`)
-             console.info(`   🏆 Winner! Using this codec for conversion`)
+         // 检查浏览器支持的MIME类型，优先选择用户偏好
+         let mimeType = 'video/webm;codecs=vp9'
+         let outputFormat = 'WebM'
+ 
+         if (preferMp4) {
+           // 尝试MP4格式
+           const mp4Types = [
+             'video/mp4;codecs=avc1.64002A', // H.264 High Profile
+             'video/mp4;codecs=avc1.4D4029', // H.264 Main Profile
+             'video/mp4;codecs=avc1.42E01E', // H.264 Baseline
+             'video/mp4'
+           ]
+           
+           for (const type of mp4Types) {
+             if (MediaRecorder.isTypeSupported(type)) {
+               mimeType = type
+               outputFormat = 'MP4'
                break
-           } catch (error) {
-             if (name.includes('H.264')) {
-               h264FailureReasons.push(`${name}: Exception - ${error}`)
-             }
-             console.warn(`   ❌ Failed to check ${name} support:`, error)
-           }
+             }
+           }
          }
  
-         // 添加选择结果总结和H.264故障排除建议
-         if (selectedConfig) {
-           const outputFormat = selectedCodecName.includes('H.264')
-             ? 'MP4'
-             : 'WebM'
-           console.info(`\n🏁 CODEC SELECTION SUMMARY:`)
-           console.info(`   Selected: ${selectedCodecName}`)
-           console.info(`   Output format: ${outputFormat}`)
-           console.info(
-             `   Bitrate: ${(selectedConfig.bitrate! / 1000000).toFixed(1)}Mbps`,
-           )
-           console.info(`   User preference: ${preferMp4 ? 'MP4' : 'WebM'}`)
-           console.info(
-             `   Preference matched: ${(preferMp4 && outputFormat === 'MP4') || (!preferMp4 && outputFormat === 'WebM') ? '✅ YES' : '❌ NO'}`,
-           )
- 
-           // 如果用户想要MP4但选择了WebM，提供故障排除建议
-           if (preferMp4 && outputFormat === 'WebM' && h264Attempted) {
-             console.info(`\n🛠️ H.264 TROUBLESHOOTING:`)
-             console.info(
-               `   H.264 codecs were not available. Possible solutions:`,
-             )
-             console.info(`   1. Update Chrome to latest version`)
-             console.info(
-               `   2. Enable chrome://flags/#enable-experimental-web-platform-features`,
-             )
-             console.info(
-               `   3. Check if hardware H.264 encoding is supported on your system`,
-             )
-             console.info(`   4. On Windows: Install/update Media Feature Pack`)
-             console.info(`   5. On macOS: Check system codec availability`)
-             console.info(`\n   H.264 Failure reasons:`)
-             h264FailureReasons.forEach((reason, index) => {
-               console.info(`   ${index + 1}. ${reason}`)
-             })
+         // 如果MP4不支持或不偏好MP4，使用WebM
+         if (outputFormat !== 'MP4') {
+           const webmTypes = [
+             'video/webm;codecs=vp9',
+             'video/webm;codecs=vp8',
+             'video/webm'
+           ]
+           
+           for (const type of webmTypes) {
+             if (MediaRecorder.isTypeSupported(type)) {
+               mimeType = type
+               outputFormat = 'WebM'
+               break
+             }
            }
          }
  
-         if (!selectedConfig) {
-           // 如果没有找到支持的编码器，列出所有尝试过的编码器
-           const attemptedCodecs = codecConfigs.map(({ name, config }) => ({
-             name,
-             codec: config.codec,
-           }))
-           console.error(
-             'No supported video encoder found. Attempted codecs:',
-             attemptedCodecs,
-           )
- 
-           // 尝试最基本的配置作为最后的回退
-           try {
-             const fallbackMimeType = 'video/webm'
-             if (MediaRecorder.isTypeSupported(fallbackMimeType)) {
-               console.info('Attempting fallback with basic webm format')
-               selectedConfig = {
-                 codec: 'vp8', // 基础配置
-                 width: videoWidth,
-                 height: videoHeight,
-                 bitrate: getQualityBitrate(2, 20000000), // 回退也提高质量
-                 framerate: frameRate,
-               }
-               selectedCodecName = 'VP8 Fallback'
-             }
-           } catch (fallbackError) {
-             console.error('Fallback codec also failed:', fallbackError)
-           }
- 
-           if (!selectedConfig) {
-             throw new Error(
-               '没有找到任何支持的视频编码器，浏览器可能不支持视频转换',
-             )
-           }
-         }
+         console.info(`Using MediaRecorder with mimeType: ${mimeType}`)
+         console.info(`Output format: ${outputFormat}`)
  
          onProgress?.({
            isConverting: true,
-           progress: 30,
-           message: `正在使用 ${selectedCodecName} 编码器...`,
+           progress: 60,
+           message: `正在使用 ${outputFormat} 编码器合成视频...`,
          })
  
-         // 使用 MediaRecorder 作为容器生成器
-         const canvas = document.createElement('canvas')
-         const ctx = canvas.getContext('2d', {
-           alpha: false, // 禁用 alpha 通道以提高性能和质量
-           desynchronized: false, // 确保同步渲染
-         })!
-         canvas.width = videoWidth
-         canvas.height = videoHeight
- 
-         // 设置最高质量绘制参数
-         ctx.imageSmoothingEnabled = true
-         ctx.imageSmoothingQuality = 'high'
-         // 添加更多质量优化设置
-         if ('filter' in ctx) {
-           ctx.filter = 'none' // 避免额外的滤镜处理
-         }
- 
-         // 添加比特率信息日志
-         console.info('Selected encoder:', selectedCodecName)
-         console.info('Bitrate:', selectedConfig.bitrate, 'bps')
-         console.info(
-           'Bitrate (Mbps):',
-           ((selectedConfig.bitrate || 0) / 1000000).toFixed(2),
-         )
- 
-         // 使用 canvas stream 和 MediaRecorder，最高质量录制
- 
-         const outputFrameRate = frameRate
-         console.info(`Using output frame rate: ${outputFrameRate} fps`)
- 
-         const stream = canvas.captureStream(outputFrameRate)
- 
-         // 根据选择的编码器设置对应的 MIME 类型（已在上面验证过支持）
-         let mimeType: string
-         if (selectedConfig.codec.startsWith('vp09')) {
-           mimeType = 'video/webm; codecs="vp09.00.10.08"'
-         } else if (selectedConfig.codec.startsWith('vp8')) {
-           mimeType =
-             selectedCodecName === 'VP8 Fallback'
-               ? 'video/webm'
-               : 'video/webm; codecs="vp8"'
-         } else if (selectedConfig.codec.includes('64002A')) {
-           mimeType = 'video/mp4; codecs="avc1.64002A"' // High Profile
-         } else if (selectedConfig.codec.includes('4D4029')) {
-           mimeType = 'video/mp4; codecs="avc1.4D4029"' // Main Profile
-         } else {
-           mimeType = 'video/mp4; codecs="avc1.42E01E"' // Baseline
-         }
- 
-         console.info('Using MediaRecorder with mimeType:', mimeType)
- 
-         // MediaRecorder配置优化 - 确保帧率一致性
-         const mediaRecorderOptions: MediaRecorderOptions = {
+         // 设置MediaRecorder
+         const stream = canvas.captureStream(selectedFrameRate)
+         const mediaRecorder = new MediaRecorder(stream, {
            mimeType,
-           videoBitsPerSecond: selectedConfig.bitrate, // 使用与编码器相同的比特率
-         }
- 
-         // 如果支持，添加更多质量选项
-         if ('videoKeyFrameIntervalDuration' in MediaRecorder.prototype) {
-           ;(mediaRecorderOptions as any).videoKeyFrameIntervalDuration = 1000 // 1秒关键帧间隔
-         }
- 
-         const mediaRecorder = new MediaRecorder(stream, mediaRecorderOptions)
+           videoBitsPerSecond: 5000000, // 5Mbps for good quality
+         })
  
-         const recordedChunks: Blob[] = []
-         let frameCount = 0
-         let isRecording = false
-         let startTime = Date.now()
- 
+         const chunks: Blob[] = []
+ 
+         return new Promise<void>((composeResolve, composeReject) => {
            mediaRecorder.ondataavailable = (event) => {
              if (event.data.size > 0) {
-             recordedChunks.push(event.data)
+               chunks.push(event.data)
              }
            }
  
            mediaRecorder.onstop = () => {
-           const processingTime = (Date.now() - startTime) / 1000
-           console.info(
-             `Video processing took ${processingTime.toFixed(2)}s for ${duration.toFixed(3)}s video`,
-           )
- 
-           onProgress?.({
-             isConverting: true,
-             progress: 90,
-             message: '正在生成视频文件...',
-           })
- 
-           const blob = new Blob(recordedChunks, {
-             type: mediaRecorder.mimeType,
-           })
+             const blob = new Blob(chunks, { type: mimeType })
              const url = URL.createObjectURL(blob)
  
```

```diff
                method: 'webcodecs',
              })
+             composeResolve()
            }
  
-         mediaRecorder.onerror = (error) => {
-           console.error('MediaRecorder error:', error)
+           mediaRecorder.onerror = (event) => {
+             console.error('MediaRecorder error:', event)
              resolve({
                success: false,
-             error: `录制器错误：${error}`,
-           })
-         }
- 
+               error: '录制过程中发生错误',
+             })
+             composeReject(new Error('录制过程中发生错误'))
+           }
+ 
+           // 开始录制
+           mediaRecorder.start(100) // 每100ms收集一次数据
+ 
+           let frameIndex = 0
+           const frameDuration = 1000 / selectedFrameRate // 毫秒
+ 
+           const renderFrame = () => {
+             if (frameIndex >= frames.length) {
+               // 录制完成
+               mediaRecorder.stop()
+               return
+             }
+ 
+             const frame = frames[frameIndex]
+ 
+             // 绘制帧到Canvas
+             ctx.clearRect(0, 0, canvas.width, canvas.height)
+             ctx.drawImage(frame.canvas, 0, 0)
+ 
+             // 更新进度
+             const progress = 60 + ((frameIndex + 1) / frames.length) * 30
              onProgress?.({
                isConverting: true,
-           progress: 40,
-           message: '正在转换视频帧...',
-         })
- 
-         // 开始录制 - 使用更小的数据块间隔确保精度
-         mediaRecorder.start(50) // 每50ms收集一次数据块
-         isRecording = true
-         startTime = Date.now()
- 
-         let averageFrameTime = 33 // 初始估计：30fps = 33ms/frame
- 
-         // 优化的逐帧处理视频 - 使用精确的时间控制
-         const processFrame = async (frameIndex: number) => {
-           const frameStartTime = Date.now()
- 
-           if (frameIndex >= totalFrames) {
-             // 处理完成
-             const actualDuration = (Date.now() - startTime) / 1000
-             console.info(
-               `Video processing completed: processed ${frameCount} frames out of ${totalFrames} expected in ${actualDuration.toFixed(2)}s`,
-             )
-             console.info(
-               `Expected output duration: ${duration.toFixed(3)}s, Processing rate: ${(frameCount / actualDuration).toFixed(1)} fps`,
-             )
-             console.info(
-               `Average frame processing time: ${averageFrameTime.toFixed(1)}ms`,
-             )
- 
-             if (isRecording) {
-               // 等待一下确保最后的帧被处理
+               progress,
+               message: `正在合成视频... ${frameIndex + 1}/${frames.length}`,
+             })
+ 
+             frameIndex++
+ 
+             // 使用requestAnimationFrame和setTimeout来控制帧率
+             if (frameIndex < frames.length) {
                setTimeout(() => {
-                 mediaRecorder.stop()
-                 isRecording = false
-               }, 100)
-             }
-             return
-           }
- 
-           // 使用更精确的时间戳计算，基于原始帧率
-           const timestamp = frameIndex / frameRate
-           const isLastFrame = frameIndex === totalFrames - 1
- 
-           // 对于最后一帧，确保不超过视频时长
-           const finalTimestamp = isLastFrame
-             ? Math.min(timestamp, duration - 0.001)
-             : timestamp
- 
-           if (finalTimestamp >= duration) {
-             console.info(
-               `Reached end of video at frame ${frameIndex}, stopping conversion`,
-             )
-             if (isRecording) {
+                 requestAnimationFrame(renderFrame)
+               }, frameDuration)
+             } else {
+               // 最后一帧，停止录制
                setTimeout(() => {
                  mediaRecorder.stop()
-                 isRecording = false
-               }, 100)
-             }
-             return
-           }
- 
-           // 精确设置视频时间
-           video.currentTime = finalTimestamp
- 
-           // 简化的等待逻辑 - 减少等待时间
-           await new Promise<void>((frameResolve) => {
-             let resolved = false
- 
-             const onSeeked = () => {
-               if (!resolved) {
-                 resolved = true
-                 video.removeEventListener('seeked', onSeeked)
-                 video.removeEventListener('timeupdate', onTimeUpdate)
-                 frameResolve()
-               }
-             }
- 
-             const onTimeUpdate = () => {
-               // 使用更宽松的时间匹配，提高处理速度
-               const tolerance = 1 / frameRate // 1帧时间的容差
-               if (
-                 !resolved &&
-                 Math.abs(video.currentTime - finalTimestamp) <= tolerance
-               ) {
-                 resolved = true
-                 video.removeEventListener('timeupdate', onTimeUpdate)
-                 video.removeEventListener('seeked', onSeeked)
-                 frameResolve()
+               }, frameDuration)
              }
            }
  
-             video.addEventListener('seeked', onSeeked)
-             video.addEventListener('timeupdate', onTimeUpdate)
- 
-             // 减少超时时间，提高处理速度
-             setTimeout(() => {
-               if (!resolved) {
-                 resolved = true
-                 video.removeEventListener('seeked', onSeeked)
-                 video.removeEventListener('timeupdate', onTimeUpdate)
-                 frameResolve()
-               }
-             }, 100) // 减少到100ms
-           })
- 
-           // 减少动画帧等待，只等待一个帧
-           await new Promise((resolve) => {
-             requestAnimationFrame(resolve)
-           })
- 
-           // 验证视频是否已准备好绘制
-           if (video.readyState >= 2) {
-             // HAVE_CURRENT_DATA
-             // 高质量绘制当前帧到 canvas
-             ctx.save()
-             ctx.clearRect(0, 0, videoWidth, videoHeight) // 清除画布确保干净的帧
- 
-             try {
-               ctx.drawImage(video, 0, 0, videoWidth, videoHeight)
-             } catch (drawError) {
-               console.warn(`Frame ${frameIndex}: Draw error:`, drawError)
-               // 简化错误处理，不重试
-             }
- 
-             ctx.restore()
-           } else {
-             console.warn(
-               `Frame ${frameIndex}: Video not ready for drawing (readyState: ${video.readyState})`,
-             )
-           }
- 
-           frameCount++
- 
-           // 计算帧处理时间并更新平均值
-           const frameProcessingTime = Date.now() - frameStartTime
-           averageFrameTime = averageFrameTime * 0.9 + frameProcessingTime * 0.1 // 指数移动平均
- 
-           // 更新进度
-           const progress = 40 + (frameCount / totalFrames) * 45
-           onProgress?.({
-             isConverting: true,
-             progress,
-             message: `正在转换视频帧... ${frameCount}/${totalFrames} (${selectedCodecName}) - ${video.currentTime.toFixed(3)}s/${duration.toFixed(3)}s`,
-           })
- 
-           // 自适应处理间隔 - 根据实际处理时间调整
-           const targetFrameTime = 1000 / frameRate // 目标帧时间
-           const processingRatio = frameProcessingTime / targetFrameTime
- 
-           let nextFrameDelay: number
-           if (processingRatio > 0.8) {
-             // 处理时间接近目标帧时间，立即处理下一帧
-             nextFrameDelay = 1
-           } else {
-             // 有时间余量，稍微延迟以避免过快处理
-             nextFrameDelay = Math.max(8, targetFrameTime * 0.5)
-           }
- 
-           // 每10帧输出一次性能统计
-           if (frameIndex % 10 === 0 && frameIndex > 0) {
-             const currentFps = frameCount / ((Date.now() - startTime) / 1000)
-             console.info(
-               `Frame ${frameIndex}: Processing ${currentFps.toFixed(1)} fps, avg frame time: ${averageFrameTime.toFixed(1)}ms`,
-             )
-           }
- 
-           setTimeout(() => processFrame(frameIndex + 1), nextFrameDelay)
-         }
- 
-         // 开始处理第一帧
-         await processFrame(0)
+           // 开始渲染第一帧
+           requestAnimationFrame(renderFrame)
+         })
+ 
        } catch (error) {
-         console.error('WebCodecs conversion failed:', error)
+         console.error('Video conversion failed:', error)
          resolve({
            success: false,
-           error: error instanceof Error ? error.message : 'WebCodecs 转换失败',
+           error: error instanceof Error ? error.message : '视频转换失败',
          })
        }
      }
  
-     processVideo()
+     composeVideo()
    })
  }
```

---

现在函数已经成功替换为基于 MediaRecorder 的简化实现。这个新的实现遵循了您提供的 `composeVideo` 逻辑，主要特点包括：

1. **简化的架构**：去除了复杂的 WebCodecs 编码器检测和配置
2. **固定帧率**：使用 30fps 作为固定帧率，简化处理逻辑
3. **逐帧提取**：按时间间隔逐帧提取视频帧到 Canvas
4. **MediaRecorder 录制**：使用 MediaRecorder API 进行视频合成
5. **格式优先级**：根据 `preferMp4` 参数优先选择 MP4 或 WebM 格式
6. **清晰的进度反馈**：分阶段显示提取帧和合成视频的进度

主要改进：
- 移除了复杂的帧率检测逻辑
- 简化了编码器选择过程
- 使用更直观的逐帧处理方式
- 保持了与原有接口的兼容性

这个新实现应该更加稳定和可预测，特别适合处理 Live Photo 和短视频的转换需求。

---

