
WebRTC提供了两种主要的数据传输通道:DataChannel和媒体流通道。 DataChannel设计用于传输任意的文本或二进制数据,例如聊天消息、文件共享或游戏状态同步。它建立在SCTP协议之上,提供可靠的、有序的或不可靠的、无序的数据传输服务。尝试通过DataChannel.send()方法直接发送一个MediaStream对象或其原始视频帧数据是不可行的,原因如下:
因此,对于视频和音频流,我们必须使用webrtc专门为媒体传输设计的api。
WebRTC通过RTCPeerConnection实例来管理媒体流的发送和接收。核心的API是addTrack()用于发送媒体,以及ontrack事件用于接收媒体。
RTCPeerConnection.addTrack(track, ...streams):
RTCPeerConnection.ontrack事件:
以下是基于原始问题代码的修正和优化,演示如何正确地设置WebRTC视频流传输。
保持原有的HTML结构,包含两个视频元素和控制按钮。
<button id='start'>发起连接</button><br>
<button id='connect'>接受连接</button><br>
<form onsubmit="return false;">
<input id='iprts' type="text" placeholder="发送文本消息" size="40">
<input id='enter' type="submit" value="发送">
</form>
<video autoplay id="vid1" width="400" height="300" controls="controls"></video>
<video autoplay id="vid2" width="400" height="300" controls="controls"></video>
<script src="client/socket.js"></script><!-- 假设 socket.io 客户端库 -->我们将重构客户端JavaScript代码,使其更符合WebRTC的最佳实践。这里假设socket.io用于信令(交换SDP和ICE Candidates)。
// 获取DOM元素
const startBtn = document.getElementById('start');
const connectBtn = document.getElementById('connect');
const sendMsgInput = document.getElementById('iprts');
const sendMsgBtn = document.getElementById('enter');
const localVideo = document.getElementById('vid1');
const remoteVideo = document.getElementById('vid2');
let localStream; // 用于存储本地媒体流
let peerConnection; // RTCPeerConnection实例
let dataChannel; // DataChannel实例
let isInitiator = false; // 标记当前客户端是否为连接发起方
// 假设的信令服务器通信
const socket = io(); // 初始化socket.io
// 辅助函数:处理ICE Candidate
function handleIceCandidate(event) {
if (event.candidate) {
// 将ICE Candidate发送给远端对等体
console.log('发送ICE Candidate:', event.candidate);
socket.emit('candidate', event.candidate);
}
}
// 辅助函数:处理接收到的媒体流
function handleTrack(event) {
console.log('收到远程媒体流:', event.streams[0]);
// 将接收到的流设置到远程视频元素
remoteVideo.srcObject = event.streams[0];
remoteVideo.addEventListener('loadedmetadata', () => {
remoteVideo.play().catch(e => console.error('播放远程视频失败:', e));
});
}
// 辅助函数:创建并配置RTCPeerConnection
async function createPeerConnection() {
peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' } // STUN服务器用于NAT穿越
]
});
peerConnection.onicecandidate = handleIceCandidate;
peerConnection.ontrack = handleTrack; // 监听远端媒体流的到来
// 设置DataChannel
if (isInitiator) {
dataChannel = peerConnection.createDataChannel('chat');
dataChannel.onopen = () => console.log('DataChannel已打开!');
dataChannel.onmessage = (event) => console.log('收到DataChannel消息:', event.data);
dataChannel.onclose = () => console.log('DataChannel已关闭!');
dataChannel.onerror = (error) => console.error('DataChannel错误:', error);
} else {
peerConnection.ondatachannel = (event) => {
dataChannel = event.channel;
dataChannel.onopen = () => console.log('DataChannel已打开!');
dataChannel.onmessage = (event) => console.log('收到DataChannel消息:', event.data);
dataChannel.onclose = () => console.log('DataChannel已关闭!');
dataChannel.onerror = (error) => console.error('DataChannel错误:', error);
};
}
// 将本地媒体流的轨道添加到PeerConnection
if (localStream) {
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
console.log('添加本地媒体轨道:', track.kind);
});
}
}
// 获取本地屏幕共享流
async function getLocalMediaStream() {
try {
localStream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true // 屏幕共享通常也包含系统音频
});
localVideo.srcObject = localStream;
localVideo.addEventListener('loadedmetadata', () => {
localVideo.play().catch(e => console.error('播放本地视频失败:', e));
});
console.log('成功获取本地媒体流');
} catch (e) {
console.error('获取本地媒体流失败:', e);
}
}
// 启动按钮点击事件:发起方
startBtn.onclick = async () => {
isInitiator = true;
await getLocalMediaStream(); // 首先获取本地媒体流
await createPeerConnection(); // 创建PeerConnection并添加轨道
try {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
console.log('发送Offer:', peerConnection.localDescription);
socket.emit('offer', peerConnection.localDescription); // 将Offer发送给信令服务器
} catch (e) {
console.error('创建Offer失败:', e);
}
};
// 连接按钮点击事件:接收方
connectBtn.onclick = async () => {
isInitiator = false;
await getLocalMediaStream(); // 获取本地媒体流
await createPeerConnection(); // 创建PeerConnection并添加轨道
// 等待接收Offer
};
// 监听信令服务器消息
socket.on('offer', async (offer) => {
if (!isInitiator) { // 只有接收方处理Offer
console.log('收到Offer:', offer);
if (!peerConnection) {
// 确保peerConnection已创建,如果connectBtn没有点击,这里需要手动创建
// 或者在connectBtn点击后才监听offer
await getLocalMediaStream();
await createPeerConnection();
}
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
try {
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
console.log('发送Answer:', peerConnection.localDescription);
socket.emit('answer', peerConnection.localDescription); // 将Answer发送给信令服务器
} catch (e) {
console.error('创建Answer失败:', e);
}
}
});
socket.on('answer', async (answer) => {
if (isInitiator) { // 只有发起方处理Answer
console.log('收到Answer:', answer);
await peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
console.log('远程描述设置成功,连接建立中...');
}
});
socket.on('candidate', async (candidate) => {
if (peerConnection && candidate) {
try {
await peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
console.log('添加ICE Candidate成功:', candidate);
} catch (e) {
console.error('添加ICE Candidate失败:', e);
}
}
});
// DataChannel文本消息发送
sendMsgBtn.onclick = () => {
if (dataChannel && dataChannel.readyState === 'open') {
dataChannel.send(sendMsgInput.value);
console.log('发送DataChannel消息:', sendMsgInput.value);
sendMsgInput.value = ''; // 清空输入框
} else {
console.warn('DataChannel未打开或未初始化,无法发送消息。');
}
};代码解释与注意事项:
通过遵循上述指导和代码示例,开发者可以有效地在WebRTC应用中实现可靠和高效的视频流传输。
以上就是WebRTC视频流传输:使用addTrack与ontrack实现媒体通信的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号