feat: implement negotiation-needed handler on UnityRenderStreaming (#334)
* wip receive video sample * wip add sample receive video on unity * fix websocket signaling * implemet onanswer and onnegatiationneeded * use addtranceiver * fix offer setting * revet signaling data handle * fix review * avoid null on uicontroler * implement receive video viewer component * fix custom editor error * async mainthread about signaling invoke event * change signaling test to unitytest * fix get answer request time on httpsignaling * fix mediastream handle on receivevideoviewer
This commit is contained in:
Родитель
6f885746e4
Коммит
e3a610d7bc
|
@ -12,3 +12,36 @@
|
|||
<div id="player"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- <!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Unity Video Sender</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Send to Unity<br />
|
||||
<button type="button" id="startVideoButton">Start Video</button>
|
||||
<button type="button" id="setUpButton">Set Up</button>
|
||||
<button type="button" id="addTrackButton">AddTrack</button>
|
||||
<button type="button" id="hangUpButton">Hang Up</button>
|
||||
<div>
|
||||
<video id="local_video" autoplay muted=true style="width: 160px; height: 120px; border: 1px solid black;"></video>
|
||||
<video id="remote_video" autoplay style="width: 160px; height: 120px; border: 1px solid black;"></video>
|
||||
</div>
|
||||
<p>SDP to send:<br />
|
||||
<textarea id="text_for_send_sdp" rows="5" cols="60" readonly="readonly">SDP to send</textarea>
|
||||
</p>
|
||||
<p>SDP to receive:<br />
|
||||
<textarea id="text_for_receive_sdp" rows="5" cols="60"></textarea>
|
||||
</p>
|
||||
|
||||
<script type="text/javascript" src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||
<script src="https://unpkg.com/event-target@latest/min.js"></script>
|
||||
<script src="https://unpkg.com/resize-observer-polyfill@1.5.0/dist/ResizeObserver.global.js"></script>
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
|
||||
<script type="module" src="app2.js"></script>
|
||||
|
||||
</body>
|
||||
</html> -->
|
|
@ -0,0 +1,38 @@
|
|||
import { SendVideo } from "./sendvideo.js";
|
||||
|
||||
const localVideo = document.getElementById('local_video');
|
||||
const remoteVideo = document.getElementById('remote_video');
|
||||
//const textForSendSdp = document.getElementById('text_for_send_sdp');
|
||||
//const textToReceiveSdp = document.getElementById('text_for_receive_sdp');
|
||||
|
||||
let sendVideo = new SendVideo();
|
||||
|
||||
let startButton = document.getElementById('startVideoButton');
|
||||
startButton.addEventListener('click', startVideo);
|
||||
let setupButton = document.getElementById('setUpButton');
|
||||
setupButton.addEventListener('click', setUp);
|
||||
let addTrackButton = document.getElementById('addTrackButton');
|
||||
addTrackButton.addEventListener('click', addTrack);
|
||||
let hangUpButton = document.getElementById('hangUpButton');
|
||||
hangUpButton.addEventListener('click', hangUp);
|
||||
|
||||
|
||||
async function startVideo() {
|
||||
await sendVideo.startVideo(localVideo);
|
||||
}
|
||||
|
||||
async function setUp() {
|
||||
await sendVideo.setupConnection(remoteVideo);
|
||||
}
|
||||
|
||||
async function addTrack() {
|
||||
await sendVideo.addTrack();
|
||||
}
|
||||
|
||||
function hangUp() {
|
||||
sendVideo.hangUp();
|
||||
remoteVideo.pause();
|
||||
remoteVideo.srcObject = null;
|
||||
// textForSendSdp.value = '';
|
||||
// textToReceiveSdp.value = '';
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
import Signaling, { WebSocketSignaling } from "./signaling.js"
|
||||
|
||||
export class SendVideo {
|
||||
constructor() {
|
||||
const _this = this;
|
||||
this.config = SendVideo.getConfiguration();
|
||||
this.pc = null;
|
||||
this.localStream = null;
|
||||
this.remoteStram = new MediaStream();
|
||||
this.negotiationneededCounter = 0;
|
||||
this.isOffer = false;
|
||||
}
|
||||
|
||||
static getConfiguration() {
|
||||
let config = {};
|
||||
config.sdpSemantics = 'unified-plan';
|
||||
config.iceServers = [{ urls: ['stun:stun.l.google.com:19302'] }];
|
||||
return config;
|
||||
}
|
||||
|
||||
async startVideo(localVideo) {
|
||||
try {
|
||||
this.localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
|
||||
localVideo.srcObject = this.localStream;
|
||||
await localVideo.play();
|
||||
} catch (err) {
|
||||
console.error('mediaDevice.getUserMedia() error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async setupConnection(remoteVideo) {
|
||||
const _this = this;
|
||||
this.remoteVideo = remoteVideo;
|
||||
this.remoteVideo.srcObject = this.remoteStram;
|
||||
|
||||
this.remoteStram.onaddtrack = async (e) => await _this.remoteVideo.play();
|
||||
|
||||
const protocolEndPoint = location.protocol + '//' + location.host + location.pathname + 'protocol';
|
||||
const createResponse = await fetch(protocolEndPoint);
|
||||
const res = await createResponse.json();
|
||||
|
||||
if (res.useWebSocket) {
|
||||
this.signaling = new WebSocketSignaling();
|
||||
} else {
|
||||
this.signaling = new Signaling();
|
||||
}
|
||||
|
||||
this.signaling.addEventListener('offer', async (e) => {
|
||||
console.error(e);
|
||||
if (_this.pc) {
|
||||
console.error('peerConnection alreay exist');
|
||||
}
|
||||
_this.prepareNewPeerConnection(false);
|
||||
const offer = e.detail;
|
||||
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
||||
await _this.pc.setRemoteDescription(desc);
|
||||
let answer = await _this.pc.createAnswer();
|
||||
await _this.pc.setLocalDescription(answer);
|
||||
_this.signaling.sendAnswer(answer.sdp);
|
||||
});
|
||||
|
||||
this.signaling.addEventListener('answer', async (e) => {
|
||||
if (!_this.pc) {
|
||||
console.error('peerConnection NOT exist!');
|
||||
return;
|
||||
}
|
||||
const answer = e.detail;
|
||||
const desc = new RTCSessionDescription({ sdp: answer.sdp, type: "answer" });
|
||||
await _this.pc.setRemoteDescription(desc);
|
||||
});
|
||||
|
||||
this.signaling.addEventListener('candidate', async (e) => {
|
||||
const candidate = e.detail;
|
||||
const iceCandidate = new RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex });
|
||||
_this.pc.addIceCandidate(iceCandidate);
|
||||
});
|
||||
|
||||
await this.signaling.start();
|
||||
this.prepareNewPeerConnection(true);
|
||||
}
|
||||
|
||||
async addTrack() {
|
||||
const _this = this;
|
||||
this.localStream.getTracks().forEach(track => _this.pc.addTrack(track, _this.localStream));
|
||||
}
|
||||
|
||||
prepareNewPeerConnection(isOffer) {
|
||||
const _this = this;
|
||||
this.isOffer = isOffer;
|
||||
// close current RTCPeerConnection
|
||||
if (this.pc) {
|
||||
console.log('Close current PeerConnection');
|
||||
this.pc.close();
|
||||
this.pc = null;
|
||||
}
|
||||
|
||||
// Create peerConnection with proxy server and set up handlers
|
||||
this.pc = new RTCPeerConnection(this.config);
|
||||
|
||||
this.pc.onsignalingstatechange = e => {
|
||||
console.log('signalingState changed:', e);
|
||||
};
|
||||
|
||||
this.pc.oniceconnectionstatechange = e => {
|
||||
console.log('iceConnectionState changed:', e);
|
||||
console.log('pc.iceConnectionState:' + _this.pc.iceConnectionState);
|
||||
if (_this.pc.iceConnectionState === 'disconnected') {
|
||||
_this.hangUp();
|
||||
}
|
||||
};
|
||||
|
||||
this.pc.onicegatheringstatechange = e => {
|
||||
console.log('iceGatheringState changed:', e);
|
||||
};
|
||||
|
||||
this.pc.ontrack = async (e) => {
|
||||
_this.remoteStram.addTrack(e.track);
|
||||
};
|
||||
|
||||
this.pc.onicecandidate = e => {
|
||||
if (e.candidate != null) {
|
||||
_this.signaling.sendCandidate(e.candidate.candidate, e.candidate.sdpMid, e.candidate.sdpMLineIndex);
|
||||
}
|
||||
};
|
||||
|
||||
this.pc.onnegotiationneeded = async () => {
|
||||
if (_this.isOffer) {
|
||||
if (_this.negotiationneededCounter === 0) {
|
||||
let offer = await _this.pc.createOffer();
|
||||
console.log('createOffer() succsess in promise');
|
||||
await _this.pc.setLocalDescription(offer);
|
||||
console.log('setLocalDescription() succsess in promise');
|
||||
_this.signaling.sendOffer(offer.sdp);
|
||||
_this.negotiationneededCounter++;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
hangUp() {
|
||||
if (this.pc) {
|
||||
if (this.pc.iceConnectionState !== 'closed') {
|
||||
this.pc.close();
|
||||
this.pc = null;
|
||||
negotiationneededCounter = 0;
|
||||
console.log('sending close message');
|
||||
this.signaling.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log('peerConnection is closed.');
|
||||
}
|
||||
}
|
|
@ -28,10 +28,29 @@ export default class Signaling extends EventTarget {
|
|||
const connection = await res.json();
|
||||
this.connectionId = connection.connectionId;
|
||||
|
||||
this.loopGetOffer();
|
||||
this.loopGetAnswer();
|
||||
this.loopGetCandidate();
|
||||
}
|
||||
|
||||
async loopGetOffer() {
|
||||
let lastTimeRequest = Date.now() - 30000;
|
||||
|
||||
while (true) {
|
||||
const res = await this.getOffer(lastTimeRequest);
|
||||
lastTimeRequest = Date.parse(res.headers.get('Date'));
|
||||
|
||||
const data = await res.json();
|
||||
const offers = data.offers;
|
||||
|
||||
offers.forEach(offer => {
|
||||
this.dispatchEvent(new CustomEvent('offer', { detail: offer }));
|
||||
});
|
||||
|
||||
await this.sleep(this.interval);
|
||||
}
|
||||
}
|
||||
|
||||
async loopGetAnswer() {
|
||||
// receive answer message from 30secs ago
|
||||
let lastTimeRequest = Date.now() - 30000;
|
||||
|
|
|
@ -103,10 +103,15 @@ export default class WSSignaling {
|
|||
const newOffer = new Offer(message.sdp, Date.now());
|
||||
offers.set(connectionId, newOffer);
|
||||
connectionPair.set(connectionId, [ws, null]);
|
||||
clients.forEach((_v, k) => k.send(JSON.stringify({from:connectionId, to:"", type:"offer", data:newOffer})));
|
||||
clients.forEach((_v, k) => {
|
||||
if (k == ws) {
|
||||
return;
|
||||
}
|
||||
k.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
|
||||
});
|
||||
}
|
||||
|
||||
private onAnswer(ws: WebSocket, message: any){
|
||||
private onAnswer(ws: WebSocket, message: any) {
|
||||
const connectionId = message.connectionId as string;
|
||||
const connectionIds = getOrCreateConnectionIds(ws);
|
||||
connectionIds.add(connectionId);
|
||||
|
@ -114,17 +119,22 @@ export default class WSSignaling {
|
|||
answers.set(connectionId, newAnswer);
|
||||
|
||||
const pair = connectionPair.get(connectionId);
|
||||
const otherSessionId = pair[0];
|
||||
connectionPair.set(connectionId, [otherSessionId, ws]);
|
||||
const otherSessionWs = pair[0];
|
||||
connectionPair.set(connectionId, [otherSessionWs, ws]);
|
||||
|
||||
const mapCandidates = candidates.get(otherSessionId);
|
||||
const mapCandidates = candidates.get(otherSessionWs);
|
||||
if (mapCandidates) {
|
||||
const arrayCandidates = mapCandidates.get(connectionId);
|
||||
for (const candidate of arrayCandidates) {
|
||||
candidate.datetime = Date.now();
|
||||
}
|
||||
}
|
||||
clients.forEach((_v, k) => k.send(JSON.stringify({from:connectionId, to:"", type:"answer", data:newAnswer})));
|
||||
clients.forEach((_v, k) => {
|
||||
if (k == ws) {
|
||||
return;
|
||||
}
|
||||
k.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer }))
|
||||
});
|
||||
}
|
||||
|
||||
private onCandidate(ws: WebSocket, message: any){
|
||||
|
@ -142,7 +152,7 @@ export default class WSSignaling {
|
|||
arr.push(candidate);
|
||||
|
||||
clients.forEach((_v, k) => {
|
||||
if(k === ws){
|
||||
if (k === ws) {
|
||||
return;
|
||||
}
|
||||
k.send(JSON.stringify({from:connectionId, to:"", type:"candidate", data:candidate}));
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e62a4b241050ded4ab2cd9f240c462c6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using Unity.WebRTC;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Unity.RenderStreaming
|
||||
{
|
||||
public class ReceiveVideoViewer : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Button sendOfferButton;
|
||||
[SerializeField] private RawImage receiveImage;
|
||||
|
||||
private MediaStream m_receiveStream;
|
||||
|
||||
void Start()
|
||||
{
|
||||
sendOfferButton.onClick.AddListener(() => RenderStreaming.Instance?.AddTransceiver());
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_receiveStream = new MediaStream();
|
||||
RenderStreaming.Instance?.AddVideoReceiveStream(m_receiveStream);
|
||||
// ToDo: need update webrtc package to 2.2
|
||||
// m_receiveStream.OnAddTrack = e =>
|
||||
// {
|
||||
// if (receiveImage != null && e.Track.Kind == TrackKind.Video)
|
||||
// {
|
||||
// var videoTrack = (VideoStreamTrack)e.Track;
|
||||
// receiveImage.texture = videoTrack.InitializeReceiver();
|
||||
// }
|
||||
// };
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
RenderStreaming.Instance?.RemoveVideoReceiveStream(m_receiveStream);
|
||||
m_receiveStream.OnAddTrack = null;
|
||||
m_receiveStream.Dispose();
|
||||
m_receiveStream = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f1e06fd32ea04af9b843af232d7e9db2
|
||||
timeCreated: 1600837296
|
|
@ -2,9 +2,9 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using Unity.WebRTC;
|
||||
using System.Text.RegularExpressions;
|
||||
using Unity.RenderStreaming.Signaling;
|
||||
using UnityEngine.InputSystem.EnhancedTouch;
|
||||
|
||||
|
@ -49,8 +49,10 @@ namespace Unity.RenderStreaming
|
|||
|
||||
[SerializeField, Tooltip("Array to set your own click event")]
|
||||
private ButtonClickElement[] arrayButtonClickEvent;
|
||||
|
||||
#pragma warning restore 0649
|
||||
|
||||
private SynchronizationContext m_mainThreadContext;
|
||||
private ISignaling m_signaling;
|
||||
private readonly Dictionary<string, RTCPeerConnection> m_mapConnectionIdAndPeer = new Dictionary<string, RTCPeerConnection>();
|
||||
private readonly Dictionary<RTCPeerConnection, DataChannelDictionary> m_mapPeerAndChannelDictionary = new Dictionary<RTCPeerConnection, DataChannelDictionary>();
|
||||
|
@ -59,9 +61,11 @@ namespace Unity.RenderStreaming
|
|||
private readonly List<SimpleCameraController> m_listController = new List<SimpleCameraController>();
|
||||
private readonly List<VideoStreamTrack> m_listVideoStreamTrack = new List<VideoStreamTrack>();
|
||||
private readonly Dictionary<MediaStreamTrack, List<RTCRtpSender>> m_mapTrackAndSenderList = new Dictionary<MediaStreamTrack, List<RTCRtpSender>>();
|
||||
private readonly List<MediaStream> m_listVideoReceiveStream = new List<MediaStream>();
|
||||
private MediaStream m_audioStream;
|
||||
private DefaultInput m_defaultInput;
|
||||
private RTCConfiguration m_conf;
|
||||
private string m_connectionId;
|
||||
|
||||
public static RenderStreaming Instance { get; private set; }
|
||||
|
||||
|
@ -78,6 +82,7 @@ namespace Unity.RenderStreaming
|
|||
WebRTC.WebRTC.Initialize(encoderType);
|
||||
m_defaultInput = new DefaultInput();
|
||||
EnhancedTouchSupport.Enable();
|
||||
m_mainThreadContext = SynchronizationContext.Current;
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
|
@ -87,11 +92,11 @@ namespace Unity.RenderStreaming
|
|||
WebRTC.WebRTC.Dispose();
|
||||
RemoteInputReceiver.Dispose();
|
||||
Unity.WebRTC.Audio.Stop();
|
||||
m_mainThreadContext = null;
|
||||
}
|
||||
public void Start()
|
||||
{
|
||||
m_audioStream = Unity.WebRTC.Audio.CaptureStream();
|
||||
|
||||
m_conf = default;
|
||||
m_conf.iceServers = iceServers;
|
||||
StartCoroutine(WebRTC.WebRTC.Update());
|
||||
|
@ -101,11 +106,17 @@ namespace Unity.RenderStreaming
|
|||
{
|
||||
if (this.m_signaling == null)
|
||||
{
|
||||
|
||||
Type t = Type.GetType(signalingType);
|
||||
object[] args = { urlSignaling, interval };
|
||||
object[] args = { urlSignaling, interval, m_mainThreadContext };
|
||||
this.m_signaling = (ISignaling)Activator.CreateInstance(t, args);
|
||||
this.m_signaling.OnOffer += OnOffer;
|
||||
this.m_signaling.OnStart += signaling => signaling.CreateConnection();
|
||||
this.m_signaling.OnCreateConnection += (signaling, id) =>
|
||||
{
|
||||
m_connectionId = id;
|
||||
CreatePeerConnection(signaling, m_connectionId, true);
|
||||
};
|
||||
this.m_signaling.OnOffer += (signaling, data) => StartCoroutine(OnOffer(signaling, data));
|
||||
this.m_signaling.OnAnswer += (signaling, data) => StartCoroutine(OnAnswer(signaling, data));
|
||||
this.m_signaling.OnIceCandidate += OnIceCandidate;
|
||||
}
|
||||
this.m_signaling.Start();
|
||||
|
@ -132,6 +143,28 @@ namespace Unity.RenderStreaming
|
|||
m_listVideoStreamTrack.Remove(track);
|
||||
}
|
||||
|
||||
public void AddVideoReceiveStream(MediaStream stream)
|
||||
{
|
||||
m_listVideoReceiveStream.Add(stream);
|
||||
}
|
||||
|
||||
public void RemoveVideoReceiveStream(MediaStream stream)
|
||||
{
|
||||
m_listVideoReceiveStream.Remove(stream);
|
||||
}
|
||||
|
||||
public void AddTransceiver()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_connectionId) ||
|
||||
!m_mapConnectionIdAndPeer.TryGetValue(m_connectionId, out var pc))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ToDo: need update webrtc package to 2.2
|
||||
// pc.AddTransceiver(TrackKind.Video);
|
||||
}
|
||||
|
||||
public void ChangeVideoParameters(VideoStreamTrack track, ulong? bitrate, uint? framerate)
|
||||
{
|
||||
foreach (var sender in m_mapTrackAndSenderList[track])
|
||||
|
@ -155,35 +188,30 @@ namespace Unity.RenderStreaming
|
|||
}
|
||||
}
|
||||
|
||||
void OnOffer(ISignaling signaling, DescData e)
|
||||
IEnumerator OnOffer(ISignaling signaling, DescData e)
|
||||
{
|
||||
RTCSessionDescription _desc;
|
||||
_desc.type = RTCSdpType.Offer;
|
||||
_desc.sdp = e.sdp;
|
||||
var connectionId = e.connectionId;
|
||||
if (m_mapConnectionIdAndPeer.ContainsKey(connectionId))
|
||||
{
|
||||
return;
|
||||
Debug.LogError($"connection:{connectionId} peerConnection already exist");
|
||||
yield break;
|
||||
}
|
||||
var pc = new RTCPeerConnection();
|
||||
m_mapConnectionIdAndPeer.Add(e.connectionId, pc);
|
||||
|
||||
pc.OnDataChannel = new DelegateOnDataChannel(channel => { OnDataChannel(pc, channel); });
|
||||
pc.SetConfiguration(ref m_conf);
|
||||
pc.OnIceCandidate = new DelegateOnIceCandidate(candidate =>
|
||||
{
|
||||
signaling.SendCandidate(e.connectionId, candidate);
|
||||
});
|
||||
pc.OnIceConnectionChange = new DelegateOnIceConnectionChange(state =>
|
||||
{
|
||||
if(state == RTCIceConnectionState.Disconnected)
|
||||
{
|
||||
pc.Close();
|
||||
m_mapConnectionIdAndPeer.Remove(e.connectionId);
|
||||
}
|
||||
});
|
||||
var pc = CreatePeerConnection(signaling, connectionId, false);
|
||||
|
||||
RTCSessionDescription _desc;
|
||||
_desc.type = RTCSdpType.Offer;
|
||||
_desc.sdp = e.sdp;
|
||||
|
||||
var opRemoteDesc = pc.SetRemoteDescription(ref _desc);
|
||||
yield return opRemoteDesc;
|
||||
|
||||
if (opRemoteDesc.IsError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {opRemoteDesc.Error.message}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
pc.SetRemoteDescription(ref _desc);
|
||||
foreach (var track in m_listVideoStreamTrack.Concat(m_audioStream.GetTracks()))
|
||||
{
|
||||
RTCRtpSender sender = pc.AddTrack(track);
|
||||
|
@ -197,29 +225,131 @@ namespace Unity.RenderStreaming
|
|||
|
||||
RTCAnswerOptions options = default;
|
||||
var op = pc.CreateAnswer(ref options);
|
||||
while (op.MoveNext())
|
||||
{
|
||||
}
|
||||
yield return op;
|
||||
|
||||
if (op.IsError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {op.Error}");
|
||||
return;
|
||||
Debug.LogError($"Network Error: {op.Error.message}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
var desc = op.Desc;
|
||||
var opLocalDesc = pc.SetLocalDescription(ref desc);
|
||||
while (opLocalDesc.MoveNext())
|
||||
{
|
||||
}
|
||||
yield return opLocalDesc;
|
||||
|
||||
if (opLocalDesc.IsError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {opLocalDesc.Error}");
|
||||
return;
|
||||
Debug.LogError($"Network Error: {opLocalDesc.Error.message}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
signaling.SendAnswer(connectionId, desc);
|
||||
}
|
||||
|
||||
RTCPeerConnection CreatePeerConnection(ISignaling signaling, string connectionId, bool isOffer)
|
||||
{
|
||||
if (m_mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer))
|
||||
{
|
||||
peer.Close();
|
||||
}
|
||||
|
||||
var pc = new RTCPeerConnection();
|
||||
m_mapConnectionIdAndPeer[connectionId] = pc;
|
||||
|
||||
pc.OnDataChannel = new DelegateOnDataChannel(channel => { OnDataChannel(pc, channel); });
|
||||
pc.SetConfiguration(ref m_conf);
|
||||
pc.OnIceCandidate = new DelegateOnIceCandidate(candidate =>
|
||||
{
|
||||
signaling.SendCandidate(connectionId, candidate);
|
||||
});
|
||||
pc.OnIceConnectionChange = new DelegateOnIceConnectionChange(state =>
|
||||
{
|
||||
if(state == RTCIceConnectionState.Disconnected)
|
||||
{
|
||||
pc.Close();
|
||||
m_mapConnectionIdAndPeer.Remove(connectionId);
|
||||
}
|
||||
});
|
||||
|
||||
pc.OnTrack = trackEvent =>
|
||||
{
|
||||
foreach (var receiveStream in m_listVideoReceiveStream)
|
||||
{
|
||||
receiveStream.AddTrack(trackEvent.Track);
|
||||
}
|
||||
};
|
||||
|
||||
pc.OnNegotiationNeeded = () => StartCoroutine(OnNegotiationNeeded(signaling, connectionId, isOffer));
|
||||
return pc;
|
||||
}
|
||||
|
||||
IEnumerator OnNegotiationNeeded(ISignaling signaling, string connectionId, bool isOffer)
|
||||
{
|
||||
if (!isOffer)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!m_mapConnectionIdAndPeer.TryGetValue(connectionId, out var pc))
|
||||
{
|
||||
Debug.LogError($"connectionId: {connectionId}, did not created peerConnection");
|
||||
yield break;
|
||||
}
|
||||
|
||||
RTCOfferOptions option = new RTCOfferOptions
|
||||
{
|
||||
offerToReceiveAudio = true,
|
||||
offerToReceiveVideo = true
|
||||
};
|
||||
var offerOp = pc.CreateOffer(ref option);
|
||||
yield return offerOp;
|
||||
|
||||
if (offerOp.IsError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {offerOp.Error.message}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (pc.SignalingState != RTCSignalingState.Stable)
|
||||
{
|
||||
Debug.LogError($"peerConnection's signaling state is not stable.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
var desc = offerOp.Desc;
|
||||
var setLocalSdp = pc.SetLocalDescription(ref desc);
|
||||
yield return setLocalSdp;
|
||||
|
||||
if (setLocalSdp.IsError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {setLocalSdp.Error.message}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
signaling.SendOffer(connectionId, desc);
|
||||
}
|
||||
|
||||
IEnumerator OnAnswer(ISignaling signaling, DescData e)
|
||||
{
|
||||
if (!m_mapConnectionIdAndPeer.TryGetValue(e.connectionId, out var pc))
|
||||
{
|
||||
Debug.Log($"connectiondId:{e.connectionId}, peerConnection not exist");
|
||||
yield break;
|
||||
}
|
||||
|
||||
var desc = new RTCSessionDescription();
|
||||
desc.type = RTCSdpType.Answer;
|
||||
desc.sdp = e.sdp;
|
||||
var opRemoteSdp = pc.SetRemoteDescription(ref desc);
|
||||
yield return opRemoteSdp;
|
||||
|
||||
if (opRemoteSdp.IsError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {opRemoteSdp.Error.message}");
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnIceCandidate(ISignaling signaling, CandidateData e)
|
||||
{
|
||||
if (!m_mapConnectionIdAndPeer.TryGetValue(e.connectionId, out var pc))
|
||||
|
@ -227,7 +357,7 @@ namespace Unity.RenderStreaming
|
|||
return;
|
||||
}
|
||||
|
||||
RTCIceCandidate _candidate = default;
|
||||
RTCIceCandidate _candidate = default;
|
||||
_candidate.candidate = e.candidate;
|
||||
_candidate.sdpMLineIndex = e.sdpMLineIndex;
|
||||
_candidate.sdpMid = e.sdpMid;
|
||||
|
|
|
@ -28,15 +28,17 @@ namespace Unity.RenderStreaming.Signaling
|
|||
{
|
||||
private float m_timeout;
|
||||
private bool m_running;
|
||||
private SynchronizationContext m_mainThreadContext;
|
||||
private Thread m_signalingThread;
|
||||
private AutoResetEvent m_wsCloseEvent;
|
||||
private WebSocket m_webSocket;
|
||||
|
||||
public delegate void OnSignedInHandler(ISignaling sender);
|
||||
|
||||
public FurioosSignaling(string url, float timeout)
|
||||
public FurioosSignaling(string url, float timeout, SynchronizationContext mainThreadContext)
|
||||
{
|
||||
m_timeout = timeout;
|
||||
m_mainThreadContext = mainThreadContext;
|
||||
m_wsCloseEvent = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
|
@ -196,7 +198,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
offer.connectionId = routedMessage.from;
|
||||
offer.sdp = msg.sdp;
|
||||
|
||||
OnOffer?.Invoke(this, offer);
|
||||
m_mainThreadContext.Post(d => OnOffer?.Invoke(this, offer), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -214,7 +216,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
candidate.sdpMLineIndex = msg.sdpMLineIndex;
|
||||
candidate.sdpMid = msg.sdpMid;
|
||||
|
||||
OnIceCandidate?.Invoke(this, candidate);
|
||||
m_mainThreadContext.Post(d => OnIceCandidate?.Invoke(this, candidate), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -11,18 +11,21 @@ namespace Unity.RenderStreaming.Signaling
|
|||
{
|
||||
private string m_url;
|
||||
private float m_timeout;
|
||||
private SynchronizationContext m_mainThreadContext;
|
||||
private bool m_running;
|
||||
private Thread m_signalingThread;
|
||||
|
||||
private string m_sessionId;
|
||||
private long m_lastTimeGetOfferRequest;
|
||||
private long m_lastTimeGetAnswerRequest;
|
||||
private long m_lastTimeGetCandidateRequest;
|
||||
|
||||
|
||||
public HttpSignaling(string url, float timeout)
|
||||
public HttpSignaling(string url, float timeout, SynchronizationContext mainThreadContext)
|
||||
{
|
||||
m_url = url;
|
||||
m_timeout = timeout;
|
||||
m_mainThreadContext = mainThreadContext;
|
||||
|
||||
if (m_url.StartsWith("https"))
|
||||
{
|
||||
|
@ -93,6 +96,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
{
|
||||
// ignore messages arrived before 30 secs ago
|
||||
m_lastTimeGetOfferRequest = DateTime.UtcNow.Millisecond - 30000;
|
||||
m_lastTimeGetAnswerRequest = DateTime.UtcNow.Millisecond - 30000;
|
||||
m_lastTimeGetCandidateRequest = DateTime.UtcNow.Millisecond - 30000;
|
||||
|
||||
|
||||
|
@ -201,7 +205,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
m_sessionId = resp.sessionId;
|
||||
Debug.Log("Signaling: HTTP connected, sessionId : " + m_sessionId);
|
||||
|
||||
OnStart?.Invoke(this);
|
||||
m_mainThreadContext.Post(d => OnStart?.Invoke(this), null);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -261,8 +265,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
m_lastTimeGetOfferRequest = DateTimeExtension.ParseHttpDate(response.Headers[HttpResponseHeader.Date])
|
||||
.ToJsMilliseconds();
|
||||
|
||||
OnCreateConnection?.Invoke(this, data.connectionId);
|
||||
|
||||
m_mainThreadContext.Post(d => OnCreateConnection?.Invoke(this, data.connectionId), null);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -285,7 +288,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
|
||||
foreach (var offer in list.offers)
|
||||
{
|
||||
OnOffer?.Invoke(this, offer);
|
||||
m_mainThreadContext.Post(d => OnOffer?.Invoke(this, offer), null);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -294,7 +297,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
private bool HTTPGetAnswers()
|
||||
{
|
||||
HttpWebRequest request =
|
||||
(HttpWebRequest)WebRequest.Create($"{m_url}/signaling/answer?fromtime={m_lastTimeGetOfferRequest}");
|
||||
(HttpWebRequest)WebRequest.Create($"{m_url}/signaling/answer?fromtime={m_lastTimeGetAnswerRequest}");
|
||||
request.Method = "GET";
|
||||
request.ContentType = "application/json";
|
||||
request.Headers.Add("Session-Id", m_sessionId);
|
||||
|
@ -305,12 +308,12 @@ namespace Unity.RenderStreaming.Signaling
|
|||
|
||||
if (list == null) return false;
|
||||
|
||||
m_lastTimeGetOfferRequest = DateTimeExtension.ParseHttpDate(response.Headers[HttpResponseHeader.Date])
|
||||
m_lastTimeGetAnswerRequest = DateTimeExtension.ParseHttpDate(response.Headers[HttpResponseHeader.Date])
|
||||
.ToJsMilliseconds();
|
||||
|
||||
foreach (var answer in list.answers)
|
||||
{
|
||||
OnAnswer?.Invoke(this, answer);
|
||||
m_mainThreadContext.Post(d => OnAnswer?.Invoke(this, answer), null);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -337,7 +340,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
foreach (var candidate in candidateContainer.candidates)
|
||||
{
|
||||
candidate.connectionId = candidateContainer.connectionId;
|
||||
OnIceCandidate?.Invoke(this, candidate);
|
||||
m_mainThreadContext.Post(d => OnIceCandidate?.Invoke(this, candidate), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,6 @@ namespace Unity.RenderStreaming.Signaling
|
|||
void CreateConnection();
|
||||
void SendOffer(string connectionId, RTCSessionDescription answer);
|
||||
void SendAnswer(string connectionId, RTCSessionDescription answer);
|
||||
void SendCandidate(string connectionId, RTCIceCandidate candidate);
|
||||
void SendCandidate(string connectionId, RTCIceCandidate candidate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,15 +12,17 @@ namespace Unity.RenderStreaming.Signaling
|
|||
{
|
||||
private string m_url;
|
||||
private float m_timeout;
|
||||
private SynchronizationContext m_mainThreadContext;
|
||||
private bool m_running;
|
||||
private Thread m_signalingThread;
|
||||
private AutoResetEvent m_wsCloseEvent;
|
||||
private WebSocket m_webSocket;
|
||||
|
||||
public WebSocketSignaling(string url, float timeout)
|
||||
public WebSocketSignaling(string url, float timeout, SynchronizationContext mainThreadContext)
|
||||
{
|
||||
m_url = url;
|
||||
m_timeout = timeout;
|
||||
m_mainThreadContext = mainThreadContext;
|
||||
m_wsCloseEvent = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
|
@ -74,7 +76,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
data.type = "answer";
|
||||
|
||||
RoutedMessage<DescData> routedMessage = new RoutedMessage<DescData>();
|
||||
routedMessage.to = connectionId;
|
||||
routedMessage.from = connectionId;
|
||||
routedMessage.data = data;
|
||||
routedMessage.type = "answer";
|
||||
|
||||
|
@ -90,7 +92,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
data.sdpMid = candidate.sdpMid;
|
||||
|
||||
RoutedMessage<CandidateData> routedMessage = new RoutedMessage<CandidateData>();
|
||||
routedMessage.to = connectionId;
|
||||
routedMessage.from = connectionId;
|
||||
routedMessage.data = data;
|
||||
routedMessage.type = "candidate";
|
||||
|
||||
|
@ -160,56 +162,34 @@ namespace Unity.RenderStreaming.Signaling
|
|||
if (routedMessage.type == "connect")
|
||||
{
|
||||
string connectionId = JsonUtility.FromJson<SignalingMessage>(content).connectionId;
|
||||
OnCreateConnection?.Invoke(this, connectionId);
|
||||
m_mainThreadContext.Post(d => OnCreateConnection?.Invoke(this, connectionId), null);
|
||||
}
|
||||
else if (routedMessage.type == "offer")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(routedMessage.from))
|
||||
{
|
||||
DescData offer = new DescData();
|
||||
offer.connectionId = routedMessage.from;
|
||||
offer.sdp = msg.sdp;
|
||||
|
||||
OnOffer?.Invoke(this, offer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Signaling: Received message from unknown peer");
|
||||
}
|
||||
DescData offer = new DescData();
|
||||
offer.connectionId = routedMessage.from;
|
||||
offer.sdp = msg.sdp;
|
||||
m_mainThreadContext.Post(d => OnOffer?.Invoke(this, offer), null);
|
||||
}
|
||||
else if (routedMessage.type == "answer")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(routedMessage.from))
|
||||
DescData answer = new DescData
|
||||
{
|
||||
DescData answer = new DescData
|
||||
{
|
||||
connectionId = routedMessage.from,
|
||||
sdp = msg.sdp
|
||||
};
|
||||
OnAnswer?.Invoke(this, answer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Signaling: Received message from unknown peer");
|
||||
}
|
||||
connectionId = routedMessage.from,
|
||||
sdp = msg.sdp
|
||||
};
|
||||
m_mainThreadContext.Post(d => OnAnswer?.Invoke(this, answer), null);
|
||||
}
|
||||
else if (routedMessage.type == "candidate")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(routedMessage.from))
|
||||
CandidateData candidate = new CandidateData
|
||||
{
|
||||
CandidateData candidate = new CandidateData
|
||||
{
|
||||
connectionId = routedMessage.@from,
|
||||
candidate = msg.candidate,
|
||||
sdpMLineIndex = msg.sdpMLineIndex,
|
||||
sdpMid = msg.sdpMid
|
||||
};
|
||||
OnIceCandidate?.Invoke(this, candidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Signaling: Received message from unknown peer");
|
||||
}
|
||||
connectionId = routedMessage.@from,
|
||||
candidate = msg.candidate,
|
||||
sdpMLineIndex = msg.sdpMLineIndex,
|
||||
sdpMid = msg.sdpMid
|
||||
};
|
||||
m_mainThreadContext.Post(d => OnIceCandidate?.Invoke(this, candidate), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +202,7 @@ namespace Unity.RenderStreaming.Signaling
|
|||
private void WSConnected(object sender, EventArgs e)
|
||||
{
|
||||
Debug.Log("Signaling: WS connected.");
|
||||
OnStart?.Invoke(this);
|
||||
m_mainThreadContext.Post(d => OnStart?.Invoke(this), null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -37,8 +37,9 @@ namespace Unity.RenderStreaming
|
|||
private Process m_ServerProcess;
|
||||
private RTCSessionDescription m_DescOffer;
|
||||
private RTCSessionDescription m_DescAnswer;
|
||||
private RTCIceCandidate m_candidate;
|
||||
private RTCIceCandidate m_candidate;
|
||||
|
||||
private SynchronizationContext m_Context;
|
||||
private ISignaling signaling1;
|
||||
private ISignaling signaling2;
|
||||
|
||||
|
@ -104,15 +105,15 @@ namespace Unity.RenderStreaming
|
|||
m_ServerProcess.Kill();
|
||||
}
|
||||
|
||||
ISignaling CreateSignaling(Type type)
|
||||
ISignaling CreateSignaling(Type type, SynchronizationContext mainThread)
|
||||
{
|
||||
if (type == typeof(WebSocketSignaling))
|
||||
{
|
||||
return new WebSocketSignaling("ws://localhost", 0.1f);
|
||||
return new WebSocketSignaling("ws://localhost", 0.1f, mainThread);
|
||||
}
|
||||
if (type == typeof(HttpSignaling))
|
||||
{
|
||||
return new HttpSignaling("http://localhost", 0.1f);
|
||||
return new HttpSignaling("http://localhost", 0.1f, mainThread);
|
||||
}
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
@ -123,7 +124,7 @@ namespace Unity.RenderStreaming
|
|||
WebRTC.WebRTC.Initialize();
|
||||
|
||||
RTCConfiguration config = default;
|
||||
RTCIceCandidate? candidate_ = null;
|
||||
RTCIceCandidate? candidate_ = null;
|
||||
config.iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } };
|
||||
|
||||
var peer1 = new RTCPeerConnection(ref config);
|
||||
|
@ -158,8 +159,9 @@ namespace Unity.RenderStreaming
|
|||
peer1.Close();
|
||||
peer2.Close();
|
||||
|
||||
signaling1 = CreateSignaling(m_SignalingType);
|
||||
signaling2 = CreateSignaling(m_SignalingType);
|
||||
m_Context = SynchronizationContext.Current;
|
||||
signaling1 = CreateSignaling(m_SignalingType, m_Context);
|
||||
signaling2 = CreateSignaling(m_SignalingType, m_Context);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
|
@ -169,50 +171,51 @@ namespace Unity.RenderStreaming
|
|||
|
||||
signaling1.Stop();
|
||||
signaling2.Stop();
|
||||
m_Context = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnConnect()
|
||||
[UnityTest]
|
||||
public IEnumerator OnConnect()
|
||||
{
|
||||
bool startRaised1 = false;
|
||||
string connectionId1 = null;
|
||||
|
||||
signaling1.Start();
|
||||
signaling1.OnStart += s => { startRaised1 = true; };
|
||||
Assert.True(Wait(() => startRaised1));
|
||||
signaling1.Start();
|
||||
yield return new WaitUntil(() => startRaised1);
|
||||
|
||||
signaling1.OnCreateConnection += (s, connectionId) => { connectionId1 = connectionId; };
|
||||
signaling1.CreateConnection();
|
||||
Assert.True(Wait(() => !string.IsNullOrEmpty(connectionId1)));
|
||||
yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1));
|
||||
Assert.IsNotEmpty(connectionId1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnOffer()
|
||||
[UnityTest]
|
||||
public IEnumerator OnOffer()
|
||||
{
|
||||
bool startRaised1 = false;
|
||||
bool startRaised2 = false;
|
||||
bool offerRaised = false;
|
||||
string connectionId1 = null;
|
||||
|
||||
signaling1.Start();
|
||||
signaling2.Start();
|
||||
signaling1.OnStart += s => { startRaised1 = true; };
|
||||
signaling2.OnStart += s => { startRaised2 = true; };
|
||||
Assert.True(Wait(() => startRaised1 && startRaised2));
|
||||
signaling1.Start();
|
||||
signaling2.Start();
|
||||
yield return new WaitUntil(() => startRaised1 && startRaised2);
|
||||
|
||||
signaling1.OnCreateConnection += (s, connectionId) => { connectionId1 = connectionId; };
|
||||
signaling1.CreateConnection();
|
||||
Assert.True(Wait(() => !string.IsNullOrEmpty(connectionId1)));
|
||||
yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1));
|
||||
|
||||
signaling2.OnOffer += (s, e) => { offerRaised = true; };
|
||||
signaling1.SendOffer(connectionId1, m_DescOffer);
|
||||
Assert.True(Wait(() => offerRaised));
|
||||
yield return new WaitUntil(() => offerRaised);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void OnAnswer()
|
||||
[UnityTest]
|
||||
public IEnumerator OnAnswer()
|
||||
{
|
||||
bool startRaised1 = false;
|
||||
bool startRaised2 = false;
|
||||
|
@ -220,27 +223,27 @@ namespace Unity.RenderStreaming
|
|||
bool answerRaised = false;
|
||||
string connectionId1 = null;
|
||||
|
||||
signaling1.Start();
|
||||
signaling2.Start();
|
||||
signaling1.OnStart += s => { startRaised1 = true; };
|
||||
signaling2.OnStart += s => { startRaised2 = true; };
|
||||
Assert.True(Wait(() => startRaised1 && startRaised2));
|
||||
signaling1.Start();
|
||||
signaling2.Start();
|
||||
yield return new WaitUntil(() => startRaised1 && startRaised2);
|
||||
|
||||
signaling1.OnCreateConnection += (s, connectionId) => { connectionId1 = connectionId; };
|
||||
signaling1.CreateConnection();
|
||||
Assert.True(Wait(() => !string.IsNullOrEmpty(connectionId1)));
|
||||
yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1));
|
||||
|
||||
signaling2.OnOffer += (s, e) => { offerRaised = true; };
|
||||
signaling1.SendOffer(connectionId1, m_DescOffer);
|
||||
Assert.True(Wait(() => offerRaised));
|
||||
yield return new WaitUntil(() => offerRaised);
|
||||
|
||||
signaling1.OnAnswer += (s, e) => { answerRaised = true; };
|
||||
signaling2.SendAnswer(connectionId1, m_DescAnswer);
|
||||
Assert.True(Wait(() => answerRaised));
|
||||
yield return new WaitUntil(() => answerRaised);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnCandidate()
|
||||
[UnityTest]
|
||||
public IEnumerator OnCandidate()
|
||||
{
|
||||
bool startRaised1 = false;
|
||||
bool startRaised2 = false;
|
||||
|
@ -250,31 +253,31 @@ namespace Unity.RenderStreaming
|
|||
bool candidateRaised2 = false;
|
||||
string connectionId1 = null;
|
||||
|
||||
signaling1.Start();
|
||||
signaling2.Start();
|
||||
signaling1.OnStart += s => { startRaised1 = true; };
|
||||
signaling2.OnStart += s => { startRaised2 = true; };
|
||||
Assert.True(Wait(() => startRaised1 && startRaised2));
|
||||
signaling1.Start();
|
||||
signaling2.Start();
|
||||
yield return new WaitUntil(() => startRaised1 && startRaised2);
|
||||
|
||||
signaling1.OnCreateConnection += (s, connectionId) => { connectionId1 = connectionId; };
|
||||
signaling1.CreateConnection();
|
||||
Assert.True(Wait(() => !string.IsNullOrEmpty(connectionId1)));
|
||||
yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1));
|
||||
|
||||
signaling2.OnOffer += (s, e) => { offerRaised = true; };
|
||||
signaling1.SendOffer(connectionId1, m_DescOffer);
|
||||
Assert.True(Wait(() => offerRaised));
|
||||
yield return new WaitUntil(() => offerRaised);
|
||||
|
||||
signaling1.OnAnswer += (s, e) => { answerRaised = true; };
|
||||
signaling2.SendAnswer(connectionId1, m_DescAnswer);
|
||||
Assert.True(Wait(() => answerRaised));
|
||||
yield return new WaitUntil(() => answerRaised);
|
||||
|
||||
signaling2.OnIceCandidate += (s, e) => { candidateRaised1 = true; };
|
||||
signaling1.SendCandidate(connectionId1, m_candidate);
|
||||
Assert.True(Wait(() => candidateRaised1));
|
||||
yield return new WaitUntil(() => candidateRaised1);
|
||||
|
||||
signaling1.OnIceCandidate += (s, e) => { candidateRaised2 = true; };
|
||||
signaling2.SendCandidate(connectionId1, m_candidate);
|
||||
Assert.True(Wait(() => candidateRaised2));
|
||||
yield return new WaitUntil(() => candidateRaised2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
m_EditorVersion: 2019.4.7f1
|
||||
m_EditorVersionWithRevision: 2019.4.7f1 (e992b1a16e65)
|
||||
m_EditorVersion: 2019.4.8f1
|
||||
m_EditorVersionWithRevision: 2019.4.8f1 (60781d942082)
|
||||
|
|
Загрузка…
Ссылка в новой задаче