使用 JS 判断 PSSH Box

现在 AI 已经非常牛逼了,如果你有这样的需求,非常明确的,建议你直接使用 AI。这是谷歌 Gemini 给出的结果:

使用 JavaScript 检测 MP4 文件中的 PSSH(保护系统特定标头)是一项复杂的任务,主要是因为:

二进制数据:MP4 文件是二进制的,而不是基于文本的。浏览器环境中的 JavaScript 在低级别上限制了对二进制数据的直接访问。
文件结构:PSSH 嵌入在 MP4 的原子结构中,需要解析才能找到它。
浏览器安全性:浏览器限制直接文件系统访问,限制您直接读取任意 MP4 文件的能力。
但是,您可以通过多种技术组合来实现这一点,主要涉及用于检索文件的 fetch API、用于二进制数据处理的 ArrayBuffer 和自定义解析逻辑。以下是一般方法的细分:

  1. 获取 MP4 文件:
async function getPSSH(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const arrayBuffer = await response.arrayBuffer();
    return findPSSH(arrayBuffer);
  } catch (error) {
    console.error("Error fetching or parsing MP4:", error;
    return null;
  }
}
  1. 解析 MP4 原子结构:

这是最具挑战性的部分。您需要实现一个能够理解 MP4 原子结构的解析器。这是一个简化的示例(您可能需要一个更强大的解析器来应对实际场景):

function findPSSH(arrayBuffer) {
  const dataView = new DataView(arrayBuffer);
  let offset = 0;

  while (offset < dataView.byteLength) {
    const size = dataView.getUint32(offset);
    const type = String.fromCharCode(
      dataView.getUint8(offset + 4),
      dataView.getUint8(offset + 5),
      dataView.getUint8(offset + 6),
      dataView.getUint8(offset + 7)
    );

    if (type === "pssh") {
       // 找到 PSSH 原子
      const psshData = new Uint8Array(
      arrayBuffer.slice(offset + 8, offset + size)
    );
    //这里是原始 pssh 数据。
    return psshData;
  }

    offset += size;
  }

  return null; // 未找到 PSSH
}

  1. 提取 PSSH 数据:
    找到“pssh”原子后,即可提取其中的数据。PSSH 结构本身有自己的格式,您可能需要根据具体用例进一步解析。
getPSSH("your_mp4_file.mp4").then((pssh) => {
  if (pssh) {
    console.log("PSSH found:", pssh);
    // 处理 pssh 数据。
    // 您可以将 Uint8Array 转换为十六进制字符串以便于查看。
    const hexPssh = Array.from(pssh).map(byte => byte.toString(16).padStart(2, '0')).join('');
    console.log("PSSH hex:", hexPssh);
  } else {
    console.log("PSSH not found.");
  }
 });

重要注意事项:

复杂性:MP4 解析很复杂。可靠的实现通常依赖于专门的库。

性能:在 JavaScript 中解析大型 MP4 文件可能非常耗费资源。

跨域请求:如果您的 MP4 文件位于不同的域中,则需要通过在服务器上设置适当的标头来处理 CORS(跨域资源共享)。

浏览器限制:浏览器具有安全限制,可能会限制您执行某些操作的能力。

替代方法:

对于服务器端处理,带有 mp4box.js 或 FFMPEG 等库的 Node.js 会更高效。

对于处理 DRM 的客户端应用程序,媒体源扩展 (MSE) API 和加密媒体扩展 (EME) API 是标准机制,它们以更受控制的方式提供对 PSSH 数据的访问。

EME 和 MSE:如果您正在使用受 DRM 保护的内容,则 EME 和 MSE API 是处理 pssh 的正确方法。EME 将为您提供 pssh 信息。
此信息提供了一般方向。对于生产级应用程序,强烈建议使用专用库或服务器端处理。

不到三分钟,这个问题就解决了,但是,这里有一个瑕疵,自己在测试中发现的,

findPSSH 中使用 while 循环进行 type 类型的对比没有任何问题,但是 mp4 box 的结构一般如下:

我发现运行到 moov 后就停止了。

const size = dataView.getUint32(offset); 

我们获取到了 moov 的长度后,理论应该继续进入 moov box 进行嗅探,然而我们并没有,因此我们可以尝试一下递归

export function findPSSH(arrayBuffer, offset) {
  const dataView = new DataView(arrayBuffer);

  while (offset < dataView.byteLength) {
    const size = dataView.getUint32(offset);
    const type = String.fromCharCode(
      dataView.getUint8(offset + 4),
      dataView.getUint8(offset + 5),
      dataView.getUint8(offset + 6),
      dataView.getUint8(offset + 7)
    );
    if (type === "moov") {
      return findPSSH(arrayBuffer, offset + 8);
    }
    if (type === "pssh") {
      // Found the PSSH atom
      const psshData = new Uint8Array(
        arrayBuffer.slice(offset + 8, offset + size)
      );
        //Here you have the raw pssh data.
      return psshData;
    }

    offset += size;
  }

  return null; // PSSH not found
}

最后 debug 效果还不错。 所以现在 AI 质量真的非常高,我们只需要稍许 debug 就可以了,无需要浪费太多时间。