• 示例工程

【免费下载链接】WebRTC-Experiment

WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!

项目地址: https://gitcode.com/gh_mirrors/we/WebRTC-Experiment
点击查看 免费下载

导读

FileBufferReader 是 WebRTC-Experiment 系列中的轻量级 JavaScript 库,核心职责是"把文件读成按指定 chunkSize 切分的 ArrayBuffer 列表",并配合你的 WebRTC DataChannel 或任意信令通道完成点对点文件传输。它的设计目标是模拟 Skype 的"受控缓冲传输"(controlled-buffers transmission)风格:发送端不一次性把整个文件塞进数据通道,而是按接收端的"就绪信号"逐块发送,从而避免慢网络下的丢包与拥塞。读完本文,你将掌握 FileBufferReader 的完整 API、端到端收发流程、多用户共享、进度条集成以及底层分块/序列化原理,可以直接在 RTCMultiConnection 或自建 PeerConnection 项目中落地文件共享功能。

项目定位与核心能力

FileBufferReader 是独立于 RTCMultiConnection 之外的一个 npm 包(包名 fbr),但其作者与 WebRTC-Experiment 仓库同一人,且仓库内提供了完整的本地运行 demo(FileBufferReader/index.html)。

它的能力可以概括为两点:

  1. 按指定 chunkSize 获取 ArrayBuffer 分块列表:读取本地文件,将其切分为多个 ArrayBuffer,并连同文件元数据一起存入 chunks 对象。
  2. 支持逐块(step-by-step)或循环即时(for-loop)发送:既可以在回调中一块一块地取(推荐),也可以自行遍历 chunks 一次性取出所有分块。

此外,由于每个 chunk 都带有 uuid、currentPosition、maxChunks 等元数据,你可以轻松实现**分块重传(retransmission)**机制——前提是把数据通道的 binaryType 设置为 arraybuffer:

WebRTC_Data_Channel.binaryType = 'arraybuffer';

设计前提与限制(务必先读)

README 明确列出了四个关键点,理解它们有助于避免踩坑:

  1. FileBufferReader 本身不做任何"传输"工作,它只负责读取文件并给出分块。发送什么、走什么通道(DataChannel、Socket.io、WebSocket……)完全由你决定。
  2. 你需要手动用自己偏好的通道共享分块:库不内置 PeerConnection 逻辑。
  3. chunks 存放在内存中,有存储上限:因此不适合用 FileBufferReader 读取/传输 1GB 及以上大小的文件。这与 FileBufferReaderHelper.js 中全量切片入内存的实现一致。
  4. 设计目标:支持受控缓冲传输,模仿 Skype 的文件分享节奏——发送端等待接收端"请求下一块"。

快速开始:安装与运行

npm / bower 安装

npm install fbr --production

# 或者使用 bower
bower install fbr

在页面中引入

本地安装后可直接引用仓库根目录构建产物:

<script src="./node_modules/fbr/FileBufferReader.js"></script>
<script src="./bower_components/fbr/FileBufferReader.js"></script>

在仓库中,可直接引用根目录的 FileBufferReader.js(完整版)或 FileBufferReader.min.js(压缩版)。

运行本地 demo

仓库根目录提供了 server.js,启动后即可体验完整的文件共享 demo:

node server.js

然后打开 http://localhost:9001/ 或 http://127.0.0.1:9001/。demo 页面(FileBufferReader/index.html)内置了 chunkSize 选择器(60000 / 50000 / 40000 / 30000 / 20000 / 16000 / 8000 字节)、拖拽上传、进度条、暂停/恢复/取消传输等完整交互,是学习真实用法的极佳范例。

独立的 Socket.io 客户端(fbr-client)

如果你想跳过 WebRTC 直接体验分块传输流程,可以用独立的 fbr-client:

npm install fbr-client

然后启动其内置服务器(仓库内对应目录为 fbr-client):

cd ./node_modules/fbr-client
node server.js port=9001

同样打开 http://localhost:9001/。

提示:修改 dev 目录下的开发文件后,可用 grunt 重新编译出根目录的 FileBufferReader.js。编译流水线见 Gruntfile.js:按顺序 concat dev/head.js、FileBufferReader.js、FileBufferReaderHelper.js、FileSelector.js、FileBufferReceiver.js、FileConverter.js、common.js、binarize.js、tail.js,再经 jsbeautifier 与 uglify 产出 min 版本。

API 总览

FileBufferReader 实例对外暴露以下核心成员:

API / 属性作用
chunks 对象存放所有文件的切块。每个文件以 file-uuid 为键,值为分块索引结构(含元数据块与真实数据块)。即使是从远端收到并经过 addChunk 存入的分块,也统一放在同一个 chunks 对象中。取值方式:var fileChunks = fileBufferReader.chunks['file-uuid']
readAsArrayBuffer(file, callback, extra)读取整个文件,将切块化的缓冲存入 chunks 对象
getNextChunk(fileUUID, callback, userid)读取 last-position,返回下一个可用的 ArrayBuffer 分块
onBegin / onEnd / onProgress 事件三个可赋值的事件回调,专门用于驱动文件进度条
addChunk(chunk, callback)把接收到的分块暂存到数组,直到整个文件接收完毕
convertToObject(buffer, callback)将 ArrayBuffer 转换回 JavaScript 对象(用于 onmessage 里判断消息类型)
convertToArrayBuffer(object, callback)反向操作,将任意 JS 对象/数据类型转换为 ArrayBuffer

源码实现位于 FileBufferReader/dev/FileBufferReader.js:构造时会创建 fbr.chunks = {} 与 fbr.users = {},并把 convertToObject / convertToArrayBuffer 直接别名到 FileConverter 的对应方法。

实战一:发送端读取文件并逐块发送

1. 引入库

在仓库环境中最简单的方式是直接引用根目录的构建产物(见上文"在页面中引入")。

2. 选择文件(可选步骤)

README 特别强调:强烈推荐使用 input[type=file].onchange 自行选择文件,而不是依赖 FileSelector,因为 FileSelector 无法妥善处理浏览器不触发 onchange 事件等失败场景。若要用 FileSelector,写法如下:

var fileSelector = new FileSelector();

// 可指定 '*.png'、'*.jpeg'、'*.mp4' 等,默认 '*.*'
fileSelector.accept = '*.*';

var btnSelectFile = document.getElementById('select-file');
btnSelectFile.onclick = function() {
    fileSelector.selectSingleFile(function(file) {
        // file == input[type=file]
    });
};

3. 读取 Buffers

var fileBufferReader = new FileBufferReader();

fileBufferReader.readAsArrayBuffer(file, function(fileUUID) {
    // var file         = fileBufferReader.chunks[fileUUID];
    // var listOfChunks = file.listOfChunks;

    // 取第一块,然后用 WebRTC 数据通道发送
    // 千万不要在循环里发送所有分块;慢网络下会出问题
    // 正确做法是等远端通知"可以发下一块"再继续
    fileBufferReader.getNextChunk(fileUUID, function(nextChunk, isLastChunk) {
        if (isLastChunk) {
            alert('File Successfully sent.');
        }
        // 使用 WebRTC 数据通道发送
        datachannel.send(nextChunk);
    });
});

readAsArrayBuffer 还接受第 3 个参数 extra,用于传入 chunkSize 和自定义数据:

var extra = {
    chunkSize: 15 * 1000,    // 默认 15KB;Firefox 的接收上限约 16k
    userid: 'sender-userid'  // 最常用的对象,用于区分发送者
};

fileBufferReader.readAsArrayBuffer(file, callback, extra);

chunkSize 的实现细节:在 FileBufferReaderHelper.js 的 fileReaderWrapper 中,chunkSize 默认取 15 * 1000 字节;如果 options.extra.chunkSize 存在则优先使用 extra 中的值。切片过程会把整个文件按 maxChunks = Math.ceil(file.size / chunkSize) 切成若干块,并用 file.slice(...) + FileReader.readAsArrayBuffer 分批读取,每批(slice)内再按 chunkSize 二次切分。

实战二:接收端处理远端分块

接收端(或同一端的 onmessage)需要区分三种情况:收到的是原始 ArrayBuffer(需转回对象)、收到"请求下一块"信号、收到真实数据块:

datachannel.onmessage = function(event) {
    var chunk = event.data;

    if (chunk instanceof ArrayBuffer || chunk instanceof DataView) {
        // WebRTC 数据通道传来的都是 ArrayBuffer
        // 需要先转回 JavaScript 对象

        fileBufferReader.convertToObject(chunk, function(object) {
            datachannel.onmessage({
                data: object
            });
        });
        return;
    }

    // 如果发送时传了 extra 数据,可在这里访问:
    // chunk.extra.senderUserName 或其他自定义字段

    // 远端请求下一块
    if (chunk.readyForNextChunk) {
        fileBufferReader.getNextChunk(chunk.uuid, function(nextChunk, isLastChunk) {
            if (isLastChunk) {
                alert('File Successfully sent.');
            }
            // 使用 WebRTC 数据通道发送
            datachannel.send(nextChunk);
        });
        return;
    }

    // 收到数据块:addChunk 会暂存分块,
    // 并在必要时通过回调返回"请求下一块"的信令对象
    fileBufferReader.addChunk(chunk, function(promptNextChunk) {
        // 请求下一块
        datachannel.send(promptNextChunk);
    });
};

关键协议对象:promptNextChunk 是一个包含 readyForNextChunk: true、currentPosition、uuid 的对象(见 FileBufferReader.js 中 addChunk 的实现),接收方每收一块就回发一次,发送方据此触发 getNextChunk。这就是"受控缓冲传输"的核心握手。

接收端的重组逻辑:在 FileBufferReceiver.js 中,收到 chunk.start 时初始化该文件的分块容器并触发 onBegin;收到带 chunk.buffer 的非末尾块时按 currentPosition 存位;收到 chunk.end 时把所有块合成 Blob(new Blob(chunksArray, { type: chunk.type })),生成 blob.url,触发 onEnd 并立即清理内存。此外它还内置了一个 chunksWaiters 重试循环:若某一块缺失(chunk.currentPosition != chunk.maxChunks && !chunks[uuid][currentPosition]),会每 5 秒重试一次回调,用于应对乱序或丢块场景。

实战三:进度条集成

方案 A:使用 FileProgressBarHandler.js

先引入进度条脚本(仓库内可参考 demo 中自行实现的进度 UI,见 FileBufferReader/index.html),准备一个容器 div:

<div id="files-container"></div>

然后接线:

// 可选:指定进度条与文件预览的容器 <DIV>
fileBufferReader.filesContainer = document.getElementById('files-container');

// 这行会把 onFileStart / onFileProgress / onFileEnd 三个回调设置到 FileBufferReader 上
FileProgressBarHandler.handle(fileBufferReader);

fileBufferReader.onBegin    = fileBufferReader.onFileStart;
fileBufferReader.onProgress = fileBufferReader.onFileProgress;
fileBufferReader.onEnd      = fileBufferReader.onFileEnd;

等价写法(用 options 对象):

var options = {};

// 可选:指定进度条与文件预览的容器 <DIV>
options.filesContainer = document.getElementById('files-container');

// 设置 onFileStart / onFileProgress / onFileEnd 回调
FileProgressBarHandler.handle(options);

fileBufferReader.onBegin    = options.onFileStart;
fileBufferReader.onProgress = options.onFileProgress;
fileBufferReader.onEnd      = options.onFileEnd;

方案 B:不用 FileProgressBarHandler,手写进度 UI

var progressHelper = {};
var outputPanel = document.body;

var FileHelper = {
    onBegin: function(file) {
        // 若传了 extra 数据可在此访问:file.extra.senderUserName 等

        var li = document.createElement('li');
        li.title = file.name;
        li.innerHTML = '<label>0%</label> <progress></progress>';
        outputPanel.insertBefore(li, outputPanel.firstChild);
        progressHelper[file.uuid] = {
            li: li,
            progress: li.querySelector('progress'),
            label: li.querySelector('label')
        };
        progressHelper[file.uuid].progress.max = file.maxChunks;
    },
    onEnd: function(file) {
        // 若传了 extra 数据可在此访问:file.extra.senderUserName 等

        progressHelper[file.uuid].li.innerHTML = '<a href="' + file.url + '" target="_blank" download="' + file.name + '">' + file.name + '</a>';
    },
    onProgress: function(chunk) {
        // 若传了 extra 数据可在此访问:chunk.extra.senderUserName 等

        var helper = progressHelper[chunk.uuid];
        helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max;
        updateLabel(helper.progress, helper.label);
    }
};

function updateLabel(progress, label) {
    if (progress.position == -1) return;
    var position = +progress.position.toFixed(2).split('.')[1] || 100;
    label.innerHTML = position + '%';
}

fileBufferReader.onBegin    = FileHelper.onBegin;
fileBufferReader.onProgress = FileHelper.onProgress;
fileBufferReader.onEnd      = FileHelper.onEnd;

回调触发时机(源码佐证):在 FileBufferReader.js 的 getNextChunk 中,发送端每次取块都会依次触发 onBegin(当块带 start 标记)、onEnd(当块带 end 标记)和 onProgress;在接收端,FileBufferReceiver.receive 收到 chunk.start 触发 onBegin、收到 chunk.end 触发 onEnd、收到带 buffer 的块触发 onProgress。两端的事件语义一致,因此同一套 UI 代码可以复用。

多用户共享

每个远端用户可以拥有独立的传输游标。做法是把 userid 作为第 3 个参数传给 getNextChunk:

fbr.readAsArrayBuffer(file, function(fileUUID) {
    ['first-user', 'second-user', 'third-user'].forEach(function(userid) {
        fbr.getNextChunk(fileUUID, function(nextChunk, isLastChunk) {
            specific_datachannel.send(nextChunk);
        }, userid);
    });
});

datachannel.onmessage = function(event) {
    fbr.getNextChunk(message.uuid, function(nextChunk, isLastChunk) {
        specific_datachannel.send(nextChunk);
    }, specific_userid);
};

关键:把具体的 userid 作为第 3 个参数传给 getNextChunk 方法。

源码原理:getNextChunk 内部维护 fbr.users[userid] 这一独立的游标记录(currentPosition 独立递增),并为取出的块附加 remoteUserId = userid(见 FileBufferReader.js 第 30-102 行)。这样每个远端都有自己的进度位置,互不干扰。

要区分"某个文件正被共享给多个用户"以便为每个用户绘制独立进度条,可检查 remoteUserId:

FileHelper.onBegin = function(file) {
    if (file.remoteUserId) {
        // 该文件正在被共享给多个用户
    }
};

FileHelper.onEnd = function(file) {
    if (file.remoteUserId) {
        // 该文件正在被共享给多个用户
    }
};

FileHelper.onProgress = function(chunk) {
    if (chunk.remoteUserId) {
        // 该文件正在被共享给多个用户
    }
};

高级用法:直接访问 chunks 并自行处理

如果不想走 getNextChunk 的受控流程,可以绕开它,直接操作 fbr.chunks:

var fbr = new FileBufferReader();
fbr.readAsArrayBuffer(file, function(fileUUID) {
    // 不调用 getNextChunk
    // 而是自己处理/使用/访问所有分块

    var thisFileChunks = fbr.chunks[fileUUID];
    var numberOfFileChunks = thisFileChunks[0].maxChunks;

    var arrayOfRealBuffers = [];

    for (var i = 1; i < numberOfFileChunks; i++) {
        var fileChunk = thisFileChunks[i];
        var realArrayBufferObject = fileChunk.buffer;
        arrayOfRealBuffers.push(realArrayBufferObject);
    }

    var fileBlob = new Blob(realArrayBufferObject, {
        type: thisFileChunks[0].type
    });

    var blobURL = URL.createObjectURL(fileBlob);
    document.write('<iframe style="width: 100%; height: 100%; border:0; " src="' + blobURL + '"></iframe>');
});

chunks 对象的结构

fileBufferReader.chunks 的结构如下(README 原文示例,文件 WebRTC.png,20 块):

fileBufferReader.chunks =
{
   // "4152661527041346" 是 file-uuid
   "4152661527041346": {

      // 索引 "0" 用于触发 "onStart" 事件
      "0": {
         "currentPosition": 0,
         "uuid": "4152661527041346",
         "maxChunks": 20,
         "size": 298540,
         "name": "WebRTC.png",
         "type": "image/png",
         "lastModifiedDate": "Wed Oct 14 2015 15:51:26 GMT+0500 (PKT)",
         "start": true,
         "userid": 0,
         "extra": {
            "userid": 0
         }
      },

      // 索引 "1" 到 "maxChunks" 才是真正的数据块

      "1": {
         "uuid": "4152661527041346",
         // 这是真正的 ArrayBuffer 对象
         "buffer": {},
         "currentPosition": 1,
         "maxChunks": 20,
         "size": 298540,
         "name": "WebRTC.png",
         "lastModifiedDate": "Wed Oct 14 2015 15:51:26 GMT+0500 (PKT)",
         "type": "image/png",
         "userid": 0,
         "extra": {
            "userid": 0
         }
      },

      ....
      ....

      // 最后一个数据块
      // 此时 "currentPosition === maxChunks"
      "20": {
         "uuid": "4152661527041346",
         "buffer": {},
         "currentPosition": 20,
         "maxChunks": 20,
         "size": 298540,
         "name": "WebRTC.png",
         "lastModifiedDate": "Wed Oct 14 2015 15:51:26 GMT+0500 (PKT)",
         "type": "image/png",
         "userid": 0,
         "extra": {
            "userid": 0
         }
      },

      // 该块用于触发 "onEnd" 事件
      "21": {
         "currentPosition": 21,
         "uuid": "4152661527041346",
         "maxChunks": 20,
         "size": 298540,
         "name": "WebRTC.png",
         "lastModifiedDate": "Wed Oct 14 2015 15:51:26 GMT+0500 (PKT)",
         "url": "blob:http%3A//domain/fe5a20d0-cdb4-4f4e-9de7-1a14340fc402",
         "type": "image/png",
         "end": true,
         "userid": 0,
         "extra": {
            "userid": 0
         }
      },

      // 可选字段,用于记录当前正在共享的是哪一块;应跳过
      "currentPosition": 0
   }
}

结构规律(README 明确说明):真实数据块从索引 1 开始,到 length - 1 之前结束。例如:

var fileChunks = fbr.chunks['file-uuid'];
var allFileIndices = Object.keys(fileChunks);

var allFileBuffers = [];
for (var chunkIndex = 1; chunkIndex < allFileIndices.length; chunkIndex++) {
    var chunk = fileChunks[chunkIndex];
    allFileBuffers.push(chunk.buffer);
}

大文件提前回调的细节:在 FileBufferReaderHelper.js 的 processChunk 中,除了末尾块会触发早回调外,当 maxChunks > 200 且当前块序号到达 200 时也会提前触发一次回调(earlyCallback),从而让发送端尽早开始传输前 200 块,不必等整个文件读完——这对大文件的首包延迟有明显改善。

辅助对象:FileSelector

FileSelector 提供选择单个文件、多个文件或整个目录的方法(源码见 FileSelector.js,内部通过动态创建 input[type=file] 并派发 click 事件实现)。

选择单个文件:

var selector = new FileSelector();
selector.accept = '*.png';
selector.selectSingleFile(function(file) {
    alert(file.name);
}, function() {
    alert('User did not select any file.');
});

选择多个文件:

var selector = new FileSelector();
selector.accept = '*.png';
selector.selectMultipleFiles(function(files) {
    files.forEach(function(file) {
        alert(file.name);
    });
}, function() {
    alert('User did not select any file.');
});

选择整个目录:

var selector = new FileSelector();
selector.accept = '*.png';
selector.selectDirectory(function(files) {
    files.forEach(function(file) {
        alert(file.webkitRelativePath);
    });
}, function() {
    alert('User did not select any file.');
});

失败回调的实现细节:FileSelector 利用"文件对话框关闭后页面重新获得焦点"这一时机,在 document.body.onfocus 里延迟 500ms 检查 input.value 是否为空,从而判断用户是否取消了选择并触发失败回调(FileSelector.js 第 46-60 行)。这正是 README 建议优先使用原生 input[type=file].onchange 的原因——onchange 不触发时该兜底方案也不一定可靠。

辅助对象:FileConverter

全局对象 FileConverter 暴露两个方法(实现见 FileConverter.js,底层调用 binarize.pack / binarize.unpack):

  1. ConvertToArrayBuffer(object, callback)
  2. ConvertToObject(buffer, callback)

用法:

var yourObject = {
    x: 0,
    y: 1,
    str: 'string',
    bool: true
};

FileConverter.ConvertToArrayBuffer(yourObject, function(arrayBuffer) {
    alert(arrayBuffer.byteLength);

    // 转回 object
    FileConverter.ConvertToObject(arrayBuffer, function(yourObject) {
        alert( JSON.stringify(yourObject) );
    });
});

序列化格式说明:binarize.js 源自 Eiji Kitamura 的二进制序列化方案(Apache-2.0 许可,见 binarize.js 文件头注释)。它支持 NULL、UNDEFINED、STRING、NUMBER、BOOLEAN、ARRAY、OBJECT、各种 TypedArray、ArrayBuffer、Blob/File 等类型;每个值在打包时写入 type(1字节) + [length(2字节,仅数组/对象)] + byte_length(4字节) + payload,采用大端序(BIG_ENDIAN)。这保证了任意 JS 对象(包括 chunk 元数据)都能无损地在 DataChannel 上往返传输。

内部机制:getNextChunk 究竟做了什么

README 给出了一段揭示内部原理的伪代码(注意其中变量名 fileUUId / currentPosition 的写法与源码略有出入,以下按其语义复述):

// 该方法解释了 FileBufferReader 的内部机制
fbr.getNextChunk = function(fileUUId, callback) {
    var fileChunks = fbr.chunks[fileUUID];
    var currentPosition = fileChunks.currentPosition;

    var nextChunk = fileChunks[currentPosition];
    FileConverter.ConvertToArrayBuffer(nextChunk, function(buffer) {
        // callback 收到两个参数:
        // 1) 转换后的 buffer
        // 2) "isLastChunk" 布尔值
        callback(buffer, currentPosition == nextChunk.maxChunks);
    });
};

// 你的代码这样调用上面的方法:
fbr.getNextChunks('file-uuid', function(buffer) {
    webrtc.channel.send(buffer);
});

对照真实源码(FileBufferReader.js 第 30-102 行),getNextChunk 的实际行为是:

  1. 若传入的是带 currentPosition 的对象(即接收端回传的元数据),则先用 fileUUID.currentPosition 更新游标,再取出 uuid;
  2. 无 userid 时,递增 fbr.chunks[fileUUID].currentPosition;有 userid 时,为每个用户维护独立的游标并递增;
  3. 若 currentPosition 已超出范围(找不到下一块),会删除该文件的 chunks 并回调一个 { chunkMissing: true, uuid, currentPosition } 对象,供接收端触发 chunkMissing 清理逻辑;
  4. 找到块后克隆一份(避免外部修改污染内部结构),多用户场景附加 remoteUserId,依次触发 onBegin / onEnd / onProgress,最后 ConvertToArrayBuffer 后回调 (buffer, isLastChunk)。

缺失块恢复:接收端在 onmessage 中检测到 chunk.chunkMissing 时,可调用 fbr.chunkMissing(chunk),它会清理接收端对应文件的残留状态(FileBufferReader.js 第 120-123 行),让发送方可以重新开始该文件的传输。

在 WebRTC-Experiment 生态中的位置

README 列出的典型应用是 RTCMultiConnection。仓库内的 RTCMultiConnection 目录将 FileBufferReader 与 RTCMultiConnection 的数据通道能力结合,提供了开箱即用的文件共享 demo(如 RTCMultiConnection/demos/file-sharing.html、RTCMultiConnection/demos/text-chat-file-sharing.html),以及 RTCMultiConnection/dev/FileProgressBarHandler.js 进度条处理器。换言之,FileBufferReader 专注于"分块 + 受控发送"这一层,而连接管理、信令、ICE 等交给上层库,二者分工清晰。

总结与选型建议

  • 如果你的项目已经有一套 WebRTC DataChannel(或 Socket.io/WebSocket)通道,只想快速获得"文件分块 + 逐块确认 + 进度回调"能力,FileBufferReader 是轻量且贴合需求的选择。
  • 由于分块全部驻留内存,请勿用于超大文件(README 明示 1GB 以上不可用);生产环境中如需大文件传输,应结合分片落盘或服务端中转方案。
  • 使用前务必设置 binaryType = 'arraybuffer',并在收发两端按"readyForNextChunk 握手"协议协作;遵循 README 的建议,优先用原生 input[type=file].onchange 而非 FileSelector 触发文件选择。

许可证

FileBufferReader.js 基于 MIT 许可证发布(见 FileBufferReader/LICENSE 与 FileBufferReader/package.json),可免费用于任何商业或非商业产品。binarize.js 部分额外保留 Apache License 2.0 声明(见其文件头注释)。

  • 示例工程

【免费下载链接】WebRTC-Experiment

WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!

项目地址: https://gitcode.com/gh_mirrors/we/WebRTC-Experiment
点击查看 免费下载
Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐