- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我有一个应用程序。在此应用程序中,我无法更改发送给对方的视频。
'use strict';
var Meeting = function (socketioHost) {
var exports = {};
var _isInitiator = false;
var _localStream;
var _remoteStream;
var _turnReady;
var _pcConfig = {'iceServers': [{'url': 'stun:stun.l.google.com:19302'}]};
var _constraints = {
video: {
width: {ideal: 320},
height: {ideal: 240},
frameRate: {ideal: 20}
},
audio: {
googEchoCancellation: true,
googAutoGainControl: true,
googNoiseSuppression: true,
googHighpassFilter: true,
googEchoCancellation2: true,
googAutoGainControl2: true,
googNoiseSuppression2: true
},
options: {
mirror: true
}
};
if(navigator.userAgent.includes("iPhone")) {
var _constraints = {
video : true
}
}
var _defaultChannel;
var _privateAnswerChannel;
var _offerChannels = {};
var _opc = {};
var _apc = {};
var _sendChannel = {};
var _room;
var _myID;
var _onRemoteVideoCallback;
var _onLocalVideoCallback;
var _onChatMessageCallback;
var _onChatReadyCallback;
var _onChatNotReadyCallback;
var _onParticipantHangupCallback;
var _host = socketioHost;
////////////////////////////////////////////////
// PUBLIC FUNCTIONS
////////////////////////////////////////////////
/**
*
* Add callback function to be called when a chat message is available.
*
* @param name of the room to join
*/
function joinRoom(name) {
_room = name;
_myID = generateID();
// Open up a default communication channel
initDefaultChannel();
if (_room !== '') {
console.log('Create or join room', _room);
_defaultChannel.emit('create or join', {room:_room, from:_myID});
}
// Open up a private communication channel
initPrivateChannel();
//console.log(_devices);
navigator.mediaDevices.getUserMedia(_constraints)
.then(handleUserMedia)
.catch(handleUserMediaError);
window.onbeforeunload = function(e){
_defaultChannel.emit('message',{type: 'bye', from:_myID});
}
}
/**
*
* Send a chat message to all channels.
*
* @param message String message to be send
*/
function sendChatMessage(message) {
console.log("Sending "+message)
for (var channel in _sendChannel) {
if (_sendChannel.hasOwnProperty(channel)) {
_sendChannel[channel].send(message);
}
}
}
/**
*
* Toggle microphone availability.
*
*/
function toggleMic() {
var tracks = _localStream.getTracks();
for (var i = 0; i < tracks.length; i++) {
if (tracks[i].kind=="audio") {
tracks[i].enabled = !tracks[i].enabled;
}
}
}
/**
*
* Toggle video availability.
*
*/
function toggleVideo() {
var tracks = _localStream.getTracks();
for (var i = 0; i < tracks.length; i++) {
if (tracks[i].kind=="video") {
tracks[i].enabled = !tracks[i].enabled;
}
}
}
/**
*
* Add callback function to be called when remote video is available.
*
* @param callback of type function(stream, participantID)
*/
function onRemoteVideo(callback) {
_onRemoteVideoCallback = callback;
}
/**
*
* Add callback function to be called when local video is available.
*
* @param callback function of type function(stream)
*/
function onLocalVideo(callback) {
_onLocalVideoCallback = callback;
}
/**
*
* Add callback function to be called when chat is available.
*
* @parama callback function of type function()
*/
function onChatReady(callback) {
_onChatReadyCallback = callback;
}
/**
*
* Add callback function to be called when chat is no more available.
*
* @parama callback function of type function()
*/
function onChatNotReady(callback) {
_onChatNotReadyCallback = callback;
}
/**
*
* Add callback function to be called when a chat message is available.
*
* @parama callback function of type function(message)
*/
function onChatMessage(callback) {
_onChatMessageCallback = callback;
}
/**
*
* Add callback function to be called when a a participant left the conference.
*
* @parama callback function of type function(participantID)
*/
function onParticipantHangup(callback) {
_onParticipantHangupCallback = callback;
}
////////////////////////////////////////////////
// INIT FUNCTIONS
////////////////////////////////////////////////
function initDefaultChannel() {
_defaultChannel = openSignalingChannel('');
_defaultChannel.on('created', function (room){
console.log('Created room ' + room);
_isInitiator = true;
});
_defaultChannel.on('join', function (room){
console.log('Another peer made a request to join room ' + room);
});
_defaultChannel.on('joined', function (room){
console.log('This peer has joined room ' + room);
});
_defaultChannel.on('message', function (message){
console.log('Client received message:', message);
if (message.type === 'newparticipant') {
var partID = message.from;
// Open a new communication channel to the new participant
_offerChannels[partID] = openSignalingChannel(partID);
// Wait for answers (to offers) from the new participant
_offerChannels[partID].on('message', function (msg){
if (msg.dest===_myID) {
if (msg.type === 'answer') {
_opc[msg.from].setRemoteDescription(new RTCSessionDescription(msg.snDescription))
.then(setRemoteDescriptionSuccess)
.catch(setRemoteDescriptionError);
} else if (msg.type === 'candidate') {
var candidate = new RTCIceCandidate({sdpMLineIndex: msg.label, candidate: msg.candidate});
console.log('got ice candidate from '+msg.from);
_opc[msg.from].addIceCandidate(candidate, addIceCandidateSuccess, addIceCandidateError);
}
}
});
// Send an offer to the new participant
createOffer(partID);
} else if (message.type === 'bye') {
hangup(message.from);
} else if(message.type === 'change') {
$('#' + message.from).remove();
if(_myID !== message.from) {
createOffer(message.from);
}
}
});
}
function initPrivateChannel() {
// Open a private channel (namespace = _myID) to receive offers
_privateAnswerChannel = openSignalingChannel(_myID);
// Wait for offers or ice candidates
_privateAnswerChannel.on('message', function (message){
if (message.dest===_myID) {
if(message.type === 'offer') {
var to = message.from;
createAnswer(message, _privateAnswerChannel, to);
} else if (message.type === 'candidate') {
var candidate = new RTCIceCandidate({sdpMLineIndex: message.label, candidate: message.candidate});
_apc[message.from].addIceCandidate(candidate, addIceCandidateSuccess, addIceCandidateError);
}
}
});
}
function requestTurn(turn_url) {
var turnExists = false;
for (var i in _pcConfig.iceServers) {
if (_pcConfig.iceServers[i].url.substr(0, 5) === 'turn:') {
turnExists = true;
_turnReady = true;
break;
}
}
if (!turnExists) {
console.log('Getting TURN server from ', turn_url);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState === 4 && xhr.status === 200) {
var turnServer = JSON.parse(xhr.responseText);
console.log('Got TURN server: ', turnServer);
_pcConfig.iceServers.push({
'url': 'turn:' + turnServer.username + '@' + turnServer.turn,
'credential': turnServer.password
});
_turnReady = true;
}
}
xhr.open('GET', turn_url, true);
xhr.send();
}
}
///////////////////////////////////////////
// UTIL FUNCTIONS
///////////////////////////////////////////
/**
*
* Call the registered _onRemoteVideoCallback
*
*/
function addRemoteVideo(stream, from) {
// call the callback
_onRemoteVideoCallback(stream, from);
}
/**
*
* Generates a random ID.
*
* @return a random ID
*/
function generateID() {
var s4 = function() {
return Math.floor(Math.random() * 0x10000).toString(16);
};
return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4();
}
////////////////////////////////////////////////
// COMMUNICATION FUNCTIONS
////////////////////////////////////////////////
/**
*
* Connect to the server and open a signal channel using channel as the channel's name.
*
* @return the socket
*/
function openSignalingChannel(channel) {
var namespace = _host + '/' + channel;
var sckt = io.connect(namespace);
return sckt;
}
function logout(from) {
hangup(from)
window.dispatchEvent(new Event('beforeunload'))
}
/**
*
* Send an offer to peer with id participantId
*
* @param participantId the participant's unique ID we want to send an offer
*/
function createOffer(participantId) {
console.log('Creating offer for peer '+participantId);
_opc[participantId] = new RTCPeerConnection(_pcConfig);
_opc[participantId].onicecandidate = handleIceCandidateAnswerWrapper(_offerChannels[participantId], participantId);
_opc[participantId].onaddstream = handleRemoteStreamAdded(participantId);
_opc[participantId].onremovestream = handleRemoteStreamRemoved;
_opc[participantId].addStream(_localStream);
try {
// Reliable Data Channels not yet supported in Chrome
_sendChannel[participantId] = _opc[participantId].createDataChannel("sendDataChannel", {reliable: false});
_sendChannel[participantId].onmessage = handleMessage;
//console.log('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' + 'You need Chrome M25 or later with RtpDataChannel enabled');
//console.log('createDataChannel() failed with exception: ' + e.message);
}
_sendChannel[participantId].onopen = handleSendChannelStateChange(participantId);
_sendChannel[participantId].onclose = handleSendChannelStateChange(participantId);
var onSuccess = function(participantId) {
return function(sessionDescription) {
var channel = _offerChannels[participantId];
// Set Opus as the preferred codec in SDP if Opus is present.
sessionDescription.sdp = preferOpus(sessionDescription.sdp);
_opc[participantId].setLocalDescription(sessionDescription);
console.log('Sending offer to channel '+ channel.name);
channel.emit('message', {snDescription: sessionDescription, from:_myID, type:'offer', dest:participantId});
}
}
_opc[participantId].createOffer(onSuccess(participantId), handleCreateOfferError);
}
function createAnswer(sdp, cnl, to) {
_apc[to] = new RTCPeerConnection(_pcConfig);
_apc[to].onicecandidate = handleIceCandidateAnswerWrapper(cnl, to);
_apc[to].onaddstream = handleRemoteStreamAdded(to);
_apc[to].onremovestream = handleRemoteStreamRemoved;
_apc[to].addStream(_localStream);
_apc[to].setRemoteDescription(new RTCSessionDescription(sdp.snDescription))
.then(setRemoteDescriptionSuccess)
.catch(setRemoteDescriptionError);
_apc[to].ondatachannel = gotReceiveChannel(to);
var onSuccess = function(channel) {
return function(sessionDescription) {
// Set Opus as the preferred codec in SDP if Opus is present.
sessionDescription.sdp = preferOpus(sessionDescription.sdp);
_apc[to].setLocalDescription(sessionDescription);
console.log('Sending answer to channel '+ channel.name);
channel.emit('message', {snDescription:sessionDescription, from:_myID, type:'answer', dest:to});
}
}
_apc[to].createAnswer(onSuccess(cnl), handleCreateAnswerError);
}
function hangup(from) {
console.log('Bye received from '+ from);
if (_opc.hasOwnProperty(from)) {
_opc[from].close();
_opc[from] = null;
}
if (_apc.hasOwnProperty(from)) {
_apc[from].close();
_apc[from] = null;
}
_onParticipantHangupCallback(from);
}
////////////////////////////////////////////////
// HANDLERS
////////////////////////////////////////////////
// SUCCESS HANDLERS
function handleUserMedia(stream) {
console.log('Adding local stream');
_onLocalVideoCallback(stream);
_localStream = stream;
_defaultChannel.emit('message', {type:'newparticipant', from: _myID});
}
function changeHandleUserMedia(stream) {
_onLocalVideoCallback(stream);
_localStream = stream;
_defaultChannel.emit('message', {type:'change', from: _myID});
}
function handleRemoteStreamRemoved(event) {
console.log('Remote stream removed. Event: ', event);
}
function handleRemoteStreamAdded(from) {
return function(event) {
//console.log('Remote stream added');
addRemoteVideo(event.stream, from);
_remoteStream = event.stream;
}
}
function handleIceCandidateAnswerWrapper(channel, to) {
return function handleIceCandidate(event) {
console.log('handleIceCandidate event');
if (event.candidate) {
channel.emit('message',
{type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate,
from: _myID,
dest:to}
);
} else {
console.log('End of candidates.');
}
}
}
function setLocalDescriptionSuccess() {}
function setRemoteDescriptionSuccess() {}
function addIceCandidateSuccess() {}
function gotReceiveChannel(id) {
return function(event) {
console.log('Receive Channel Callback');
_sendChannel[id] = event.channel;
_sendChannel[id].onmessage = handleMessage;
_sendChannel[id].onopen = handleReceiveChannelStateChange(id);
_sendChannel[id].onclose = handleReceiveChannelStateChange(id);
}
}
function handleMessage(event) {
console.log('Received message: ' + event.data);
_onChatMessageCallback(event.data);
}
function handleSendChannelStateChange(participantId) {
return function() {
var readyState = _sendChannel[participantId].readyState;
console.log('Send channel state is: ' + readyState);
// check if we have at least one open channel before we set hat ready to false.
var open = checkIfOpenChannel();
enableMessageInterface(open);
}
}
function handleReceiveChannelStateChange(participantId) {
return function() {
var readyState = _sendChannel[participantId].readyState;
// check if we have at least one open channel before we set hat ready to false.
var open = checkIfOpenChannel();
enableMessageInterface(open);
}
}
function checkIfOpenChannel() {
var open = false;
for (var channel in _sendChannel) {
if (_sendChannel.hasOwnProperty(channel)) {
open = (_sendChannel[channel].readyState == "open");
if (open == true) {
break;
}
}
}
return open;
}
function enableMessageInterface(shouldEnable) {
if (shouldEnable) {
_onChatReadyCallback();
} else {
_onChatNotReadyCallback();
}
}
// ERROR HANDLERS
function handleCreateOfferError(event){
console.log('createOffer() error: ', event);
}
function handleCreateAnswerError(event){
console.log('createAnswer() error: ', event);
}
function handleUserMediaError(error){
console.log('getUserMedia error: ', error);
}
function setLocalDescriptionError(error) {
console.log('setLocalDescription error: ', error);
}
function setRemoteDescriptionError(error) {
console.log('setRemoteDescription error: ', error);
}
function addIceCandidateError(error) {}
////////////////////////////////////////////////
// CODEC
////////////////////////////////////////////////
// Set Opus as the default audio codec if it's present.
function preferOpus(sdp) {
var sdpLines = sdp.split('\r\n');
var mLineIndex;
// Search for m line.
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('m=audio') !== -1) {
mLineIndex = i;
break;
}
}
if (mLineIndex === null || mLineIndex === undefined) {
return sdp;
}
// If Opus is available, set it as the default in m line.
for (i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('opus/48000') !== -1) {
var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
if (opusPayload) {
sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload);
}
break;
}
}
// Remove CN in m line and sdp.
sdpLines = removeCN(sdpLines, mLineIndex);
sdp = sdpLines.join('\r\n');
return sdp;
}
function extractSdp(sdpLine, pattern) {
var result = sdpLine.match(pattern);
return result && result.length === 2 ? result[1] : null;
}
// Set the selected codec to the first in m line.
function setDefaultCodec(mLine, payload) {
var elements = mLine.split(' ');
var newLine = [];
var index = 0;
for (var i = 0; i < elements.length; i++) {
if (index === 3) { // Format of media starts from the fourth.
newLine[index++] = payload; // Put target payload to the first.
}
if (elements[i] !== payload) {
newLine[index++] = elements[i];
}
}
return newLine.join(' ');
}
// Strip CN from sdp before CN constraints is ready.
function removeCN(sdpLines, mLineIndex) {
var mLineElements = sdpLines[mLineIndex].split(' ');
// Scan from end for the convenience of removing an item.
for (var i = sdpLines.length-1; i >= 0; i--) {
var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);
if (payload) {
var cnPos = mLineElements.indexOf(payload);
if (cnPos !== -1) {
// Remove CN payload from m line.
mLineElements.splice(cnPos, 1);
}
// Remove CN line in sdp
sdpLines.splice(i, 1);
}
}
sdpLines[mLineIndex] = mLineElements.join(' ');
return sdpLines;
}
////////////////////////////////////////////////
// EXPORT PUBLIC FUNCTIONS
////////////////////////////////////////////////
exports.joinRoom = joinRoom;
exports.toggleMic = toggleMic;
exports.toggleVideo = toggleVideo;
exports.onLocalVideo = onLocalVideo;
exports.onRemoteVideo = onRemoteVideo;
exports.onChatReady = onChatReady;
exports.onChatNotReady = onChatNotReady;
exports.onChatMessage = onChatMessage;
exports.sendChatMessage = sendChatMessage;
exports.onParticipantHangup = onParticipantHangup;
exports.changeHandleUserMedia = changeHandleUserMedia;
exports.logout = logout;
exports.opc = _opc;
exports.apc = _apc;
return exports;
};
我从这里提供我的链接,而且效果很好。你能举例说明我如何做到这一点吗?
$( document ).ready(function() {
/////////////////////////////////
// CREATE MEETING
/////////////////////////////////
meeting = new Meeting(host);
meeting.onLocalVideo(function(stream) {
//alert(stream.getVideoTracks().length);
document.querySelector('#localVideo').srcObject = stream;
$("#micMenu").on("click",function callback(e) {
$(this).toggleText("mic_off", "mic");
meeting.toggleMic();
});
$("#videoMenu").on("click",function callback(e) {
$(this).toggleText("videocam_off", "videocam");
meeting.toggleVideo();
});
$("#speakerMenu").on("click", function callback(e) {
$(this).toggleText("volume_off", "volume_up");
$("#localVideo").prop('muted', true);
});
$('#chatMenu').on('click', function callback(e) {
$(this).toggleText('speaker_notes_off', 'chat');
});
$('#close').on('click', function callback(e) {
meeting.logout($('.videoWrap').eq(1).attr('id'));
});
$(document).on('change', '#videoInput', function callback(e) {
var mediaParams = {
video: {mandatory: {sourceId: $(this).val()}}
};
navigator.mediaDevices.getUserMedia(mediaParams)
.then(function(stream){
meeting.handleUserMedia(stream);
})
.catch(function(e) { });
});
}
);
meeting.onRemoteVideo(function(stream, participantID) {
addRemoteVideo(stream, participantID);
}
);
meeting.onParticipantHangup(function(participantID) {
// Someone just left the meeting. Remove the participants video
removeRemoteVideo(participantID);
}
);
meeting.onChatReady(function() {
console.log("Chat is ready");
}
);
meeting.onChatNotReady(function() {
console.log("Chat is not ready");
}
);
var room = window.location.pathname.match(/([^\/]*)\/*$/)[1];
meeting.joinRoom(room);
}); // end of document.ready
显然我正在更改本地视频。但我无法为其他用户更改它。
最佳答案
在我的实践中,本地和远程视频都可以查看,我的代码如下
包.json:
{
"name": "peer to peer",
"version": "1.0.0",
"description": "point to point by webrtc & websocket",
"main": "index.js",
"scripts": {
"dev": "node index.js"
},
"author": "webrtc",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"express-ws": "^4.0.0"
}
}
索引.js:
const app = require('express')();
const wsInstance = require('express-ws')(app);
app.ws('/', ws => {
ws.on('message', data => {
// post cast
wsInstance.getWss().clients.forEach(server => {
if (server !== ws) {
server.send(data);
}
});
});
});
app.get('/', (req, res) => {
res.sendFile('./client/index.html', { root: __dirname });
});
app.get('/p2p', (req, res) => {
res.sendFile('./client/p2p.html', { root: __dirname });
});
app.listen(8091);
索引.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>p2p webrtc</title>
<style>
.container {
width: 250px;
margin: 100px auto;
padding: 10px 30px;
border-radius: 4px;
border: 1px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
color: #303133;
}
</style>
</head>
<body>
<div class="container">
<p>seheme:</p>
<ul>
<li>open<a href="/p2p?type=answer" target="_blank">answer</a>;</li>
<li>open<a href="/p2p?type=offer" target="_blank">offer</a>;</li>
<li> comfirm connected ws ;</li>
<li> offer send 'start' button;</li>
</ul>
</div>
</body>
</html>
p2p.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.container {
width: 100%;
display: flex;
display: -webkit-flex;
justify-content: space-around;
padding-top: 20px;
}
.video-box {
position: relative;
width: 800px;
height: 400px;
}
#remote-video {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
border: 1px solid #eee;
background-color: #F2F6FC;
}
#local-video {
position: absolute;
right: 0;
bottom: 0;
width: 240px;
height: 120px;
object-fit: cover;
border: 1px solid #eee;
background-color: #EBEEF5;
}
.start-button {
position: absolute;
left: 50%;
top: 50%;
width: 100px;
display: none;
line-height: 40px;
outline: none;
color: #fff;
background-color: #409eff;
border: none;
border-radius: 4px;
cursor: pointer;
transform: translate(-50%, -50%);
}
.logger {
width: 40%;
padding: 14px;
line-height: 1.5;
color: #4fbf40;
border-radius: 6px;
background-color: #272727;
}
.logger .error {
color: #DD4A68;
}
</style>
</head>
<body>
<div class="container">
<div class="video-box">
<video id="remote-video"></video>
<video id="local-video" muted></video>
<button class="start-button" onclick="startLive()">start</button>
</div>
<div class="logger"></div>
</div>
<script>
const message = {
el: document.querySelector('.logger'),
log (msg) {
this.el.innerHTML += `<span>${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
},
error (msg) {
this.el.innerHTML += `<span class="error">${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
}
};
const target = location.search.slice(6);
const localVideo = document.querySelector('#local-video');
const remoteVideo = document.querySelector('#remote-video');
const button = document.querySelector('.start-button');
localVideo.onloadeddata = () => {
message.log('play local video');
localVideo.play();
}
remoteVideo.onloadeddata = () => {
message.log('play remote video');
remoteVideo.play();
}
document.title = target === 'offer' ? 'offer' : 'answer';
message.log('Multi-hole channel (WebSocket) is being created...');
const socket = new WebSocket('ws://localhost:8091');
socket.onopen = () => {
message.log('The signaling channel is successfully created!');
target === 'offer' && (button.style.display = 'block');
}
socket.onerror = () => message.error('Failed to create signaling channel!');
socket.onmessage = e => {
const { type, sdp, iceCandidate } = JSON.parse(e.data)
if (type === 'answer') {
peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
} else if (type === 'answer_ice') {
peer.addIceCandidate(iceCandidate);
} else if (type === 'offer') {
startLive(new RTCSessionDescription({ type, sdp }));
} else if (type === 'offer_ice') {
peer.addIceCandidate(iceCandidate);
}
};
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
!PeerConnection && message.error('The browser does not support WebRTC!');
const peer = new PeerConnection();
peer.ontrack = e => {
if (e && e.streams) {
message.log('Receive the other party's audio/video stream data...');
remoteVideo.srcObject = e.streams[0];
}
};
peer.onicecandidate = e => {
if (e.candidate) {
message.log('Collect and send candidates');
socket.send(JSON.stringify({
type: `${target}_ice`,
iceCandidate: e.candidate
}));
} else {
message.log('The candidate collection is complete!');
}
};
async function startLive (offerSdp) {
target === 'offer' && (button.style.display = 'none');
let stream;
try {
message.log('Try to call the local camera/microphone');
stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
message.log('The camera/microphone is successfully acquired!');
localVideo.srcObject = stream;
} catch {
message.error('Camera/microphone acquisition failed!');
return;
}
message.log(`------ WebRTC ${target === 'offer' ? 'offer' : 'answer'}seheme ------`);
message.log('Add a media track to the track set');
stream.getTracks().forEach(track => {
peer.addTrack(track, stream);
});
if (!offerSdp) {
message.log('Create a local SDP');
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);
message.log(`Transmission initiator local SDP`);
socket.send(JSON.stringify(offer));
} else {
message.log('SDP received from the sender');
await peer.setRemoteDescription(offerSdp);
message.log('Create receiver (answer) SDP');
const answer = await peer.createAnswer();
message.log(`Transmission receiver (response) SDP`);
socket.send(JSON.stringify(answer));
await peer.setLocalDescription(answer);
}
}
</script>
</body>
</html>
使用代码:
npm install //npm install dependency
npm run dev //open localhost:8091
希望能帮到你!
关于javascript - WEBRTC 带 socket 的开关量输入设备,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68906519/
运行 PostgreSQL(7.4 和 8.x),我认为这是可行的,但现在我遇到了错误。 我可以单独运行查询,它工作得很好,但如果我使用 UNION 或 UNION ALL,它会抛出错误。 这个错误:
我试图为我的应用程序创建一个导航,使用抽屉导航我的 fragment 之一(HomeFragment)有一个 ViewPager,可容纳 3 个 fragment (Bundy Clock、Annou
以我目前正在开发的应用为例: - 它有一个包含多个项目的抽屉导航;现在有两个项目让我感兴趣,我将它们称为 X 和 Y。 X 和 Y 都在单击时显示包含 x 元素或 y 元素列表的 fragment 选
我有一个形状为 (370,275,210) 的 NumPy 数组,我想将其重新整形为 (275,210,370)。我将如何在 Python 中实现这一点? 370是波段数,275是行数,210是图像包
我们如何与被子 UIViewController 阻止的父 UIViewController(具有按钮)交互。显然,触摸事件不会通过子 Nib 。 (启用用户交互) 注意:我正在加载默认和自定义 NI
我是 Jpa 新手,我想执行过程 我的代码如下 private static final String PERSISTENCE_UNIT_NAME = "todos"; private static
与安装了 LAMP 的 GCE 相比,选择与 Google Cloud SQL 链接的 GCE 实例有哪些优势? 我确定 GCE 是可扩展的,但是安装在其上的 mysql 数据库的可扩展性如何? 使用
这个问题在这里已经有了答案: Value receiver vs. pointer receiver (3 个答案) 关闭 3 年前。 我刚接触 golang。只是想了解为 Calc 类型声明的两种
我不小心按了一个快捷键,一个非常漂亮的断线出现在日期上。 有点像 # 23 Jun 2010 -------------------- 有人知道有问题的快捷方式吗?? (我在 mac 上工作!) 在
我正在Scala中编写正则表达式 val regex = "^foo.*$".r 这很好,但是如果我想做 var x = "foo" val regex = s"""^$x.*$""".r 现在我们有
以下 XML 文档在技术上是否相同? James Dean 19 和: James Dean 19 最佳答案 这两个文档在语义上是相同的。在 X
我在对数据帧列表运行稳健的线性回归模型(使用 MASS 库中的 rlm)时遇到问题。 可重现的示例: var1 <- c(1:100) var2 <- var1*var1 df1 <- data.f
好的,我有一个自定义数字键盘,可以在标签(numberField)中将数字显示为 0.00,现在我需要它显示 $0.00。 NSString *digit = sender.currentTitle;
在基于文档的应用程序中,使用 XIB 文件,创建新窗口时其行为是: 根据最后一个事件的位置进行定位和调整大小 window 。 如果最后一个事件窗口仍然可见,则新窗口 窗口应该是级联的,这样它就不会直
我想使用参数进行查询,如下所示: SELECT * FROM MATABLE WHERE MT_ID IN (368134, 181956) 所以我考虑一下 SELECT * FROM MATABLE
我遇到一些性能问题。 我有一个大约有 200 万行的表。 CREATE TABLE [dbo].[M8]( [M8_ID] [int] IDENTITY(1,1) NOT NULL,
我在 jquery 中的按键功能遇到问题。我不知道为什么按键功能不起作用。我已经使用了正确的 key 代码。在我的函数中有 2 个代码,其中包含 2 个事件键,按一个键表示 (+) 代码 107 和(
我想显示音频波形,我得到了此代码,它需要.raw音频输入并显示音频波形,但是当我放入.3gp,.mp3音频时,我得到白噪声,有人可以帮助我如何使其按需与.3gp一起使用使用.3gp音频运行它。 Inp
我无法让 stristr 函数返回真值,我相信这是因为我的搜索中有一个 $ 字符。 当我这样做时: var_dump($nopricecart); 完整的 $nopricecart 值是 $0 ,我得
如果我有这样的循环: for(int i=0;i O(n) 次。所以do some执行了O(n)次。如果做某事是线性时间,那么代码片段的复杂度是O(n^2)。 关于algorithm - 带 If 语
我是一名优秀的程序员,十分优秀!