Added Signaling feature (#13)
* ✨ Add signaling web server
add signaling web server
* renamed cmd
* Updated WebApp
* updated build pipeline
* Added C# signaling script
* removed unused file
This commit is contained in:
Родитель
2974180fcf
Коммит
1917e965b5
|
@ -30,6 +30,7 @@ pack:
|
|||
dependencies:
|
||||
{% for platform in platforms %}
|
||||
- .yamato/upm-ci-webrtc.yml#build_{{ platform.name }}
|
||||
- .yamato/upm-ci-webapp.yml#pack_{{ platform.name }}
|
||||
{% endfor %}
|
||||
|
||||
{% for editor in editors %}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
platforms:
|
||||
- name: win
|
||||
type: Unity::VM
|
||||
image: package-ci/win10:stable
|
||||
flavor: m1.xlarge
|
||||
pack_command: pack_webapp.cmd
|
||||
test_command: test_webapp.cmd
|
||||
projects:
|
||||
- packagename: com.unity.webapp.renderstreaming
|
||||
---
|
||||
{% for project in projects %}
|
||||
{% for platform in platforms %}
|
||||
pack_{{ platform.name }}:
|
||||
name : Pack {{ project.packagename }} on {{ platform.name }}
|
||||
agent:
|
||||
type: {{ platform.type }}
|
||||
image: {{ platform.image }}
|
||||
flavor: {{ platform.flavor}}
|
||||
commands:
|
||||
- {{ platform.pack_command }}
|
||||
artifacts:
|
||||
packages:
|
||||
paths:
|
||||
- "Assets/bin~/**/*"
|
||||
{% endfor %}
|
||||
|
||||
{% for platform in platforms %}
|
||||
test_{{ platform.name }}:
|
||||
name : Test {{ project.packagename }} on {{ platform.name }}
|
||||
agent:
|
||||
type: {{ platform.type }}
|
||||
image: {{ platform.image }}
|
||||
flavor: {{ platform.flavor}}
|
||||
commands:
|
||||
- {{ platform.test_command }}
|
||||
artifacts:
|
||||
logs:
|
||||
paths:
|
||||
- "WebApp/output.log"
|
||||
- "WebApp/coverage/**/*"
|
||||
dependencies:
|
||||
{% for platform in platforms %}
|
||||
- .yamato/upm-ci-webapp.yml#pack_{{ platform.name }}
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
{% endfor %}
|
|
@ -10,7 +10,7 @@ platforms:
|
|||
# image: package-ci/win10:stable
|
||||
image: renderstreaming/win10:latest
|
||||
flavor: m1.large
|
||||
build_command: build.cmd
|
||||
build_command: build_plugin.cmd
|
||||
projects:
|
||||
- name: webrtc
|
||||
packagename: com.unity.webrtc
|
||||
|
@ -98,7 +98,7 @@ publish:
|
|||
dependencies:
|
||||
- .yamato/upm-ci-webrtc.yml#pack
|
||||
{% for editor in test_editors %}
|
||||
{% for platform in test_platforms %}
|
||||
{% for platform in platforms %}
|
||||
- .yamato/upm-ci-webrtc.yml#test_{{ platform.name }}_{{ editor.version }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -38,7 +38,7 @@ RenderSettings:
|
|||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
|
||||
m_IndirectSpecularColor: {r: 0.4465934, g: 0.49642956, b: 0.5748249, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
|
@ -50,12 +50,11 @@ LightmapSettings:
|
|||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_TemporalCoherenceThreshold: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 10
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 10
|
||||
m_AtlasSize: 512
|
||||
|
@ -63,6 +62,7 @@ LightmapSettings:
|
|||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
|
@ -77,10 +77,16 @@ LightmapSettings:
|
|||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 256
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 2
|
||||
m_PVRDenoiserTypeDirect: 0
|
||||
m_PVRDenoiserTypeIndirect: 0
|
||||
m_PVRDenoiserTypeAO: 0
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVREnvironmentMIS: 0
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 5
|
||||
|
@ -89,6 +95,7 @@ LightmapSettings:
|
|||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ShowResolutionOverlay: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_LightingDataAsset: {fileID: 0}
|
||||
m_UseShadowmask: 1
|
||||
--- !u!196 &4
|
||||
|
@ -113,11 +120,59 @@ NavMeshSettings:
|
|||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &8165320
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8165321}
|
||||
- component: {fileID: 8165322}
|
||||
m_Layer: 0
|
||||
m_Name: Render Streaming
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &8165321
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8165320}
|
||||
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}
|
||||
--- !u!114 &8165322
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8165320}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 045786cf504bd7347842d6948241cbd0, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
urlSignaling: http://localhost
|
||||
urlSTUN: stun:stun.l.google.com:19302
|
||||
interval: 0
|
||||
text: {fileID: 0}
|
||||
--- !u!1 &170076733
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 170076735}
|
||||
|
@ -133,15 +188,17 @@ GameObject:
|
|||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 170076733}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 8
|
||||
serializedVersion: 9
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_InnerSpotAngle: 21.80208
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
|
@ -151,6 +208,24 @@ Light:
|
|||
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}
|
||||
|
@ -158,19 +233,23 @@ Light:
|
|||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingLayerMask: 1
|
||||
m_Lightmapping: 1
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
--- !u!4 &170076735
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 170076733}
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
|
@ -183,7 +262,8 @@ Transform:
|
|||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 282840814}
|
||||
|
@ -200,20 +280,24 @@ GameObject:
|
|||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 282840810}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &282840813
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 282840810}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_ClearFlags: 2
|
||||
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
|
||||
|
@ -247,7 +331,8 @@ Camera:
|
|||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 282840810}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
|
@ -256,3 +341,69 @@ Transform:
|
|||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1632470018
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1632470021}
|
||||
- component: {fileID: 1632470020}
|
||||
- component: {fileID: 1632470019}
|
||||
m_Layer: 0
|
||||
m_Name: EventSystem
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1632470019
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1632470018}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1077351063, guid: f70555f144d8491a825f0804e09c671c, 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 &1632470020
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1632470018}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -619905303, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_FirstSelected: {fileID: 0}
|
||||
m_sendNavigationEvents: 1
|
||||
m_DragThreshold: 10
|
||||
--- !u!4 &1632470021
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1632470018}
|
||||
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: 3
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cc7ace171347147499aba2a6042b3285
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.UI;
|
||||
using Unity.WebRTC;
|
||||
|
||||
namespace Unity.RenderStreaming
|
||||
{
|
||||
public class RenderStreaming : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private string urlSignaling = "http://localhost";
|
||||
|
||||
[SerializeField]
|
||||
private string urlSTUN = "stun:stun.l.google.com:19302";
|
||||
|
||||
[SerializeField]
|
||||
private float interval = 5.0f;
|
||||
|
||||
private Signaling signaling;
|
||||
private Dictionary<string, RTCPeerConnection> pcs = new Dictionary<string, RTCPeerConnection>();
|
||||
private RTCConfiguration conf;
|
||||
private string sessionId;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
WebRTC.WebRTC.Initialize();
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
WebRTC.WebRTC.Finalize();
|
||||
}
|
||||
public IEnumerator Start()
|
||||
{
|
||||
signaling = new Signaling(urlSignaling);
|
||||
var opCreate = signaling.Create();
|
||||
yield return opCreate;
|
||||
if (opCreate.webRequest.isNetworkError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {opCreate.webRequest.error}");
|
||||
yield break;
|
||||
}
|
||||
var newResData = opCreate.webRequest.DownloadHandlerJson<NewResData>().GetObject();
|
||||
sessionId = newResData.sessionId;
|
||||
|
||||
conf = default;
|
||||
conf.iceServers = new RTCIceServer[]
|
||||
{
|
||||
new RTCIceServer { urls = new string[] { urlSTUN } }
|
||||
};
|
||||
|
||||
StartCoroutine(LoopPolling());
|
||||
}
|
||||
|
||||
IEnumerator LoopPolling()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return StartCoroutine(GetOffer());
|
||||
yield return StartCoroutine(GetCandidate());
|
||||
yield return new WaitForSeconds(interval);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator GetOffer()
|
||||
{
|
||||
var op = signaling.GetOffer(sessionId);
|
||||
yield return op;
|
||||
|
||||
if (op.webRequest.isNetworkError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {op.webRequest.error}");
|
||||
yield break;
|
||||
}
|
||||
var obj = op.webRequest.DownloadHandlerJson<OfferListResData>().GetObject();
|
||||
foreach (var offer in obj.offers)
|
||||
{
|
||||
RTCSessionDescription _desc = default;
|
||||
_desc.type = RTCSdpType.Offer;
|
||||
_desc.sdp = offer.sdp;
|
||||
var connectionId = offer.connectionId;
|
||||
if(pcs.ContainsKey(connectionId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var pc = new RTCPeerConnection();
|
||||
pcs.Add(offer.connectionId, pc);
|
||||
|
||||
pc.SetConfiguration(ref conf);
|
||||
pc.onIceCandidate = delegate (ref RTCIceCandidate candidate) { StartCoroutine(OnIceCandidate(connectionId, candidate)); };
|
||||
pc.SetRemoteDescription(ref _desc);
|
||||
|
||||
StartCoroutine(Answer(connectionId));
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator Answer(string connectionId)
|
||||
{
|
||||
RTCAnswerOptions options = default;
|
||||
var pc = pcs[connectionId];
|
||||
var op = pc.CreateAnswer(ref options);
|
||||
yield return op;
|
||||
if (op.isError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {op.error}");
|
||||
yield break;
|
||||
}
|
||||
var opLocalDesc = pc.SetLocalDescription(ref op.desc);
|
||||
yield return opLocalDesc;
|
||||
if (opLocalDesc.isError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {opLocalDesc.error}");
|
||||
yield break;
|
||||
}
|
||||
var op3 = signaling.PostAnswer(this.sessionId, connectionId, op.desc.sdp);
|
||||
yield return op3;
|
||||
if (op3.webRequest.isNetworkError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {op3.webRequest.error}");
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator GetCandidate()
|
||||
{
|
||||
var op = signaling.GetCandidate(sessionId);
|
||||
yield return op;
|
||||
|
||||
if (op.webRequest.isNetworkError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {op.webRequest.error}");
|
||||
yield break;
|
||||
}
|
||||
var obj = op.webRequest.DownloadHandlerJson<CandidateListResData>().GetObject();
|
||||
foreach (var candidate in obj.candidates)
|
||||
{
|
||||
if (!pcs.ContainsKey(candidate.connectionId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
RTCIceCandidate _candidate = default;
|
||||
_candidate.candidate = candidate.candidate;
|
||||
pcs[candidate.connectionId].AddIceCandidate(ref _candidate);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator OnIceCandidate(string connectionId, RTCIceCandidate candidate)
|
||||
{
|
||||
var opCandidate = signaling.PostCandidate(sessionId, connectionId, candidate.candidate);
|
||||
yield return opCandidate;
|
||||
if (opCandidate.webRequest.isNetworkError)
|
||||
{
|
||||
Debug.LogError($"Network Error: {opCandidate.webRequest.error}");
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 045786cf504bd7347842d6948241cbd0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,208 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Unity.RenderStreaming
|
||||
{
|
||||
public class DownloadHandlerJson<T> : DownloadHandlerScript
|
||||
{
|
||||
private T m_obj;
|
||||
|
||||
public DownloadHandlerJson() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public DownloadHandlerJson(byte[] buffer) : base(buffer)
|
||||
{
|
||||
}
|
||||
|
||||
protected override byte[] GetData() { return null; }
|
||||
|
||||
protected override bool ReceiveData(byte[] data, int dataLength)
|
||||
{
|
||||
if (data == null || data.Length < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var text = System.Text.Encoding.UTF8.GetString(data);
|
||||
try
|
||||
{
|
||||
m_obj = JsonUtility.FromJson<T>(text);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Debug.LogError(text);
|
||||
throw e;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public T GetObject()
|
||||
{
|
||||
return m_obj;
|
||||
}
|
||||
}
|
||||
|
||||
static class DownloadHandlerExtension
|
||||
{
|
||||
public static T FromJson<T>(this DownloadHandler handler)
|
||||
{
|
||||
return JsonUtility.FromJson<T>(handler.text);
|
||||
}
|
||||
}
|
||||
|
||||
static class UnityWebRequestExtension
|
||||
{
|
||||
public static UnityWebRequestAsyncOperation SendWebRequest<T>(this UnityWebRequest own)
|
||||
{
|
||||
own.downloadHandler = new DownloadHandlerJson<T>();
|
||||
var req = own.SendWebRequest();
|
||||
return req;
|
||||
}
|
||||
|
||||
public static DownloadHandlerJson<T> DownloadHandlerJson<T>(this UnityWebRequest own)
|
||||
{
|
||||
return own.downloadHandler as DownloadHandlerJson<T>;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable 0649
|
||||
[Serializable]
|
||||
class NewResData
|
||||
{
|
||||
public string sessionId;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class OfferListResData
|
||||
{
|
||||
public OfferResData[] offers;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class CandidateListResData
|
||||
{
|
||||
public CandidateResData[] candidates;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class OfferResData
|
||||
{
|
||||
public string connectionId;
|
||||
public string sdp;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class AnswerResData
|
||||
{
|
||||
public string connectionId;
|
||||
public string sdp;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class CandidateResData
|
||||
{
|
||||
public string connectionId;
|
||||
public string candidate;
|
||||
}
|
||||
#pragma warning restore 0649
|
||||
|
||||
|
||||
public class Signaling
|
||||
{
|
||||
public string Url { get; }
|
||||
|
||||
public Signaling(string url)
|
||||
{
|
||||
Url = url;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class OfferReqData
|
||||
{
|
||||
public string connectionId;
|
||||
public string sdp;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class AnswerReqData
|
||||
{
|
||||
public string connectionId;
|
||||
public string sdp;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class CandidateReqData
|
||||
{
|
||||
public string connectionId;
|
||||
public string candidate;
|
||||
}
|
||||
|
||||
public UnityWebRequestAsyncOperation Create()
|
||||
{
|
||||
var req = new UnityWebRequest($"{Url}/signaling", "PUT");
|
||||
var op = req.SendWebRequest<NewResData>();
|
||||
return op;
|
||||
}
|
||||
|
||||
public UnityWebRequestAsyncOperation Delete()
|
||||
{
|
||||
var req = new UnityWebRequest($"{Url}/signaling", "DELETE");
|
||||
var op = req.SendWebRequest();
|
||||
return op;
|
||||
}
|
||||
|
||||
public UnityWebRequestAsyncOperation PostOffer(string sessionId, string connectionId, string sdp)
|
||||
{
|
||||
var obj = new OfferReqData { connectionId = connectionId, sdp = sdp };
|
||||
var data = new System.Text.UTF8Encoding().GetBytes(JsonUtility.ToJson(obj));
|
||||
var req = new UnityWebRequest($"{Url}/signaling/offer", "POST");
|
||||
req.SetRequestHeader("Session-Id", sessionId);
|
||||
req.uploadHandler = new UploadHandlerRaw(data);
|
||||
var op = req.SendWebRequest();
|
||||
return op;
|
||||
}
|
||||
public UnityWebRequestAsyncOperation GetOffer(string sessionId)
|
||||
{
|
||||
var req = new UnityWebRequest($"{Url}/signaling/offer", "GET");
|
||||
req.SetRequestHeader("Session-Id", sessionId);
|
||||
var op = req.SendWebRequest<OfferListResData>();
|
||||
return op;
|
||||
}
|
||||
public UnityWebRequestAsyncOperation PostAnswer(string sessionId, string connectionId, string sdp)
|
||||
{
|
||||
var obj = new AnswerReqData { connectionId = connectionId, sdp = sdp };
|
||||
var data = new System.Text.UTF8Encoding().GetBytes(JsonUtility.ToJson(obj));
|
||||
var req = new UnityWebRequest($"{Url}/signaling/answer", "POST");
|
||||
req.SetRequestHeader("Session-Id", sessionId);
|
||||
req.uploadHandler = new UploadHandlerRaw(data);
|
||||
var op = req.SendWebRequest();
|
||||
return op;
|
||||
}
|
||||
public UnityWebRequestAsyncOperation GetAnswer(string sessionId, string connectionId)
|
||||
{
|
||||
var req = new UnityWebRequest($"{Url}/signaling/answer", "GET");
|
||||
req.SetRequestHeader("Session-Id", sessionId);
|
||||
var op = req.SendWebRequest<AnswerResData>();
|
||||
return op;
|
||||
}
|
||||
|
||||
public UnityWebRequestAsyncOperation PostCandidate(string sessionId, string connectionId, string candidate)
|
||||
{
|
||||
var obj = new CandidateReqData { connectionId = connectionId, candidate = candidate };
|
||||
var data = new System.Text.UTF8Encoding().GetBytes(JsonUtility.ToJson(obj));
|
||||
var req = new UnityWebRequest($"{Url}/signaling/candidate", "POST");
|
||||
req.SetRequestHeader("Session-Id", sessionId);
|
||||
req.uploadHandler = new UploadHandlerRaw(data);
|
||||
var op = req.SendWebRequest();
|
||||
return op;
|
||||
}
|
||||
public UnityWebRequestAsyncOperation GetCandidate(string sessionId)
|
||||
{
|
||||
var req = new UnityWebRequest($"{Url}/signaling/candidate", "GET");
|
||||
req.SetRequestHeader("Session-Id", sessionId);
|
||||
var op = req.SendWebRequest<CandidateListResData>();
|
||||
return op;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6ee3c31b4dd467b47a7050eff711f2c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,21 @@
|
|||
# References
|
||||
# https://github.com/MicrosoftDocs/visualstudio-docs/blob/master/docs/ide/editorconfig-code-style-settings-reference.md#example-editorconfig-file
|
||||
|
||||
###############################
|
||||
# Core EditorConfig Options #
|
||||
###############################
|
||||
|
||||
root = true
|
||||
|
||||
# All files
|
||||
[*]
|
||||
indent_style = space
|
||||
|
||||
# Code files
|
||||
[*.{ts,js,json,html}]
|
||||
end_of_line = crlf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
charset = utf-8-bom
|
||||
trim_trailing_whitespace = true
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="icon" href="/public/images/favicon.ico" type="image/x-icon">
|
||||
<link type="text/css" rel="stylesheet" href="style.css">
|
||||
<script type="text/javascript" src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||
<script type="module" src="app.js"></script>
|
||||
</head>
|
||||
<div id="player"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
roots: ['<rootDir>/src/', '<rootDir>/test/'],
|
||||
transform: {
|
||||
'^.+\\.tsx?$': 'ts-jest'
|
||||
},
|
||||
testRegex: '(/__tests__/.*|\\.(test|spec))\\.[tj]sx?$',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
coverageDirectory: './coverage/',
|
||||
collectCoverage: true
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,14 +1,41 @@
|
|||
{
|
||||
"name": "WebApp",
|
||||
"version": "0.0.1",
|
||||
"name": "signalingwebserver",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prestart": "npm install",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"test": "jest --env=node --colors --coverage test",
|
||||
"newman": "newman run test/renderstreaming.postman_collection.json",
|
||||
"start": "node ./build/index.js",
|
||||
"dev": "ts-node ./src/index.ts",
|
||||
"lint": "tslint --project tsconfig.json --fix 'src/*.ts'",
|
||||
"pack": "pkg ."
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.3",
|
||||
"debug": "~2.6.9",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/node": "^11.12.0",
|
||||
"express": "~4.16.0",
|
||||
"http-errors": "~1.6.2",
|
||||
"jade": "~1.11.0",
|
||||
"morgan": "~1.9.0",
|
||||
"socket.io": "~2.0.4"
|
||||
"debug": "~2.6.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.12",
|
||||
"jest": "^24.8.0",
|
||||
"newman": "^4.4.1",
|
||||
"pkg": "^4.4.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"ts-node": "^8.1.0",
|
||||
"tslint": "^5.14.0",
|
||||
"tslint-config-airbnb": "^5.11.1",
|
||||
"typescript": "^3.3.4000"
|
||||
},
|
||||
"bin": "build/index.js",
|
||||
"pkg": {
|
||||
"assets": [
|
||||
"public/**/*"
|
||||
],
|
||||
"targets": [
|
||||
"node10"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 472 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 66 KiB |
|
@ -0,0 +1,40 @@
|
|||
import { VideoPlayer } from "./video-player.js";
|
||||
import { registerKeyboardEvents, registerMouseEvents } from "./register-events.js";
|
||||
|
||||
let playButton;
|
||||
|
||||
showPlayButton();
|
||||
|
||||
function showPlayButton() {
|
||||
if (!document.getElementById('playButton')) {
|
||||
let playButtonImg = document.createElement('img');
|
||||
playButtonImg.id = 'playButton';
|
||||
playButtonImg.src = 'images/Play.png';
|
||||
playButtonImg.alt = 'Start Streaming';
|
||||
if (!playButton) {
|
||||
playButton = document.getElementById('player').appendChild(playButtonImg);
|
||||
}
|
||||
playButton.addEventListener('click', function () {
|
||||
onClickPlayButton();
|
||||
playButton.style.display = 'none';
|
||||
});
|
||||
}
|
||||
else {
|
||||
playButton.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function onClickPlayButton() {
|
||||
const playerDiv = document.getElementById('player');
|
||||
const element = document.createElement('video');
|
||||
playerDiv.appendChild(element);
|
||||
const videoPlayer = setupVideoPlayer(element);
|
||||
registerKeyboardEvents(videoPlayer);
|
||||
registerMouseEvents(videoPlayer, element);
|
||||
}
|
||||
|
||||
function setupVideoPlayer(element, clientConfig) {
|
||||
let videoPlayer = new VideoPlayer(element, clientConfig);
|
||||
videoPlayer.setupConnection();
|
||||
return videoPlayer;
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
let InputEvent = {
|
||||
KeyDown : 0,
|
||||
KeyUp : 1,
|
||||
MouseDown : 2,
|
||||
MouseUp : 3,
|
||||
MouseMove : 4,
|
||||
MouseWheel : 5
|
||||
};
|
||||
|
||||
let isPlayMode = false;
|
||||
|
||||
export function registerKeyboardEvents(videoPlayer) {
|
||||
const _videoPlayer = videoPlayer;
|
||||
document.addEventListener('keydown', function (e) {
|
||||
console.log("key down " + e.key + ", repeat = " + e.repeat);
|
||||
_videoPlayer && _videoPlayer.sendMsg(new Uint8Array([InputEvent.KeyDown, e.key]).buffer);
|
||||
}, false);
|
||||
document.addEventListener('keyup', function (e) {
|
||||
console.log("key up " + e.key);
|
||||
_videoPlayer && _videoPlayer.sendMsg(new Uint8Array([InputEvent.KeyUp, e.key]).buffer);
|
||||
}, false);
|
||||
}
|
||||
|
||||
export function registerMouseEvents(videoPlayer, playerElement) {
|
||||
const _videoPlayer = videoPlayer;
|
||||
const _playerElement = playerElement;
|
||||
const _document = document;
|
||||
playerElement.requestPointerLock = playerElement.requestPointerLock ||
|
||||
playerElement.mozRequestPointerLock || playerElement.webkitRequestPointerLock;
|
||||
|
||||
// Listen to lock state change events
|
||||
document.addEventListener('pointerlockchange', pointerLockChange, false);
|
||||
document.addEventListener('mozpointerlockchange', pointerLockChange, false);
|
||||
document.addEventListener('webkitpointerlockchange', pointerLockChange, false);
|
||||
// Listen to mouse events
|
||||
playerElement.addEventListener('click', playVideo, false);
|
||||
playerElement.addEventListener('mousedown', sendMouseDown, false);
|
||||
playerElement.addEventListener('mouseup', sendMouseUp, false);
|
||||
playerElement.addEventListener('mousewheel', sendMouseWheel, false);
|
||||
// ios workaround for not allowing auto-play
|
||||
playerElement.addEventListener('touchend', playVideoWithTouch , false);
|
||||
|
||||
|
||||
function pointerLockChange() {
|
||||
if (_document.pointerLockElement === playerElement ||
|
||||
_document.mozPointerLockElement === playerElement ||
|
||||
_document.webkitPointerLockElement === playerElement) {
|
||||
isPlayMode = false;
|
||||
console.log('Pointer locked');
|
||||
document.addEventListener('mousemove', sendMousePosition, false);
|
||||
}
|
||||
else {
|
||||
console.log('The pointer lock status is now unlocked');
|
||||
document.removeEventListener('mousemove', sendMousePosition, false);
|
||||
}
|
||||
}
|
||||
function playVideo() {
|
||||
if (_playerElement.paused) {
|
||||
_playerElement.play();
|
||||
}
|
||||
if (!isPlayMode) {
|
||||
_playerElement.requestPointerLock();
|
||||
isPlayMode = true;
|
||||
}
|
||||
}
|
||||
function playVideoWithTouch() {
|
||||
if (_playerElement.paused) {
|
||||
_playerElement.play();
|
||||
}
|
||||
}
|
||||
function sendMousePosition(e) {
|
||||
console.log("deltaX: " + e.movementX + ", deltaY: " + e.movementY);
|
||||
let data = new DataView(new ArrayBuffer(5));
|
||||
data.setUint8(0, InputEvent.MouseMove);
|
||||
data.setInt16(1, e.movementX, true);
|
||||
data.setInt16(3, e.movementY, true);
|
||||
_videoPlayer.sendMsg(data.buffer);
|
||||
}
|
||||
function sendMouseDown(e) {
|
||||
console.log("mouse button " + e.button + " down");
|
||||
let data = new DataView(new ArrayBuffer(2));
|
||||
data.setUint8(0, InputEvent.MouseDown);
|
||||
data.setUint8(1, e.button);
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
}
|
||||
function sendMouseUp(e) {
|
||||
console.log("mouse button " + e.button + " up");
|
||||
let data = new DataView(new ArrayBuffer(2));
|
||||
data.setUint8(0, InputEvent.MouseUp);
|
||||
data.setUint8(1, e.button);
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
}
|
||||
function sendMouseWheel(e) {
|
||||
console.log("mouse wheel with delta " + e.wheelDelta);
|
||||
let data = new DataView(new ArrayBuffer(3));
|
||||
data.setUint8(0, InputEvent.MouseWheel);
|
||||
data.setInt16(1, e.wheelDelta, true);
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
export default class SignalingChannel {
|
||||
headers(sessionId) {
|
||||
if(sessionId != undefined)
|
||||
{
|
||||
return {'Content-Type': 'application/json', 'Session-Id': sessionId};
|
||||
}
|
||||
else {
|
||||
return {'Content-Type': 'application/json'};
|
||||
}
|
||||
};
|
||||
|
||||
url(method) {
|
||||
return location.protocol + '//' + location.host + '/signaling/' + method;
|
||||
};
|
||||
|
||||
async send(sessionId, data) {
|
||||
let method = undefined;
|
||||
if ('type' in data) {
|
||||
switch (data.type) {
|
||||
case 'offer':
|
||||
method = 'offer';
|
||||
break;
|
||||
case 'answer':
|
||||
method = 'answer';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
method = 'candidate';
|
||||
}
|
||||
const response = await fetch(this.url(method), {method: 'POST', headers: this.headers(sessionId), body: JSON.stringify(data)});
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
async create() {
|
||||
const response = await fetch(this.url(''), {method: 'PUT', headers: this.headers()});
|
||||
return await response.json();
|
||||
};
|
||||
async delete(sessionId) {
|
||||
await fetch(this.url(''), {method: 'DELETE', headers: this.headers(sessionId)});
|
||||
return;
|
||||
};
|
||||
async getOffer(sessionId) {
|
||||
const response = await fetch(this.url('offer'), {method: 'GET', headers: this.headers(sessionId)});
|
||||
return await response.json();
|
||||
};
|
||||
async getAnswer(sessionId) {
|
||||
const response = await fetch(this.url('answer'), {method: 'GET', headers: this.headers(sessionId)});
|
||||
return await response.json();
|
||||
};
|
||||
async getCandidate(sessionId) {
|
||||
const response = await fetch(this.url('candidate'), {method: 'GET', headers: this.headers(sessionId)});
|
||||
return await response.json();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
import SignalingChannel from "./signaling-channel.js"
|
||||
|
||||
export class VideoPlayer {
|
||||
constructor(element, options) {
|
||||
const _this = this;
|
||||
if(options == undefined) {
|
||||
options = {};
|
||||
}
|
||||
this.cfg = options;
|
||||
this.cfg.sdpSemantics = 'unified-plan';
|
||||
this.cfg.iceServers = [{urls: ['stun:stun.l.google.com:19302']}];
|
||||
this.pc = null;
|
||||
this.channel = null;
|
||||
this.offerOptions = {
|
||||
offerToReceiveAudio: true,
|
||||
offerToReceiveVideo: true,
|
||||
};
|
||||
this.video = element;
|
||||
this.video.id = 'Video';
|
||||
this.video.playsInline = true;
|
||||
this.video.addEventListener('loadedmetadata', function () {
|
||||
_this.video.play();
|
||||
}, true);
|
||||
this.interval = 5000;
|
||||
this.signalingChannel = new SignalingChannel();
|
||||
this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec));
|
||||
}
|
||||
|
||||
async setupConnection() {
|
||||
var _this = this;
|
||||
// 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.cfg);
|
||||
this.pc.onsignalingstatechange = function (e) {
|
||||
console.log('signalingState changed:', e);
|
||||
};
|
||||
this.pc.oniceconnectionstatechange = function (e) {
|
||||
console.log('iceConnectionState changed:', e);
|
||||
};
|
||||
this.pc.onicegatheringstatechange = function (e) {
|
||||
console.log('iceGatheringState changed:', e);
|
||||
};
|
||||
this.pc.ontrack = function (e) {
|
||||
console.log('New track added: ', e.streams);
|
||||
_this.video.srcObject = e.streams[0];
|
||||
};
|
||||
this.pc.onicecandidate = function (e) {
|
||||
console.log('Send ICE candidate', e);
|
||||
_this.signalingChannel.send(_this.sessionId, e);
|
||||
};
|
||||
// Create data channel with proxy server and set up handlers
|
||||
this.channel = this.pc.createDataChannel('ProxyDataChannel');
|
||||
console.log('Create datachannel.');
|
||||
this.channel.onopen = function () {
|
||||
console.log('Datachannel connected.');
|
||||
};
|
||||
this.channel.onerror = function (e) {
|
||||
console.log("The error " + e.error.message + " occurred\n while handling data with proxy server.");
|
||||
};
|
||||
this.channel.onclose = function () {
|
||||
console.log('Datachannel disconnected.');
|
||||
};
|
||||
|
||||
const createResponse = await this.signalingChannel.create();
|
||||
this.sessionId = createResponse.sessionId;
|
||||
|
||||
// create offer
|
||||
const offer = await this.pc.createOffer(this.offerOptions);
|
||||
|
||||
// set local sdp
|
||||
offer.sdp = offer.sdp.replace(/useinbandfec=1/, 'useinbandfec=1;stereo=1;maxaveragebitrate=1048576');
|
||||
const desc = new RTCSessionDescription({sdp:offer.sdp, type:"offer"});
|
||||
await this.pc.setLocalDescription(desc);
|
||||
|
||||
await this.sendOffer(offer);
|
||||
|
||||
this.loopGetAnswer(this.sessionId, this.interval);
|
||||
this.loopGetCandidate(this.sessionId, this.interval);
|
||||
};
|
||||
|
||||
async sendOffer(offer) {
|
||||
// signaling
|
||||
const res = await this.signalingChannel.send(this.sessionId, offer);
|
||||
this.connectionId = res.connectionId;
|
||||
}
|
||||
|
||||
async loopGetAnswer(sessionId, interval) {
|
||||
while(true) {
|
||||
const res = await this.signalingChannel.getAnswer(sessionId);
|
||||
if(res.answers.length > 0) {
|
||||
const answer = res.answers[0];
|
||||
await this.setAnswer(sessionId, answer.sdp);
|
||||
}
|
||||
await this.sleep(interval);
|
||||
}
|
||||
}
|
||||
|
||||
async loopGetCandidate(sessionId, interval) {
|
||||
while(true) {
|
||||
const res = await this.signalingChannel.getCandidate(sessionId);
|
||||
if(res.candidates.length > 0) {
|
||||
for(let candidate of res.candidates) {
|
||||
const iceCandidate = new RTCIceCandidate({ candidate: candidate.candidate });
|
||||
await this.pc.addIceCandidate(iceCandidate);
|
||||
}
|
||||
}
|
||||
await this.sleep(interval);
|
||||
}
|
||||
}
|
||||
|
||||
async setAnswer(sessionId, sdp) {
|
||||
const desc = new RTCSessionDescription({sdp:sdp, type:"answer"});
|
||||
await this.pc.setRemoteDescription(desc);
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.pc) {
|
||||
console.log('Close current PeerConnection');
|
||||
this.pc.close();
|
||||
this.pc = null;
|
||||
}
|
||||
};
|
||||
|
||||
sendMsg(msg) {
|
||||
switch (this.channel.readyState) {
|
||||
case 'connecting':
|
||||
console.log('Connection not ready');
|
||||
break;
|
||||
case 'open':
|
||||
this.channel.send(msg);
|
||||
break;
|
||||
case 'closing':
|
||||
console.log('Attempt to sendMsg message while closing');
|
||||
break;
|
||||
case 'closed':
|
||||
console.log( 'Attempt to sendMsg message while connection closed.');
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
body{
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#player{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
background-color: #323232;
|
||||
}
|
||||
|
||||
#playButton{
|
||||
width: 15%;
|
||||
max-width: 200px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#Video{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@echo off
|
||||
pushd %~dp0
|
||||
call npm run start
|
||||
popd
|
||||
pause
|
|
@ -0,0 +1,38 @@
|
|||
import * as express from 'express';
|
||||
import { Server } from 'http';
|
||||
import { createServer } from './server';
|
||||
|
||||
export interface Options {
|
||||
port?: number;
|
||||
}
|
||||
|
||||
export class RenderStreaming {
|
||||
public static run(argv: string[]) {
|
||||
const program = require('commander');
|
||||
const readOptions = (): Options => {
|
||||
if (Array.isArray(argv)) {
|
||||
program
|
||||
.usage('[options] <apps...>')
|
||||
.option('-p, --port <n>', 'Port to start the server on', process.env.PORT || 80)
|
||||
.parse(argv);
|
||||
return {
|
||||
port: program.port,
|
||||
};
|
||||
}
|
||||
};
|
||||
const options = readOptions();
|
||||
return new RenderStreaming(options);
|
||||
}
|
||||
|
||||
public server: express.Application;
|
||||
public httpServer?: Server;
|
||||
public options: Options;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.options = options;
|
||||
this.server = createServer();
|
||||
this.httpServer = this.server.listen(this.options.port);
|
||||
}
|
||||
}
|
||||
|
||||
RenderStreaming.run(process.argv);
|
|
@ -0,0 +1,27 @@
|
|||
const isDebug: boolean = true;
|
||||
|
||||
export enum LogLevel {
|
||||
info,
|
||||
log,
|
||||
warn,
|
||||
error,
|
||||
}
|
||||
|
||||
export function log(level: LogLevel, ...args: any[]): void {
|
||||
if (isDebug) {
|
||||
switch (level) {
|
||||
case LogLevel.log:
|
||||
console.log(...args);
|
||||
break;
|
||||
case LogLevel.info:
|
||||
console.info(...args);
|
||||
break;
|
||||
case LogLevel.warn:
|
||||
console.warn(...args);
|
||||
break;
|
||||
case LogLevel.error:
|
||||
console.error(...args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import * as express from 'express';
|
||||
import * as bodyParser from 'body-parser';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import signaling from './signaling';
|
||||
|
||||
import { log, LogLevel } from './log';
|
||||
|
||||
export const createServer = () => {
|
||||
const app: express.Application = express();
|
||||
// const signal = require('./signaling');
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
app.use('/signaling', signaling);
|
||||
app.use(express.static(path.join(__dirname, '/../public/stylesheets')));
|
||||
app.use(express.static(path.join(__dirname, '/../public/scripts')));
|
||||
app.use('/images', express.static(path.join(__dirname, '/../public/images')));
|
||||
app.get('/', (req, res) => {
|
||||
const indexPagePath: string = path.join(__dirname, '/../index.html');
|
||||
fs.access(indexPagePath, (err) => {
|
||||
if (err) {
|
||||
log(LogLevel.warn, `Can't find file ' ${indexPagePath}`);
|
||||
res.status(404).send(`Can't find file ${indexPagePath}`);
|
||||
} else {
|
||||
res.sendFile(indexPagePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
return app;
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
import { Request, Response, Router } from 'express';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
const express = require('express');
|
||||
const router: Router = express.Router();
|
||||
|
||||
const clients: Map<string, Set<string>> = new Map<string, Set<string>>();
|
||||
|
||||
const offers: Map<string, string> = new Map<string, string>();
|
||||
const answers: Map<string, string> = new Map<string,string>();
|
||||
const candidates: Map<string, string> = new Map<string, string>();
|
||||
|
||||
router.use((req: Request, res: Response, next) => {
|
||||
if (req.url == '/') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
const id : string = req.header('session-id');
|
||||
if (!clients.has(id)) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/offer', (req: Request, res: Response) => {
|
||||
const _offers = Array.from(offers);
|
||||
const obj = _offers.map(v => { return { "connectionId" :v[0], "sdp": v[1], }});
|
||||
res.json({ offers : obj });
|
||||
});
|
||||
|
||||
router.get('/answer', (req: Request, res: Response) => {
|
||||
const sessionId : string = req.header('session-id');
|
||||
let connectionIds = Array.from(clients.get(sessionId));
|
||||
connectionIds = connectionIds.filter(v => answers.has(v));
|
||||
const _answers = connectionIds.map(v => { return [v, answers.get(v)] });
|
||||
const obj = _answers.map(v => { return { "connectionId" :v[0], "sdp": v[1], }});
|
||||
res.json({ answers: obj });
|
||||
});
|
||||
|
||||
router.get('/candidate', (req: Request, res: Response) => {
|
||||
const sessionId : string = req.header('session-id');
|
||||
let connectionIds = Array.from(clients.get(sessionId));
|
||||
connectionIds = connectionIds.filter(v => candidates.has(v));
|
||||
const _candidates = connectionIds.map(v => { return [v, candidates.get(v)] });
|
||||
const obj = _candidates.map(v => { return { "connectionId" :v[0], "candidate": v[1], }});
|
||||
res.json({ candidates : obj });
|
||||
});
|
||||
|
||||
router.put('', (req: Request, res: Response) => {
|
||||
const id: string = uuid();
|
||||
clients.set(id, new Set<string>());
|
||||
res.json({ sessionId : id });
|
||||
});
|
||||
|
||||
router.delete('', (req: Request, res: Response) => {
|
||||
const id : string = req.header('session-id');
|
||||
clients.delete(id);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
router.post('/offer', (req: Request, res: Response) => {
|
||||
const sessionId : string = req.header('session-id');
|
||||
let connectionIds = clients.get(sessionId);
|
||||
const connectionId : string = uuid();
|
||||
connectionIds.add(connectionId);
|
||||
offers.set(connectionId, req.body.sdp);
|
||||
res.json({ connectionId : connectionId });
|
||||
});
|
||||
|
||||
router.post('/answer', (req: Request, res: Response) => {
|
||||
const connectionId : string = req.body.connectionId;
|
||||
answers.set(connectionId, req.body.sdp);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
router.post('/candidate', (req: Request, res: Response) => {
|
||||
const connectionId : string = req.body.connectionId;
|
||||
candidates.set(connectionId, req.body.candidate);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -0,0 +1,7 @@
|
|||
test('basic', () => {
|
||||
expect('hello').toBe('hello');
|
||||
});
|
||||
|
||||
test('basic2', () => {
|
||||
expect(1+1).toBe(2);
|
||||
});
|
|
@ -0,0 +1,550 @@
|
|||
{
|
||||
"info": {
|
||||
"_postman_id": "c4ee0e83-3ad3-400d-9947-187687d72f22",
|
||||
"name": "renderstreaming",
|
||||
"description": "# Introduction\nWhat does your API do?\n\n# Overview\nThings that the developers should know about\n\n# Authentication\nWhat is the preferred way of using the API?\n\n# Error Codes\nWhat errors and status codes can a user expect?\n\n# Rate limit\nIs there a limit to the number of requests an user can send?",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "/signaling",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "38efc5aa-83d4-436a-8c8a-c80993a1dfcd",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });",
|
||||
"pm.test(\"The response has a valid JSON body\", function () {",
|
||||
" pm.response.to.be.json;",
|
||||
" var jsonData = pm.response.json();",
|
||||
" pm.response.to.have.jsonBody(\"sessionId\");",
|
||||
"});",
|
||||
"pm.environment.set(\"session_Id\", pm.response.json().sessionId);"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}/signaling",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
],
|
||||
"path": [
|
||||
"signaling"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "/signaling/offer",
|
||||
"event": [
|
||||
{
|
||||
"listen": "prerequest",
|
||||
"script": {
|
||||
"id": "1f7448eb-7fbe-4e06-8f57-1ab1a52f642a",
|
||||
"exec": [
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
},
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "65d5877e-6458-404d-abae-3f6a2feff5d3",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });",
|
||||
"pm.environment.set(\"connection_id\", JSON.stringify(pm.response.json().connectionId));"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"name": "Content-Type",
|
||||
"value": "application/json",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "Session-Id",
|
||||
"value": "{{session_Id}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"sdp\": {{sdp}}\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}/signaling/offer",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
],
|
||||
"path": [
|
||||
"signaling",
|
||||
"offer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "Default",
|
||||
"originalRequest": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "offer",
|
||||
"host": [
|
||||
"offer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": "Text",
|
||||
"header": [],
|
||||
"cookie": [],
|
||||
"body": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "/signaling/answer",
|
||||
"event": [
|
||||
{
|
||||
"listen": "prerequest",
|
||||
"script": {
|
||||
"id": "1f7448eb-7fbe-4e06-8f57-1ab1a52f642a",
|
||||
"exec": [
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
},
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "88f991c5-074f-4040-9629-4ab3538d37d6",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"name": "Content-Type",
|
||||
"type": "text",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Session-Id",
|
||||
"type": "text",
|
||||
"value": "{{session_Id}}"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n\t\"connectionId\": {{connection_id}},\n\t\"sdp\" : {{sdp}}\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}/signaling/answer",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
],
|
||||
"path": [
|
||||
"signaling",
|
||||
"answer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "Default",
|
||||
"originalRequest": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "offer",
|
||||
"host": [
|
||||
"offer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": "Text",
|
||||
"header": [],
|
||||
"cookie": [],
|
||||
"body": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "\b/signaling/candidate",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "028da34c-ba2a-4404-a46d-82c303642cae",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"name": "Content-Type",
|
||||
"value": "application/json",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "Session-Id",
|
||||
"value": "{{session_Id}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n\t\"connectionId\": {{connection_id}},\n\t\"candidate\" : {{candidate}}\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}/signaling/candidate",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
],
|
||||
"path": [
|
||||
"signaling",
|
||||
"candidate"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "Default",
|
||||
"originalRequest": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "icecandidate",
|
||||
"host": [
|
||||
"icecandidate"
|
||||
]
|
||||
}
|
||||
},
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": "Text",
|
||||
"header": [],
|
||||
"cookie": [],
|
||||
"body": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "/",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "42e4303e-4f2f-4c16-a23a-2390bf3dac32",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "/signaling/offer",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "fda4f1b3-3b8b-4c88-83bc-333ccfee434d",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });",
|
||||
"pm.test(\"The response has a valid JSON body\", function () {",
|
||||
" pm.response.to.be.json;",
|
||||
" pm.response.to.have.jsonBody(\"offers\");",
|
||||
" var jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.offers.length).to.be.above(0);",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Session-Id",
|
||||
"value": "{{session_Id}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}/signaling/offer",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
],
|
||||
"path": [
|
||||
"signaling",
|
||||
"offer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "/signaling/answer",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "fda4f1b3-3b8b-4c88-83bc-333ccfee434d",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });",
|
||||
"pm.test(\"The response has a valid JSON body\", function () {",
|
||||
" pm.response.to.be.json;",
|
||||
" pm.response.to.have.jsonBody(\"answers\");",
|
||||
" var jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.answers.length).to.be.above(0);",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Session-Id",
|
||||
"value": "{{session_Id}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}/signaling/answer",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
],
|
||||
"path": [
|
||||
"signaling",
|
||||
"answer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "/signaling/candidate",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "c3371877-1fc1-418d-a5f8-2829e4be4c0a",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });",
|
||||
"pm.test(\"The response has a valid JSON body\", function () {",
|
||||
" pm.response.to.be.json;",
|
||||
" pm.response.to.have.jsonBody(\"candidates\");",
|
||||
" var jsonData = pm.response.json();",
|
||||
" pm.expect(jsonData.candidates.length).to.be.above(0);",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Session-Id",
|
||||
"value": "{{session_Id}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}/signaling/candidate",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
],
|
||||
"path": [
|
||||
"signaling",
|
||||
"candidate"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "/signaling",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "38efc5aa-83d4-436a-8c8a-c80993a1dfcd",
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [
|
||||
{
|
||||
"key": "Session-Id",
|
||||
"value": "{{session_Id}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://{{url}}/signaling",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"{{url}}"
|
||||
],
|
||||
"path": [
|
||||
"signaling"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"event": [
|
||||
{
|
||||
"listen": "prerequest",
|
||||
"script": {
|
||||
"id": "5b49db9f-4fc8-41c3-90b3-4d2b84c39f69",
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "0a8f284b-249d-40b5-be4f-e77111ed9471",
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
""
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"id": "eb98d1fb-ddbb-47b0-b61e-d90fed8cdbe7",
|
||||
"key": "url",
|
||||
"value": "localhost",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "e3e3a573-e02f-49c4-8348-787a0ad3fd06",
|
||||
"key": "session_Id",
|
||||
"value": "",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "0a23febf-48c8-45e2-91e6-c6d07725f36a",
|
||||
"key": "sdp",
|
||||
"value": "\"v=0\\r\\no=- 7060022371716902156 2 IN IP4 127.0.0.1\\r\\ns=-\\r\\nt=0 0\\r\\na=group:BUNDLE 0\\r\\na=msid-semantic: WMS 3fd630e8-9285-4f82-adbc-a7e31c334740\\r\\nm=audio 9 UDP\\/TLS\\/RTP\\/SAVPF 111 103 104 9 0 8 110 112 113 126\\r\\nc=IN IP4 0.0.0.0\\r\\na=rtcp:9 IN IP4 0.0.0.0\\r\\na=ice-ufrag:yOxE\\r\\na=ice-pwd:Ekoek1dl79NlWZS2dfmrW7Cr\\r\\na=ice-options:trickle\\r\\na=fingerprint:sha-256 02:F8:39:CE:EF:A7:77:B4:8D:56:8A:A1:C8:14:0C:1D:00:DF:99:14:ED:CE:05:4B:94:F9:EE:36:EE:4F:82:61\\r\\na=setup:actpass\\r\\na=mid:0\\r\\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\\r\\na=extmap:2 http:\\/\\/www.ietf.org\\/id\\/draft-holmer-rmcat-transport-wide-cc-extensions-01\\r\\na=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid\\r\\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\\r\\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\\r\\na=sendonly\\r\\na=msid:3fd630e8-9285-4f82-adbc-a7e31c334740 a573e4b8-b844-4ab9-b5b2-62c5bea85e9e\\r\\na=rtcp-mux\\r\\na=rtpmap:111 opus\\/48000\\/2\\r\\na=rtcp-fb:111 transport-cc\\r\\na=fmtp:111 minptime=10;useinbandfec=1\\r\\na=rtpmap:103 ISAC\\/16000\\r\\na=rtpmap:104 ISAC\\/32000\\r\\na=rtpmap:9 G722\\/8000\\r\\na=rtpmap:0 PCMU\\/8000\\r\\na=rtpmap:8 PCMA\\/8000\\r\\na=rtpmap:110 telephone-event\\/48000\\r\\na=rtpmap:112 telephone-event\\/32000\\r\\na=rtpmap:113 telephone-event\\/16000\\r\\na=rtpmap:126 telephone-event\\/8000\\r\\na=ssrc:2852212123 cname:GybrBKZh3U5xSqQq\\r\\na=ssrc:2852212123 msid:3fd630e8-9285-4f82-adbc-a7e31c334740 a573e4b8-b844-4ab9-b5b2-62c5bea85e9e\\r\\na=ssrc:2852212123 mslabel:3fd630e8-9285-4f82-adbc-a7e31c334740\\r\\na=ssrc:2852212123 label:a573e4b8-b844-4ab9-b5b2-62c5bea85e9e\\r\\n\"",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "85ad3843-bbae-41b0-b4a6-d3184d965bfe",
|
||||
"key": "candidate",
|
||||
"value": "\"\"",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "c4fd615e-6918-43ab-bdea-d5780119cb53",
|
||||
"key": "connection_id",
|
||||
"value": "",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.spec.ts"],
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": ["dom","es5"],
|
||||
"sourceMap": false,
|
||||
"outDir":"build",
|
||||
"rootDir":"src"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "tslint-config-airbnb"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
cd WebApp
|
||||
call npm install
|
||||
call npm run build
|
||||
call npm run pack
|
||||
cd ..\
|
||||
mkdir Assets\bin~
|
||||
move WebApp\*.exe Assets\bin~
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="windows-sdk-10-version-1809-all" version="10.0.17763.1" />
|
||||
</packages>
|
|
@ -0,0 +1,6 @@
|
|||
cd WebApp
|
||||
call npm install
|
||||
call npm run lint
|
||||
call npm run test
|
||||
start npm run dev
|
||||
call npm run newman
|
Загрузка…
Ссылка в новой задаче