Newer
Older
safe_production_front / src / utils / sustainIframeTest.ts
// 定义分析结果的类型
interface AnalysisResult {
  found: boolean
  offset: number | null
  elapsedTime: number | null
  error: string | null
}

// 异步函数用于分析 FLV 视频
export async function analyzeFLV(
  videoUrl: string,
  timeoutSeconds = 10,
): Promise<AnalysisResult> {
  const startTime = new Date()
  console.log(`开始分析时间: ${startTime.toLocaleTimeString()}`)
  console.log('开始请求实时流')

  const timeoutMs = timeoutSeconds * 1000
  let result: AnalysisResult = {
    found: false,
    offset: null,
    elapsedTime: null,
    error: null,
  }

  try {
    const response = await fetch(videoUrl)
    if (!response.ok) {
      throw new Error(`请求失败: ${response.status}`)
    }

    const reader = response.body!.getReader()
    let buffer = new Uint8Array()
    let iFrameFound = false
    let iFrameOffset: number | null = null
    let iFrameTime: Date | null = null

    const timeoutId = setTimeout(() => {
      if (!iFrameFound) {
        const elapsedTime = (new Date().getTime() - startTime.getTime()) / 1000
        console.log(`经过 ${elapsedTime.toFixed(2)} 秒,未检测到 I 帧`)
        reader.cancel()
      }
    }, timeoutMs)

    while (!iFrameFound) {
      const { value, done } = await reader.read()
      if (done) {
        break
      }
      buffer = concatUint8Arrays(buffer, value!)
      const analysisResult = checkFirstFrame(buffer, startTime)
      if (analysisResult.found) {
        iFrameFound = true
        iFrameOffset = analysisResult.offset
        iFrameTime = new Date()
      }
    }

    clearTimeout(timeoutId)

    if (iFrameFound) {
      const elapsedTime = (iFrameTime!.getTime() - startTime.getTime()) / 1000
      console.log(
        `经过 ${elapsedTime.toFixed(2)} 秒,在位置 ${iFrameOffset} 检测到 I 帧`,
      )
      result = {
        found: true,
        offset: iFrameOffset,
        elapsedTime,
        error: null,
      }
    }
  }
  catch (err) {
    const error = err as Error
    console.error(`错误: ${error.message}`)
    result = {
      found: false,
      offset: null,
      elapsedTime: null,
      error: error.message,
    }
  }

  return result
}

// 合并两个 Uint8Array
function concatUint8Arrays(a: Uint8Array, b: Uint8Array): Uint8Array {
  const result = new Uint8Array(a.length + b.length)
  result.set(a, 0)
  result.set(b, a.length)
  return result
}

// 检查是否找到第一个 I 帧
function checkFirstFrame(
  buffer: Uint8Array,
  startTime: Date,
): { found: boolean; offset: number | null } {
  const dataView = new DataView(buffer.buffer)
  const uint8 = new Uint8Array(buffer.buffer)

  console.log('开始验证 FLV 文件头')
  // 1. 验证FLV文件头
  if (
    uint8.length < 3
    || uint8[0] !== 0x46
    || uint8[1] !== 0x4C
    || uint8[2] !== 0x56
  ) {
    console.log('数据不足或不是有效的 FLV 文件')
    return { found: false, offset: null }
  }
  console.log('FLV 文件头验证通过')

  console.log('开始检查是否包含视频流')
  // 2. 检查是否包含视频流
  if (uint8.length < 5) {
    console.log('数据不足,无法检查视频流')
    return { found: false, offset: null }
  }
  const hasVideo = (uint8[4] & 0x01) !== 0
  if (!hasVideo) {
    console.log('文件不包含视频流')
    return { found: false, offset: null }
  }
  console.log('文件包含视频流')

  // 3. 解析Tags
  let offset = 13 // FLV头(9) + 第一个PreviousTagSize(4)
  console.log(`开始解析 Tags,初始偏移量: ${offset}`)

  while (offset + 11 < buffer.length) {
    const tagType = dataView.getUint8(offset)
    const dataSize
      = (dataView.getUint8(offset + 1) << 16)
      | (dataView.getUint8(offset + 2) << 8)
      | dataView.getUint8(offset + 3)

    console.log(`当前 Tag 类型: ${tagType},数据大小: ${dataSize}`)

    // 遇到视频Tag时处理
    if (tagType === 9) {
      const payloadOffset = offset + 11
      if (payloadOffset >= buffer.length) {
        console.log('数据不足,无法解析视频帧')
        return { found: false, offset: null }
      }

      console.log(`找到视频 Tag,有效负载偏移量: ${payloadOffset}`)

      // 获取帧类型(前4位)
      const frameInfo = dataView.getUint8(payloadOffset)
      const frameType = frameInfo >> 4
      if (frameType === 1) {
        return { found: true, offset: payloadOffset }
      }
    }

    // 移动到下一个Tag
    offset += 11 + dataSize + 4 // 当前Tag头+数据+下一个PreviousTagSize
    console.log(`移动到下一个 Tag,新的偏移量: ${offset}`)
  }

  console.log('未找到视频帧,继续等待数据')
  return { found: false, offset: null }
}

// 示例调用
// const videoUrl = 'your_flv_video_url';
// analyzeFLV(videoUrl, 15).then((result) => {
//     console.log('分析结果:', result);
// });