diff --git a/.yamato/meta/environments.yml b/.yamato/meta/environments.yml index 852aa3ec0..408528ce2 100644 --- a/.yamato/meta/environments.yml +++ b/.yamato/meta/environments.yml @@ -13,6 +13,7 @@ platforms: run_editor_tests_command: run_editor_tests.cmd import_unity_package_command: import_unity_package.cmd unity_path: .Editor\Unity.exe + packed_webapp_name: webserver.exe test_params: # todo(kazuki) : this comment is workaround for avoiding template test error on Yamato. # "PackageTestSuite.PackageValidationTests.PackageHasTests" unittest is failed @@ -33,6 +34,7 @@ platforms: run_editor_tests_command: ./run_editor_tests.sh import_unity_package_command: ./import_unity_package.sh unity_path: .Editor/Unity.app/Contents/MacOS/Unity + packed_webapp_name: webserver_mac - name: linux type: Unity::VM::GPU image: renderstreaming/ubuntu-18.04:latest @@ -41,6 +43,7 @@ platforms: run_editor_tests_command: ./run_editor_tests.sh import_unity_package_command: ./import_unity_package.sh unity_path: .Editor/Unity + packed_webapp_name: webserver projects: - name: renderstreaming-hd packagename: com.unity.template.renderstreaming-hd diff --git a/.yamato/upm-ci-template.yml b/.yamato/upm-ci-template.yml index 235754fb4..5c91ad523 100644 --- a/.yamato/upm-ci-template.yml +++ b/.yamato/upm-ci-template.yml @@ -22,8 +22,6 @@ pack_{{ project.name }}: - .yamato/upm-ci-webapp.yml#pack_{{ platform.name }} {% endfor %} -# todo(kazuki): workaround renderstreaming-rtx template test error on Yamato. -# {% for editor in editors %} {% for platform in platforms %} {% for param in platform.test_params %} @@ -34,14 +32,22 @@ test_{{ project.name }}_{{ param.platform }}_{{ param.backend }}_{{ platform.nam image: {{ platform.image }} flavor: {{ platform.flavor}} commands: + - npm install upm-ci-utils@{{ upm.package_version }} -g --registry {{ upm.registry_url }} {% if platform.name == "win" %} - - mkdir upm-ci~\templates\ProjectData~\WebApp - - xcopy WebApp upm-ci~\templates\ProjectData~\WebApp /s/e/i + - | + set WEBAPP_PATH=%cd%\Webapp\bin~\{{ platform.packed_webapp_name }} + upm-ci template test -u {{ editor.version }} --project-path {{ project.packagename }} --platform {{ param.platform }} --backend {{ param.backend }} --extra-utr-arg="--timeout=3000" {% else %} - - cp -r WebApp upm-ci~/templates/ProjectData~/ + - | + export WEBAPP_PATH=$(pwd)/WebApp/bin~/{{ platform.packed_webapp_name }} + upm-ci template test -u {{ editor.version }} --project-path {{ project.packagename }} --platform {{ param.platform }} --backend {{ param.backend }} --extra-utr-arg="--timeout=3000" {% endif %} - - npm install upm-ci-utils@{{ upm.package_version }} -g --registry {{ upm.registry_url }} - - upm-ci template test -u {{ editor.version }} --project-path {{ project.packagename }} --platform {{ param.platform }} --backend {{ param.backend }} + triggers: + branches: + only: + - "/.*/" + except: + - "master" artifacts: logs: paths: diff --git a/.yamato/upm-ci-webapp.yml b/.yamato/upm-ci-webapp.yml index e25b30042..83d162cfd 100644 --- a/.yamato/upm-ci-webapp.yml +++ b/.yamato/upm-ci-webapp.yml @@ -50,7 +50,7 @@ test_{{ platform.name }}: only: - "/.*/" except: - - "master" + - "master" artifacts: logs: paths: diff --git a/WebApp/index.html b/WebApp/index.html index fc8f9ed8d..68823326a 100644 --- a/WebApp/index.html +++ b/WebApp/index.html @@ -17,24 +17,20 @@ - Unity Video Sender + 1 on 1 Sample -Send to Unity
+1 on 1 Sample
-
- - + +
-

SDP to send:
- -

-

SDP to receive:
- +

ConnectionID:
+

diff --git a/WebApp/package-lock.json b/WebApp/package-lock.json index 7806a222e..4bc74d444 100644 --- a/WebApp/package-lock.json +++ b/WebApp/package-lock.json @@ -1,6 +1,6 @@ { "name": "webserver", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1197,6 +1197,14 @@ } } }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -4504,6 +4512,25 @@ } } }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -4816,6 +4843,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5358,9 +5390,9 @@ "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", "dev": true, "requires": { - "ip-regex": "2.1.0", - "psl": "1.1.31", - "punycode": "2.1.1" + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "uuid": { @@ -5402,9 +5434,9 @@ "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", "dev": true, "requires": { - "ip-regex": "2.1.0", - "psl": "1.1.31", - "punycode": "2.1.1" + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "uuid": { diff --git a/WebApp/package.json b/WebApp/package.json index cc44ecf86..645fd09f4 100644 --- a/WebApp/package.json +++ b/WebApp/package.json @@ -18,6 +18,7 @@ "@types/ws": "^7.2.2", "debug": "~2.6.9", "express": "~4.16.0", + "morgan": "^1.10.0", "uuid": "^3.4.0", "ws": "^7.2.1" }, diff --git a/WebApp/public/scripts/app2.js b/WebApp/public/scripts/app2.js index f0fcc394c..c13ef54d3 100644 --- a/WebApp/public/scripts/app2.js +++ b/WebApp/public/scripts/app2.js @@ -2,8 +2,7 @@ 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'); +const textForConnectionId = document.getElementById('text_for_connection_id'); let sendVideo = new SendVideo(); @@ -11,28 +10,20 @@ 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); + await sendVideo.startVideo(localVideo); } async function setUp() { - await sendVideo.setupConnection(remoteVideo); -} - -async function addTrack() { - await sendVideo.addTrack(); + await sendVideo.setupConnection(remoteVideo, textForConnectionId.value); } function hangUp() { - sendVideo.hangUp(); - remoteVideo.pause(); - remoteVideo.srcObject = null; - // textForSendSdp.value = ''; - // textToReceiveSdp.value = ''; + sendVideo.hangUp(); + remoteVideo.pause(); + remoteVideo.srcObject = null; } \ No newline at end of file diff --git a/WebApp/public/scripts/sendvideo.js b/WebApp/public/scripts/sendvideo.js index e4757b70e..4024b8ed6 100644 --- a/WebApp/public/scripts/sendvideo.js +++ b/WebApp/public/scripts/sendvideo.js @@ -1,153 +1,155 @@ 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; + constructor() { + const _this = this; + this.config = SendVideo.getConfiguration(); + this.pc = null; + this.localStream = null; + this.remoteStram = new MediaStream(); + this.isOffer = false; + this.connectionId = null; + } + + 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); } + } - static getConfiguration() { - let config = {}; - config.sdpSemantics = 'unified-plan'; - config.iceServers = [{ urls: ['stun:stun.l.google.com:19302'] }]; - return config; - } + async setupConnection(remoteVideo, connectionId) { + const _this = this; + this.connectionId = connectionId; + this.remoteVideo = remoteVideo; + this.remoteVideo.srcObject = this.remoteStram; - 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); - } - } + this.remoteStram.onaddtrack = async (e) => await _this.remoteVideo.play(); - 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); - } + const protocolEndPoint = location.protocol + '//' + location.host + location.pathname + 'protocol'; + const createResponse = await fetch(protocolEndPoint); + const res = await createResponse.json(); - async addTrack() { - const _this = this; - this.localStream.getTracks().forEach(track => _this.pc.addTrack(track, _this.localStream)); + if (res.useWebSocket) { + this.signaling = new WebSocketSignaling(); + } else { + this.signaling = new Signaling(); } - 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++; - } - } - }; + this.signaling.addEventListener('connect', async (e) => { + const data = e.detail; + _this.prepareNewPeerConnection(data.peerExists, data.connectionId); + + if (data.peerExists) { + _this.localStream.getTracks().forEach(track => _this.pc.addTrack(track, _this.localStream)); + } + }); + + this.signaling.addEventListener('offer', async (e) => { + if (_this.pc) { + console.error('peerConnection alreay exist'); + } + const offer = e.detail; + _this.prepareNewPeerConnection(false, offer.connectionId); + 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(_this.connectionId, 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(); + await this.signaling.createConnection(this.connectionId); + } + + prepareNewPeerConnection(isOffer, connectionId) { + const _this = this; + this.isOffer = isOffer; + // close current RTCPeerConnection + if (this.pc) { + console.log('Close current PeerConnection'); + this.pc.close(); + this.pc = null; } - 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.'); + // 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(connectionId, e.candidate.candidate, e.candidate.sdpMid, e.candidate.sdpMLineIndex); + } + }; + + this.pc.onnegotiationneeded = async () => { + if (_this.isOffer) { + 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(_this.connectionId, offer.sdp); + } + }; + } + + async hangUp() { + if (this.pc) { + if (this.pc.iceConnectionState !== 'closed') { + this.pc.close(); + this.pc = null; + console.log('sending close message'); + this.signaling.stop(); + return; + } } + console.log('peerConnection is closed.'); + await this.signaling.deleteConnection(this.connectionId); + this.connectionId = null; + } } diff --git a/WebApp/public/scripts/signaling.js b/WebApp/public/scripts/signaling.js index 542eb9222..c4ced9c5b 100644 --- a/WebApp/public/scripts/signaling.js +++ b/WebApp/public/scripts/signaling.js @@ -1,3 +1,5 @@ +import uuid4 from 'https://cdn.jsdelivr.net/gh/tracker1/node-uuid4/browser.mjs'; + export default class Signaling extends EventTarget { constructor() { @@ -24,13 +26,15 @@ export default class Signaling extends EventTarget { const session = await createResponse.json(); this.sessionId = session.sessionId; - const res = await this.createConnection(); - const connection = await res.json(); - this.connectionId = connection.connectionId; + const id = uuid4(); + const connection = await this.createConnection(id); this.loopGetOffer(); this.loopGetAnswer(); this.loopGetCandidate(); + + this.connectionId = connection.connectionId; + return this.connectionId; } async loopGetOffer() { @@ -98,30 +102,37 @@ export default class Signaling extends EventTarget { this.sessionId = null; } - async createConnection() { - return await fetch(this.url('connection'), { method: 'PUT', headers: this.headers() }); + async createConnection(connectionId) { + const data = { 'connectionId': connectionId }; + const res = await fetch(this.url('connection'), { method: 'PUT', headers: this.headers(), body: JSON.stringify(data) }); + const json = await res.json(); + this.dispatchEvent(new CustomEvent('connect', {detail: json})); + return json; }; - async deleteConnection() { - const data = { 'connectionId': this.connectionId }; - return await fetch(this.url('connection'), { method: 'DELETE', headers: this.headers(), body: JSON.stringify(data) }); + async deleteConnection(connectionId) { + const data = { 'connectionId': connectionId }; + const res = await fetch(this.url('connection'), { method: 'DELETE', headers: this.headers(), body: JSON.stringify(data) }); + const json = await res.json(); + this.dispatchEvent(new CustomEvent('disconnect', {detail: json})); + return json; }; - async sendOffer(sdp) { - const data = { 'sdp': sdp, 'connectionId': this.connectionId }; + async sendOffer(connectionId, sdp) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; await fetch(this.url('offer'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) }); }; - async sendAnswer(sdp) { - const data = { 'sdp': sdp, 'connectionId': this.connectionId }; + async sendAnswer(connectionId, sdp) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; await fetch(this.url('answer'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) }); }; - async sendCandidate(candidate, sdpMid, sdpMLineIndex) { + async sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex) { const data = { 'candidate': candidate, 'sdpMLineIndex': sdpMLineIndex, 'sdpMid': sdpMid, - 'connectionId': this.connectionId + 'connectionId': connectionId }; await fetch(this.url('candidate'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) }); }; @@ -152,7 +163,8 @@ export class WebSocketSignaling extends EventTarget { this.connectionId = null; this.websocket.onopen = () => { - this.websocket.send(JSON.stringify({ type: "connect" })); + const id = uuid4(); + this.websocket.send(JSON.stringify({ type: "connect", connectionId: id })); } this.websocket.onmessage = (event) => { @@ -166,8 +178,10 @@ export class WebSocketSignaling extends EventTarget { switch (msg.type) { case "connect": this.connectionId = msg.connectionId; + this.dispatchEvent(new CustomEvent('connect', { detail: msg })); break; case "disconnect": + this.dispatchEvent(new CustomEvent('disconnect', { detail: msg })); break; case "offer": this.dispatchEvent(new CustomEvent('offer', { detail: msg.data })); @@ -189,34 +203,47 @@ export class WebSocketSignaling extends EventTarget { while(this.connectionId == null){ await sleep(100); } + return this.connectionId; } stop() { this.websocket.send(JSON.stringify({ type: "disconnect", from: this.connectionId })); } - sendOffer(sdp) { - const data = { 'sdp': sdp, 'connectionId': this.connectionId }; - const sendJson = JSON.stringify({ type: "offer", from: this.connectionId, data: data }); + createConnection(connectionId) { + const sendJson = JSON.stringify({ type: "connect", connectionId: connectionId }); + console.log(sendJson); + this.websocket.send(sendJson); + }; + + deleteConnection(connectionId) { + const sendJson = JSON.stringify({ type: "disconnect", connectionId: connectionId }); + console.log(sendJson); + this.websocket.send(sendJson); + }; + + sendOffer(connectionId, sdp) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; + const sendJson = JSON.stringify({ type: "offer", from: connectionId, data: data }); console.log(sendJson); this.websocket.send(sendJson); } - sendAnswer(sdp) { - const data = { 'sdp': sdp, 'connectionId': this.connectionId }; - const sendJson = JSON.stringify({ type: "answer", from: this.connectionId, data: data }); + sendAnswer(connectionId, sdp) { + const data = { 'sdp': sdp, 'connectionId': connectionId }; + const sendJson = JSON.stringify({ type: "answer", from: connectionId, data: data }); console.log(sendJson); this.websocket.send(sendJson); } - sendCandidate(candidate, sdpMLineIndex, sdpMid) { + sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid) { const data = { 'candidate': candidate, 'sdpMLineIndex': sdpMLineIndex, 'sdpMid': sdpMid, - 'connectionId': this.connectionId + 'connectionId': connectionId }; - const sendJson = JSON.stringify({ type: "candidate", from: this.connectionId, data: data }); + const sendJson = JSON.stringify({ type: "candidate", from: connectionId, data: data }); console.log(sendJson); this.websocket.send(sendJson); } diff --git a/WebApp/public/scripts/video-player.js b/WebApp/public/scripts/video-player.js index 9d44636f3..50dc82f6f 100644 --- a/WebApp/public/scripts/video-player.js +++ b/WebApp/public/scripts/video-player.js @@ -15,6 +15,7 @@ export class VideoPlayer { offerToReceiveAudio: true, offerToReceiveVideo: true, }; + this.connectionId = null; // main video this.localStream = new MediaStream(); @@ -97,7 +98,7 @@ export class VideoPlayer { }; this.pc.onicecandidate = function (e) { if (e.candidate != null) { - _this.signaling.sendCandidate(e.candidate.candidate, e.candidate.sdpMid, e.candidate.sdpMLineIndex); + _this.signaling.sendCandidate(_this.connectionId, e.candidate.candidate, e.candidate.sdpMid, e.candidate.sdpMLineIndex); } }; // Create data channel with proxy server and set up handlers @@ -142,7 +143,7 @@ export class VideoPlayer { }); // setup signaling - await this.signaling.start(); + this.connectionId = await this.signaling.start(); // Add transceivers to receive multi stream. // It can receive two video tracks and one audio track from Unity app. @@ -157,7 +158,7 @@ export class VideoPlayer { // set local sdp const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" }); await this.pc.setLocalDescription(desc); - await this.signaling.sendOffer(offer.sdp); + await this.signaling.sendOffer(this.connectionId, offer.sdp); }; resizeVideo() { diff --git a/WebApp/src/class/answer.ts b/WebApp/src/class/answer.ts index 9d4d579a8..1de32df06 100644 --- a/WebApp/src/class/answer.ts +++ b/WebApp/src/class/answer.ts @@ -1,8 +1,8 @@ export default class Answer { - sdp: string; - datetime: number; - constructor(sdp: string, datetime: number) { - this.sdp = sdp; - this.datetime = datetime; - } + sdp: string; + datetime: number; + constructor(sdp: string, datetime: number) { + this.sdp = sdp; + this.datetime = datetime; + } } \ No newline at end of file diff --git a/WebApp/src/class/candidate.ts b/WebApp/src/class/candidate.ts index 1fee3a078..84c047c31 100644 --- a/WebApp/src/class/candidate.ts +++ b/WebApp/src/class/candidate.ts @@ -1,12 +1,12 @@ export default class Candidate { - candidate: string; - sdpMLineIndex: number; - sdpMid: string; - datetime: number; - constructor(candidate: string, sdpMLineIndex: number, sdpMid: string, datetime: number) { - this.candidate = candidate; - this.sdpMLineIndex = sdpMLineIndex; - this.sdpMid = sdpMid; - this.datetime = datetime; - } + candidate: string; + sdpMLineIndex: number; + sdpMid: string; + datetime: number; + constructor(candidate: string, sdpMLineIndex: number, sdpMid: string, datetime: number) { + this.candidate = candidate; + this.sdpMLineIndex = sdpMLineIndex; + this.sdpMid = sdpMid; + this.datetime = datetime; + } } \ No newline at end of file diff --git a/WebApp/src/class/offer.ts b/WebApp/src/class/offer.ts index 88689a1a6..eaf9ba955 100644 --- a/WebApp/src/class/offer.ts +++ b/WebApp/src/class/offer.ts @@ -1,8 +1,8 @@ export default class Offer { - sdp: string; - datetime: number; - constructor(sdp: string, datetime: number) { - this.sdp = sdp; - this.datetime = datetime; - } + sdp: string; + datetime: number; + constructor(sdp: string, datetime: number) { + this.sdp = sdp; + this.datetime = datetime; + } } diff --git a/WebApp/src/index.ts b/WebApp/src/index.ts index e6f38315e..546467669 100644 --- a/WebApp/src/index.ts +++ b/WebApp/src/index.ts @@ -13,6 +13,8 @@ export interface Options { keyfile?: string; certfile?: string; websocket?: boolean; + mode?: string; + logging?: string; } export class RenderStreaming { @@ -27,6 +29,8 @@ export class RenderStreaming { .option('-k, --keyfile ', 'https key file (default server.key)', process.env.KEYFILE || 'server.key') .option('-c, --certfile ', 'https cert file (default server.cert)', process.env.CERTFILE || 'server.cert') .option('-w, --websocket', 'Enable Websocket Signaling', process.env.WEBSOCKET || false) + .option('-m, --mode ', 'Choose Communication mode public or private (default public)', process.env.MODE || 'public') + .option('-l, --logging ', 'Choose http logging type combined, dev, short, tiny or none.(default dev)', process.env.LOGGING || 'dev') .parse(argv); return { port: program.port, @@ -34,6 +38,8 @@ export class RenderStreaming { keyfile: program.keyfile, certfile: program.certfile, websocket: program.websocket, + mode: program.mode, + logging: program.logging, }; } }; @@ -74,8 +80,10 @@ export class RenderStreaming { if (this.options.websocket) { console.log(`start websocket signaling server ws://${this.getIPAddress()[0]}`) //Start Websocket Signaling server - new WSSignaling(this.server); + new WSSignaling(this.server, this.options.mode); } + + console.log(`start as ${this.options.mode} mode`); } getIPAddress(): string[] { @@ -93,4 +101,4 @@ export class RenderStreaming { } } -RenderStreaming.run(process.argv); +RenderStreaming.run(process.argv); \ No newline at end of file diff --git a/WebApp/src/server.ts b/WebApp/src/server.ts index dd4f457da..99df8b1b2 100644 --- a/WebApp/src/server.ts +++ b/WebApp/src/server.ts @@ -5,13 +5,19 @@ import * as fs from 'fs'; import signaling from './signaling'; import { log, LogLevel } from './log'; +import * as morgan from 'morgan'; export const createServer = (config): express.Application => { const app: express.Application = express(); + app.set('isPrivate', config.mode == "private"); + // logging http access + if (config.logging != "none") { + app.use(morgan(config.logging)); + } // const signal = require('./signaling'); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); - app.get('/protocol', (req, res) => res.json({useWebSocket: config.websocket})); + app.get('/protocol', (req, res) => res.json({ useWebSocket: config.websocket })); app.use('/signaling', signaling); app.use(express.static(path.join(__dirname, '/../public/stylesheets'))); app.use(express.static(path.join(__dirname, '/../public/scripts'))); diff --git a/WebApp/src/signaling.ts b/WebApp/src/signaling.ts index b31ae076c..bbc17d04a 100644 --- a/WebApp/src/signaling.ts +++ b/WebApp/src/signaling.ts @@ -51,6 +51,20 @@ router.get('/offer', (req: Request, res: Response) => { const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; let arrayOffers = Array.from(offers); + + if (req.app.get('isPrivate')) { + const sessionId: string = req.header('session-id'); + const connectionIds = Array.from(clients.get(sessionId)); + + const arr = []; + for (const connectionId of connectionIds) { + if (offers.has(connectionId)) { + arr.push([connectionId, offers.get(connectionId)]); + } + } + arrayOffers = arr; + } + if (fromTime > 0) { arrayOffers = arrayOffers.filter((v) => v[1].datetime > fromTime); } @@ -110,16 +124,48 @@ router.put('', (req: Request, res: Response) => { router.delete('', (req: Request, res: Response) => { const id: string = req.header('session-id'); + const connectionIds = clients.get(id); + if (connectionIds) { + connectionIds.forEach(connectionId => { + connectionPair.delete(connectionId); + offers.delete(connectionId); + answers.delete(connectionId); + }); + } + candidates.delete(id); clients.delete(id); res.sendStatus(200); }); router.put('/connection', (req: Request, res: Response) => { const sessionId: string = req.header('session-id'); - const connectionId: string = uuid(); + const { connectionId } = req.body; + if(connectionId == null) { + res.status(400).send({ error: new Error(`connectionId is required`) }); + return; + } + let peerExists = false; + if (req.app.get('isPrivate')) { + if (connectionPair.has(connectionId)) { + const pair = connectionPair.get(connectionId); + + if (pair[0] != null && pair[1] != null) { + const err = new Error(`${connectionId}: This connection id is already used.`); + console.log(err); + res.status(400).send({ error: err }); + return; + } else if (pair[0] != null) { + connectionPair.set(connectionId, [pair[0], sessionId]); + peerExists = true; + } + } else { + connectionPair.set(connectionId, [sessionId, null]); + } + } + const connectionIds = getOrCreateConnectionIds(sessionId); connectionIds.add(connectionId); - res.json({ connectionId }); + res.json({ connectionId: connectionId, peerExists: peerExists }); }); router.delete('/connection', (req: Request, res: Response) => { @@ -135,7 +181,19 @@ router.post('/offer', (req: Request, res: Response) => { const sessionId: string = req.header('session-id'); const { connectionId } = req.body; offers.set(connectionId, new Offer(req.body.sdp, Date.now())); - connectionPair.set(connectionId, [sessionId, null]); + + if (res.app.get('isPrivate')) { + const pair = connectionPair.get(connectionId); + const otherSessionId = pair[0] == sessionId ? pair[1] : pair[0]; + if (otherSessionId == null) { + const err = new Error(`${connectionId}: This connection id is not ready other session.`); + console.log(err); + res.status(400).send({ error: err }); + return; + } + } else { + connectionPair.set(connectionId, [sessionId, null]); + } res.sendStatus(200); }); @@ -149,7 +207,10 @@ router.post('/answer', (req: Request, res: Response) => { // add connectionPair const pair = connectionPair.get(connectionId); const otherSessionId = pair[0]; - connectionPair.set(connectionId, [otherSessionId, sessionId]); + + if (!res.app.get('isPrivate')) { + connectionPair.set(connectionId, [otherSessionId, sessionId]); + } // update datetime for candidates const mapCandidates = candidates.get(otherSessionId); diff --git a/WebApp/src/websocket.ts b/WebApp/src/websocket.ts index 3ab8558f8..7fc1e6c54 100644 --- a/WebApp/src/websocket.ts +++ b/WebApp/src/websocket.ts @@ -1,6 +1,5 @@ import * as websocket from "ws"; import { Server } from 'http'; -import { v4 as uuid } from 'uuid'; import Offer from './class/offer'; import Answer from './class/answer'; import Candidate from './class/candidate'; @@ -21,141 +20,211 @@ const answers: Map = new Map(); const candidates: Map> = new Map>(); function getOrCreateConnectionIds(settion: WebSocket): Set { - let connectionIds = null; - if (!clients.has(settion)) { - connectionIds = new Set(); - clients.set(settion, connectionIds); - } - connectionIds = clients.get(settion); - return connectionIds; + let connectionIds = null; + if (!clients.has(settion)) { + connectionIds = new Set(); + clients.set(settion, connectionIds); + } + connectionIds = clients.get(settion); + return connectionIds; } export default class WSSignaling { - server: Server; - wss: websocket.Server; - - constructor(server: Server) { - this.server = server; - this.wss = new websocket.Server({ server }); - - this.wss.on('connection', (ws: WebSocket) => { - - clients.set(ws, new Set()); - - ws.onclose = (_event: CloseEvent) => { - clients.delete(ws); - } - - ws.onmessage = (event: MessageEvent) => { - - // JSON Schema expectation - // type: connect, disconnect, offer, answer, candidate - // from: from connection id - // to: to connection id - // data: any message data structure - - const msg = JSON.parse(event.data); - if (!msg || !this) { - return; - } - - console.log(msg); - - switch (msg.type) { - case "connect": - this.onConnect(ws); - break; - case "disconnect": - this.onDisconnect(ws, msg.data); - break; - case "offer": - this.onOffer(ws, msg.data); - break; - case "answer": - this.onAnswer(ws, msg.data); - break; - case "candidate": - this.onCandidate(ws, msg.data); - break; - default: - break; - } - }; + server: Server; + wss: websocket.Server; + isPrivate: boolean; + + constructor(server: Server, mode: string) { + this.server = server; + this.wss = new websocket.Server({ server }); + this.isPrivate = mode == "private"; + + this.wss.on('connection', (ws: WebSocket) => { + + clients.set(ws, new Set()); + + ws.onclose = (_event: CloseEvent) => { + + const connectionIds = clients.get(ws); + connectionIds.forEach(connectionId => { + connectionPair.delete(connectionId); + offers.delete(connectionId); + answers.delete(connectionId); }); - } - private onConnect(ws: WebSocket){ - const connectionId: string = uuid(); - const connectionIds = getOrCreateConnectionIds(ws); - connectionIds.add(connectionId); - ws.send(JSON.stringify({type:"connect", connectionId:connectionId})); + clients.delete(ws); + candidates.delete(ws); + } + + ws.onmessage = (event: MessageEvent) => { + + // type: connect, disconnect JSON Schema + // connectionId: connect or disconnect connectionId + + // type: offer, answer, candidate JSON Schema + // from: from connection id + // to: to connection id + // data: any message data structure + + const msg = JSON.parse(event.data); + if (!msg || !this) { + return; + } + + console.log(msg); + + switch (msg.type) { + case "connect": + this.onConnect(ws, msg.connectionId); + break; + case "disconnect": + this.onDisconnect(ws, msg.connectionId); + break; + case "offer": + this.onOffer(ws, msg.data); + break; + case "answer": + this.onAnswer(ws, msg.data); + break; + case "candidate": + this.onCandidate(ws, msg.data); + break; + default: + break; + } + }; + }); + } + + private onConnect(ws: WebSocket, connectionId: string) { + let peerExists = false; + if (this.isPrivate) { + if (connectionPair.has(connectionId)) { + const pair = connectionPair.get(connectionId); + + if (pair[0] != null && pair[1] != null) { + ws.send(JSON.stringify({ type: "error", message: `${connectionId}: This connection id is already used.` })); + return; + } else if (pair[0] != null) { + connectionPair.set(connectionId, [pair[0], ws]); + peerExists = true; + } + } else { + connectionPair.set(connectionId, [ws, null]); + } } - private onDisconnect(ws: WebSocket, message: any){ - const connectionIds = clients.get(ws); - const connectionId = message.connectionId as string; - connectionIds.delete(connectionId); - connectionPair.delete(connectionId); + const connectionIds = getOrCreateConnectionIds(ws); + connectionIds.add(connectionId); + ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, peerExists: peerExists })); + } + + private onDisconnect(ws: WebSocket, connectionId: string) { + const connectionIds = clients.get(ws); + connectionIds.delete(connectionId); + + if (connectionPair.has(connectionId)) { + const pair = connectionPair.get(connectionId); + const otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; + if (otherSessionWs) { + otherSessionWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId })); + } + } + connectionPair.delete(connectionId); + } + + private onOffer(ws: WebSocket, message: any) { + const connectionId = message.connectionId as string; + const newOffer = new Offer(message.sdp, Date.now()); + offers.set(connectionId, newOffer); + + if (this.isPrivate) { + const pair = connectionPair.get(connectionId); + const otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; + if (otherSessionWs) { + otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer })); + } else { + ws.send(JSON.stringify({ type: "error", message: `${connectionId}: This connection id is not ready other session.` })); + } + return; } - private onOffer(ws: WebSocket, message: any){ - const connectionId = message.connectionId as string; - const newOffer = new Offer(message.sdp, Date.now()); - offers.set(connectionId, newOffer); - connectionPair.set(connectionId, [ws, null]); - clients.forEach((_v, k) => { - if (k == ws) { - return; - } - k.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer })); - }); + connectionPair.set(connectionId, [ws, null]); + 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) { + const connectionId = message.connectionId as string; + const connectionIds = getOrCreateConnectionIds(ws); + connectionIds.add(connectionId); + const newAnswer = new Answer(message.sdp, Date.now()); + answers.set(connectionId, newAnswer); + + let otherSessionWs = null; + + if (this.isPrivate) { + const pair = connectionPair.get(connectionId); + otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; + } else { + const pair = connectionPair.get(connectionId); + otherSessionWs = pair[0]; + connectionPair.set(connectionId, [otherSessionWs, ws]); } - private onAnswer(ws: WebSocket, message: any) { - const connectionId = message.connectionId as string; - const connectionIds = getOrCreateConnectionIds(ws); - connectionIds.add(connectionId); - const newAnswer = new Answer(message.sdp, Date.now()); - answers.set(connectionId, newAnswer); + const mapCandidates = candidates.get(otherSessionWs); + if (mapCandidates) { + const arrayCandidates = mapCandidates.get(connectionId); + for (const candidate of arrayCandidates) { + candidate.datetime = Date.now(); + } + } - const pair = connectionPair.get(connectionId); - const otherSessionWs = pair[0]; - connectionPair.set(connectionId, [otherSessionWs, ws]); - - 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) => { - if (k == ws) { - return; - } - k.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer })) - }); + if (this.isPrivate) { + otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer })); + return; } - private onCandidate(ws: WebSocket, message: any){ - const connectionId = message.connectionId; + clients.forEach((_v, k) => { + if (k == ws) { + return; + } + k.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer })); + }); + } - if (!candidates.has(ws)) { - candidates.set(ws, new Map()); - } - const map = candidates.get(ws); - if (!map.has(connectionId)) { - map.set(connectionId, []); - } - const arr = map.get(connectionId); - const candidate = new Candidate(message.candidate, message.sdpMLineIndex, message.sdpMid, Date.now()); - arr.push(candidate); - - clients.forEach((_v, k) => { - if (k === ws) { - return; - } - k.send(JSON.stringify({from:connectionId, to:"", type:"candidate", data:candidate})); - }); + private onCandidate(ws: WebSocket, message: any) { + const connectionId = message.connectionId; + + if (!candidates.has(ws)) { + candidates.set(ws, new Map()); } -} \ No newline at end of file + const map = candidates.get(ws); + if (!map.has(connectionId)) { + map.set(connectionId, []); + } + const arr = map.get(connectionId); + const candidate = new Candidate(message.candidate, message.sdpMLineIndex, message.sdpMid, Date.now()); + arr.push(candidate); + + if (this.isPrivate) { + const pair = connectionPair.get(connectionId); + const otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; + if (otherSessionWs) { + otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate })); + } + return; + } + + clients.forEach((_v, k) => { + if (k === ws) { + return; + } + k.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate })); + }); + } +} diff --git a/WebApp/test/renderstreaming.postman_collection.json b/WebApp/test/renderstreaming.postman_collection.json index de0a01688..603a9fa0f 100644 --- a/WebApp/test/renderstreaming.postman_collection.json +++ b/WebApp/test/renderstreaming.postman_collection.json @@ -77,7 +77,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\r\n\t\"connectionId\": \"b808d31f-e22b-4ee8-85fb-4c4b587f8065\"\r\n}" }, "url": { "raw": "http://{{url}}/signaling/connection", @@ -255,7 +255,7 @@ ] }, { - "name": "\b/signaling/candidate", + "name": "/signaling/candidate", "event": [ { "listen": "test", diff --git a/com.unity.renderstreaming/Editor/WebAppDownloader.cs b/com.unity.renderstreaming/Editor/WebAppDownloader.cs index 456c73af3..49305c14e 100644 --- a/com.unity.renderstreaming/Editor/WebAppDownloader.cs +++ b/com.unity.renderstreaming/Editor/WebAppDownloader.cs @@ -37,7 +37,7 @@ public static string GetWebAppURL(string version) } string path = string.Format("releases/download/{0}", version); string fileName = GetFileName(); - return System.IO.Path.Combine(URLRoot, System.IO.Path.Combine(path, fileName)); + return $"{URLRoot}/{path}/{fileName}"; } public static string GetURLDocumentation(string version) diff --git a/com.unity.template.renderstreaming-hd/Assets/Scenes/1on1Sample.unity b/com.unity.template.renderstreaming-hd/Assets/Scenes/1on1Sample.unity new file mode 100644 index 000000000..312fd8dab --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Scenes/1on1Sample.unity @@ -0,0 +1,1784 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &54907169 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 54907172} + - component: {fileID: 54907171} + - component: {fileID: 54907170} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &54907170 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 54907169} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7a68c43fe1f2a47cfa234b5eeaa98012, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 10 + m_ObsoleteShadowResolutionTier: 1 + m_ObsoleteUseShadowQualitySettings: 0 + m_ObsoleteCustomShadowResolution: 512 + m_ObsoleteContactShadows: 0 + m_PointlightHDType: 0 + m_SpotLightShape: 0 + m_AreaLightShape: 0 + m_Intensity: 10000 + m_EnableSpotReflector: 0 + m_LuxAtDistance: 1 + m_InnerSpotPercent: 0 + m_LightDimmer: 1 + m_VolumetricDimmer: 1 + m_LightUnit: 2 + m_FadeDistance: 10000 + m_AffectDiffuse: 1 + m_AffectSpecular: 1 + m_NonLightmappedOnly: 0 + m_ShapeWidth: 0.5 + m_ShapeHeight: 0.5 + m_AspectRatio: 1 + m_ShapeRadius: 0 + m_SoftnessScale: 1 + m_UseCustomSpotLightShadowCone: 0 + m_CustomSpotLightShadowCone: 30 + m_MaxSmoothness: 0.99 + m_ApplyRangeAttenuation: 1 + m_DisplayAreaLightEmissiveMesh: 0 + m_AreaLightCookie: {fileID: 0} + m_AreaLightShadowCone: 120 + m_UseScreenSpaceShadows: 0 + m_InteractsWithSky: 1 + m_AngularDiameter: 0.53 + m_FlareSize: 2 + m_FlareTint: {r: 1, g: 1, b: 1, a: 1} + m_FlareFalloff: 4 + m_SurfaceTexture: {fileID: 0} + m_SurfaceTint: {r: 1, g: 1, b: 1, a: 1} + m_Distance: 150000000 + m_UseRayTracedShadows: 0 + m_NumRayTracingSamples: 4 + m_FilterTracedShadow: 1 + m_FilterSizeTraced: 16 + m_SunLightConeAngle: 0.5 + m_LightShadowRadius: 0.5 + m_SemiTransparentShadow: 0 + m_ColorShadow: 1 + m_EvsmExponent: 15 + m_EvsmLightLeakBias: 0 + m_EvsmVarianceBias: 0.00001 + m_EvsmBlurPasses: 0 + m_LightlayersMask: 1 + m_LinkShadowLayers: 1 + m_ShadowNearPlane: 0.1 + m_BlockerSampleCount: 24 + m_FilterSampleCount: 16 + m_MinFilterSize: 0.01 + m_KernelSize: 5 + m_LightAngle: 1 + m_MaxDepthBias: 0.001 + m_ShadowResolution: + m_Override: 512 + m_UseOverride: 1 + m_Level: 1 + m_ShadowDimmer: 1 + m_VolumetricShadowDimmer: 1 + m_ShadowFadeDistance: 10000 + m_UseContactShadow: + m_Override: 0 + m_UseOverride: 1 + m_Level: 0 + m_RayTracedContactShadow: 0 + m_ShadowTint: {r: 0, g: 0, b: 0, a: 1} + m_PenumbraTint: 0 + m_NormalBias: 0.75 + m_SlopeBias: 0.5 + m_ShadowUpdateMode: 0 + m_BarnDoorAngle: 90 + m_BarnDoorLength: 0.05 + m_ShadowCascadeRatios: + - 0.05 + - 0.2 + - 0.3 + m_ShadowCascadeBorders: + - 0.2 + - 0.2 + - 0.2 + - 0.2 + m_ShadowAlgorithm: 0 + m_ShadowVariant: 0 + m_ShadowPrecision: 0 + useOldInspector: 0 + useVolumetric: 1 + featuresFoldout: 1 + showAdditionalSettings: 0 + m_AreaLightEmissiveMeshShadowCastingMode: 0 + m_AreaLightEmissiveMeshMotionVectorGenerationMode: 0 +--- !u!108 &54907171 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 54907169} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 10000 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 2 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 5500 + m_UseColorTemperature: 1 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0.53 +--- !u!4 &54907172 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 54907169} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 0.99999994} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &174875020 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 174875021} + - component: {fileID: 174875023} + - component: {fileID: 174875022} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &174875021 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 174875020} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 2128695119} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &174875022 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 174875020} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 35 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 148 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: HangUp +--- !u!222 &174875023 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 174875020} + m_CullTransparentMesh: 0 +--- !u!1 &185332754 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 185332755} + - component: {fileID: 185332758} + - component: {fileID: 185332757} + - component: {fileID: 185332756} + m_Layer: 5 + m_Name: InputField + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &185332755 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 185332754} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 2143861285} + - {fileID: 678698532} + m_Father: {fileID: 932364532} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &185332756 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 185332754} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d199490a83bb2b844b9695cbf13b01ef, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 185332757} + m_TextComponent: {fileID: 678698533} + m_Placeholder: {fileID: 2143861286} + m_ContentType: 0 + m_InputType: 0 + m_AsteriskChar: 42 + m_KeyboardType: 0 + m_LineType: 0 + m_HideMobileInput: 0 + m_CharacterValidation: 0 + m_CharacterLimit: 0 + m_OnEndEdit: + m_PersistentCalls: + m_Calls: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] + m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_CustomCaretColor: 0 + m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} + m_Text: + m_CaretBlinkRate: 0.85 + m_CaretWidth: 1 + m_ReadOnly: 0 +--- !u!114 &185332757 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 185332754} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &185332758 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 185332754} + m_CullTransparentMesh: 0 +--- !u!1 &346362249 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 346362250} + - component: {fileID: 346362253} + - component: {fileID: 346362252} + - component: {fileID: 346362251} + m_Layer: 5 + m_Name: SetUpButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &346362250 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 346362249} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1676245737} + m_Father: {fileID: 932364532} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 1, y: 1} +--- !u!114 &346362251 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 346362249} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 346362252} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &346362252 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 346362249} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &346362253 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 346362249} + m_CullTransparentMesh: 0 +--- !u!1 &528948622 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 528948626} + - component: {fileID: 528948625} + - component: {fileID: 528948624} + - component: {fileID: 528948623} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &528948623 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 528948622} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &528948624 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 528948622} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!223 &528948625 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 528948622} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &528948626 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 528948622} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: + - {fileID: 1354028706} + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &678698531 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 678698532} + - component: {fileID: 678698534} + - component: {fileID: 678698533} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &678698532 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 678698531} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 185332755} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.5} + m_SizeDelta: {x: -20, y: -13} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &678698533 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 678698531} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 30 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 3 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 1 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: +--- !u!222 &678698534 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 678698531} + m_CullTransparentMesh: 0 +--- !u!1 &710419879 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 710419883} + - component: {fileID: 710419882} + - component: {fileID: 710419881} + - component: {fileID: 710419880} + m_Layer: 0 + m_Name: UICamera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &710419880 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 710419879} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 23c1ce4fb46143f46bc5cb5224c934f6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 7 + m_ObsoleteRenderingPath: 0 + m_ObsoleteFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + clearColorMode: 1 + backgroundColorHDR: {r: 0.025, g: 0.07, b: 0.19, a: 0} + clearDepth: 1 + volumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + volumeAnchorOverride: {fileID: 0} + antialiasing: 0 + SMAAQuality: 2 + dithering: 0 + stopNaNs: 0 + taaSharpenStrength: 0.6 + physicalParameters: + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + flipYMode: 0 + fullscreenPassthrough: 0 + allowDynamicResolution: 0 + customRenderingSettings: 0 + invertFaceCulling: 0 + probeLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + hasPersistentHistory: 0 + m_RenderingPathCustomFrameSettings: + bitDatas: + data1: 734440390720 + data2: 536805376 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + materialQuality: 0 + renderingPathCustomFrameSettingsOverrideMask: + mask: + data1: 0 + data2: 0 + defaultFrameSettings: 0 +--- !u!81 &710419881 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 710419879} + m_Enabled: 1 +--- !u!20 &710419882 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 710419879} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 0 + m_AllowMSAA: 0 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &710419883 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 710419879} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &932364531 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 932364532} + - component: {fileID: 932364533} + m_Layer: 5 + m_Name: Button + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &932364532 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 932364531} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 185332755} + - {fileID: 346362250} + - {fileID: 2128695119} + m_Father: {fileID: 1354028706} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 20, y: 20} + m_SizeDelta: {x: -40, y: -600} + m_Pivot: {x: 0, y: 0} +--- !u!114 &932364533 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 932364531} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 20 + m_Right: 20 + m_Top: 20 + m_Bottom: 20 + m_ChildAlignment: 0 + m_Spacing: 20 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 1 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 +--- !u!1 &1180904219 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1180904220} + - component: {fileID: 1180904221} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1180904220 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1180904219} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1363584100} + - {fileID: 1221238572} + m_Father: {fileID: 1354028706} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 20, y: 120} + m_SizeDelta: {x: -40, y: -140} + m_Pivot: {x: 0, y: 0} +--- !u!114 &1180904221 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1180904219} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 20 + m_Right: 20 + m_Top: 20 + m_Bottom: 20 + m_ChildAlignment: 0 + m_Spacing: 20 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 1 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 +--- !u!1 &1221238571 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1221238572} + - component: {fileID: 1221238574} + - component: {fileID: 1221238573} + m_Layer: 5 + m_Name: RemoteImage + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1221238572 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1221238571} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1180904220} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1221238573 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1221238571} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Texture: {fileID: 0} + m_UVRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 +--- !u!222 &1221238574 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1221238571} + m_CullTransparentMesh: 0 +--- !u!1 &1334313017 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1334313020} + - component: {fileID: 1334313019} + - component: {fileID: 1334313018} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1334313018 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1334313017} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1334313019 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1334313017} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1334313020 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1334313017} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1354028705 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1354028706} + m_Layer: 5 + m_Name: Frame + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1354028706 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1354028705} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1180904220} + - {fileID: 932364532} + m_Father: {fileID: 528948626} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 1280, y: 720} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &1363584099 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1363584100} + - component: {fileID: 1363584102} + - component: {fileID: 1363584101} + m_Layer: 5 + m_Name: LocalImage + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1363584100 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1363584099} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1180904220} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1363584101 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1363584099} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Texture: {fileID: 0} + m_UVRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 +--- !u!222 &1363584102 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1363584099} + m_CullTransparentMesh: 0 +--- !u!1 &1676245736 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1676245737} + - component: {fileID: 1676245739} + - component: {fileID: 1676245738} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1676245737 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1676245736} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 346362250} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1676245738 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1676245736} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 35 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 148 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: SetUp +--- !u!222 &1676245739 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1676245736} + m_CullTransparentMesh: 0 +--- !u!1 &1915034400 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1915034403} + - component: {fileID: 1915034402} + - component: {fileID: 1915034405} + - component: {fileID: 1915034404} + - component: {fileID: 1915034401} + m_Layer: 0 + m_Name: RenderStreaming + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1915034401 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1915034400} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dd07afc0983a13046a910fd730a45421, type: 3} + m_Name: + m_EditorClassIdentifier: + setUpButton: {fileID: 346362251} + hangUpButton: {fileID: 2128695120} + connectionIdInput: {fileID: 185332756} + localVideoImage: {fileID: 1363584101} + remoteVideoImage: {fileID: 1221238573} + videoStream: {fileID: 1915034404} + receiveVideoViewer: {fileID: 1915034405} +--- !u!114 &1915034402 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1915034400} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 045786cf504bd7347842d6948241cbd0, type: 3} + m_Name: + m_EditorClassIdentifier: + urlSignaling: https://localhost:443 + signalingType: Unity.RenderStreaming.Signaling.HttpSignaling + iceServers: + - credential: + credentialType: 0 + urls: + - stun:stun.l.google.com:19302 + username: + interval: 5 + hardwareEncoderSupport: 0 + arrayButtonClickEvent: [] +--- !u!224 &1915034403 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1915034400} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1915034404 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1915034400} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7f37261e4dcc3ef4a949ca413a2cddee, type: 3} + m_Name: + m_EditorClassIdentifier: + streamingSize: {x: 1280, y: 720} + deviceIndex: 0 +--- !u!114 &1915034405 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1915034400} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f1e06fd32ea04af9b843af232d7e9db2, type: 3} + m_Name: + m_EditorClassIdentifier: + streamingSize: {x: 1280, y: 720} + connectionId: +--- !u!1 &2128695118 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2128695119} + - component: {fileID: 2128695122} + - component: {fileID: 2128695121} + - component: {fileID: 2128695120} + m_Layer: 5 + m_Name: HangUpButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2128695119 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2128695118} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 174875021} + m_Father: {fileID: 932364532} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 1, y: 1} +--- !u!114 &2128695120 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2128695118} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 2128695121} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &2128695121 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2128695118} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &2128695122 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2128695118} + m_CullTransparentMesh: 0 +--- !u!1 &2143861284 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2143861285} + - component: {fileID: 2143861287} + - component: {fileID: 2143861286} + m_Layer: 5 + m_Name: Placeholder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2143861285 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2143861284} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 185332755} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.5} + m_SizeDelta: {x: -20, y: -13} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2143861286 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2143861284} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.5} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 30 + m_FontStyle: 2 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Enter RoomID +--- !u!222 &2143861287 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2143861284} + m_CullTransparentMesh: 0 diff --git a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime.meta b/com.unity.template.renderstreaming-hd/Assets/Scenes/1on1Sample.unity.meta similarity index 67% rename from com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime.meta rename to com.unity.template.renderstreaming-hd/Assets/Scenes/1on1Sample.unity.meta index 2036762a9..3b1cd15cd 100644 --- a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime.meta +++ b/com.unity.template.renderstreaming-hd/Assets/Scenes/1on1Sample.unity.meta @@ -1,6 +1,5 @@ fileFormatVersion: 2 -guid: 25af854ea48aa4c23b44e2660ef2c19d -folderAsset: yes +guid: 47884c8c55e53a74f93bde81d6d0a178 DefaultImporter: externalObjects: {} userData: diff --git a/com.unity.template.renderstreaming-hd/Assets/Scenes/ReceiveVideo.unity b/com.unity.template.renderstreaming-hd/Assets/Scenes/ReceiveVideo.unity index b642f09b5..18ba471b0 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scenes/ReceiveVideo.unity +++ b/com.unity.template.renderstreaming-hd/Assets/Scenes/ReceiveVideo.unity @@ -407,8 +407,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 825, y: -25} - m_SizeDelta: {x: -850, y: 100} + m_AnchoredPosition: {x: 500, y: -25} + m_SizeDelta: {x: -650, y: 100} m_Pivot: {x: 0, y: 1} --- !u!114 &499727481 MonoBehaviour: @@ -539,7 +539,6 @@ RectTransform: m_LocalScale: {x: 0, y: 0, z: 0} m_Children: - {fileID: 499727480} - - {fileID: 662432478} - {fileID: 1363584100} m_Father: {fileID: 0} m_RootOrder: 4 @@ -562,205 +561,8 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: streamingSize: {x: 1280, y: 720} - sendOfferButton: {fileID: 662432479} + connectionId: receiveImage: {fileID: 1363584101} ---- !u!1 &576362883 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 576362884} - - component: {fileID: 576362886} - - component: {fileID: 576362885} - m_Layer: 5 - m_Name: Text - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &576362884 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 576362883} - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] - m_Father: {fileID: 662432478} - m_RootOrder: 0 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 0} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!114 &576362885 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 576362883} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} - m_RaycastTarget: 1 - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_FontData: - m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} - m_FontSize: 14 - m_FontStyle: 0 - m_BestFit: 0 - m_MinSize: 10 - m_MaxSize: 40 - m_Alignment: 4 - m_AlignByGeometry: 0 - m_RichText: 1 - m_HorizontalOverflow: 0 - m_VerticalOverflow: 0 - m_LineSpacing: 1 - m_Text: SendOffer ---- !u!222 &576362886 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 576362883} - m_CullTransparentMesh: 0 ---- !u!1 &662432477 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 662432478} - - component: {fileID: 662432481} - - component: {fileID: 662432480} - - component: {fileID: 662432479} - m_Layer: 5 - m_Name: SendOfferButton - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &662432478 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 662432477} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: - - {fileID: 576362884} - m_Father: {fileID: 528948626} - m_RootOrder: 1 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 500, y: -50} - m_SizeDelta: {x: -1000, y: 50} - m_Pivot: {x: 0, y: 1} ---- !u!114 &662432479 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 662432477} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Navigation: - m_Mode: 3 - m_SelectOnUp: {fileID: 0} - m_SelectOnDown: {fileID: 0} - m_SelectOnLeft: {fileID: 0} - m_SelectOnRight: {fileID: 0} - m_Transition: 1 - m_Colors: - m_NormalColor: {r: 1, g: 1, b: 1, a: 1} - m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} - m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} - m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} - m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} - m_ColorMultiplier: 1 - m_FadeDuration: 0.1 - m_SpriteState: - m_HighlightedSprite: {fileID: 0} - m_PressedSprite: {fileID: 0} - m_SelectedSprite: {fileID: 0} - m_DisabledSprite: {fileID: 0} - m_AnimationTriggers: - m_NormalTrigger: Normal - m_HighlightedTrigger: Highlighted - m_PressedTrigger: Pressed - m_SelectedTrigger: Selected - m_DisabledTrigger: Disabled - m_Interactable: 1 - m_TargetGraphic: {fileID: 662432480} - m_OnClick: - m_PersistentCalls: - m_Calls: [] ---- !u!114 &662432480 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 662432477} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 1} - m_RaycastTarget: 1 - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} - m_Type: 1 - m_PreserveAspect: 0 - m_FillCenter: 1 - m_FillMethod: 4 - m_FillAmount: 1 - m_FillClockwise: 1 - m_FillOrigin: 0 - m_UseSpriteMesh: 0 - m_PixelsPerUnitMultiplier: 1 ---- !u!222 &662432481 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 662432477} - m_CullTransparentMesh: 0 --- !u!1 &710419879 GameObject: m_ObjectHideFlags: 0 @@ -1124,7 +926,7 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 528948626} - m_RootOrder: 2 + m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 1, y: 1} diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/CameraStreamer.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/CameraStreamer.cs index 64f85a5a0..1a020f5d5 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/CameraStreamer.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/CameraStreamer.cs @@ -1,29 +1,13 @@ -using System; using Unity.WebRTC; using UnityEngine; namespace Unity.RenderStreaming { [RequireComponent(typeof(Camera))] - public class CameraStreamer : MonoBehaviour + public class CameraStreamer : VideoStreamBase { - [SerializeField, Tooltip("Streaming size should match display aspect ratio")] - private Vector2Int streamingSize = new Vector2Int(1280, 720); - private Camera m_camera; - private VideoStreamTrack m_track; - - public void ChangeBitrate(int bitrate) - { - RenderStreaming.Instance?.ChangeVideoParameters( - m_track, Convert.ToUInt64(bitrate), null); - } - - public void ChangeFramerate(int framerate) - { - RenderStreaming.Instance?.ChangeVideoParameters( - m_track, null, Convert.ToUInt32(framerate)); - } + public override Texture SendTexture => m_camera.targetTexture; void Awake() { @@ -35,6 +19,8 @@ void OnEnable() // todo(kazuki): remove bitrate parameter because it is not supported m_track = m_camera.CaptureStreamTrack(streamingSize.x, streamingSize.y, 1000000); RenderStreaming.Instance?.AddVideoStreamTrack(m_track); + + OnEnableComplete?.Invoke(); } void OnDisable() diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/OneOnOneSample.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/OneOnOneSample.cs new file mode 100644 index 000000000..d2182cee0 --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/OneOnOneSample.cs @@ -0,0 +1,54 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace Unity.RenderStreaming +{ + public class OneOnOneSample : MonoBehaviour + { +#pragma warning disable 0649 + [SerializeField] private Button setUpButton; + [SerializeField] private Button hangUpButton; + [SerializeField] private InputField connectionIdInput; + [SerializeField] private RawImage localVideoImage; + [SerializeField] private RawImage remoteVideoImage; + [SerializeField] private VideoStreamBase videoStream; + [SerializeField] private ReceiveVideoViewer receiveVideoViewer; +#pragma warning restore 0649 + + void Awake() + { + setUpButton.interactable = true; + hangUpButton.interactable = false; + connectionIdInput.interactable = true; + setUpButton.onClick.AddListener(SetUp); + hangUpButton.onClick.AddListener(HangUp); + connectionIdInput.onValueChanged.AddListener(input => receiveVideoViewer.ChangeConnectionId(input)); + connectionIdInput.text = $"{Random.Range(0, 99999):D5}"; + videoStream.OnEnableComplete += () => { + receiveVideoViewer.enabled = true; + localVideoImage.texture = videoStream.SendTexture; + }; + receiveVideoViewer.OnUpdateReceiveTexture += texture => remoteVideoImage.texture = texture; + } + + private void SetUp() + { + setUpButton.interactable = false; + hangUpButton.interactable = true; + connectionIdInput.interactable = false; + videoStream.enabled = true; + } + + private void HangUp() + { + videoStream.enabled = false; + receiveVideoViewer.enabled = false; + localVideoImage.texture = null; + remoteVideoImage.texture = null; + setUpButton.interactable = true; + hangUpButton.interactable = false; + connectionIdInput.interactable = true; + connectionIdInput.text = $"{Random.Range(0, 99999):D5}"; + } + } +} diff --git a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/RenderStreamingRTXTest.cs.meta b/com.unity.template.renderstreaming-hd/Assets/Scripts/OneOnOneSample.cs.meta similarity index 83% rename from com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/RenderStreamingRTXTest.cs.meta rename to com.unity.template.renderstreaming-hd/Assets/Scripts/OneOnOneSample.cs.meta index 43d351f6e..6c08f8183 100644 --- a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/RenderStreamingRTXTest.cs.meta +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/OneOnOneSample.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 47afa9dc02dda4559b9db79018fc609c +guid: dd07afc0983a13046a910fd730a45421 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/ReceiveVideoViewer.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/ReceiveVideoViewer.cs index 4aae65ccf..54a52609d 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/ReceiveVideoViewer.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/ReceiveVideoViewer.cs @@ -1,43 +1,80 @@ -using System; -using Unity.WebRTC; +using Unity.WebRTC; using UnityEngine; -using UnityEngine.UI; namespace Unity.RenderStreaming { public class ReceiveVideoViewer : MonoBehaviour { + public delegate void OnUpdateReceiveTextureHandler(Texture receiveTexture); + public OnUpdateReceiveTextureHandler OnUpdateReceiveTexture; + [SerializeField] private Vector2Int streamingSize = new Vector2Int(1280, 720); - [SerializeField] private Button sendOfferButton; - [SerializeField] private RawImage receiveImage; + [SerializeField] private string connectionId; private MediaStream m_receiveStream; + private Texture m_receiveTexture; - void Start() - { - sendOfferButton.onClick.AddListener(() => RenderStreaming.Instance?.AddTransceiver()); - } + public Texture ReceiveTexture => m_receiveTexture; void OnEnable() { m_receiveStream = new MediaStream(); - RenderStreaming.Instance?.AddVideoReceiveStream(m_receiveStream); + RenderStreaming.Instance?.AddVideoReceiveViewer(this); m_receiveStream.OnAddTrack = e => { - if (receiveImage != null && e.Track.Kind == TrackKind.Video) + if (e.Track.Kind == TrackKind.Video) { var videoTrack = (VideoStreamTrack)e.Track; - receiveImage.texture = videoTrack.InitializeReceiver(streamingSize.x, streamingSize.y); + m_receiveTexture = videoTrack.InitializeReceiver(streamingSize.x, streamingSize.y); + OnUpdateReceiveTexture?.Invoke(m_receiveTexture); } }; + m_receiveStream.OnRemoveTrack = e => + { + if (e.Track.Kind == TrackKind.Video) + { + OnUpdateReceiveTexture?.Invoke(null); + m_receiveTexture = null; + e.Track.Dispose(); + } + }; + + RenderStreaming.Instance?.OpenConnection(connectionId); } void OnDisable() { - RenderStreaming.Instance?.RemoveVideoReceiveStream(m_receiveStream); + RenderStreaming.Instance?.CloseConnection(connectionId); + RenderStreaming.Instance?.RemoveVideoReceiveViewer(this); m_receiveStream.OnAddTrack = null; m_receiveStream.Dispose(); m_receiveStream = null; + m_receiveTexture = null; + } + + public void AddTrack(string connectionId, MediaStreamTrack track) + { + if (!string.IsNullOrEmpty(this.connectionId) && connectionId != this.connectionId) + { + return; + } + + m_receiveStream.AddTrack(track); + } + + public void RemoveTrack(string connectionId, MediaStreamTrack track) + { + if (!string.IsNullOrEmpty(this.connectionId) && connectionId != this.connectionId) + { + return; + } + + m_receiveStream.RemoveTrack(track); + } + + public void ChangeConnectionId(string connectionId) + { + this.connectionId = connectionId; } } } diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/RenderStreaming.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/RenderStreaming.cs index 1e69514de..2238c0fc8 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/RenderStreaming.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/RenderStreaming.cs @@ -13,19 +13,22 @@ namespace Unity.RenderStreaming using DataChannelDictionary = Dictionary; [Serializable] - public class ButtonClickEvent : UnityEngine.Events.UnityEvent { } + public class ButtonClickEvent : UnityEngine.Events.UnityEvent + { + } [Serializable] public class ButtonClickElement { [Tooltip("Specifies the ID on the HTML")] public int elementId; + public ButtonClickEvent click; } public class RenderStreaming : MonoBehaviour { - #pragma warning disable 0649 +#pragma warning disable 0649 [SerializeField, Tooltip("Signaling server url")] private string urlSignaling = "http://localhost"; @@ -35,10 +38,7 @@ public class RenderStreaming : MonoBehaviour [SerializeField, Tooltip("Array to set your own STUN/TURN servers")] private RTCIceServer[] iceServers = new RTCIceServer[] { - new RTCIceServer() - { - urls = new string[] { "stun:stun.l.google.com:19302" } - } + new RTCIceServer() {urls = new string[] {"stun:stun.l.google.com:19302"}} }; [SerializeField, Tooltip("Time interval for polling from signaling server")] @@ -54,18 +54,29 @@ public class RenderStreaming : MonoBehaviour private SynchronizationContext m_mainThreadContext; private ISignaling m_signaling; - private readonly Dictionary m_mapConnectionIdAndPeer = new Dictionary(); - private readonly Dictionary m_mapPeerAndChannelDictionary = new Dictionary(); - private readonly Dictionary m_remoteInputAndCameraController = new Dictionary(); - private readonly Dictionary m_mapChannelAndRemoteInput = new Dictionary(); + + private readonly Dictionary m_mapConnectionIdAndPeer = + new Dictionary(); + + private readonly Dictionary m_mapPeerAndChannelDictionary = + new Dictionary(); + + private readonly Dictionary m_remoteInputAndCameraController = + new Dictionary(); + + private readonly Dictionary m_mapChannelAndRemoteInput = + new Dictionary(); + private readonly List m_listController = new List(); private readonly List m_listVideoStreamTrack = new List(); - private readonly Dictionary> m_mapTrackAndSenderList = new Dictionary>(); - private readonly List m_listVideoReceiveStream = new List(); + + private readonly Dictionary> m_mapTrackAndSenderList = + new Dictionary>(); + + private readonly List m_listVideoReceiveViewer = new List(); private MediaStream m_audioStream; private DefaultInput m_defaultInput; private RTCConfiguration m_conf; - private string m_connectionId; public static RenderStreaming Instance { get; private set; } @@ -94,6 +105,7 @@ public void OnDestroy() Unity.WebRTC.Audio.Stop(); m_mainThreadContext = null; } + public void Start() { m_audioStream = Unity.WebRTC.Audio.CaptureStream(); @@ -107,18 +119,16 @@ void OnEnable() if (this.m_signaling == null) { Type t = Type.GetType(signalingType); - object[] args = { urlSignaling, interval, m_mainThreadContext }; + object[] args = {urlSignaling, interval, m_mainThreadContext}; this.m_signaling = (ISignaling)Activator.CreateInstance(t, args); - this.m_signaling.OnStart += signaling => signaling.CreateConnection(); - this.m_signaling.OnCreateConnection += (signaling, id) => - { - m_connectionId = id; - CreatePeerConnection(signaling, m_connectionId, true); - }; + this.m_signaling.OnStart += signaling => signaling.OpenConnection(Guid.NewGuid().ToString()); + this.m_signaling.OnCreateConnection += OnCreateConnection; + this.m_signaling.OnDestroyConnection += OnDestroyConnection; 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(); } @@ -143,27 +153,24 @@ public void RemoveVideoStreamTrack(VideoStreamTrack track) m_listVideoStreamTrack.Remove(track); } - public void AddVideoReceiveStream(MediaStream stream) + public void AddVideoReceiveViewer(ReceiveVideoViewer viewer) { - m_listVideoReceiveStream.Add(stream); + m_listVideoReceiveViewer.Add(viewer); } - public void RemoveVideoReceiveStream(MediaStream stream) + public void RemoveVideoReceiveViewer(ReceiveVideoViewer viewer) { - m_listVideoReceiveStream.Remove(stream); + m_listVideoReceiveViewer.Remove(viewer); } - public void AddTransceiver() + public void OpenConnection(string connectionId) { - if (string.IsNullOrEmpty(m_connectionId) || - !m_mapConnectionIdAndPeer.TryGetValue(m_connectionId, out var pc)) - { - return; - } + m_signaling?.OpenConnection(connectionId); + } - RTCRtpTransceiver transceiver = pc.AddTransceiver(TrackKind.Video); - // ToDO: need webrtc package version 2.3 - // transceiver.Direction = RTCRtpTransceiverDirection.RecvOnly; + public void CloseConnection(string connectionId) + { + m_signaling?.CloseConnection(connectionId); } public void ChangeVideoParameters(VideoStreamTrack track, ulong? bitrate, uint? framerate) @@ -173,9 +180,10 @@ public void ChangeVideoParameters(VideoStreamTrack track, ulong? bitrate, uint? RTCRtpSendParameters parameters = sender.GetParameters(); foreach (var encoding in parameters.Encodings) { - if(bitrate != null) encoding.maxBitrate = bitrate; + if (bitrate != null) encoding.maxBitrate = bitrate; if (framerate != null) encoding.maxFramerate = framerate; } + sender.SetParameters(parameters); } } @@ -189,30 +197,32 @@ void OnDisable() } } - IEnumerator OnOffer(ISignaling signaling, DescData e) + void OnCreateConnection(ISignaling signaling, string connectionId, bool peerExists) { - var connectionId = e.connectionId; - if (m_mapConnectionIdAndPeer.ContainsKey(connectionId)) + var pc = CreatePeerConnection(signaling, connectionId, peerExists); + + if (!peerExists) { - Debug.LogError($"connection:{connectionId} peerConnection already exist"); - yield break; + return; } - 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; + AddTracks(connectionId, pc); + } - if (opRemoteDesc.IsError) + void OnDestroyConnection(ISignaling signaling, string connectionId) + { + if (m_mapConnectionIdAndPeer.TryGetValue(connectionId, out var pc)) { - Debug.LogError($"Network Error: {opRemoteDesc.Error.message}"); - yield break; + RemoveTracks(connectionId, pc); + m_mapPeerAndChannelDictionary.Remove(pc); + pc.Dispose(); } + m_mapConnectionIdAndPeer.Remove(connectionId); + } + + void AddTracks(string connectionId, RTCPeerConnection pc) + { // ToDo: need webrtc package version 2.3 // foreach (var transceiver in pc.GetTransceivers() // .Where(x => x.Receiver.Track.Kind == TrackKind.Video) @@ -241,6 +251,7 @@ IEnumerator OnOffer(ISignaling signaling, DescData e) list = new List(); m_mapTrackAndSenderList.Add(track, list); } + list.Add(sender); } @@ -252,8 +263,53 @@ IEnumerator OnOffer(ISignaling signaling, DescData e) list = new List(); m_mapTrackAndSenderList.Add(track, list); } + list.Add(sender); } + } + + void RemoveTracks(string id, RTCPeerConnection pc) + { + foreach (var sender in pc.GetSenders()) + { + if (m_mapTrackAndSenderList.TryGetValue(sender.Track, out var list)) + { + list.Remove(sender); + } + } + + foreach (var receiver in pc.GetReceivers()) + { + foreach (var viewer in m_listVideoReceiveViewer) + { + viewer.RemoveTrack(id, receiver.Track); + } + } + } + + IEnumerator OnOffer(ISignaling signaling, DescData e) + { + var connectionId = e.connectionId; + RTCPeerConnection pc = null; + if (!m_mapConnectionIdAndPeer.TryGetValue(connectionId, out pc)) + { + 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; + } + + AddTracks(connectionId, pc); RTCAnswerOptions options = default; var op = pc.CreateAnswer(ref options); @@ -296,7 +352,7 @@ RTCPeerConnection CreatePeerConnection(ISignaling signaling, string connectionId }); pc.OnIceConnectionChange = new DelegateOnIceConnectionChange(state => { - if(state == RTCIceConnectionState.Disconnected) + if (state == RTCIceConnectionState.Disconnected) { pc.Close(); m_mapConnectionIdAndPeer.Remove(connectionId); @@ -305,9 +361,9 @@ RTCPeerConnection CreatePeerConnection(ISignaling signaling, string connectionId pc.OnTrack = trackEvent => { - foreach (var receiveStream in m_listVideoReceiveStream) + foreach (var viewer in m_listVideoReceiveViewer) { - receiveStream.AddTrack(trackEvent.Track); + viewer.AddTrack(connectionId, trackEvent.Track); } }; @@ -328,11 +384,7 @@ IEnumerator OnNegotiationNeeded(ISignaling signaling, string connectionId, bool yield break; } - RTCOfferOptions option = new RTCOfferOptions - { - offerToReceiveAudio = true, - offerToReceiveVideo = true - }; + RTCOfferOptions option = new RTCOfferOptions {offerToReceiveAudio = true, offerToReceiveVideo = true}; var offerOp = pc.CreateOffer(ref option); yield return offerOp; @@ -404,6 +456,7 @@ void OnDataChannel(RTCPeerConnection pc, RTCDataChannel channel) channels = new DataChannelDictionary(); m_mapPeerAndChannelDictionary.Add(pc, channels); } + channels.Add(channel.Id, channel); if (channel.Label != "data") @@ -425,7 +478,7 @@ void OnDataChannel(RTCPeerConnection pc, RTCDataChannel channel) SimpleCameraController controller = m_listController .FirstOrDefault(_controller => !m_remoteInputAndCameraController.ContainsValue(_controller)); - if(controller != null) + if (controller != null) { controller.SetInput(input); m_remoteInputAndCameraController.Add(input, controller); @@ -458,6 +511,7 @@ void OnCloseChannel(RTCDataChannel channel) m_remoteInputAndCameraController.Add(newInput, controller); } } + m_remoteInputAndCameraController.Remove(input); m_mapChannelAndRemoteInput.Remove(channel); diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/FurioosSignaling.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/FurioosSignaling.cs index 3a444f6d3..28237c623 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/FurioosSignaling.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/FurioosSignaling.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Authentication; using System.Text; using System.Threading; using Unity.WebRTC; @@ -70,6 +69,7 @@ public void Stop() //todo: not implemented public event OnConnectHandler OnCreateConnection; + public event OnDisconnectHandler OnDestroyConnection; public event OnOfferHandler OnOffer; #pragma warning disable 0067 // this event is never used in this class @@ -110,11 +110,16 @@ public void SendCandidate(string connectionId, RTCIceCandidate candidate) WSSend(routedMessage); } - public void CreateConnection() + public void OpenConnection(string connectionId) { this.WSSend("{\"type\":\"connect\"}"); } + public void CloseConnection(string connectionId) + { + throw new NotImplementedException(); + } + private void WSManage() { while (m_running) diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/HttpSignaling.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/HttpSignaling.cs index 15647a978..c5cc92242 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/HttpSignaling.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/HttpSignaling.cs @@ -43,12 +43,17 @@ public void Start() public void Stop() { - m_running = false; + if (m_running) + { + m_running = false; + m_signalingThread?.Join(); + m_signalingThread = null; + } } public event OnStartHandler OnStart; public event OnConnectHandler OnCreateConnection; - + public event OnDisconnectHandler OnDestroyConnection; public event OnOfferHandler OnOffer; #pragma warning disable 0067 // this event is never used in this class @@ -87,9 +92,14 @@ public void SendCandidate(string connectionId, RTCIceCandidate candidate) HTTPPost("signaling/candidate", data); } - public void CreateConnection() + public void OpenConnection(string connectionId) + { + HTTPConnect(connectionId); + } + + public void CloseConnection(string connectionId) { - HTTPConnect(); + HTTPDisonnect(connectionId); } private void HTTPPooling() @@ -220,6 +230,7 @@ private bool HTTPDelete() request.Method = "DELETE"; request.ContentType = "application/json"; request.KeepAlive = false; + request.Headers.Add("Session-Id", m_sessionId); Debug.Log($"Signaling: Removing HTTP connection from {m_url}"); @@ -248,7 +259,7 @@ private bool HTTPPost(string path, object data) return (HTTPParseTextResponse(HTTPGetResponse(request)) != null); } - private bool HTTPConnect() + private bool HTTPConnect(string connectionId) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create($"{m_url}/signaling/connection"); @@ -257,18 +268,43 @@ private bool HTTPConnect() request.Headers.Add("Session-Id", m_sessionId); request.KeepAlive = false; + using (Stream dataStream = request.GetRequestStream()) + { + byte[] bytes = new System.Text.UTF8Encoding().GetBytes($"{{\"connectionId\":\"{connectionId}\"}}"); + dataStream.Write(bytes, 0, bytes.Length); + dataStream.Close(); + } + HttpWebResponse response = HTTPGetResponse(request); CreateConnectionResData data = HTTPParseJsonResponse(response); if (data == null) return false; - m_lastTimeGetOfferRequest = DateTimeExtension.ParseHttpDate(response.Headers[HttpResponseHeader.Date]) - .ToJsMilliseconds(); - - m_mainThreadContext.Post(d => OnCreateConnection?.Invoke(this, data.connectionId), null); + Debug.Log("Signaling: HTTP create connection, connectionId : " + connectionId); + m_mainThreadContext.Post(d => OnCreateConnection?.Invoke(this, data.connectionId, data.peerExists), null); return true; } + private bool HTTPDisonnect(string connectionId) + { + HttpWebRequest request = + (HttpWebRequest)WebRequest.Create($"{m_url}/signaling/connection"); + request.Method = "Delete"; + request.ContentType = "application/json"; + request.Headers.Add("Session-Id", m_sessionId); + request.KeepAlive = false; + + using (Stream dataStream = request.GetRequestStream()) + { + byte[] bytes = new System.Text.UTF8Encoding().GetBytes($"{{\"connectionId\":\"{connectionId}\"}}"); + dataStream.Write(bytes, 0, bytes.Length); + dataStream.Close(); + } + + Debug.Log("Signaling: HTTP delete connection, connectionId : " + connectionId); + return (HTTPParseTextResponse(HTTPGetResponse(request)) != null); + } + private bool HTTPGetOffers() { HttpWebRequest request = diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/ISignaling.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/ISignaling.cs index 1f0fb71b7..012c3b575 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/ISignaling.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/ISignaling.cs @@ -3,7 +3,8 @@ namespace Unity.RenderStreaming.Signaling { public delegate void OnStartHandler(ISignaling signaling); - public delegate void OnConnectHandler(ISignaling signaling, string connectionId); + public delegate void OnConnectHandler(ISignaling signaling, string connectionId, bool peerExists); + public delegate void OnDisconnectHandler(ISignaling signaling, string connectionId); public delegate void OnOfferHandler(ISignaling signaling, DescData e); public delegate void OnAnswerHandler(ISignaling signaling, DescData e); public delegate void OnIceCandidateHandler(ISignaling signaling, CandidateData e); @@ -15,11 +16,13 @@ public interface ISignaling event OnStartHandler OnStart; event OnConnectHandler OnCreateConnection; + event OnDisconnectHandler OnDestroyConnection; event OnOfferHandler OnOffer; event OnAnswerHandler OnAnswer; event OnIceCandidateHandler OnIceCandidate; - void CreateConnection(); + void OpenConnection(string connectionId); + void CloseConnection(string connectionId); void SendOffer(string connectionId, RTCSessionDescription answer); void SendAnswer(string connectionId, RTCSessionDescription answer); void SendCandidate(string connectionId, RTCIceCandidate candidate); diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/SignalingMessage.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/SignalingMessage.cs index cf0b387e0..d15f08098 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/SignalingMessage.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/SignalingMessage.cs @@ -12,7 +12,7 @@ public class SignalingMessage public string message; public string sessionId; public string connectionId; - public string peerId; + public bool peerExists; public string sdp; public string type; public string candidate; @@ -45,6 +45,13 @@ class OpenSessionData [Serializable] class CreateConnectionResData + { + public string connectionId; + public bool peerExists; + } + + [Serializable] + class DestroyConnectionResData { public string connectionId; } @@ -61,7 +68,7 @@ class AnswerResDataList public DescData[] answers; } - + [Serializable] class CandidateContainerResDataList { diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/WebSocketSignaling.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/WebSocketSignaling.cs index b88aad11e..7c477b313 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/WebSocketSignaling.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/Signaling/WebSocketSignaling.cs @@ -40,12 +40,14 @@ public void Stop() { m_running = false; m_webSocket?.Close(); - m_signalingThread.Abort(); + m_signalingThread.Join(); + m_signalingThread = null; } } public event OnStartHandler OnStart; public event OnConnectHandler OnCreateConnection; + public event OnDisconnectHandler OnDestroyConnection; public event OnOfferHandler OnOffer; #pragma warning disable 0067 // this event is never used in this class @@ -99,9 +101,14 @@ public void SendCandidate(string connectionId, RTCIceCandidate candidate) WSSend(routedMessage); } - public void CreateConnection() + public void OpenConnection(string connectionId) { - this.WSSend("{\"type\":\"connect\"}"); + this.WSSend($"{{\"type\":\"connect\", \"connectionId\":\"{connectionId}\"}}"); + } + + public void CloseConnection(string connectionId) + { + this.WSSend($"{{\"type\":\"disconnect\", \"connectionId\":\"{connectionId}\"}}"); } private void WSManage() @@ -161,8 +168,13 @@ private void WSProcessMessage(object sender, MessageEventArgs e) { if (routedMessage.type == "connect") { - string connectionId = JsonUtility.FromJson(content).connectionId; - m_mainThreadContext.Post(d => OnCreateConnection?.Invoke(this, connectionId), null); + msg = JsonUtility.FromJson(content); + m_mainThreadContext.Post(d => OnCreateConnection?.Invoke(this, msg.connectionId, msg.peerExists), null); + } + else if (routedMessage.type == "disconnect") + { + msg = JsonUtility.FromJson(content); + m_mainThreadContext.Post(d => OnDestroyConnection?.Invoke(this, msg.connectionId), null); } else if (routedMessage.type == "offer") { @@ -191,6 +203,11 @@ private void WSProcessMessage(object sender, MessageEventArgs e) }; m_mainThreadContext.Post(d => OnIceCandidate?.Invoke(this, candidate), null); } + else if (routedMessage.type == "error") + { + msg = JsonUtility.FromJson(content); + Debug.LogError(msg.message); + } } } catch (Exception ex) diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/VideoStreamBase.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/VideoStreamBase.cs new file mode 100644 index 000000000..07ad8328e --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/VideoStreamBase.cs @@ -0,0 +1,31 @@ +using System; +using Unity.WebRTC; +using UnityEngine; + +namespace Unity.RenderStreaming +{ + public abstract class VideoStreamBase : MonoBehaviour + { + public delegate void OnEnableCompleteHandler(); + + public OnEnableCompleteHandler OnEnableComplete; + + [SerializeField, Tooltip("Streaming size should match display aspect ratio")] + protected Vector2Int streamingSize = new Vector2Int(1280, 720); + + protected VideoStreamTrack m_track; + public virtual Texture SendTexture { get; } + + public void ChangeBitrate(int bitrate) + { + RenderStreaming.Instance?.ChangeVideoParameters( + m_track, Convert.ToUInt64(bitrate), null); + } + + public void ChangeFramerate(int framerate) + { + RenderStreaming.Instance?.ChangeVideoParameters( + m_track, null, Convert.ToUInt32(framerate)); + } + } +} diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/VideoStreamBase.cs.meta b/com.unity.template.renderstreaming-hd/Assets/Scripts/VideoStreamBase.cs.meta new file mode 100644 index 000000000..204524f6c --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/VideoStreamBase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8bcc82901c3f48a88d3408251afa3365 +timeCreated: 1606988906 \ No newline at end of file diff --git a/com.unity.template.renderstreaming-hd/Assets/Scripts/WebCamStreamer.cs b/com.unity.template.renderstreaming-hd/Assets/Scripts/WebCamStreamer.cs index 2563021df..1d7b35417 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Scripts/WebCamStreamer.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Scripts/WebCamStreamer.cs @@ -1,35 +1,25 @@ -using System; using System.Collections; using Unity.WebRTC; using UnityEngine; namespace Unity.RenderStreaming { - [RequireComponent(typeof(Camera))] - public class WebCamStreamer : MonoBehaviour + public class WebCamStreamer : VideoStreamBase { - [SerializeField, Tooltip("Streaming size should match display aspect ratio")] - private Vector2Int streamingSize = new Vector2Int(1280, 720); - [SerializeField, Tooltip("Device index of web camera")] private int deviceIndex = 0; - private VideoStreamTrack m_track; private WebCamTexture m_webCamTexture; + private Coroutine m_startVideoCorutine; - public void ChangeBitrate(int bitrate) - { - RenderStreaming.Instance?.ChangeVideoParameters( - m_track, Convert.ToUInt64(bitrate), null); - } + public override Texture SendTexture => m_webCamTexture; - public void ChangeFramerate(int framerate) + void OnEnable() { - RenderStreaming.Instance?.ChangeVideoParameters( - m_track, null, Convert.ToUInt32(framerate)); + m_startVideoCorutine = StartCoroutine(StartVideo()); } - IEnumerator Start() + IEnumerator StartVideo() { if (WebCamTexture.devices.Length == 0) { @@ -48,14 +38,28 @@ IEnumerator Start() m_webCamTexture = new WebCamTexture(userCameraDevice.name, streamingSize.x, streamingSize.y); m_webCamTexture.Play(); yield return new WaitUntil(() => m_webCamTexture.didUpdateThisFrame); - + m_track = new VideoStreamTrack(gameObject.name, m_webCamTexture); RenderStreaming.Instance?.AddVideoStreamTrack(m_track); + + OnEnableComplete?.Invoke(); } void OnDisable() { RenderStreaming.Instance?.RemoveVideoStreamTrack(m_track); + m_track.Dispose(); + m_track = null; + + if (m_startVideoCorutine != null) + { + StopCoroutine(m_startVideoCorutine); + } + + if (m_webCamTexture != null) + { + m_webCamTexture.Stop(); + } } } } diff --git a/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/ConditionalIgnore.cs b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/ConditionalIgnore.cs new file mode 100644 index 000000000..f5fbf1132 --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/ConditionalIgnore.cs @@ -0,0 +1,21 @@ +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.RenderStreaming +{ + public class ConditionalIgnore + { + public const string IL2CPP = "IgnoreIL2CPP"; + + [RuntimeInitializeOnLoadMethod] + static void OnLoad() + { +#if ENABLE_IL2CPP + ConditionalIgnoreAttribute.AddConditionalIgnoreMapping(IL2CPP, true); +#else + ConditionalIgnoreAttribute.AddConditionalIgnoreMapping(IL2CPP, false); +#endif + + } + } +} diff --git a/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/ConditionalIgnore.cs.meta b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/ConditionalIgnore.cs.meta new file mode 100644 index 000000000..87fc99932 --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/ConditionalIgnore.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5b6ef4d8d4a64791951abcc8b5a8decf +timeCreated: 1607924977 \ No newline at end of file diff --git a/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/PrivateSignalingTest.cs b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/PrivateSignalingTest.cs new file mode 100644 index 000000000..ba24adb5b --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/PrivateSignalingTest.cs @@ -0,0 +1,349 @@ +using System; +using System.Collections; +using System.Threading; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Unity.RenderStreaming.Signaling; +using Unity.WebRTC; +using UnityEngine; +using UnityEngine.TestTools; +using Debug = UnityEngine.Debug; + +namespace Unity.RenderStreaming +{ + [TestFixture(typeof(WebSocketSignaling))] + [TestFixture(typeof(HttpSignaling))] + [ConditionalIgnore(ConditionalIgnore.IL2CPP, "Process.Start does not implement in IL2CPP.")] + public class PrivateSignalingTest : IPrebuildSetup + { + private readonly Type m_SignalingType; + private Process m_ServerProcess; + private RTCSessionDescription m_DescOffer; + private RTCSessionDescription m_DescAnswer; + private RTCIceCandidate m_candidate; + + private SynchronizationContext m_Context; + private ISignaling signaling1; + private ISignaling signaling2; + + public PrivateSignalingTest() + { + } + + public PrivateSignalingTest(Type type) + { + m_SignalingType = type; + } + + public void Setup() + { +#if UNITY_EDITOR + string dir = System.IO.Directory.GetCurrentDirectory(); + string fileName = System.IO.Path.Combine(dir, Editor.WebAppDownloader.GetFileName()); + if (System.IO.File.Exists(fileName) || System.IO.File.Exists(TestUtility.GetWebAppLocationFromEnv())) + { + // already exists. + return; + } + + bool downloadRaised = false; + Editor.WebAppDownloader.DownloadCurrentVersionWebApp(dir, success => { downloadRaised = true; }); + TestUtility.Wait(() => downloadRaised, 10000); +#endif + } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + m_ServerProcess = new Process(); + + string fileName = TestUtility.GetWebAppLocationFromEnv(); + + if (string.IsNullOrEmpty(fileName)) + { + Debug.Log($"webapp file not found in {fileName}"); + string dir = System.IO.Directory.GetCurrentDirectory(); + fileName = System.IO.Path.Combine(dir, TestUtility.GetFileName()); + } + + Assert.IsTrue(System.IO.File.Exists(fileName), $"webapp file not found in {fileName}"); + ProcessStartInfo startInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + FileName = fileName, + UseShellExecute = false + }; + + string arguments = "-m private"; + + if (m_SignalingType == typeof(WebSocketSignaling)) + { + arguments += " -w"; + } + + startInfo.Arguments = arguments; + + m_ServerProcess.StartInfo = startInfo; + m_ServerProcess.OutputDataReceived += (sender, e) => + { + Debug.Log(e.Data); + }; + bool success = m_ServerProcess.Start(); + Assert.True(success); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + m_ServerProcess.Kill(); + m_ServerProcess.WaitForExit(); + m_ServerProcess.Dispose(); + m_ServerProcess = null; + } + + ISignaling CreateSignaling(Type type, SynchronizationContext mainThread) + { + if (type == typeof(WebSocketSignaling)) + { + return new WebSocketSignaling("ws://localhost", 0.1f, mainThread); + } + + if (type == typeof(HttpSignaling)) + { + return new HttpSignaling("http://localhost", 0.1f, mainThread); + } + + throw new ArgumentException(); + } + + [UnitySetUp, Timeout(1000)] + public IEnumerator UnitySetUp() + { + WebRTC.WebRTC.Initialize(); + + RTCConfiguration config = default; + RTCIceCandidate? candidate_ = null; + config.iceServers = new[] {new RTCIceServer {urls = new[] {"stun:stun.l.google.com:19302"}}}; + + var peer1 = new RTCPeerConnection(ref config); + var peer2 = new RTCPeerConnection(ref config); + peer1.OnIceCandidate = candidate => { candidate_ = candidate; }; + + MediaStream stream = WebRTC.Audio.CaptureStream(); + peer1.AddTrack(stream.GetTracks().First()); + + RTCOfferOptions offerOptions = new RTCOfferOptions(); + var op1 = peer1.CreateOffer(ref offerOptions); + yield return op1; + m_DescOffer = op1.Desc; + var op2 = peer1.SetLocalDescription(ref m_DescOffer); + yield return op2; + var op3 = peer2.SetRemoteDescription(ref m_DescOffer); + yield return op3; + + RTCAnswerOptions answerOptions = new RTCAnswerOptions(); + var op4 = peer2.CreateAnswer(ref answerOptions); + yield return op4; + m_DescAnswer = op4.Desc; + var op5 = peer2.SetLocalDescription(ref m_DescAnswer); + yield return op5; + var op6 = peer1.SetRemoteDescription(ref m_DescAnswer); + yield return op6; + + yield return new WaitUntil(() => candidate_ != null); + m_candidate = candidate_.Value; + + stream.Dispose(); + peer1.Close(); + peer2.Close(); + + m_Context = SynchronizationContext.Current; + signaling1 = CreateSignaling(m_SignalingType, m_Context); + signaling2 = CreateSignaling(m_SignalingType, m_Context); + } + + [TearDown] + public void TearDown() + { + WebRTC.WebRTC.Dispose(); + m_Context = null; + } + + [UnityTest] + public IEnumerator CheckPeerExists() + { + bool startRaised1 = false; + bool startRaised2 = false; + + signaling1.OnStart += s => { startRaised1 = true; }; + signaling1.Start(); + signaling2.OnStart += s => { startRaised2 = true; }; + signaling2.Start(); + + yield return new WaitUntil(() => startRaised1 && startRaised2); + + const string connectionId = "12345"; + string receiveConnectionId1 = null; + string receiveConnectionId2 = null; + bool receivePeerExists1 = false; + bool receivePeerExists2 = false; + + signaling1.OnCreateConnection += (s, id, peerExists) => + { + receiveConnectionId1 = id; + receivePeerExists1 = peerExists; + }; + signaling1.OpenConnection(connectionId); + yield return new WaitUntil(() => !string.IsNullOrEmpty(receiveConnectionId1)); + Assert.AreEqual(connectionId, receiveConnectionId1); + Assert.IsFalse(receivePeerExists1); + + signaling2.OnCreateConnection += (s, id, peerExists) => + { + receiveConnectionId2 = id; + receivePeerExists2 = peerExists; + }; + signaling2.OpenConnection(connectionId); + yield return new WaitUntil(() => !string.IsNullOrEmpty(receiveConnectionId2)); + Assert.AreEqual(connectionId, receiveConnectionId2); + Assert.IsTrue(receivePeerExists2); + + signaling1.CloseConnection(receiveConnectionId1); + signaling2.CloseConnection(receiveConnectionId2); + signaling1.Stop(); + signaling2.Stop(); + yield return new WaitForSeconds(1); + } + + [UnityTest] + public IEnumerator OnOffer() + { + bool startRaised1 = false; + bool startRaised2 = false; + bool offerRaised2 = false; + const string connectionId = "12345"; + string connectionId1 = null; + string connectionId2 = null; + + signaling1.OnStart += s => { startRaised1 = true; }; + signaling2.OnStart += s => { startRaised2 = true; }; + signaling1.Start(); + signaling2.Start(); + yield return new WaitUntil(() => startRaised1 && startRaised2); + + signaling1.OnCreateConnection += (s, id, peerExists) => { connectionId1 = id; }; + signaling1.OpenConnection(connectionId); + yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1)); + + signaling2.OnOffer += (s, e) => { offerRaised2 = true; }; + + LogAssert.Expect(LogType.Error, new Regex(".")); + signaling1.SendOffer(connectionId, m_DescOffer); + yield return new WaitForSeconds(5); + // Do not receive offer other signaling if not connected same sendoffer connectionId in private mode + Assert.IsFalse(offerRaised2); + + signaling2.OnCreateConnection += (s, id, peerExists) => { connectionId2 = id; }; + signaling2.OpenConnection(connectionId); + yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId2)); + + signaling1.SendOffer(connectionId, m_DescOffer); + yield return new WaitUntil(() => offerRaised2); + + signaling1.CloseConnection(connectionId1); + signaling2.CloseConnection(connectionId2); + signaling1.Stop(); + signaling2.Stop(); + yield return new WaitForSeconds(1); + } + + + [UnityTest] + public IEnumerator OnAnswer() + { + bool startRaised1 = false; + bool startRaised2 = false; + bool offerRaised = false; + bool answerRaised = false; + const string connectionId = "12345"; + string connectionId1 = null; + string connectionId2 = null; + + signaling1.OnStart += s => { startRaised1 = true; }; + signaling2.OnStart += s => { startRaised2 = true; }; + signaling1.Start(); + signaling2.Start(); + yield return new WaitUntil(() => startRaised1 && startRaised2); + + signaling1.OnCreateConnection += (s, id, peerExists) => { connectionId1 = id; }; + signaling1.OpenConnection(connectionId); + signaling2.OnCreateConnection += (s, id, peerExists) => { connectionId2 = id; }; + signaling2.OpenConnection(connectionId); + yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1) && !string.IsNullOrEmpty(connectionId2)); + + signaling2.OnOffer += (s, e) => { offerRaised = true; }; + signaling1.SendOffer(connectionId1, m_DescOffer); + yield return new WaitUntil(() => offerRaised); + + signaling1.OnAnswer += (s, e) => { answerRaised = true; }; + signaling2.SendAnswer(connectionId1, m_DescAnswer); + yield return new WaitUntil(() => answerRaised); + + signaling1.CloseConnection(connectionId1); + signaling2.CloseConnection(connectionId2); + signaling1.Stop(); + signaling2.Stop(); + yield return new WaitForSeconds(1); + } + + [UnityTest] + public IEnumerator OnCandidate() + { + bool startRaised1 = false; + bool startRaised2 = false; + bool offerRaised = false; + bool answerRaised = false; + bool candidateRaised1 = false; + bool candidateRaised2 = false; + const string connectionId = "12345"; + string connectionId1 = null; + string connectionId2 = null; + + signaling1.OnStart += s => { startRaised1 = true; }; + signaling2.OnStart += s => { startRaised2 = true; }; + signaling1.Start(); + signaling2.Start(); + yield return new WaitUntil(() => startRaised1 && startRaised2); + + signaling1.OnCreateConnection += (s, id, peerExists) => { connectionId1 = id; }; + signaling1.OpenConnection(connectionId); + signaling2.OnCreateConnection += (s, id, peerExists) => { connectionId2 = id; }; + signaling2.OpenConnection(connectionId); + yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1) && !string.IsNullOrEmpty(connectionId2)); + + signaling2.OnOffer += (s, e) => { offerRaised = true; }; + signaling1.SendOffer(connectionId1, m_DescOffer); + yield return new WaitUntil(() => offerRaised); + + signaling1.OnAnswer += (s, e) => { answerRaised = true; }; + signaling2.SendAnswer(connectionId1, m_DescAnswer); + yield return new WaitUntil(() => answerRaised); + + signaling2.OnIceCandidate += (s, e) => { candidateRaised1 = true; }; + signaling1.SendCandidate(connectionId1, m_candidate); + yield return new WaitUntil(() => candidateRaised1); + + signaling1.OnIceCandidate += (s, e) => { candidateRaised2 = true; }; + signaling2.SendCandidate(connectionId1, m_candidate); + yield return new WaitUntil(() => candidateRaised2); + + signaling1.CloseConnection(connectionId1); + signaling2.CloseConnection(connectionId2); + signaling1.Stop(); + signaling2.Stop(); + yield return new WaitForSeconds(1); + } + } +} diff --git a/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/PrivateSignalingTest.cs.meta b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/PrivateSignalingTest.cs.meta new file mode 100644 index 000000000..d9979af7d --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/PrivateSignalingTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 814f2e203b3b488b8e189da9bec934d5 +timeCreated: 1606972537 \ No newline at end of file diff --git a/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/SignalingTest.cs b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/SignalingTest.cs index 8a878af73..a075740ba 100644 --- a/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/SignalingTest.cs +++ b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/SignalingTest.cs @@ -14,25 +14,9 @@ namespace Unity.RenderStreaming { [TestFixture(typeof(WebSocketSignaling))] [TestFixture(typeof(HttpSignaling))] - [Ignore("todo: need to upgrade com.unity.renderstreaming version 2.2")] + [ConditionalIgnore(ConditionalIgnore.IL2CPP, "Process.Start does not implement in IL2CPP.")] public class SignalingTest : IPrebuildSetup { - static bool Wait(Func condition, int millisecondsTimeout = 1000, int millisecondsInterval = 100) - { - if (millisecondsTimeout < millisecondsInterval) - { - throw new ArgumentException(); - } - - int time = 0; - while (!condition() && millisecondsTimeout > time) - { - Thread.Sleep(millisecondsInterval); - time += millisecondsInterval; - } - return millisecondsTimeout > time; - } - private readonly Type m_SignalingType; private Process m_ServerProcess; private RTCSessionDescription m_DescOffer; @@ -52,37 +36,42 @@ public SignalingTest(Type type) m_SignalingType = type; } - // // todo:(kazuki) need to upgrade com.unity.renderstreaming version 2.2 - // // this is override method for IPrebuildSetup public void Setup() { - //#if UNITY_EDITOR - // string dir = System.IO.Directory.GetCurrentDirectory(); - // string fileName = Editor.WebAppDownloader.GetFileName(); - // if (System.IO.File.Exists(System.IO.Path.Combine(dir, fileName))) - // { - // // already exists. - // return; - // } - // bool downloadRaised = false; - // Editor.WebAppDownloader.DownloadCurrentVersionWebApp(dir, success => { downloadRaised = true; }); - // Wait(() => downloadRaised, 10000); - //#endif +#if UNITY_EDITOR + string dir = System.IO.Directory.GetCurrentDirectory(); + string fileName = System.IO.Path.Combine(dir, Editor.WebAppDownloader.GetFileName()); + if (System.IO.File.Exists(fileName) || System.IO.File.Exists(TestUtility.GetWebAppLocationFromEnv())) + { + // already exists. + return; + } + + bool downloadRaised = false; + Editor.WebAppDownloader.DownloadCurrentVersionWebApp(dir, success => { downloadRaised = true; }); + TestUtility.Wait(() => downloadRaised, 10000); +#endif } [OneTimeSetUp] public void OneTimeSetUp() { - // todo: download webapp for signaling test m_ServerProcess = new Process(); - string dir = System.IO.Directory.GetCurrentDirectory(); - string filename = System.IO.Path.Combine(dir, "webserver.exe"); + string fileName = TestUtility.GetWebAppLocationFromEnv(); + if (string.IsNullOrEmpty(fileName)) + { + Debug.Log($"webapp file not found in {fileName}"); + string dir = System.IO.Directory.GetCurrentDirectory(); + fileName = System.IO.Path.Combine(dir, TestUtility.GetFileName()); + } + + Assert.IsTrue(System.IO.File.Exists(fileName), $"webapp file not found in {fileName}"); ProcessStartInfo startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Hidden, - FileName = filename, + FileName = fileName, UseShellExecute = false }; @@ -184,8 +173,8 @@ public IEnumerator OnConnect() signaling1.Start(); yield return new WaitUntil(() => startRaised1); - signaling1.OnCreateConnection += (s, connectionId) => { connectionId1 = connectionId; }; - signaling1.CreateConnection(); + signaling1.OnCreateConnection += (s, connectionId, peerExists) => { connectionId1 = connectionId; }; + signaling1.OpenConnection(Guid.NewGuid().ToString()); yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1)); Assert.IsNotEmpty(connectionId1); } @@ -197,6 +186,7 @@ public IEnumerator OnOffer() bool startRaised2 = false; bool offerRaised = false; string connectionId1 = null; + string connectionId2 = null; signaling1.OnStart += s => { startRaised1 = true; }; signaling2.OnStart += s => { startRaised2 = true; }; @@ -204,9 +194,11 @@ public IEnumerator OnOffer() signaling2.Start(); yield return new WaitUntil(() => startRaised1 && startRaised2); - signaling1.OnCreateConnection += (s, connectionId) => { connectionId1 = connectionId; }; - signaling1.CreateConnection(); - yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1)); + signaling1.OnCreateConnection += (s, connectionId, peerExists) => { connectionId1 = connectionId; }; + signaling1.OpenConnection(Guid.NewGuid().ToString()); + signaling2.OnCreateConnection += (s, connectionId, peerExists) => { connectionId2 = connectionId; }; + signaling2.OpenConnection(Guid.NewGuid().ToString()); + yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1) && !string.IsNullOrEmpty(connectionId2)); signaling2.OnOffer += (s, e) => { offerRaised = true; }; signaling1.SendOffer(connectionId1, m_DescOffer); @@ -222,6 +214,7 @@ public IEnumerator OnAnswer() bool offerRaised = false; bool answerRaised = false; string connectionId1 = null; + string connectionId2 = null; signaling1.OnStart += s => { startRaised1 = true; }; signaling2.OnStart += s => { startRaised2 = true; }; @@ -229,9 +222,11 @@ public IEnumerator OnAnswer() signaling2.Start(); yield return new WaitUntil(() => startRaised1 && startRaised2); - signaling1.OnCreateConnection += (s, connectionId) => { connectionId1 = connectionId; }; - signaling1.CreateConnection(); - yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1)); + signaling1.OnCreateConnection += (s, connectionId, peerExists) => { connectionId1 = connectionId; }; + signaling1.OpenConnection(Guid.NewGuid().ToString()); + signaling2.OnCreateConnection += (s, connectionId, peerExists) => { connectionId2 = connectionId; }; + signaling2.OpenConnection(Guid.NewGuid().ToString()); + yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1) && !string.IsNullOrEmpty(connectionId2)); signaling2.OnOffer += (s, e) => { offerRaised = true; }; signaling1.SendOffer(connectionId1, m_DescOffer); @@ -252,6 +247,7 @@ public IEnumerator OnCandidate() bool candidateRaised1 = false; bool candidateRaised2 = false; string connectionId1 = null; + string connectionId2 = null; signaling1.OnStart += s => { startRaised1 = true; }; signaling2.OnStart += s => { startRaised2 = true; }; @@ -259,9 +255,11 @@ public IEnumerator OnCandidate() signaling2.Start(); yield return new WaitUntil(() => startRaised1 && startRaised2); - signaling1.OnCreateConnection += (s, connectionId) => { connectionId1 = connectionId; }; - signaling1.CreateConnection(); - yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1)); + signaling1.OnCreateConnection += (s, connectionId, peerExists) => { connectionId1 = connectionId; }; + signaling1.OpenConnection(Guid.NewGuid().ToString()); + signaling2.OnCreateConnection += (s, connectionId, peerExists) => { connectionId2 = connectionId; }; + signaling2.OpenConnection(Guid.NewGuid().ToString()); + yield return new WaitUntil(() => !string.IsNullOrEmpty(connectionId1) && !string.IsNullOrEmpty(connectionId2)); signaling2.OnOffer += (s, e) => { offerRaised = true; }; signaling1.SendOffer(connectionId1, m_DescOffer); diff --git a/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/TestUtility.cs b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/TestUtility.cs new file mode 100644 index 000000000..547005cd9 --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/TestUtility.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading; +using UnityEngine; + +namespace Unity.RenderStreaming +{ + public class TestUtility + { + const string FileNameWebAppForMac = "webserver_mac"; + const string FileNameWebAppForLinux = "webserver"; + const string FileNameWebAppForWin = "webserver.exe"; + + public static string GetFileName() + { + switch (Application.platform) + { + case RuntimePlatform.OSXEditor: + case RuntimePlatform.OSXPlayer: + return FileNameWebAppForMac; + case RuntimePlatform.WindowsPlayer: + case RuntimePlatform.WindowsEditor: + return FileNameWebAppForWin; + case RuntimePlatform.LinuxPlayer: + case RuntimePlatform.LinuxEditor: + return FileNameWebAppForLinux; + default: + throw new ArgumentOutOfRangeException($"this platform ({Application.platform} does not support."); + } + } + + public static bool Wait(Func condition, int millisecondsTimeout = 1000, int millisecondsInterval = 100) + { + if (millisecondsTimeout < millisecondsInterval) + { + throw new ArgumentException(); + } + + int time = 0; + while (!condition() && millisecondsTimeout > time) + { + Thread.Sleep(millisecondsInterval); + time += millisecondsInterval; + } + + return millisecondsTimeout > time; + } + + public static string GetWebAppLocationFromEnv() + { + var path = Environment.GetEnvironmentVariable("WEBAPP_PATH"); + + if (!string.IsNullOrEmpty(path)) + { + return path; + } + + var args = Environment.GetCommandLineArgs(); + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "-webapp-path") + { + return args[i + 1]; + } + } + + return null; + } + } +} diff --git a/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/TestUtility.cs.meta b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/TestUtility.cs.meta new file mode 100644 index 000000000..490e444b4 --- /dev/null +++ b/com.unity.template.renderstreaming-hd/Assets/Tests/Runtime/TestUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8d9a35324d004430a70f4b1b4f5bf534 +timeCreated: 1607479264 \ No newline at end of file diff --git a/com.unity.template.renderstreaming-hd/Packages/com.unity.template.renderstreaming-hd/package.json b/com.unity.template.renderstreaming-hd/Packages/com.unity.template.renderstreaming-hd/package.json index 03d9ed4c9..e3c5a87b6 100644 --- a/com.unity.template.renderstreaming-hd/Packages/com.unity.template.renderstreaming-hd/package.json +++ b/com.unity.template.renderstreaming-hd/Packages/com.unity.template.renderstreaming-hd/package.json @@ -1,12 +1,12 @@ { "name": "com.unity.template.renderstreaming-hd", "displayName": "Render Streaming HDRP", - "version": "2.1.1-preview", + "version": "2.2.1-preview", "type": "template", "unity": "2019.4", "description": "This is HDRP sample scene project integrated with Unity Render Streaming technology.\n\nAlong with its signaling web server, you could play around the scene in any webrtc supported web browser.", "dependencies": { - "com.unity.renderstreaming": "2.1.1-preview", + "com.unity.renderstreaming": "2.2.1-preview", "com.unity.render-pipelines.high-definition": "7.3.1" } } \ No newline at end of file diff --git a/com.unity.template.renderstreaming-hd/Packages/packages-lock.json b/com.unity.template.renderstreaming-hd/Packages/packages-lock.json index 4563742ed..26fd5cd79 100644 --- a/com.unity.template.renderstreaming-hd/Packages/packages-lock.json +++ b/com.unity.template.renderstreaming-hd/Packages/packages-lock.json @@ -103,7 +103,7 @@ "depth": 0, "source": "embedded", "dependencies": { - "com.unity.renderstreaming": "2.1.1-preview", + "com.unity.renderstreaming": "2.2.1-preview", "com.unity.render-pipelines.high-definition": "7.3.1" } }, diff --git a/com.unity.template.renderstreaming-rtx/Assets/Tests b/com.unity.template.renderstreaming-rtx/Assets/Tests new file mode 120000 index 000000000..2eec2a5eb --- /dev/null +++ b/com.unity.template.renderstreaming-rtx/Assets/Tests @@ -0,0 +1 @@ +../../com.unity.template.renderstreaming-hd/Assets/Tests \ No newline at end of file diff --git a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/RenderStreamingRTXTest.cs b/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/RenderStreamingRTXTest.cs deleted file mode 100644 index d8a67de0b..000000000 --- a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/RenderStreamingRTXTest.cs +++ /dev/null @@ -1,12 +0,0 @@ -using NUnit.Framework; - -namespace Unity.RenderStreaming -{ - public class RenderStreamingTest - { - [Test] - public void Test() - { - } - } -} diff --git a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/Unity.Template.RenderStreamingRTX.RuntimeTests.asmdef b/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/Unity.Template.RenderStreamingRTX.RuntimeTests.asmdef deleted file mode 100644 index ebfc81fc3..000000000 --- a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/Unity.Template.RenderStreamingRTX.RuntimeTests.asmdef +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "Unity.Template.RenderStreamingRTX.RuntimeTests", - "references": [ - "GUID:27619889b8ba8c24980f49ee34dbb44a", - "GUID:0acc523941302664db1f4e527237feb3" - ], - "includePlatforms": [ - "Editor", - "LinuxStandalone64", - "macOSStandalone", - "WindowsStandalone64" - ], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": true, - "precompiledReferences": [ - "nunit.framework.dll" - ], - "autoReferenced": true, - "defineConstraints": [ - "UNITY_INCLUDE_TESTS" - ], - "versionDefines": [], - "noEngineReferences": false -} \ No newline at end of file diff --git a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/Unity.Template.RenderStreamingRTX.RuntimeTests.asmdef.meta b/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/Unity.Template.RenderStreamingRTX.RuntimeTests.asmdef.meta deleted file mode 100644 index 1b2925318..000000000 --- a/com.unity.template.renderstreaming-rtx/Assets/Tests/Runtime/Unity.Template.RenderStreamingRTX.RuntimeTests.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: be68485c486da4ac7b0cd099185322f0 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.template.renderstreaming-rtx/Packages/com.unity.template.renderstreaming-rtx/package.json b/com.unity.template.renderstreaming-rtx/Packages/com.unity.template.renderstreaming-rtx/package.json index 6c2ac8670..03b80539f 100644 --- a/com.unity.template.renderstreaming-rtx/Packages/com.unity.template.renderstreaming-rtx/package.json +++ b/com.unity.template.renderstreaming-rtx/Packages/com.unity.template.renderstreaming-rtx/package.json @@ -1,12 +1,12 @@ { "name": "com.unity.template.renderstreaming-rtx", "displayName": "Render Streaming Raytracing", - "version": "2.1.1-preview", + "version": "2.2.1-preview", "type": "template", "unity": "2019.4", "description": "This is HDRP Raytracing sample scene project integrated with Unity Render Streaming technology.\n\nAlong with its signaling web server, you could play around the scene in any webrtc supported web browser.", "dependencies": { - "com.unity.renderstreaming": "2.1.1-preview", + "com.unity.renderstreaming": "2.2.1-preview", "com.unity.render-pipelines.high-definition": "7.3.1" } } \ No newline at end of file diff --git a/com.unity.template.renderstreaming-rtx/Packages/packages-lock.json b/com.unity.template.renderstreaming-rtx/Packages/packages-lock.json index 4ffa32f5b..237eb40ed 100644 --- a/com.unity.template.renderstreaming-rtx/Packages/packages-lock.json +++ b/com.unity.template.renderstreaming-rtx/Packages/packages-lock.json @@ -109,7 +109,7 @@ "depth": 0, "source": "embedded", "dependencies": { - "com.unity.renderstreaming": "2.1.1-preview", + "com.unity.renderstreaming": "2.2.1-preview", "com.unity.render-pipelines.high-definition": "7.3.1" } },